/*
******************************************************************************************************************
  JavaScript MetaData Tagger for Synergic
	Copyright (c) 2004-2005
	National Research Council
	Universit� de Moncton
	
	NRC, Institute for Information Technology - eBusiness, eLearning
	UdeM, IDITAE
	
	Programmed by Luc Belliveau
	luc.belliveau@nrc-cnrc.gc.ca
	
  All Rights Reserved
******************************************************************************************************************	

  tested on IE 6 and Firefox 1.0.6
	
	to modify the output of the tagger, you shouldn't modify this file, instead you should create your own decendant of the 
	tagger definition, and override the outputElements function.  like so:
	
	setInheritance(myCustomDef, jsNRCMetaDataDefinition);
	function myCustomDef(parent) {
		this.parentOutputElements = this.outputElements;
		this.outputElements = custom_outputElements;

	  function custom_outputElements(parent) {
		  // optionally call the inherited method
			// this.parentOutputElements()
			this.Element = new jsNRCDiv(parent, 'this is my overriden md definition');
		}
	};
	
	You may also need to override a few other functions depending on how you output the data, such as the Duplicate function, 
	and the RemoveFromScreen function.
	
	In order for the new definition to be in effect, the user configuration variable "jsMetaDataDefinitionObject" needs to be 
	changed to myCustomDef.  The configuration variables, if modified should be placed in the html template file, after the 
	<script> tag which includes this file into the template.

*/

  // Setup user configurable parameters
	
	// which object to use as the main definition
	var jsMetaDataDefinitionObject = jsNRCMetaDataDefinition;
	
	// CSS classses
	var classMetaDataRepBlock = new String('MetaDataRepBlock');
	var classMetaDataNoRepBlock = new String('MetaDataNoRepBlock');
	var classMetaDataRepInsideBlock = new String('MetaDataRepInsideBlock');
	var classMetaDataNoRepInsideBlock = new String('MetaDataNoRepInsideBlock');

	var classMetaDataListWindowBarRep = new String('MetaDataListWindowBarRep');
	var classMetaDataListWindowBarNoRep = new String('MetaDataListWindowBarNoRep');
	var classMetaDataListWindowBarRepInside = new String('MetaDataListWindowBarRepInside');
	var classMetaDataListWindowBarNoRepInside = new String('MetaDataListWindowBarNoRepInside');

	var classMetaDataSectionHeader = new String('MetaDataSectionHeader');
	var classMetaDataListItemAction = new String('MetaDataListItemAction');
	var classMetaDataIdentifier = new String('MetaDataIdentifier');
	var classMetaDataControlList = new String('MetaDataControlList');
	
	// when a field is moved up or down, it's style is changed to <classMetaDataListWindowBarMoved> for <windowBarMoveTimeout> milleseconds.
	var classMetaDataListWindowBarMoved = new String('MetaDataListWindowBarMoved');
	var windowBarMoveTimeout = 1500;
	

/* **************************************************************************************************** */
	if (typeof pathprefix == 'undefined') pathprefix = '';
	if (typeof overlibpathprefix == 'undefined') overlibpathprefix = '';
  document.writeln('<script src="'+pathprefix+'tagger_comp.js" type="text/javascript"></script>');
  document.writeln('<script src="'+pathprefix+'tagger_md_comp.js" type="text/javascript"></script>');
  document.writeln('<script src="'+overlibpathprefix+'overlib.js" type="text/javascript"></script>');

