Login   Register  
PHP Classes
elePHPant
Icontem

File: JS_toolbucket/SoftMoon.FormFieldGenie.js

Recommend this page to a friend!
Stumble It! Stumble It! Bookmark in del.icio.us Bookmark in del.icio.us
  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: 2012-02-17 00:27
Size: 26,346 bytes
 

Contents

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 <http://www.gnu.org/licenses/>   */

//  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(fieldNode.name+"\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 (fieldNode.style && (fieldNode.style.display==='none' || fieldNode.style.visibility==='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())  {
			group.name=updateName(group);
			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 field.name;
			if (field.name.charAt(field.name.length-(2+indxTier))!="[")  {
				if (updateValue=="non-implicit"  &&  valueUpdater(field))  return field.name;
				if (field.name.charAt(field.name.length-(1+indxTier))!="]")  {  // non-indexed  name
					if (updateValue=="non-indexed"  &&  valueUpdater(field))  return field.name;
					if ((valIncr=field.name.match(/(.*[^0-9])?([0-9]+)$/))!==null)
						return ((typeof valIncr[1] !== "undefined") ? valIncr[1] : "") + (Number(valIncr[2])+indxOffset).toString();
					else  return field.name;  }
				else  {  //indexed with contained value  name[value]
					if (updateValue=="indexed"  &&  valueUpdater(field))  return field.name;
					return updateTieredName(field.name, field.name.length-indxTier);  }  }
			else  {  //indexed with no contained value  name[]
				if (updateValue=="implicit"  &&  valueUpdater(field))  return field.name;
				if (field.tagName=='INPUT'  &&  field.type=='checkbox'  &&  field.name.substr(-3)=="][]")
					return updateTieredName(field.name, field.name.length-2);
				else  return field.name;  }  }

		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, lastPosition=field.name.match(pcre);
	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 field.name.substring(0, lastPosition.index+1) +indx+ field.name.substring(lastPosition.index+lastPosition[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