Custom Web Software Development for the 21st CenturySM

PHP, SQLs, JavaScript, Ajax, HTML5, XHTML, & CSS3: Innovative Enterprise level Scripting for interactive sites, SaaS, & cross-platform desktop apps
  • Home
  • Automotive Dealer Website Development
  • Free software: PHP and JavaScript
  • PHP Rainbow-Maker
  • JavaScript MasterColorPicker
  • UniDOM.js x-browser solution +
  • JavaScript <input type="picker">
  • FormFieldGenie Text-Input Auto-Pop-Up
  • JavaScript <input type="range">
  • Form Input eMail Validation Script
  • PHP eMail Validation Script
  • PHP Utility Functions
  • Web Developer Resources
  • FormFieldGenie 3.0 Text-Input Auto-Pop-Up

    This JavaScript powered multifunctional class allows you to create an end-user-manipulatable HTML form that requires an unknown number of text-input form fields.  When you don’t want to limit your visitors to a minimal number of input options, but don’t want your page display to be bloated with a large number of empty input boxes, this is the utility you need.  Since the version 2.2 release, an end-user can even cut, copy, and paste entire sections of an HTML form to/from multi-clip clipboards.  We use this class in our MasterColorPicker package, and you can see useful working integrated examples there (once for the “MyPalette” sub-application, and again for the “Color Filter” extension).

    What do you use this for?  When you have to ask someone to list each of something you request, and they may need one (1) form field, or they may need fifty (50), or anywhere in between or even more!  Consider asking about someone’s children and grandchildren.  In one family there may be two children, each with one grandchild.  What about a large family with 7 children, each with an average of 5 grandchildren?  Do you create a form that accommodates the large family, forcing the small family to wade through your web page like trying to navigate swamp water?  And what about the family with 12 children and 78 grandchildren?  Of course there are functions that say “click this button to create a new form field”. Clicking once or twice may be OK.  I've seen that approach used to upload files, and it annoyed me when I had to upload two dozen, simply because it broke the “flow” selecting each file; and that was all point and click.  When you have to actually type in a phrase, then switch to the mouse to click, then back to the keyboard... This is much easier.

    The best way to understand this class is to exemplify it, so we created a working demo.  You should also view the highlighted demo source code to understand what is happening, why and how.  You can download the JavaScript file from our download page.

    Using this class is simple and strait-forward, requiring only a few JavaScript event handlers be added to the form field(s) you want duplicated (cloned).  But it is no simple class: not only can it clone the form field that the event handlers are attached to, but any associated form fields, their labels, and any other DOM nodes that go along.  We will attempt to explain its use in terms that a non-programmer can understand, so you can incorporate this class successfully in your site, but a basic amount of HTML understanding and a minimum amount of knowledge on using JavaScript in your HTML pages is required.  It has a list of options that allow you to fine-tune the way it works making it incredibly versatile.  Plus it allows plug-in callback functions that allow it infinite flexibility.  It intelligently updates the name associated with each form field cloned based on each individual name; if your given name is incompatible with this class’ capabilities, your custom plugin can handle it.  And it does all this without any additional markup to your HTML.

    When adding a new form-field or group of form-fields (using the popNewField() method), the FormFieldGenie can create (clone) one based on what already exists in the form (more on that below), or you can explicitly give it a form-field or group of form-fields to clone.  You may define an explicit DOM node (form-field or group of form-fields) to clone when creating an instance of the FormFieldGenie;  for example:

    myGenie=new SoftMoon.WebWare.FormFieldGenie({……my options……}, ……my DOM node to clone……); myGenie.popNewField(……)

    After creating an instance of the FormFieldGenie, you may also set the instance.clone (myGenie.clone in these examples) to the explicit DOM node (form-field or group of form-fields) you want to clone (if any).  An example of passing an explicit node to clone:

    myGenie=new SoftMoon.WebWare.FormFieldGenie({……my options……}); myGenie.clone= ……my DOM node to clone…… myGenie.popNewField(………)

    The publicly accessible properties of a FormFieldGenie instance are:

    The publicly accessible methods of a FormFieldGenie instance are:

    popNewField(fieldNodeGroup, opts)
    returns true if a new fieldNodeGroup is ‘popped’ or false if not.
    deleteField(fieldNodeGroup, opts)
    returns true if the fieldNodeGroup was deleted, false if not.
    cutField(fieldNodeGroup, opts)
    returns true if the fieldNodeGroup was deleted, false if not.  fieldNodeGroup will always be copied to the clipboard.
    copyField(fieldNodeGroup, opts)
    returns null. fieldNodeGroup will always be copied to the clipboard.
    pasteField(fieldNodeGroup, opts)
    returns false if the clipboard clip is empty, true if it is pasted.

    Note you can paste two different ways using three different methods:

    ( see “clip” in “options” below for more info on %%your-clip-reference%% )

    The difference between popNewField() and pasteField() is that pasteField() will return false if the clip is empty, while popNewField() will simply pop a new “blank” clone if the clip is empty.  After creating an instance of the FormFieldGenie, the clipboard Object may be accessed through instance.clipboard;  each clipboard Object property may contain an individual clip (DOM node).

    The first parameter passed to this class’ methods is the entire DOM node you want auto-regenerated, deleted, cut, copied, inserted before, or pasted-over.  A quick look at the source code of the demonstration example will help clarify this.  If you only need the form field itself repeated, pass the value of this to the function.  Remember, in an event handler for form fields, the keyword this refers to the DOM node of the form-field itself.  If the field has a <label> tag (or any other tag) around it that you want cloned also, simply pass this.parentNode.  If you want to clone a whole group of fields and associated text, simply repeat parentNode in a chain as many times as needed to move up the DOM tree. 

    From there, the class makes one requirement: there must be a tag enclosing the DOM nodes you want cloned.  It is into this enclosing tag the clones are added.  First, let’s be clear that any HTML “element” tags may be used; it does not matter to the class.  For the sake of discussion, we will refer to these these as follows:

    The fieldNodeGroupFieldset may contain any other HTML besides the clones; but the clones will always be added to the fieldNodeGroupFieldset directly following the last fieldNodeGroup.  See also the groupClass and groupTag options described below.  Beyond this, you are free to develop your page the way you see fit. 

    Any time your keyboard cursor is focused on a fieldNode and then you leave it by pressing the tab key or by clicking the mouse button somewhere else, the class looks at all the fieldNodeGroups within fieldNodeGroupFieldset.  If any are empty (except for the last one), by default it deletes them (you can change this default action with options; see below).  It then looks within the last fieldNodeGroup and by default finds the first text-based <input> tag (only <input> type='text', type='password', or type='file'), or if there are no <input>s, by default finds the first <textarea>, then looks to see if anything was typed in (or similar for type='file').  If this fieldNode has been filled by the user, the class ‘pops’ a new fieldNodeGroup.

    If the end-user pressed the tab key from within a fieldNode (to exit it) and a new fieldNodeGroup was popped, the cursor is focused within a specified fieldNode of the new fieldNodeGroup.  If nothing was popped, the cursor is passed onto the next form field as usual.  If the end-user clicked out of the fieldNode, the usual action is taken depending on where you click, whether or not a new fieldNodeGroup was popped.

    That sums up the main framework of the class.  Its true power begins to shine when you look at how it updates the names associated with each form field cloned, and how the available options you can pass to the class can modify the default actions of the main framework and the process of updating the field names.  This gives it the ability to work with virtually any HTML form of any complexity, and the corresponding supporting server side script.

    When this class updates a name, it considers several things.  Is the name indexed, or not? (i.e. name[index] or name[index][index] etc.)  If it is indexed, is the last index virtual? (i.e. name[] or name[index][] etc.)  Remember in PHP, when multiple form fields return the same virtual indexed name they are automatically placed into a sequentially numbered array.  If the field’s name is not indexed, this function considers whether the last characters are numeric digits, or not. (i.e. name or name123 etc.)  Remember when a form returns multiple fields of the same name without using virtual indexes, the last one overwrites all the others.  It should be mentioned here that if these built-in processes for updating the name don’t suit your needs, you may write your own plug-in that can do anything you want!  We’ll look at that in the section on options, below.  The one other thing to be considered when updating the name is a special case involving the value of checkboxes and radio-buttons; this involves reasons similar to those involved when updating virtually indexed names, and we will take a look at this further down...

    Virtually Indexed Names

    Lets look at these first.  There is generally no need to do anything to these names.  The server side script (PHP) handles them as they should be.  The exception is when you use checkboxes and radio buttons.  Take a look at the source code of the demonstration example and also at the test-demo itself.  Notice how the nickname section uses checkboxes within the fieldNodeGroup to be cloned.  If we simply clone these and leave their names unchanged, the resulting data no longer co-ordinates with its associate data.  You get a sequential array of all the checkboxes checked by the user, but there is no way to tell which array element corresponds to which nickname.  So checkboxes and radio buttons with virtually indexed names are instead treated as fully indexed names.

    Fully Indexed Names

    The class looks for the last index in the name that is numeric and increases it by one for each new field popped.  Note this also applies to checkboxes and radio buttons with virtually indexed names (see the paragraph above).  If no index is found to be numeric, the name is left unchanged.

    Non-indexed Names

    Non-indexed names without digits at the end are simply left alone.  This is generally only useful for radio buttons or <button> tags.  Names that end in a sequence of digits have that sequence numerically increased by one for each new field popped.  Pay close attention to the favorite cars example and the data it produces when the form is submitted.  Note how only the last sequence of digits is increased, and the rest are left alone.

    Special case: checkboxes and radio buttons with “indexed” values

    In some cases the need arises for the value of these form fields to be updated, not the name.  The favorite pets section in the demonstration exemplifies this.  If the value is a number within square brackets (i.e. [0] or [1] or [1756] etc.) it can be updated instead of the name.  Furthermore, you can control what style name (virtual, indexed, non-indexed, or combinations) allows updating the value; while allowing flexibility, this becomes truly invaluable when fieldNodeGroups are nested within each other.  Before we look at how to control this using options, let’s look at options in general.

    Passing Options to the FormFieldGenie

    Passing options to this class is simple: use an object with properties named accordingly.  Any properties may be included or not, making it simple to control any option without worrying about the others.  If you are not so familiar with JavaScript, pay close attention to the demonstration example and how to define an object right in the event handler text.  You can also define your object in a separate <script></script>, then simply pass it to the class by name.  If you don’t understand how to do this, please refer to a good book on JavaScript programming. 

    Options may be defined or passed at three levels: global defaults, instance defaults, and when calling a method.  The class’ methods, popNewField(), cutField(), copyField(), pasteField(), and deleteField(), only take two parameters (the fieldNodeGroup and the options object).  By setting the option values of the defaults property of FormFieldGenie, you can control the option defaults globally.  When creating an instance (myGenie = new SoftMoon.WebWare.FormFieldGenie( { ...options... } )) you can override the global defaults, and when calling instance methods you can override the instance defaults.

    The options object may contain these following properties: (and more, but they will be ignored)

    maxTotal:  number
    maximum number of clones (fieldNodeGroups) in the fieldNodeGroupFieldset.  Note that the default value is 100.  There is no minTotal, as this would impose restrictions on how the fieldNodeGroupFieldset is structured.  To retain a minimum total, use a custom function for dumpEmpties (see below) which can make this distinction.
    indxTier:  number
    Number of index “tiers” to ignore at the end of a name; used to skip over tier(s) when updating names.  climbTiers must be true (see directly below).  Example: name → myField[4][3][2] when indxTier=2 the FormFieldGenie updates/modifies the index that contains “4”.  Note the Genie looks for the next numeric index, so note the following example: name → myField[4][subsection][3][2] when indxTier=2 the FormFieldGenie updates/modifies the index that contains “4”.
    climbTiers:  true | false
    Check all levels of indices for a numeric value (true is default), or only the last?
    updateValue:  "all" | "non-implicit" | "non-indexed" | "indexed" | "implicit"
    Controls the application of updating values instead of names in checkbox and radio-button fields that have values formatted similar to "[0]" Any other passed condition yields no values updated. No passed condition yields the default action "all".
    ===↓ examples ↓===
    all             name  name[string]  name[number]  name[]
    non-implicit    name  name[string]  name[number]
    non-indexed     name
    indexed         name[string]  name[number]
    implicit        name[]
    ===↑ examples only show final indices or lack of; indexed names may have additional indices ↑===
    focusField:  number
    ========= this applies to pasteField() and popNewField() only ========= Pass the field number (counted from zero) of the text/filename field you want the cursor focused on, if the user pressed the tab key or opts.focus=true, when popping or when pasting a new fieldNodeGroup.
    focus: true | false
    ========= this applies to pasteField() and popNewField() only ========= If true, the focusField (see above) will receive focus, whether or not the tab key was pressed.  If false, the focusField will not receive focus when the tab key is pressed.  If no value is passed, then the tab key will cause the focusField to receive focus when popping a new fieldNodeGroup.
    dumpEmpties:  true | false | function(empty_fieldNodeGroupInQuestion, deleteFlag) { …your custom code makes the distinction… }
    remove emptied fields on the fly? ========= this applies to deleteField() and popNewField() only, and not when inserting or pasting ========= If a function is supplied, it should return true | false | null and if null is returned, the function should remove the field itself.  If you use deleteField(), the fieldNodeGroup will be removed even if dumpEmpties===false;  however, if dumpEmpties is a function, it will be called with the value of deleteFlag=true and its return value (true|false) will be respected.
    checkForEmpty:  "all" | "one" | "some"
    ========= this applies to deleteField() and popNewField() only, and not when inserting or pasting ========= If set, the corresponding text/filename fields in the fieldNodeGroup will be checked.  By default only the first one is checked.  If 'one' or 'some', the checkField option should be used also.  If 'some', each of the first checkField number of fields will be checked.
    checkField:  number
    ========= this applies to deleteField() and popNewField() only, and not when inserting or pasting ========= Used in conjunction with checkForEmpty
    Pass the field number (counted from zero) of the field or fields you want checked for being “empty” when popping.  If checkForEmpty='some' the each of the first number of fields will be checked.
    updateName:  function(field, indxOffset, fieldNodeGroupFieldset, params) { …your plugin code… }
    Pass a plugin callback function to handle the process of updating each name. The function will be passed each individual form DOM object (<input> or <textarea> or <select> or <button>) one at a time in the field variable.  The indxOffset variable contains the numerical positional offset of the new field compared to the field passed.  The function should pass back a string of the new name, or null.  If a string is returned, the name of the DOM object will be set to that value;  no need for your function to alter the name directly, unless returning null.  If null is returned, the usual process of updating the name continues.  The callback function may do anything it needs including partial updating the name directly (to be continued by the usual process), and/or updating the value, and/or updating the parentNode text, and/or whatever you can imagine...
    cbParams:  variable
    This will be passed through to the updateName() plugin callback function as the fourth variable (params), and to the isActiveField(), cloneCustomizer(), eventRegistrar() and groupCusomizer() plugin callback functions as the second or third.  It may be any type as required by your plugin callback functions, but if they share you may want to use an object with separate properties.
    isActiveField:  function(fieldNode, params) { …your customizing code… }
    This can replace the standard function to check if a form field is currently active or not; i.e. is it disabled, or is it even displayed at all?  You may add/subtract your own rules, perhaps checking the status of another element.  Inactive elements will not be considered when deciding to pop a new fieldNodeGroup or dump an empty one.  The params variable is the user’s call-back parameters (see cbParams above in this list of user-options).  Your function should return true|false.
    cloneCustomizer:  function(fieldNodeGroup, pasteOver, params) { …your customizing code… }
    If there is something special you want to do to each fieldNodeGroup cloned, you may pass a function to handle that.  All field names will have been updated, but the node will not yet have been added to the document.  The passed variable pasteOver will be true | false | 'paste-over'  — true if pasting and inserting, 'paste-over' if pasting over an existing fieldNodeGroup (the old existing one will be discarded).  The params variable is the user’s call-back parameters (see cbParams above in this list of user-options).  This function is called only when a new fieldNodeGroup is being popped or pasted over.
    eventRegistrar:  function(fieldNodeGroup, pasteOver, params) { …your customizing code… }
    While HTML attributes including event handlers are cloned when a DOM node is cloned, DOM level 2 (and similar for MSIE) event handlers are not cloned.  If you need event handlers registered for any elements in your cloned fieldNodeGroup, you must do them “by hand” through this function.  The function will be passed the fieldNodeGroup after it has been added to the document.  See cloneCustomizer (above) for info on pasteOver and params.  This function is called only when a new fieldNodeGroup is being popped or pasted over.
    groupCusomizer:  function(fieldNodeGroupFieldset, pasteOver, params) { …your customizing code… }
    This is called when a new fieldNodeGroup is being popped, pasted, or when a fieldNodeGroup is deleted or was empty and has been dumped.  It is called from a setTimeout function, so the DOM will be fully updated.  Use it to do any final customizing.  Note it is passed the whole fieldNodeGroupFieldset node containing all fieldNodeGroups including the new one after it has been added to the document, not simply the newly cloned group.  See cloneCustomizer (above) for info on pasteOver and params.
    doso:  true | 'insert' | 'paste'
    ========= this applies to popNewField() and pasteField() only ========= If you pass (Boolean)true when using popNewField(), a new fieldNodeGroup will be popped at the end of the fieldNodeGroupFieldset regardless of whether the last fieldNodeGroup is empty;  but not exceeding maxTotal.  Empty fieldNodeGroups may be removed as usual.  Empty fieldNodeGroups will not be automatically removed if "insert" when using popNewField().  If you pass "insert" or "paste" when using popNewField(), a new field will be popped and inserted before the passed fieldNodeGroup, regardless of whether the last field is empty; but not exceeding maxTotal.
    With popNewField(), “insert” inserts an empty fieldNodeGroup.
    With pasteField(), “insert” inserts the selected clip.
    With popNewField(), “paste” inserts the selected clip.
    addTo:  true
    ========= this applies to popNewField() only ========= If you pass opts.addto=true, then the value that would be passed into popNewField() as fieldNodeGroup will be instead considered the fieldNodeGroupFieldset.  This will allow you to add a new fieldNodeGroup to an empty fieldNodeGroupFieldset but only if •the Genie.clone is set; •or opts.doso='paste' while the clipboard has contents.  Passing opts.addto=true acts similar as passing opts.doso=true in that it will always pop a new field (unless as noted above the fieldNodeGroupFieldset is empty and there is no clone and no paste).  Note that pasteField() with opts.doso='insert' internally calls calls popNewField(), and this option may then take effect.
    clip:  Object-member-identifier === ( instanceof Number || String.match( /^[_a-z][_a-z0-9]*$/i ) )
    ( a.k.a. %%your-clip-reference%% )
    This is a reference to the member of the clipboard object associated with an instance of the FormFieldGenie.  Each FormFieldGenie instance has its own clipboard, and each clipboard can hold an “unlimited” number of clips (limited by the machine).  You may copy, cut and paste into/from any clip.
    groupClass:  string or RegExp
    If you supply a groupClass then the Genie will only consider childnodes of the fieldNodeGroupFieldset that have a matching CSS class to be fieldNodeGroups. 
    groupTag:  string
    If you supply a groupTag then the Genie will only consider childnodes of the fieldNodeGroupFieldset that are matching DOM nodes to be fieldNodeGroups. 

    Most of these options do not need much explanation, especially if you study the demonstration example.  A few do, so we will touch on them here.

    By using the groupClass and/or groupTag, you may include other tags in your fieldNodeGroupFieldset, including form-inputs or tags with form-inputs as children, that are not to be duplicated, automatically cleared, or otherwise messed-with by the Genie. 

    When you want to use a plugin function to update names, you may write one that accepts a set of parameters, and then pass different parameters to your plugin for different form fields.  There is an included plugin with the demonstration example and the downloadable source code that shows how this works.  The supplied standard demo plugin can accept two different parameters (passed in the one object).  You may create your own “order” and pass it through to this plugin, should you choose.  If you understand Perl compatible regular expressions, and can understand how this simple plugin functions, then you could also pass a custom RegExp to the plugin and have even greater control over how it updates the name.  We chose using the first index, because this works well with this plugin’s logic.  Pay attention to the fact that the logic requires matching a single character, then the “incremental word” follows.  Another example of a naming style that would work with this function’s logic is name_first  name_second using the RegExp /_([a-z]+)$/ or name_first[]  using /_([a-z]+)\[/