/* **************************************************************************************************** */

	var jsNRCMetaDataCustomFields = new Array();
	var MetaDataVocabularies = new Array();
	var moveEventObjectReference = new Array();
	var moveEventTimeoutReference = new Array();
	
	function MetaDataVocabulary(VocabularyID, VocabularyName) {
	  this.VocabularyID = VocabularyID;
		this.VocabularyName = VocabularyName;
		this.VocabularyEntries = new Array();
		
		this.AddEntry = AddEntry;
		function AddEntry(VocEntryID, Caption) {
		  this.VocabularyEntries['vocEntry'+VocEntryID] = new MetaDataVocabularyEntry(VocEntryID, Caption);
		};

		this.populateList = populateList;
		function populateList(elem, selectedID, selectedValue) {
		  elem.options[0] = new Option('N/A', -1, false);
		  for (var i in this.VocabularyEntries) {
			  elem.options[elem.options.length] = new Option(this.VocabularyEntries[i].Caption, this.VocabularyEntries[i].VocEntryID, false);
				if (this.VocabularyEntries[i].VocEntryID == selectedID)
				  elem.options[elem.options.length-1].selected = true;
				if (this.VocabularyEntries[i].Caption == selectedValue)
				  elem.options[elem.options.length-1].selected = true;
			};
		};
	};
	
	function MetaDataVocabularyEntry(VocEntryID, Caption) {
		this.VocEntryID = VocEntryID;
		this.Caption = Caption;
	};

	function jsNRCMetaDataDefinition(parent) {
	  this.Element = null;
		this.MetaDataElement = null;
		this.ComponentClass = '';
		this.FieldID = 0;
		this.FieldIdentifier = '';
		this.FieldName = '';
		this.FieldDescription = '';
		this.FieldSize = 0;
		this.FieldMaximumReps = 1;
		this.FieldRequired = 1;
		this.FieldSection = 0;
		this.FieldOrdered = 0;
		this.VocabularyID = 0;
		this.SortID = 0;
		this.MetaDataID = 0;
		this.MetaDataParentID = 0;
		this.MetaDataValue = '';
		this.Attributes = new Array();
		this.VocEntryID = 0;
		this.DefaultMethod = '';
		this.ObjectID = 0;
		this.modified = 1;
		this.instanceName = '';
		
		/* 	Recursive operations that modify the current object cannot be referenced until the 
				function has completed; so in cases where it's required to keep track of changes throughout
				the modification of a node, this element can be set.  (example is finding the lowest metadataid
				when the calling function is generating new ids as it goes)
		*/
		this.recursiveRoot = '';
		
		/* new fields for XML Schema Compatibility */
		this.FieldMinimumReps = 0;
		this.restrictionPatterns = new Array();
		
		
		this.className = (typeof this.className == 'undefined') ? 'jsNRCMetaDataDefinition' : this.className;
		
		this.children = new Array();
		if ( typeof(parent) == "undefined") {
			this.ParentDefinition = this;
			this.rootElement = this;
		} else {
			this.ParentDefinition = parent;
			this.rootElement = parent.rootElement;
		}

		this.RedrawWindowBars = RedrawWindowBars;
		this.generateNewMetaDataID = generateNewMetaDataID;
		this.resetMetaDataID = resetMetaDataID;
		this.resetOwnership = resetOwnership;
		this.groupFieldsTogether = groupFieldsTogether;
		this.determineBarListClass = determineBarListClass;
		this.determineListClass = determineListClass;
		
		this.childrenCount = childrenCount;
		function childrenCount() {
			var i=0;
			for (k in this.children) i++;
			return i;
		}
		
		this.Destroy = Destroy;
		function Destroy() {
		  if (this.Element != null) {
	//			if (this.rootElement.countDefinitions() - def.countDefinitions() <= 3) { alert('If you remove this item the schema will be empty, cannot continue.'); return; };
			  this.RemoveFromScreen();
				sibling = '';
				for (var child in this.ParentDefinition.children) 
					if (child != this) {
						sibling = this.ParentDefinition.children[child];
						break;
					};
				this.ParentDefinition.children = new removeChild(this.ParentDefinition.children, this);
				if (sibling != '') sibling.RedrawWindowBars();
			};
		};
		
		this.RemoveFromScreen = RemoveFromScreen;
		function RemoveFromScreen() {
		  if (this.Element != null) {
		    this.Element.parentNode.parentNode.parentNode.removeChild(this.Element.parentNode.parentNode);
				this.Element = null;
				this.MetaDataElement = null;
			};
		};
		
		function RedrawWindowBars() {
		  for (var i in this.ParentDefinition.children) 
			  this.ParentDefinition.children[i].outputWindowBar();
		};
		
		this.getObjectPath = getObjectPath;
		function getObjectPath(currentDef) {
			if (typeof currentDef == 'undefined') currentDef = this;
			if (currentDef == currentDef.ParentDefinition) return currentDef.instanceName;
			for (var child in currentDef.ParentDefinition.children) {
				if (currentDef.ParentDefinition.children[child] == currentDef) {
					return this.getObjectPath(currentDef.ParentDefinition) + '.children[\'' + child + '\']';
				}
			}
		}
		
		this.Duplicate = Duplicate;
		function Duplicate() {
			var sourceObj = this;
			
			if (countDefinitionsBasedOnFieldID(sourceObj.ParentDefinition, sourceObj.FieldID) < sourceObj.FieldMaximumReps) {
				var def = new duplicateObject(sourceObj);
				def.resetMetaDataID();
				sourceObj.ParentDefinition.children['md'+def.MetaDataID] = def;
				def.resetOwnership(sourceObj.ParentDefinition);
				def.groupFieldsTogether();
				
				var li = new jsNRCMetaDataListItem(def.Element.parentNode.parentNode.parentNode); 
				var ul = new jsNRCMetaDataList(li, def.determineListClass());
				def.outputElements(ul);
				
				def.Element.parentNode.parentNode.parentNode.insertBefore(def.Element.parentNode.parentNode, sourceObj.Element.parentNode.parentNode.nextSibling);
				def.RedrawWindowBars();
			} else {
				alert('This field is limited to ' + sourceObj.FieldMaximumReps + ' repetitions.');				
			}
		};
		
		this.Move = Move;
		function Move(nextDef, direction) {
		  var newArray = new Array();
			var oldIndex = null;
			
			for (var i in this.ParentDefinition.children) {
			  if (this.ParentDefinition.children[i] == this) {
				  oldIndex = i;
					break;
				};
			};
			for (var i in this.ParentDefinition.children) {
			  if (direction == 'down') {
					if (this.ParentDefinition.children[i] != this)
					  newArray[i] = this.ParentDefinition.children[i];
				  if (this.ParentDefinition.children[i] == nextDef)
					  newArray[oldIndex] = this;
				} else if (direction == 'up') {
				  if (this.ParentDefinition.children[i] == nextDef)
					  newArray[oldIndex] = this;
					if (this.ParentDefinition.children[i] != this)
					  newArray[i] = this.ParentDefinition.children[i];
				};
			};
			this.ParentDefinition.children = newArray;
			this.RedrawWindowBars();
		};
		
		this.MoveDown = MoveDown;
		function MoveDown() {
			clearTimeout(moveEventTimeoutReference[this.MetaDataID]);
		  var def = this;
		  moveEventObjectReference[def.MetaDataID] = def.Element;
			def.Element.parentNode.parentNode.parentNode.insertBefore(def.Element.parentNode.parentNode, def.Element.parentNode.parentNode.nextSibling.nextSibling);
			var nextdef = locateNextDefinitionBasedOnFieldID(def, def.FieldID);
			def.Move(nextdef, 'down');
			moveEventTimeoutReference[def.MetaDataID] = setTimeout("moveEventObjectReference["+def.MetaDataID+"].className = '"+moveEventObjectReference[def.MetaDataID].className+"'", windowBarMoveTimeout);
			moveEventObjectReference[def.MetaDataID].className = classMetaDataListWindowBarMoved;
		};
		
		this.MoveUp = MoveUp;
		function MoveUp() {
			clearTimeout(moveEventTimeoutReference[this.MetaDataID]);
			var def = this;
		  moveEventObjectReference[def.MetaDataID] = def.Element;
			def.Element.parentNode.parentNode.parentNode.insertBefore(def.Element.parentNode.parentNode, def.Element.parentNode.parentNode.previousSibling);
			var nextdef = locatePrevDefinitionBasedOnFieldID(def, def.FieldID);
			def.Move(nextdef, 'up');
			moveEventTimeoutReference[def.MetaDataID] = setTimeout("moveEventObjectReference["+def.MetaDataID+"].className = '"+moveEventObjectReference[def.MetaDataID].className+"'", windowBarMoveTimeout);
			moveEventObjectReference[def.MetaDataID].className = classMetaDataListWindowBarMoved;
		};

		this.ShowHelp = ShowHelp;
		function ShowHelp() {
			overlib(this.FieldDescription, LUCDISABLEOTHERS, STICKY, WIDTH, 500, CLOSECLICK, CLOSETEXT, 'Close', CAPTION,'Help', BGCOLOR, '#4f4f4f', FGCOLOR, '#FFFFFF', CLOSECOLOR, '#FFFFFF');
		};
		
		this.ResetDefault = ResetDefault;
		function ResetDefault() {
			if (this.modified == 0) { this.modified = 1; } else { this.modified = 0; };
			this.RedrawWindowBars();
		};
		
		this.generateSerialDelimiter = generateSerialDelimiter;
		function generateSerialDelimiter() {
		  var pool = new String("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-+=!@#$%^&*()_+~?\/.,><:");
			var c = true;
			var count = 0;
			while (c) {
				var delimiter = '';
				for (var i = 0; i <= 3; i++) {
				  r = parseFloat(Math.random()) * parseInt(pool.length);
					delimiter += pool.charAt(r);
				};
				for (var i in this) {
				  c = false;
				  if ((typeof(this[i]) != 'object') && (typeof(this[i]) != 'function')) {
					  var s = new String(this[i].toString());
						if (s.indexOf(delimiter) >= 0) {
						  delimiter = '';
							c = true;
							count++;
							if (count > 100) {
							  delimiter = '!problem getting serializer!';
								c = false;
								break;
							};
							break;
						};
					};
				};
			};
			return delimiter;
		};

		this.serialize = serialize;
		function serialize() {
		  var output = serializeDef(this);
			return output;
		}
	
		function serializeDef(obj) {
			var output = '';
			var delimiter = obj.generateSerialDelimiter();
			output += 'begin:\nobject:jsNRCMetaDataDefinition\n';
			output += 'delimiter: ' + delimiter + '\n';
			for (var i in obj) {
			  if ((typeof(obj[i]) != 'object') && (typeof(obj[i]) != 'function'))
			    output += '  ' + typeof(obj[i]) + ' ' + i + ': ' + delimiter + obj[i] + delimiter + '\n';
				if (i == 'Attributes')
					for (var a in obj[i]) {
						output += '   array ' + i + ': ' + delimiter + a + delimiter + obj[i][a] + delimiter + '\n';
					}
			}
			output += ':end\n';
			for (var i in obj.children) {
			  output += serializeDef(obj.children[i]);
			};
			return output;
		};
		
		this.SaveMetaData = SaveMetaData;
		function SaveMetaData() {
		  return saveData(this);
		};
		
		function saveData(def) {
		  var retvalue = true;
		  if (def.MetaDataElement != null) {
			  if (def.MetaDataElement.UpdateDefWithObject(def) == false)
				  retvalue = false;
			};
			for (child in def.children) {
			  if (saveData(def.children[child]) == false)
				  retvalue = false;
			};
			return retvalue;
		};

/*
		this.retreiveParameter = retreiveParameter;
		function retreiveParameter(paramName) {
		  var params = this.DataTypeComponentParameters.match(/[^=;]+/g);
			for (var i = 0; i < params.length; i+=2) {
 			  if (params[i] == paramName)
				  return params[i+1];
			};
		};
*/
		this.outputWindowBar = outputWindowBar;
		function outputWindowBar() {
			this.Element.innerHTML = '';
			if (this.FieldDescription != '') {
			  var div = new jsNRCDiv(this.Element, '', classMetaDataListItemAction);
				this.actionHelp = new jsNRCMetaDataListItemAction(div, 'help', '', this);
			}
			
			if (this.DefaultMethod != '') {
			  var div = new jsNRCDiv(this.Element, '', classMetaDataListItemAction);
				if (this.modified == 1) {
				  this.actionRevertDefault = new jsNRCMetaDataListItemAction(div, 'default', '', this);
				} else {
				  this.actionRevertDefault = new jsNRCMetaDataListItemAction(div, 'cancel reset', '', this);
				};
			}
			
			if (this.FieldMaximumReps != 1) {
			  var def = locateDefinitionBasedOnFieldID(this.ParentDefinition, this.FieldID);
				if (def != this) {
					var div = new jsNRCDiv(this.Element, '', classMetaDataListItemAction);
					this.actionRemove = new jsNRCMetaDataListItemAction(div, 'remove', '', this);
				};
				var div = new jsNRCDiv(this.Element, '', classMetaDataListItemAction);
				this.actionAdd = new jsNRCMetaDataListItemAction(div, 'add', '', this);
			};
			if (this.FieldOrdered == 1) {
				if (countDefinitionsBasedOnFieldID(this.ParentDefinition, this.FieldID) > 1) {
				  var def = locateLastDefinitionBasedOnFieldID(this.ParentDefinition, this.FieldID);
					if (def != this) {
						var div = new jsNRCDiv(this.Element, '', classMetaDataListItemAction);
				    this.actionMoveDown = new jsNRCMetaDataListItemAction(div,'move down', '', this);
					};
				  var def = locateDefinitionBasedOnFieldID(this.ParentDefinition, this.FieldID);
					if (def != this) {
						var div = new jsNRCDiv(this.Element, '', classMetaDataListItemAction);
				    this.actionMoveUp = new jsNRCMetaDataListItemAction(div,'move up', '', this);
					};
				};
			};

			this.spanIdentifier = new jsNRCSpan(this.Element, this.FieldIdentifier, classMetaDataIdentifier); 
			this.spanName = new jsNRCSpan(this.Element, this.FieldName); 
		
		};
		
		this.countDefinitions = countDefinitions;
		function countDefinitions() {
			var defCount = 1;
			for (var i in this.children)
			defCount += this.children[i].countDefinitions();
			return defCount;
		}

		this.displayTagger = displayTagger;
		function displayTagger(parent) {
			this.outputElements(parent);
			
		}
		
		this.refreshElement = refreshElement;
		function refreshElement() {
			var parent = this.Element.parentNode.parentNode.parentNode;
			var sibling = this.Element.parentNode.parentNode.nextSibling;
			this.RemoveFromScreen();
			var li = new jsNRCMetaDataListItem(parent, ''); 
			if (sibling != null) parent.insertBefore(li, sibling);
			var ul = new jsNRCMetaDataList(li, this.determineListClass());
			this.outputElements(ul);
		}
		
		this.outputElements = outputElements;
		function outputElements(parent) {
			// draw the box header 
			var li = new jsNRCMetaDataListItem(parent, this.determineBarListClass());
			this.Element = li;
			
			// draw the title bar and buttons
			this.outputWindowBar();
			
			// draw the actual input field
			if (this.ComponentClass != '') {
				var li = new jsNRCMetaDataListItem(parent, classMetaDataControlList);
				this.MetaDataElement = new jsNRCMetaDataCustomFields[this.ComponentClass](li); 
				this.MetaDataElement.UpdateObjectWithDef(this);
			};
			
			// render children
			for (var child in this.children) {
				var li = new jsNRCMetaDataListItem(parent, ''); 
				var ul = new jsNRCMetaDataList(li, this.children[child].determineListClass());
				this.children[child].outputElements(ul);
			};
		};
		
		function generateNewFieldID() {
			var lowestID = 0;
			lowestID = findLowestFieldID(this.rootElement, lowestID);
			lowestID--;
			return lowestID;
		}

		this.resetFieldID = resetFieldID;
		function resetFieldID() {
			if (this.rootElement.recursiveRoot == '') this.rootElement.recursiveRoot = this;
			this.FieldID = this.generateNewFieldID();
			this.Attributes = new Array()
			for (var i in this.children) {
				this.children[i].resetFieldID();
			};
			if (this.rootElement.recursiveRoot == this) this.rootElement.recursiveRoot = '';
		};


		function resetOwnership(newOwner) {
			this.ParentDefinition = newOwner;
			this.MetaDataParentID = newOwner.MetaDataID;
			for (var i in this.children)
			  this.children[i].resetOwnership(this);
		}
		
		function resetMetaDataID() {
			if (this.rootElement.recursiveRoot == '') this.rootElement.recursiveRoot = this;
			this.MetaDataID = this.generateNewMetaDataID();
			this.MetaDataValue = '';
			//this.Attributes = new Array();
			this.MetaDataVocValue = 0;
			for (var i in this.children) 
				this.children[i].resetMetaDataID();
			if (this.rootElement.recursiveRoot == this) this.rootElement.recursiveRoot = '';
		};

		function generateNewMetaDataID() {
			var lowestID = 0;
			lowestID = findLowestMetaDataID(this.rootElement, lowestID);
			lowestID--;
			return lowestID;
		}

		function groupFieldsTogether() {
			var newChildrenArray = new Array();
			var FieldIDs = new Array();
			for (var i in this.ParentDefinition.children) {
				if (!existsIn(FieldIDs, this.ParentDefinition.children[i].FieldID)) {
					FieldIDs[FieldIDs.length] = this.ParentDefinition.children[i].FieldID;
				};
			};
			for (var i in FieldIDs) {
				for (var y in this.ParentDefinition.children) {
					if (this.ParentDefinition.children[y].FieldID == FieldIDs[i]) {
						newChildrenArray[y] = this.ParentDefinition.children[y];
					};
				};
			};
			this.ParentDefinition.children = newChildrenArray;
		};

		function determineBarListClass() {
			if (this.FieldSection == 1) {
				return classMetaDataSectionHeader;
			} else {
				if (this.MetaDataParentID == this.rootElement.MetaDataParentID) {
					if (this.FieldMaximumReps != 1) {
						return classMetaDataListWindowBarRep;
					} else {
						return classMetaDataListWindowBarNoRep;
					};
				} else {
					if (this.FieldMaximumReps != 1) {
						return classMetaDataListWindowBarRepInside;
					} else {
						return classMetaDataListWindowBarNoRepInside;
					};
				};
			};
		};

		function determineListClass() {
			if (this.MetaDataParentID == this.rootElement.MetaDataParentID) {
				if (this.FieldMaximumReps != 1) {
					return classMetaDataRepBlock;
				} else {
					return classMetaDataNoRepBlock;
				};
			} else {
				if (this.FieldMaximumReps != 1) {
					return classMetaDataRepInsideBlock;
				} else {
					return classMetaDataNoRepInsideBlock;
				};
			};
		};

		
		
	};
	
	/* -------------- end of jsNRCMetaDataDefinition class ---------------- */
	
	function setInheritance(newObject, parentPrototype) {
		newObject.prototype = new parentPrototype();
		newObject.prototype.parentConstructor = newObject.prototype.constructor;
		newObject.prototype.constructor = newObject;
		newObject.prototype.baseClass = parentPrototype.prototype.constructor;
		cname = new String(newObject);
		cname = cname.match(/function (.*)\(/);
		newObject.prototype.className = cname[1];
	};
	
	function duplicateObject(sourceObject) {
	  for (var i in sourceObject) {
		  if (i == 'children') {
			  this[i] = new Array();
			  for (var child in sourceObject[i]) {
			    this[i][child] = new duplicateObject(sourceObject[i][child]);
				}
		  } else {
		    this[i] = sourceObject[i];
			};
		};
	};

	function removeChild(sourceArray, rem) {
	  for (var i in sourceArray) 
			if (sourceArray[i] != rem) 
				this[i] = sourceArray[i];
	};
	
	function GenerateUniqueDelimiter(values) {
	  var pool = new String("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
		var c = true;
		var count = 0;
		while (c) {
			c = false;
			var delimiter = '';
			for (var i = 0; i <= 3; i++) {
			  r = parseFloat(Math.random()) * parseInt(pool.length);
				delimiter += pool.charAt(r);
			};
			for (var i in values) {
				if (values[i].indexOf(delimiter) >= 0) {
				  delimiter = '';
					c = true;
					count++;
				};
				if (count > 100) {
				  delimiter = ':FIELD_COMPLEX:';
					c = false;
				};
			};
		};
		return delimiter;	
	};
	
	function existsIn(arg, value) {
	  for (i in arg)
		  if (arg[i] == value) return true;
		return false;
	};
	
	/* Find references to definition objects based on metadataid, fieldid.. find the next definition matching a fieldid, things like that */
	/* ------------------------------------------------  Definition Locate Functions -----------------------------------------*/
	function locateDefinitionBasedOnMetaDataID(def, MetaDataID) {
	  if (def.MetaDataID == MetaDataID) {
		  return def;
		} else {
		  for (var i in def.children) {
				t = locateDefinitionBasedOnMetaDataID(def.children[i],MetaDataID);
				if (t != null) {return t};
			};
		};
		return null;
	};

	// this function limits itself to 1 level of recursive searching (used to detect add/remove status)
	function locateDefinitionBasedOnFieldID(def, FieldID) {
	  if (def.FieldID == FieldID) {
		  return def;
		} else {
		  for (var i in def.children) 
			if (def.children[i].FieldID == FieldID) return def.children[i];
		};
		return null;
	};
	
	function locateNextDefinitionBasedOnFieldID(def, FieldID) {
	  var found = false;
		for (var i in def.ParentDefinition.children) {
		if (def.ParentDefinition.children[i].FieldID == FieldID) {
			  if (found) {
				  return def.ParentDefinition.children[i];
				} else if (def.ParentDefinition.children[i] == def){
				  found = true;
				};
			};
	  };
		return null;
	};

	function locatePrevDefinitionBasedOnFieldID(def, FieldID) {
		var prevdef = null;
		for (var i in def.ParentDefinition.children) {
		  if (def.ParentDefinition.children[i] == def)
			  return prevdef;
		  if (def.ParentDefinition.children[i].FieldID == FieldID)
			  prevdef = def.ParentDefinition.children[i];
	  };
		return null;
	};
	
	function locateLastDefinitionBasedOnFieldID(def, FieldID) {
		var lastDef = null;
		for (var i in def.children) {
			if (def.children[i].FieldID == FieldID) lastDef = def.children[i];
		};
		return lastDef;
	};
	
	function countDefinitionsBasedOnFieldID(def, FieldID) {
	  var defCount = 0;
		if (def.FieldID == FieldID) {
		  defCount++;
		} 
	  for (var i in def.children) 
		  if (def.children[i].FieldID == FieldID)
			defCount++;

		return defCount;
	};
	
	function findLowestMetaDataID(def, current) {
		var lowestID = current;
		if (def.MetaDataID < lowestID)
		  lowestID = def.MetaDataID;
		for (var i in def.children)
		  	lowestID = findLowestMetaDataID(def.children[i], lowestID);
		if (def == def.rootElement)
			lowestID = findLowestMetaDataID(def.rootElement.recursiveRoot, lowestID);
		return lowestID;
	}

	function findLowestFieldID(def, current) {
		var lowestID = current;
		if (def.FieldID < lowestID)
		  lowestID = def.FieldID;
		for (var i in def.children)
		  lowestID = findLowestFieldID(def.children[i], lowestID);
		if (def == def.rootElement)
			lowestID = findLowestFieldID(def.rootElement.recursiveRoot, lowestID);
			lowestID = findLowestFieldID(def.rootElement.recursiveRoot, lowestID);
		return lowestID;
	}
	
	/* ----------------------------------------------------------------------------------------------------------- */
