Revision [8316]

This is an old revision of AdvancedFormOpen made by JavaWoman on 2005-05-19 06:12:04.

 

Advanced Form Open


The WikkaCore Wikka core has a FormOpen() method that creates the opening tag for a form. However, it has a number of limitations, such as no way to specify an id and/or class attribute, and not supporting enctype needed for a file upload form. This leads to ugly workarounds and inconsistent (and sometimes invalid) code.

The following replacement for FormOpen() addresses these issues and makes sure the generated code is valid XHTML. It uses a number of new supporting methods that will be more generally useful as well.

New FormOpen() method

The folowing code should replace the FormOpen() method in wikka.php (at line 694 in the 1.1.6.0. release version:
  1.     /**
  2.      * Build an opening form tag with specified or generated attributes.
  3.      *
  4.      * This method builds an opening form tag, taking care that the result is valid XHTML
  5.      * no matter where the parameters come from: invalid parameters are ignored and defaults used.
  6.      * This enables this method to be used with user-provided parameter values.
  7.      *
  8.      * The form will always have the required action attribute and an id attribute to provide
  9.      * a 'hook' for styling and scripting. This method tries its best to ensure the id attribute
  10.      * is unique, among other things by adding a 'form_' prefix to make it different from ids for
  11.      * other elements.
  12.      * For a file upload form ($file=TRUE) the appropriate method and enctype attributes are generated.
  13.      *
  14.      * When rewriting is not active, a hidden field is attached as well to pass on the page name.
  15.      * NOTE: is this really needed??
  16.      *
  17.      * @author      {@link http://wikka.jsnx.com/JavaWoman JavaWoman}
  18.      * @copyright   Copyright © 2005, Marjolein Katsma
  19.      * @license     http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  20.      *
  21.      * @access  public
  22.      * @uses    existsHandler()
  23.      * @uses    existsPage()
  24.      * @uses    Href()
  25.      * @uses    MiniHref()
  26.      * @uses    ID_LENGTH
  27.      *
  28.      * @param   string  $method     optional: "method" which consists of handler and possibly a query string
  29.      *                              to be used as part of action attribute
  30.      * @param   string  $tag        optional: page name to be used for action attribute;
  31.      *                              if not specified, the current page will be used
  32.      * @param   string  $formMethod optional: method attribute; must be POST (default) or GET;
  33.      *                              anything but POST is ignored and considered as GET
  34.      * @param   string  $id         optional: id attribute
  35.      * @param   string  $class      optional: class attribute
  36.      * @param   boolean $file       optional: specifies whether there will be a file upload field;
  37.      *                              default: FALSE; if TRUE sets method attribute to POST and generates
  38.      *                              appropriate enctype attribute
  39.      * @return  string opening form tag and hidden input field when not rewriting.
  40.      */
  41.     function FormOpen($method='',$tag='',$formMethod='POST',$id='',$class='',$file=FALSE)
  42.     {
  43.         // initializations
  44.         static $seq = 1;
  45.         static $aIds = array();
  46.         $attrMethod = '';                                               # no method for HTML default GET
  47.         $attrClass = '';
  48.         $attrEnctype = '';                                              # default no enctype -> HTML default application/x-www-form-urlencoded
  49.         $hiddenval = '';
  50.         // validations
  51.         $validMethod = $this->existsHandler($method);
  52.         $validPage = $this->existsPage($tag);
  53.         $validId = preg_match('/^[A-Za-z][A-Za-z0-9_:.-]*$/',$id);      # http://www.w3.org/TR/html4/types.html#type-id
  54.         // derivations (MiniHref supplies current page name if none specified)
  55.         $page = ($validPage) ? $tag : '';
  56.         $method = ($validMethod) ? $method : '';
  57.  
  58.         // form action (action is a required attribute!)
  59.         $attrAction = ' action="'.$this->Href($method, $page).'"';
  60.         // form method (ignore anything but POST) and enctype
  61.         if (TRUE === $file)
  62.         {
  63.             $attrMethod = ' method="POST"';                             # required for file upload
  64.             $attrEnctype = ' enctype="multipart/form-data"';            # required for file upload
  65.         }
  66.         elseif (preg_match('/^POST$/i',$formMethod))                    # ignore case...
  67.         {
  68.             $attrMethod = ' method="POST"';                             # ...but generate uppercase
  69.         }
  70.         // form id
  71.         if ('' == $id || !$validId || in_array($id,$aIds))              # ignore specified id if it is invalid or exists already
  72.         {
  73.             $id = substr(md5($method.$tag.$formMethod.$class),0,ID_LENGTH); # @@@ maybe make length configurable
  74.             if (in_array($id,$aIds))
  75.             {
  76.                 $id .= '_'.++$seq;                                      # add suffiX to make ID unique
  77.             }
  78.             $attrId = ' id="form_'.$id.'"';
  79.         }
  80.         else
  81.         {
  82.             $attrId = ' id="form_'.$id.'"';
  83.         }
  84.         $aIds[] = $id;                                                  # keep track of both specified and generated ids!
  85.         // form class
  86.         if ('' != $class)
  87.         {
  88.             $attrClass = ' class="'.$class.'"';
  89.         }
  90.  
  91.         // build HTML fragment
  92.         $result = '<form'.$attrAction.$attrMethod.$attrEnctype.$attrId.$attrClass.'>'."\n";
  93.         if (!$this->config['rewrite_mode'])                             # @@@ is this bit really necessary?
  94.         {
  95.             $hiddenval = $this->MiniHref($method, $page);
  96.             $result .= '<fieldset class="hidden"><input type="hidden" name="wakka" value="'.$hiddenval.'" /></fieldset>'."\n";
  97.         }
  98.  
  99.         return $result;
  100.     }


Supporting methods and other code

As can be seen (and is documented in the docblock) the new FormOpen() method uses a method and a constant that don't exist yet in wikka.php.

ID_LENGTH constant

To avoid excessively long id strings when an id is generated, we take a substring of a shorter length; this length is set via ID_LENGTH. Alternatively, it could be made a configurable value via the configuration file - but for now I've just chosen a reasonable length.

To define it, find this in wikka.php:
define("WAKKA_VERSION", "1.1.6.0");


and insert the following code right after that line:
// other constants
/**
 * Length to use for generated part of id attribute.
 */

define('ID_LENGTH',10);                                         # @@@ maybe make configurable


New existsHandler() method

This method parallels the existsPage() method: it checks whether a handler actually exists. It takes as input what in Wikka is called $method which can be a handler name followed by an (optional) query string; the method chops off the query string and goes looking in the configured handlers path whether a handler file by the specified name exists.

Insert in the //MISC section of wikka.php, right after the ReturnSafeHTML() method:
    /**
     * Check if a handler (specified after page name) really exists.
     *
     * May be passed as handler plus query string; we'll need to look at handler only
     * so we strip off any querystring first.
     *
     * @author      {@link http://wikka.jsnx.com/JavaWoman JavaWoman}
     * @copyright   Copyright © 2005, Marjolein Katsma
     * @license     http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
     *
     * @access  public
     * @uses    _recurseDirs()
     * @uses    DIRSEP
     * @param   string $method "method" which starts with name of handler to check existence of
     * @return  boolean TRUE if handler is found, FALSE otherwise
     */

    function existsHandler($method)
    {
        // initializations
        $exists = FALSE;
        // initialize class constants
        if (!defined('DIRSEP'))
        {
            $this->_initSystem();
        }

        // first strip off any query string
        $parts = preg_split('/&/',$method,1);               # return only one part
        $handler = $parts[0];
        // then check if rest corresponds to a file in the /handlers tree
        $handlersdirtree = $this->_recurseDirs(realpath($this->config['handler_path']));
        foreach ($handlersdirtree as $dirpath)
        {
            if (file_exists($dirpath.DIRSEP.$handler.'php'))
            {
                $exists = TRUE;
                break;
            }
        }
        return $exists;
    }


More supporting code
This public method in turn uses two new private methods, _recurseDirs() and _initSystem().

New _recurseDirs() method

This recursive utility method will build a list of directory paths starting with a given (relative) directory name. This is handy for finding a file with a particular name when it is not known which subdirectory it might live in. We use it now to check for the existence of a handler by a particular name without assuming it is a "page" handler and must be in the page subdirectory (!) but it could also be used for a "smart include" where we store bits of include code categorized in a directory tree without needing to specify a full path for the include or endlessly extending the PHP include path.

Insert right after the existsHandler() method:
    /**
     * Build a list of all subdirectories off a specified base directory (including that "base").
     *
     * The algorithm uses a recursive "depth first" algorithm to find all subdirectories.
     *
     * @author      {@link http://wikka.jsnx.com/JavaWoman JavaWoman}
     * @copyright   Copyright © 2004, Marjolein Katsma
     * @license     http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
     *
     * @access  private
     * @uses    _recurseDirs() (recursion!)
     * @uses    DIRSEP
     * @todo    maybe allow a "breadth first" algorithm as well
     *
     * @param   string  $dir  required: absolute path of the directory to search
     * @return  array   list of directories found (including the base directory as first element)
     */

    function _recurseDirs($dir)
    {
        // array to gather names while recursing
        static $aDirList = array();                                                 # static: gather results through recursion

        $aDirList[] = $dir;                                                         # add current dir to list
        $dh = opendir($dir);
        while (FALSE !== ($thing = readdir($dh)))
        {
            $next = $dir.DIRSEP.$thing;
            if (is_dir($next) && '.' != $thing && '..' != $thing)
            {
                $this->_recurseDirs($next);                                         # ignore return value here
            }
        }
        closedir($dh);

        // return result
        return $aDirList;                                                           # only return final result
    }


Naming
Note that because this is a private method, I've chosen to use a _ prefix for the method name - a common convention for naming private methods.

New _initSystem() method

Some things PHP may need to work with are actually platform-dependent. We'd like to treat them as "constants" but they need to be derived once. The _initSystem() method handles this: by checking whether one of the constants is defined already we can call this method only when needed.

Insert right after the _recurseDirs() method:
    /**
     * Initialize constants (pseudo class constants).
     *
     * Constants for system-dependent values like separators are derived here.
     *
     * @author      {@link http://wikka.jsnx.com/JavaWoman JavaWoman}
     * @copyright   Copyright © 2005, Marjolein Katsma
     * @license     http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
     *
     * @access  private
     */

    function _initSystem()
    {
        define('PATHSEP', PATH_SEPARATOR);  # system-dependent include path separator
        define('DIRSEP', DIRECTORY_SEPARATOR);  # system-dependent directory separator
    }


As it is, this now only uses PHP built-in constants (providing a more convenient short form); it may later be extended with other platform-dependent values and maybe functions.

Naming
As with _recurseDirs(), the _ prefix signals a private method.

Advantages

This new FormOpen() method will solve a number of existing problems in current released and beta code.

Released (1.1.6.0)

Beta features

User contributions
And, of course, user-contributed extensions can use the new method to easily generate valid and consistent forms that can be easily styled.

Exception
There is one exception: the GoogleFormActionInfo google form action needs a form with an external action URI; that isn't handled by FormOpen() which generates forms only for the installed Wikka system. I think we can live with that. :)

Tests? Comments?

Tests (even harsh tests) and comments are very welcome.

--JavaWoman


CategoryDevelopment
There is one comment on this page. [Display comment]
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki