PHP Classes

File: JS_toolbucket/SoftMoon.FormFieldGenie.js

Recommend this page to a friend!
  Classes of Joseph   Rainbow Maker   JS_toolbucket/SoftMoon.FormFieldGenie.js   Download  
File: JS_toolbucket/SoftMoon.FormFieldGenie.js
Role: Auxiliary data
Content type: text/plain
Description: HTML form input auto-popper
Class: Rainbow Maker
Create transparent gradient images
Author: By
Last change:
Date: 12 years ago
Size: 26,346 bytes


Class file image Download
/* PopNewField version 2.0 written by and Copyright © 2010,2011,2012 Joe Golembieski, Softmoon-Webware This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The original copyright information must remain intact. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <> */ // tabspacing: 2 word-wrap: none function concatNodes(nlist) { // use as a method of a node-list-array as array.concat will not work with DOM nodes. for (var i=0; i<nlist.length; i++) {this.push(nlist[i])} } if (typeof SoftMoon !== 'object') SoftMoon=new Object; SoftMoon.FormFieldGenie=function(opts) { this.defaults=new Object; for (o in SoftMoon.FormFieldGenie.defaults) { this.defaults[o]= (typeof opts=='object' && opts.hasOwnProperty(o)) ? opts[o] : SoftMoon.FormFieldGenie.defaults[o]; } } // Apple's Safari and Google's Chrome do not generate an onkeypress event for the Tab Key; use onkeydown SoftMoon.FormFieldGenie.catchTab=function(evnt) { evnt=(evnt || window.event); var code=(evnt.charCode || evnt.keyCode); if (code==9) tabbedOut=true; else tabbedOut=false; if (typeof catchTab.catchKey == 'function') catchTab.catchKey(code); return true; } var tabbedOut=false; /* pass into popNewField: inputNodeGroup = DOM node object - either the text-input / text-box, or the containing node. If a containing node, it may contain any other DOM nodes including nested 'inputNodeGroups'. opts = { maxTotal: maximum number of clones (inputNodeGroups) in this <fieldset> (or other <parent>). There is no minTotal, as this would impose restrictions on how the inputNodeGroupFieldset is structured. To retain a mimimum total, use a custom function for dumpEmpties which can make this distinction. indxTier: number of _characters_ to ignore at the end of a name; used to skip over tier(s) when updating names. 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 (string value) condition passed yields no values updated (use "no" or "none" or "nay" or "nyet" etc). 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 indice or lack of; indexed names may have additional indices === focusField: number Pass the field number (counted from ZERO) of the text/filename field you want the curser focused on when popping a new fieldNodeGroup. dumpEmpties: true | false | function(empty_inputNodeGroupInQuestion) remove emptied fields on the fly? if a function is supplied, it should return true | false | null and if null is returned, the function should remove the field itself. checkForEmpty: 'all' | 'one' | 'some' If set, the corresponding text/filename fields in the nodeGroup 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 Used in conjunction with checkForEmpty Pass the field number (counted from ZERO) of the field or fields you want checked for "Empty" when popping. If checkForEmpty='some' the each of the first number of fields will be checked. callback: function(field, indxOffset, 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 from partial updating the name directly (to be continued by the usual process), to updating the value, to updating the parentNode text, or whatever you can imagine... cbParams: This will be passed through to the update-name plugin callback function as the third variable (params), and to the isActiveField, cloneCustomizer, eventRegistrar and groupCusomizer plugin callback functions as the second. 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. Your function should return true|false. cloneCutomizer: function(fieldNodeGroup, params) { your customizing code } If there is something special you want to do to each nodeGroup 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. This Function is called only when a new fieldNodeGroup is being popped. eventRegistrar: function(fieldNodeGroup, params) { your customizing code } While HTML attributes including event handlers are 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. This Function is called only when a new fieldNodeGroup is being popped. groupCusomizer: function(fieldNodeGroupFieldset, params) { your customizing code } This is called when a new fieldNodeGroup is being popped AND when an empty fieldNodeGroup has been dumped. Use it to do any final customizing. Note it is passed the WHOLE node containing all fieldNodeGroups, not the newly cloned group. doso: true | "insert" | "delete" If you pass (boolean)true, a new field will be popped at the end regardless of whether the last field is empty; but not exceeding maxTotal. Empty inputNodeGroups will be removed as usual. Empty inputNodeGroups will NOT be automatically removed if "insert" or "delete". If you pass "insert", a new field will be popped and inserted BEFORE the passed inputNodeGroup, regardless of whether the last field is empty; but not exceeding maxTotal. If you pass "delete", the inputNodeGroup will be removed even if dumpEmpties===false; however, if dumpEmpties is a function, it will be called and it's return value will be respected. } */ SoftMoon.FormFieldGenie.defaults={ //you may re-define defaults globally through these properties - ¡but do not add or delete properties! maxTotal: 100, IndxTier: 0, climbTiers: true, updateValue: "all", focusField: 0, dumpEmpties: true, /*boolean or user function returns boolean|null*/ checkForEmpty: "one", checkField: 0, isActiveField: null, /*user function - replaces standard function*/ cloneCustomizer: null, /*user function*/ eventRegistrar: null, /*user function*/ groupCustomizer: null /*user function*/ } //You may completely replace this Class default function globally by redefining SoftMoon.FormFieldGenie.isActiveField (as a function) //You may override this default per instance (or per call) // through instance.defaults.isActiveField (or by passing opts.isActiveField) //You may call this Class function from your custom instance function SoftMoon.FormFieldGenie.isActiveField=function(fieldNode) { if (fieldNode.disabled) return false; // alert("\nwidth: "+fieldNode.offsetWidth+"\nheight: "+fieldNode.offsetHeight); //Opera does not seem to set the dimentions of a newly created <input type='file' /> tag as required // by this functiononal class. if (SoftMoon.FormFieldGenie.browser!=="Opera" && typeof fieldNode.offsetWidth == 'number' && (fieldNode.offsetWidth<4 || fieldNode.offsetHeight<4)) return false; // However, it will recognize display: none; from a stylesheet, where some others require the style to be inline. do { if ( && ('none' ||'hidden')) { return false;} } // alert(fieldNode.nodeName); while (fieldNode=fieldNode.parentNode); return true; } SoftMoon.FormFieldGenie.dumpEmpties=function(elmnt, minCount, nName) { var count=0; if (typeof minCount != 'number') minCount=1; elmnt=elmnt.parentNode.childNodes; for (var i=0; i<elmnt.length; i++) { if (elmnt[i].nodeType===Node.ELEMENT_NODE && (typeof nName != 'string' || elmnt[i].nodeName===nName)) count++; } return (count>minCount); } SoftMoon.FormFieldGenie.prototype.browser=function() { try {var browser=navigator.userAgent.match(/(Opera|Chrome|Safari|Firefox|MSIE)/)[0];} catch(e) {var browser="MSIE";} // if it's not a modern browser, assume it's as inept as Microsoft's Internet Explorer return browser; }(); //invoke the function SoftMoon.FormFieldGenie.prototype.popNewField=function(inputNodeGroup, opts) { testFlag=false; // define internal "defaults" var dflt=(typeof this.defaults == "object") ? this.defaults : false; var maxTotal=100, indxTier=0, climbTiers=true, updateValue="all", focusField=0, dumpEmpties=SoftMoon.FormFieldGenie.dumpEmpties || true, checkOne=true, checkAll=false, checkField=0, isActiveField, cloneCustomizer=null, eventRegistrar=null, groupCustomizer=null; //reset to instance/global defaults if (dflt) { if (typeof dflt.maxTotal == "number" && dflt.maxTotal>=1) maxTotal=dflt.maxTotal; if (typeof dflt.indxTier == "number" && dflt.indxTier>=0) indxTier=dflt.indxTier; if (typeof dflt.climbTiers == "boolean") climbTiers=dflt.climbTiers; if (typeof dflt.updateValue == "string") updateValue=dflt.updateValue; if (typeof dflt.focusField == "number" && dflt.focusField>=0) focusField=dflt.focusField; if (typeof dflt.dumpEmpties == "boolean" || typeof dflt.dumpEmpties == "function") dumpEmpties=dflt.dumpEmpties; if (dflt.checkForEmpty==="all") {checkAll=true; checkOne=false;} if (dflt.checkForEmpty==="one") {checkOne=true; checkAll=false;} if (dflt.checkForEmpty==="some") {checkAll=false; checkOne=false;} if ((dflt.checkForEmpty==="some" || dflt.checkForEmpty==="one") && typeof dflt.checkField == "number" && dflt.checkField>=0) checkField=dflt.checkField; if (typeof dflt.isActiveField == "function") isActiveField=dflt.isActiveField; if (typeof dflt.cloneCustomizer == "function") cloneCustomizer=dflt.cloneCustomizer; if (typeof dflt.eventRegistrar == "function") eventRegistrar=dflt.eventRegistrar; if (typeof dflt.groupCustomizer == "function") groupCustomizer=dflt.groupCustomizer; } if (typeof opts !== "object") opts=false; else { // reset to current options if (typeof opts.maxTotal == "number" && opts.maxTotal>=1) maxTotal=opts.maxTotal; if (typeof opts.indxTier == "number" && opts.indxTier>=0) indxTier=opts.indxTier; if (typeof opts.climbTiers == "boolean") climbTiers=opts.climbTiers; if (typeof opts.updateValue == "string") updateValue=opts.updateValue; if (typeof opts.focusField == "number" && opts.focusField>=0) focusField=opts.focusField; if (typeof opts.dumpEmpties == "boolean" || typeof opts.dumpEmpties == "function") dumpEmpties=opts.dumpEmpties; if (opts.checkForEmpty==="all") {checkAll=true; checkOne=false;} if (opts.checkForEmpty==="one") {checkOne=true; checkAll=false;} if (opts.checkForEmpty==="some") {checkAll=false; checkOne=false;} if ((opts.checkForEmpty==="some" || opts.checkForEmpty==="one") && typeof opts.checkField == "number" && opts.checkField>=0) checkField=opts.checkField; if (typeof opts.isActiveField == "function" || opts.isActiveField===null) isActiveField=opts.isActiveField; if (typeof opts.cloneCustomizer == "function" || opts.cloneCustomizer===null) cloneCustomizer=opts.cloneCustomizer; if (typeof opts.eventRegistrar == "function" || opts.eventRegistrar===null) eventRegistrar=opts.eventRegistrar; if (typeof opts.groupCustomizer == "function" || opts.groupCustomizer===null) groupCustomizer=opts.groupCustomizer; } if (typeof isActiveField !== "function") isActiveField=SoftMoon.FormFieldGenie.isActiveField; function getField(fieldNode, check) { if (fieldNode.nodeType!=1) return null; if (!fieldNode.hasChildNodes()) { switch (fieldNode.nodeName) { case "INPUT": { if (fieldNode.type!=='text' && fieldNode.type!=='password' && fieldNode.type!=='file') return null; } case "TEXTAREA": { if (!isActiveField(fieldNode, (opts) ? opts.cbParams : null)) return null; return (check) ? ((fieldNode.value.length==0)^(check=="isFull?")) : fieldNode; } default: return null; } } else var fields=function(fldNode) { var fields=new Array(), n; fields.concat=concatNodes; for (var i=0; i<fldNode.childNodes.length; i++) { n=fldNode.childNodes[i]; if (n.nodeType!=1) continue; if (n.hasChildNodes()) {fields.concat(arguments.callee(n)); continue;} switch (n.nodeName) { case "INPUT": {if (n.type!=='text' && n.type!=='password' && n.type!=='file') continue;} case "TEXTAREA": {if (isActiveField(n, (opts) ? opts.cbParams : null)) fields.push(n);} } } return fields; }(fieldNode); //invoke the function passing fieldNode as the value of fldNode if (check) { if (checkOne) //{ if (testFlag) alert("name:="+fields[checkField].name+"=\nvalue:="+fields[checkField].value +"=\nlength:"+ fields[checkField].value.length +"\nfields.length: "+ fields.length +"\ncheckField: "+ checkField); return (fields.length>checkField) ? (fields[checkField].value.length==0)^(check=="isFull?") : null; //} for (var i=0; i<fields.length; i++) { if ((fields[i].value.length==0)^(check=="isFull?")) {if (!checkAll && i>=checkField) return true;} else return false; } return (fields.length) ? true : null; } else return (fields.length>focusField) ? fields[focusField] : null; } function getNextGroup(nodeGroup) { do {nodeGroup=nodeGroup.nextSibling} while (nodeGroup!==null && (nodeGroup.nodeType!==1 || getField(nodeGroup)===null)); return nodeGroup; } function updateGroupNames(group, indxOffset, resetFlag) { //also reset default values unless resetFlag=false if (typeof indxOffset != "number") indxOffset=1; if (typeof resetFlag != "boolean") resetFlag=true; var elmnt, inputNodes=['input', 'textarea', 'select', 'button' /*, 'map' */], field, i; // you could in theory? use the <map> tag with the javascript: pseudoprotocol in the URLs, in which the script // uses the current name attribute to do something (like pre-enter an index number into a corresponding text field). if (!group.hasChildNodes()) {; if (resetFlag) updateValsEtc(group); } else while (elmnt=inputNodes.pop()) { if (field=group.getElementsByTagName(elmnt)) { for (i=0; i<field.length; i++) { field[i].name=updateName(field[i]); if (resetFlag) updateValsEtc(field[i]); } } } //extend updateGroupNames() function updateValsEtc(field) { if (field.nodeName==='INPUT' && field.type.toLowerCase()==='file') { // alert("==="+field.value+"==="+field.type.toLowerCase()+"==="); continue; field.value=""; //most browsers ignore this anyway if (field.value=="") return; var prop, newFileField=document.createElement('input'); // alert('new'); for (prop in field) { try { if (prop!=='value' && prop!=='defaultValue' && prop!=='id') // && prop!=='attributes' && prop!=='baseURI' && prop!=='document' && prop!=='childNodes' && prop!=='children' && prop!=='parentNode' {newFileField[prop]=field[prop];} } //Opera had a bug that will not propogate the copy of a copy of a copy properly. catch(e) {} } field.parentNode.replaceChild(newFileField, field); return; } if (field.defaultValue!==undefined) field.value=field.defaultValue; if (field.defaultChecked!==undefined) field.checked=field.defaultChecked; if (field.selectedIndex!==undefined) field.selectedIndex=selectDefaults(field); } function updateName(field) { if (opts && typeof opts.callback == "function") { var fieldName=opts.callback(field, indxOffset, opts.cbParams); if (typeof fieldName == "string") return fieldName; } if (updateValue=="all" && valueUpdater(field)) return; if (!="[") { if (updateValue=="non-implicit" && valueUpdater(field)) return; if (!="]") { // non-indexed name if (updateValue=="non-indexed" && valueUpdater(field)) return; if ((*[^0-9])?([0-9]+)$/))!==null) return ((typeof valIncr[1] !== "undefined") ? valIncr[1] : "") + (Number(valIncr[2])+indxOffset).toString(); else return; } else { //indexed with contained value name[value] if (updateValue=="indexed" && valueUpdater(field)) return; return updateTieredName(,; } } else { //indexed with no contained value name[] if (updateValue=="implicit" && valueUpdater(field)) return; if (field.tagName=='INPUT' && field.type=='checkbox' &&"][]") return updateTieredName(,; else return; } } function valueUpdater(field) { var valIncr; if (field.tagName=='INPUT' && (field.type=='radio' || field.type=='checkbox') && (valIncr=field.value.match(/^\[([0-9]+)\]$/))) { field.value="["+(Number(valIncr[1])+indxOffset).toString()+"]"; return true; } } //find and update the last index with a numeric value, or return the original name if none are numeric function updateTieredName(fieldName, position) { var indx; position=(typeof position == "number") ? fieldName.lastIndexOf("[", position-1) : fieldName.lastIndexOf("["); do {indx=( Number(fieldName.substring(position+1, fieldName.indexOf("]", position))) +indxOffset ).toString();} while (indx=="NaN" && climbTiers && position>3 && (position=fieldName.lastIndexOf("[", position-1)) != (-1)); return (indx=="NaN") ? fieldName : fieldName.substring(0, position+1) +indx+ fieldName.substring(fieldName.indexOf("]", position)); } function selectDefaults(slct) { var allOptns=slct.getElementsByTagName('option'), slctdOpt=null; if (allOptns.length==0) return null; for (var i=allOptns.length-1; i>=0; i--) {if (allOptns[i].selected=allOptns[i].defaultSelected) slctdOpt=i;} return slctdOpt; } /*close updateGroupNames*/ } var inputNodeGroupFieldset=inputNodeGroup.parentNode; //may be any parent tag; not limited to <fieldset> <ol> <td> <div> etc. inputNodeGroupFieldset.firstGroup=function() { var firstGroup=this.firstChild; while (firstGroup!==null && (firstGroup.nodeType!==1 || getField(firstGroup)===null)) { firstGroup=firstGroup.nextSibling; } return firstGroup; } inputNodeGroupFieldset.lastGroup=function() { var lastGroup=this.lastChild; while (lastGroup!==null && lastGroup.nodeType!==1) {lastGroup=lastGroup.previousSibling;} return lastGroup; } if (opts && opts.doso==="insert") { var fieldCount=0, fieldPos, fieldNode=inputNodeGroupFieldset.firstGroup(); while (fieldNode) { if (++fieldCount>maxTotal) return; else { if (fieldNode===inputNodeGroup) fieldPos=fieldCount; fieldNode=getNextGroup(fieldNode); } } //the last field should have standard default values, so we clone this one. //the server-side script may accept the list and spit it back out as default values in the form; // if it does this, one more (empty) inputNodeGroup should be added at the end var newField=inputNodeGroupFieldset.lastGroup().cloneNode(true); fieldNode=inputNodeGroup; do {updateGroupNames(fieldNode, 1, false);} while ((fieldNode=getNextGroup(fieldNode))!==null); updateGroupNames(newField, fieldPos-fieldCount); if (typeof cloneCustomizer == "function") cloneCustomizer(newField, (opts) ? opts.cbParams : null); inputNodeGroupFieldset.insertBefore(newField, inputNodeGroup); if (typeof eventRegistrar == "function") eventRegistrar(newField, (opts) ? opts.cbParams : null); if (typeof groupCustomizer == "function") groupCustomizer(inputNodeGroupFieldset, (opts) ? opts.cbParams : null); setTimeout(function() {getField(newField).focus();}, 1); return; } if (opts && opts.doso==="delete") { if ( typeof dumpEmpties == 'function' && !dumpEmpties(inputNodeGroup, true) ) return; var nextNode=getNextGroup(inputNodeGroup); inputNodeGroupFieldset.removeChild(inputNodeGroup); while (nextNode!==null) {updateGroupNames(nextNode, -1, false); nextNode=getNextGroup(nextNode);} if (typeof groupCustomizer == "function") groupCustomizer(inputNodeGroupFieldset, (opts) ? opts.cbParams : null); if (opts.refocus) setTimeout(function() {getField(inputNodeGroupFieldset.lastGroup()).focus();}, 1); return; } var nextNode, fieldFlag, flag, fieldCount=0, removedCount=0; var fieldNode=inputNodeGroupFieldset.firstGroup(); // remove sibling node Groups with empty text fields if (fieldNode!==null) do { nextNode=getNextGroup(fieldNode); fieldFlag=getField(fieldNode, "isEmpty?"); if (fieldFlag!==null) { if (dumpEmpties && nextNode!==null && fieldFlag) { if ( typeof dumpEmpties == 'function' && !(flag=dumpEmpties(fieldNode)) ) { if (flag===false) { fieldCount++; if (removedCount<0) updateGroupNames(fieldNode, removedCount, false); } if (flag===null) removedCount--; } else { inputNodeGroupFieldset.removeChild(fieldNode); removedCount--; } } else { fieldCount++; if (removedCount<0) updateGroupNames(fieldNode, removedCount, false); } } } while (nextNode!==null && (fieldNode=nextNode)); testFlag=true; //alert(fieldCount +"\n"+ maxTotal) if (fieldCount<maxTotal && (getField(inputNodeGroupFieldset.lastGroup(), "isFull?") || (opts && opts.doso))) { // create a new node containing an empty text-input field // clone the node at the end of the <fieldset> (or other <parent>) of the node passed to keep names sequential // clone the whole node to allow wrapper tags // (for example <label> or <fieldset>), other text, other fields, etc. // update all form-control-tag "name"s and reset default values // if the TAB key was pressed to exit this input field, focus the curser at the newly generated field. var newField=inputNodeGroupFieldset.lastGroup().cloneNode(true); updateGroupNames(newField); if (typeof cloneCustomizer == "function") cloneCustomizer(newField, (opts) ? opts.cbParams : null); inputNodeGroupFieldset.appendChild(newField); if (typeof eventRegistrar == "function") eventRegistrar(newField, (opts) ? opts.cbParams : null); } if ((removedCount<0 || newField) && typeof groupCustomizer == "function") groupCustomizer(inputNodeGroupFieldset, (opts) ? opts.cbParams : null); if (tabbedOut && (removedCount<0 || newField)) setTimeout(function () {getField(inputNodeGroupFieldset.lastGroup()).focus();}, 1); } //===================================================================================\\ // plugin for popNewField SoftMoon.FormFieldGenie.updateNameByList=function(field, indxOffset, params) { if (typeof params !== 'object') params=false; var pcre=(params && typeof params.pcre == 'object' && params.pcre instanceof RegExp) ? params.pcre : new RegExp(/\[([a-z]+|[0-9]+)\]/); var order=(params && typeof params.order == 'object' && params.order instanceof Array) ? params.order : ["first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth", "eleventh"]; var indx,; if ( (indx=(Number(lastPosition[1])+indxOffset).toString()) === "NaN" ) { for (var i=0; i<order.length;) {i++; if (lastPosition[1]===order[i-1]) break;} if (i+indxOffset>order.length) indx=(i+indxOffset).toString(); else indx=order[i-1+indxOffset]; } else {if (Number(indx)<order.length && Number(indx)>0) indx=order[Number(indx)-1];} return, lastPosition.index+1) +indx+[1].length+1); } // create a new custom order for the standard plugin updateNameByList SoftMoon.FormFieldGenie.RomanOrder=new Object(); SoftMoon.FormFieldGenie.RomanOrder.order=new Array('i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii', 'ix', 'x', 'xi', 'xii', 'xiii'); SoftMoon.FormFieldGenie.RomanOrder.pcre=null; //use the default Regular Expression; or you may customize this property