/* * DEK JavaScript Library * Copyright(c) 2008-2010, DEK International * * Version 1.0.12 *//* ############################## Domino ############################## *//** * A Utilities class for things related to Notes and Domino. * @class Core.Domino * @singleton */Core.Domino = {	/**	 * Attempts to extract the required alias from a string.  Alias are seperated by "|".  	 * @method getAlias	 * @static	 * @param {String} text The text containing the aliases.	 * @param {Number} element The alias you wish to extract, 1 is the first element.  For negative numbers aliases are counted back from the end, i.e. -1 is the last element.	 */	getAlias: function(text, element) {		if(element==0) {			return text;		} else {			var elements = text.split("|");			var index;			if(element>0) {				index = element-1;				index = Math.min(index, elements.length);			} else {				index = elements.length+element;				index = Math.max(0, index);			}			return elements[index].trim();		}	},	/**	 * Used by the "Web Navigation Bar" sub form to prompt the user if they want to go offline.	 * @method goOffline	 * @static	 * @param {String} url The url for the offline profile document.	 */	goOffline: function(url) {		var msg = "<p>This will intall Lotus Domino Sync Manager if it is not already installed and create a new subscription for this application.</p>";		msg += "<p>If you have already created a subscription for this application, you can open the offline version by pressing on the \"Open Offline\" button in the Sync Manager.</p>";		Core.Ui.msgbox("Go Offline", msg, ["Create Subscription", "Cancel"], Core.Domino.doGoOffline.bind([url]));	},	/* private called by goOffline to check the users response */	doGoOffline: function(event, url) {		if(event.result=="Create Subscription") {			window.open(url, "_top");		}	},	/**	 * This function abbreviates a Notes name.	 * @method abbreviate	 * @static	 * @param {String} name The name to abbreviate.	 * @return {String} The abbreviated name.	 */	abbreviate: function(name) {		var components = new String(name).split("/");		for(var index=0; index<components.length; index++) {			var equalsPos = components[index].indexOf("=");			if(equalsPos>=0) {				components[index] = components[index].substr(equalsPos+1);			}		}		return components.join("/");	},	/*	 * Creates and returns a text represntaion of the Core.Domino class.	 * @method toString	 	 * @return {String}	 */	toString : function() {		return "[Core.Domino]";	}};/* ############################## Page ############################## *//** * Represnts a Domino Page. * @class Core.Domino.Page * @extends Core.Form */Core.Domino.Page = Core.create(Core.Form, {	/**	 * Creates a new Domino page.	 * @method init	 * @constructor	 * @param {HTMLForm} optional form The DOM form element generated by Notes.  Defaults to the first form on the page.	 * @param {Boolean} createSpecialFields (Optional) If true the createSpecialFields method is called. Defaults to false.	 * @param {String} dbPath (Optional) The path of the current database.  Defaults to the value in the dbPath input on the form.	 * @param {String} viewName (Optional) The name of the current view (or view a document was opened from).  Defaults to the value in the viewName input on the form.	 */		init: function(form, createSpecialFields, dbPath, viewName) {		Core.Form.prototype.init.call(this, form ? form : document.forms[0], createSpecialFields);		this.dbPath = dbPath ? dbPath : this.getValue("dbPath");		this.viewName = viewName ? viewName : this.getValue("viewName");				//this.initNavBar();	},	/**	 * The equivalent to <code>@ReplaceSubstring(@ReplicaID; ":"; "")</code> in formula	 * @method getDbId	 * @return {String} This databases replica id.	 */	getDbId : function() {		return this.getValue("dbId");	},	/**	 * The equivalent to <code>"/"+@WebDbName</code> in formula	 * @method getDbUrl	 * @return {String} This databases relative url.	 */	getDbUrl : function() {		return "/"+this.dbPath;	},	/**	 * @method getViewName	 * @return {String} The name of the current view (or view a document was opened from).	 */	getViewName : function() {		if(this.viewName=="") {			return "0"		} else {			return this.viewName;			}	},	/**	 * Used to get the relative url for a view in the current database.  The url will include the parameter count set to -1 in 	 * order to return all the documents and time set to the current time to avoid Internet Explore caching the result.	 * @method getViewUrl	 * @param {String} viewName The name of the view requred	 * @param {String} category (Optional) When specified the RestrictToCategory parameter is used to restict the content of the view.	 * @return {String} This databases relative url.	 */	getViewUrl : function(viewName, category) {		if(!viewName) {			viewName = this.getViewName();			}		if(category) {			category="&RestrictToCategory="+category;		} else {			category="";					}		return this.getDbUrl()+"/"+viewName+"?OpenView"+category+"&count=-1&time=" + (new Date()).getTime();	},	/**	 * Used to get a realitive url for an agent in this database	 * @method getAgentUrl	 * @param {String} agentName The agent name.	 * @return {String} The relative url for the agent.	 */	getAgentUrl : function(agentName) {		return this.getDbUrl()+"/"+agentName+"?OpenAgent";	},		/**	 * Used to perform a dbLookup.  The database lookup is performed using an AJAX request so 	 * the result is not returned from this method but passed to a listener.	 * @method dbLookup	 * @param {String} database The setting profile name of the database to perform the lookup in, use a blank string to specifiy the current database.	 * @param {String} view The view name to perform the lookup on.	 * @param {String} key What to look for in the view.	 * @param Number/{String} column The column number or field name to return.	 * @param {Function} listener The function to be called with the results, it will be passed an array of strings.	 */	dbLookup: function(database, view, key, column, listener) {		var url = this.getAgentUrl("dbLookup")+"&database="+database+"&view="+view+"&key="+key+"&column="+column		Core.addRequest("GET", url, "", this.processDbLookup.bind(this, [listener]));	},	/* process the result of a db lookup and return the results to the listener */	processDbLookup: function(event, listener) {		var xml = event.request.responseXML;			var results = new Array();						if(xml.getElementsByTagName("agent").length>0) {			var entries = xml.getElementsByTagName("value");					if(entries.length>0) {				for(var index=0; index<entries.length; index++) {					var text = "";					for(var node=0; node<entries[index].childNodes.length; node++) {						text += entries[index].childNodes[node].nodeValue;					}					results.push(text);				}			}				}				listener(results);	},	/**	 * Reads a system setting from the database.  The database lookup is performed using an AJAX request so 	 * the result is not returned from this method but passed to a listener.	 * @method getSystemSetting	 * @param {String} key The key to lookup.	 * @param {Function} listener The function to be called with the results, it will be passed an array of strings.	 */	getSystemSetting: function(key, listener) {		this.dbLookup("", "lkSettings", key, 2, listener);	},	/**	 * This updates a system setting in the database by calling the "setSystemSetting" agent.  If the key dosen't exsist it will be created.	 * @method setSystemSetting	 * @param {String} key The System Setting Key	 * @param {String}/{Array} value A string or array of string representing the value for the key.	 */	setSystemSetting: function(key, value) {		if(value.join) {			value = value.join(";");		}		Core.addRequest("POST", this.getAgentUrl("setSystemSetting") + "&key="+key, value);	},		/**	 * This creates a view using a Core.Ui.Table, a Core.Domino.ViewData data source and a 	 * Core.Domino.ViewData.PageListNavigator.  It takes an options object which is first past	 * to the Core.Domino.ViewData and then to the Core.Ui.Table, see these classes for a list	 * of possible opptions.  You do not need to specifiy <code>data</code>, <code>design</code>	 * or <code>nav</code> as these will be set automaticly.</p>	 * <p>There is one additional options used by this method called <code>addDefaultActions</code>. 	 * When this is set to true, <code>Core.Domino.ViewAction.addActions</code> is called for this view.	 * @method createView	 * @param {Object} options The options for the ViewData and Table.	 * @return {Core.Ui.Table} A reference to the table created.	 * @see Core.Domino.ViewData 	 * @see Core.Ui.Table	 */	createView: function(options) {		Core.copyNew(options, Core.Options);		if(!options.hasOption("nav")) {			options.nav = new Core.Domino.ViewData.PageListNavigator(!options.hasOption("category"));		}		if(options.hasOption("viewName")) {			options.cookieName = options.readOption("viewName", "");			options.cookiePath = this.getDbUrl();		}						var viewData = new Core.Domino.ViewData(this, options);		options.data = viewData;		options.design = viewData.getDesignData();				var table = new Core.Ui.Table(options);						if(options.readBooleanOption("addDefaultActions", false)) {			Core.Domino.ViewAction.addActions(this, Core.get(options.readOption("element", null)), table, options.readOption("viewName", ""));		}				viewData.load();				return table;	},	/**	 * This creates a view using a Core.Ui.Table, a Core.Domino.ViewData data source and a 	 * Core.Domino.ViewData.AlphabetNavigator.  It takes an options object which is first past	 * to the Core.Domino.ViewData and then to the Core.Ui.Table, see these classes for a list	 * of possible opptions.  You do not need to specifiy <code>data</code>, <code>design</code>	 * or <code>nav</code> as these will be set automaticly.</p>	 * @method createAlphabetView	 * @param {Object} options The options for the ViewData and Table.	 * @return {Core.Ui.Table} A reference to the table created.	 * @see Core.Domino.ViewData 	 * @see Core.Ui.Table	 */	createAlphabetView: function(options) {		Core.copyNew(options, Core.Options);		options.nav = new Core.Domino.ViewData.AlphabetNavigator();		if(options.hasOption("viewName")) {			options.cookieName = options.readOption("viewName", "");			options.cookiePath = this.getDbUrl();		}		options.firstLoadCmd = "StartKey=A&UntilKey=A_";		options.step = "-1";		//options.entriesPerPage = "-1";						var viewData = new Core.Domino.ViewData(this, options);		options.data = viewData;		options.design = viewData.getDesignData();				var table = new Core.Ui.Table(options);								viewData.load();				return table;	},	/**	 * This creates an outline using a Core.Ui.Tree	 * @method createOutline	 * @param {String} outlineName The name of the outline to base this tree is on.	 * @param {Core.Element} element The element in which to place the Tree.	 * @return {Core.Ui.Tree} A reference to the tree created.	 */	createOutline: function(outlineName, element) {		var data = new Core.Domino.OutlineData(this.getDbUrl()+"/"+outlineName+"?ReadEntries");		data.load();		return new Core.Ui.Tree({			data: data, 			element: element, 			cookieName: outlineName, 			cookiePath: this.getDbUrl()		});	},	/**	 * This creates an outline using a Core.Ui.Tree but uses a view as a data source.  All the 	 * visible columns in the view are concatinated to form the text for the tree node.	 * @method createViewOutline	 * @param {String} viewName The name of the view to create the tree from.	 * @param {Core.Element} element The element in which to place the Tree.	 * @param Boolean flatHierarchy (Optional) When true the text for the tree node is seperated at backslashes (\) and added as child nodes.  Defaults to false.	 * @return {Core.Ui.Tree} A reference to the tree created.	 */	createViewOutline: function(viewName, element, flatHierarchy) {		var viewData = new Core.Domino.ViewOutlineData(this, {viewName:viewName, deapthFirst: true});		var translatedData;		if(flatHierarchy) {			translatedData = new Core.Data.Translation({data: viewData, positionCell:"title", positionSeperator: "\\"});		}		viewData.load();		return new Core.Ui.Tree({			data: flatHierarchy ? translatedData : viewData, 			element: element, 			cookieName: viewName, 			cookiePath: this.getDbUrl()		});	},	/**	 * This creates an outline using a Core.Ui.Navigation	 * @method createNavigationOutline	 * @param {String} outlineName The name of the outline to base this navigation is on.	 * @param {Core.Element} element The element in which to place the navigation.	 * @param {String} url (Optional) The url of the last selected entry, this overides any state saved by a cookie.	 * @param {String} active (Optional) The title of the active entry. The title should be the section title followed by the link title sperated by a /.  For example "Activities/By Company".  This overrides any state saved by a cookie or passed to the url parameter.	 * @return {Core.Ui.Navigation} A reference to the navigation object created.	 */	createNavigationOutline: function(outlineName, element, url, active) {		var data = new Core.Domino.OutlineData(this.getDbUrl()+"/"+outlineName+"?ReadEntries");		data.load();		return new Core.Ui.Navigation({			data: data, 			element: element,			lastUrl: url,			active: active, 			cookieName: outlineName, 			cookiePath: this.getDbUrl()		});	},		/**	 * Used to get all the field values from a document.	 * @method getDocument	 * @param {String} unid The unid of the document you want to load.	 * @param {Function} listener The function to be called with the results, it will be passed an object with each property being a field.	 */	getDocument: function(unid, listener) {		var doc = new Core.Domino.DocumentData(this, unid);		var process = this.processDocument.bind(this, [listener]);		doc.addListeners(process, process);	},	processDocument: function(event, listener) {		if(event && event.newRows && event.newRows.length>0) {			var row = event.newRows[0];			var data = {};			var cellNames = row.getCellNames();			for(var index=0; index<cellNames.length; index++) {				data[cellNames[index]] = row.getValue(cellNames[index]);			}			listener(data);		}	},		initNavBar: function() {		var navBarText = Core.readCookie("navBar");		if(!navBarText) {			new Core.HttpRequest("GET", this.getAgentUrl("loadNavBar"), "", this.loadNavBar.bind(this));		} else {			this.drawNavBar(Core.JSON.parse(navBarText));		}	},	loadNavBar: function(event) {		if(event && event.request.responseText) {			var navBarText = event.request.responseText			Core.createCookie("navBar", navBarText, null, this.getDbUrl());			var navBar = Core.JSON.parse(navBarText);			this.drawNavBar(navBar);		}	},	drawNavBar: function(navBar) {		var bar = Core.get("layoutBar");		var ul;				if(navBar.links.length>0) {			ul = bar.createChild({tag:"ul", klass:"links"});			for(var index=0; index<navBar.links.length; index++) {				var link = navBar.links[index];				ul.createChild({tag:"li", html: link.current ? "<b>"+link.title+"</b>" : link.url && link.url!="" ? "<a href=\""+link.url+"\" target=\""+link.replicaId+"\">"+link.title+"</a>" : link.title});			}		}				ul = bar.createChild({tag:"ul", klass:"settings"});		ul.createChild({tag:"li", html: this.getValue("currentUserAbbreviate")});		if(navBar.settings.goOffline && window.location.hostname!="127.0.0.1:89") {			var li = ul.createChild({tag:"li"});			var link = li.createChild({tag:"a", id: "dolsLink", href:"javascript:", html: "Go Offline"});			link.addListener("click", Core.Domino.goOffline.bind([this.getDbUrl()+"/$All/"+navBar.settings.goOffline]));		}		if(navBar.settings.help) {			var li = ul.createChild({tag:"li", html:"<a href=\""+userGuide+"\" target=\"_blank\">Help</a>"});		}		if(navBar.settings.signOut) {			var li = ul.createChild({tag:"li", html:"<a href=\""+this.getDbUrl()+"?logout&redirectto="+this.getDbUrl()+"\">Sign Out</a>"});		}	},			/*	 * Creates and returns a text represntaion of this domino page	 * @method toString	 * @return {String}	 */	toString : function() {		return "[Core.Domino.Page "+this.serialize()+"]";	}});/* ############################## Document ############################## *//** * Represnts a Domino document, this differes from a page in the fact there is a backend document assosiated with this. * @class Core.Domino.Document * @extends Core.Domino.Page */Core.Domino.Document = Core.create(Core.Domino.Page, {	/**	 * Creates a new Domino Document.	 * @method init	 * @constructor	 * @param {HTMLForm} optional form The DOM form element generated by Notes.  Defaults to the first form on the page.	 * @param {Boolean} createSpecialFields (Optional) If true the createSpecialFields method is called. Defaults to true.	 * @param {String} dbPath (Optional) The path of the current database.  Defaults to the value in the dbPath input on the form.	 * @param {String} viewName (Optional) The name of the current view (or view a document was opened from).  Defaults to the value in the viewName input on the form.	 * @param {String} docId (Optional) The document ID for this document.  Defaults to the value in the docId input on the form.	 */		init: function(form, createSpecialFields, dbPath, viewName, docId) {		Core.Domino.Page.prototype.init.call(this, form, typeof(createSpecialFields)=="boolean" ? createSpecialFields : true, dbPath, viewName);		this.docId = docId ? docId : this.getValue("docId");		this.editMode = (this.form.isDocBeingEdited.value==="true");				Core.EventManager.addListener(this.form, "submit", this.save.bind(this));		this.promptOnClose=this.isDocBeingEdited();	},	/**	 * @method getDocId	 * @return {String} The document ID for this document.	 */	getDocId: function() {		return this.docId;	},		/**	 * Used to get a realitive url for this document, it does not include a ?OpenDocument or ?EditDocument.	 * @method getDocUrl	 * @return {String} The relative url for the document.	 */	getDocUrl: function() {		return this.getDbUrl()+"/"+this.getViewName()+"/"+this.docId;	},	/**	 * Checks to see if this document has not been saved yet.	 * @method isNewDoc	 * @return Boolean True if the document is new.	 */		isNewDoc: function() {		return this.form.isNewDoc.value=="true";	},	/**	 * Checks to see if the document is being edited	 * @method isDocBeingEdited	 * @return Boolean True if the document is being edited.	 */	isDocBeingEdited: function() {		return this.editMode;	},	/** 	 * If view is not already specificed, this will set it.  This is then used when closing this document.	 * @method setDefaultViewName	 * @param {String} view The name of the view.	 */	 setDefaultViewName : function(view) {		if(this.viewName=="") {			this.viewName=view;			this.setValue("viewName", view);		}	},	/**	 * Set a boolean to specify if the user should be prompted when the document is closed with out saving.	 * @method setPromptOnClose	 * @param Boolean prompt True if the user should be prompted, false otherwise	 */	setPromptOnClose: function(prompt) {		this.promptOnClose=prompt;	},		/**	 * This will set an input of the specified name to the value passed, it will also look for an element called "dispX" where x is the element name and set that to the value too.	 * @method setComputedValue	 * @param {String} field The name of an input.	 * @param {String} value The value to set the string	 */	setComputedValue : function(input, value) {		this.setValue(input, value);		var element = Core.get("disp"+input.substr(0,1).toUpperCase()+input.substr(1));		if(element!=null) {			element.setHtml(value);		}	},		/**	 * Closes this document and opens the view this document was opened from.  If the view is unknown	 * opens the database root.  Will prompt the user first if promptOnClose is set to true.	 * @method close	 * @param Object event (Optional) An event objcet, used when triggered from a button for example.	 * @param Boolean force (Optional) When true no prompt will be shown despite the value of promptOnClose.	 */	close: function(event, force) {		if(force || !this.promptOnClose) {			this.doClose();		} else {			Core.Ui.msgbox("Close", "Do you wish to close without saving this document?", ["Yes", "No"], this.doClose.bind(this));		}	},	doClose: function(event) {		if(!event || !event.result || event.result=="Yes") {			var url = this.getDbUrl();			if(this.getViewName()!="0") {				url += "/"+this.getViewName()+"?OpenView";			} 			window.open(url, "_top");		}	},	/**	 * Switch this document to edit mode.	 * @method edit	 */	edit: function(event) {		window.open(this.getDocUrl()+"?EditDocument", "_top");	},	/** 	 * Validates the form, if the validation passes, the form is submitted.	 * @method save	 */		save: function(event) {		if(event) {			Core.EventManager.catchEvent(event);		}		if(this.validate()) {			this.submit();		}	},	/**	 * Prompts the user if they want to delete this document.  If they confirm the deleteDocument 	 * agent called for this document which adds the delete flag to this document.	 * @method softDelete	 */	softDelete: function(event) {		Core.Ui.msgbox("Delete Document", "<p>Delete this document?</p>", ["Delete","Cancel"], this.doSoftDelete.bind(this));	},	doSoftDelete: function(event) {		if(event.result=="Delete") {			Core.addRequest("GET", this.getAgentUrl("deleteDocuments")+"&ids="+this.docId, "", this.close.bind(this, [true]));		}	},		/**	 * This is used to automaticly copy the contents of one input to another every time the field is updated.  	 * This is useful when the domino backend field is hidden from the user and another entry field is used 	 * to collect data from the user.	 * @method setSyncField	 * @param String/HTMLInput/Core.Element source Either the name of an input, the input element or Core.Element for the input element to watch for changes.	 * @param String/HTMLInput/Core.Element destination Either the name of an input, the input element or Core.Element for the input element to update with the sources value.	 */	setSyncField: function(source, destination) {		this.addListener(source, "change", this.syncField.bind(this, [source, destination], true));	},	syncField: function(source, destination) {		this.setValue(destination, this.getValue(source));	},		/**	 * This will perform a dbLookup and use the results to set the avalible options in a combo box.	 * @method updateComboOptions	 * @param String/HTMLInput/Core.Element source Either the name, the input element or Core.Element for the combo input element whose options should be updated.	 * @param {String} database The setting profile name of the database to perform the lookup in, use a blank string to specifiy the current database.	 * @param {String} view The view name to perform the lookup on.	 * @param {String} key What to look for in the view.	 * @param Number/{String} column The column number or field name to return.	 * @param Boolean sort (Optional) When true the new entries will be sorted, defaults to false.	 * @param {Array} staticEntries (Optional) An array of strings containg all the static entries that should be added to the top of the combo box.	 */	updateComboOptions: function(input, db, view, key, column, sort, staticEntries) {		this.dbLookup(db, view, key, column, this.doUpdateComboOptions.bind(this, [input, sort, staticEntries]));	},	doUpdateComboOptions: function(results, input, sort, staticEntries) {		if(this.hasInput(input)) {			sort = typeof(sort)=="boolean" ? sort : false;			staticEntries = staticEntries ? staticEntries : new Array();			var oldValue = this.getValue(input);			var element = this.getElement(input);							element.getDom().options.length=0;			if(staticEntries.length>0) {				for(var index=0; index<staticEntries.length; index++) {					var value = Core.Domino.getAlias(staticEntries[index], -1);					var display = Core.Domino.getAlias(staticEntries[index], 1);					var selected = value==oldValue;											element.getDom().options[index] = new Option(display, value, false, selected);				}			}						if(results.length>0) {				if(sort) {					results.sort(this.sortUpdateComboOptions);				}				for(var index=0; index<results.length; index++) {					var value = Core.Domino.getAlias(results[index], -1);					var display = Core.Domino.getAlias(results[index], 1);					var selected = value==oldValue;									element.getDom().options[index+staticEntries.length] = new Option(display, value, false, selected);				}			} else {				if(staticEntries.length==0) {					element.getDom().options[0] = new Option("", "", false, true);				}			}		}	},	sortUpdateComboOptions: function(a, b) {		var displayA = Core.Domino.getAlias(a, 1);		var displayB = Core.Domino.getAlias(b, 1);		return displayA == displayB ? 0 : displayA < displayB ? -1 : 1;	},		/**	  * Creates the history view for this document .	  * @method createHistoryView	  * @param Object options The options for the Core.Ui.Table used to display the history. You do not need to specifiy data or design as these will be set automaticly.	  * @param {Array} columns (Optional) An array of four booleans, each one representing a column to show in the history, when true ths column is shown.  The four columns are User, Type, Action and Date.  When unspecified all columns are shown.	  * @param {String} field (Optional) The field which contains the history, defaults to "history".	  * @return {Core.Ui.Table} A table with this documents history in it.	  * @see Core.Ui.Table	  */	createHistoryView: function(options, columns, field) {		// design		columns = typeof(columns)=="undefined" ? [true, true, true, true] : columns;		field = typeof(field)=="undefined" ? "history" : field;				var columnsArray = new Array();		if(columns[0]) {			columnsArray.push({name: "user", title: "User", width: "120px"});		}		if(columns[1]) {			columnsArray.push({name: "type", title: "Type", width: "120px"});		}		if(columns[2]) {			columnsArray.push({name: "action", title: "Action",width: "auto"});		}		if(columns[3]) {			columnsArray.push({name: "date", title: "Date and Time", width: "150px"});		}		options.design = new Core.Data.Array(columnsArray);			// data			options.data = new Core.Domino.HistoryData(this, field);		options.data.load();						// table		var table = new Core.Ui.Table(options);		return table;	},	/**	 * Creates a dialog and displays a history view in it.  The dialog is then shown.	 * @method showHistoryDialog	 * @param Object event (Optional) Normally this method would be triggered by a user event, this should be that event.	 * @param {String} field (Optional) The field which contains the history, defaults to "history"	 */	showHistoryDialog: function(event, field) {		field = typeof(field)=="undefined" ? "history" : field;		if(!this.historyDialogs) {			this.historyDialogs=[];		}		if(!this.historyDialogs[field]) {			this.historyDialogs[field] = new Core.Domino.HistoryDialog(this, field);		}		this.historyDialogs[field].show();	},	/**	 * Creates a dialog that allows a user to enter a comment, this comment is then added to the document history,	 * @method showHistoryCommentDialog	 * @param {String} title The title of the dialog	 * @param Boolean instantSave When true the document will be saved with the new comment when the user closes the dialog, otherwise it will not happen until the user saves the form.	 * @param {Function} listener A temporary listener for the dialog close event.	 */	showHistoryCommentDialog: function(title, instantSave, listener) {		if(!this.historyCommentDialog) {			content = "";			content +="<div class=\"fields\">";			content +="<table cellpadding=\"0\" cellspacing=\"0\">";			content +="<tr class=\"first\"><td><textarea name=\"comments\" class=\"full\"></textarea></td></tr>";			content +="</table>";			content +="</div>";							this.historyCommentDialog = new Core.Ui.Dialog(true, false, "", "", "", ["OK", "Cancel"]);			this.historyCommentDialog.closeEvent.addListener(this.addHistoryComment.bind(this));			var formElement = this.historyCommentDialog.getContentElement().createChild({tag: "form", html:content});			this.historyCommentForm = new Core.Form(formElement.getDom());		}		if(!title) {			title = "Add Comment";		}		this.historyCommentDialog.setTitle(title);		this.historyCommentForm.setValue("comments", "");		if(instantSave) {			this.historyCommentDialog.closeEvent.addTempListener(this.saveHistoryComment.bind(this));		}		if(listener) {			this.historyCommentDialog.closeEvent.addTempListener(listener);		}		this.historyCommentDialog.show();	},	/* private	 * Called when the history dialog is closed, if the user pressesd ok, the history is updated with the comment	 * @method addHistoryComment	 * @param Object event The event information object for the dialog close event.	 */	addHistoryComment: function(event) {		if(event.result=="OK") {			var comment = this.historyCommentForm.getValue("comments");			comment = comment.replace(/\r/g, "");			comment = comment.replace(/\n/g, "<br/>");			comment = comment.replace(/"/g, "&quot;");			if(comment.trim()!="") {				var oldHistory = this.getValue("history").trim();				this.setValue("history", (oldHistory=="" ? "" : oldHistory+",")+"{user: \"" + this.getValue("currentUserCN") + "\", type: \"Comment\", action: \"" + comment + "\", date: \"" + (new Date).format("dd-mmm-yyyy hh:nn:ss") + "\"}");			}		}	},	/* private	 * Called when the history dialog is closed if instantSave was true, if the user pressesd ok, the history is saved in the backend document.	 * @method saveHistoryComment	 * @param Object event The event information object for the dialog close event.	 */	saveHistoryComment: function(event) {		if(event.result=="OK") {			var comment = this.historyCommentForm.getValue("comments");			comment = comment.replace(/\r/g, "");			comment = comment.replace(/\n/g, "<br/>");			comment = comment.replace(/"/g, "&quot;");			if(comment.trim()!="") {				Core.addRequest("POST", this.getAgentUrl("updateHistory")+"&id="+this.getDocId()+"&user="+this.getValue("currentUserCN")+"&type=Comment", comment, this.updateHistoryListener.bind(this));			}		}	},	/**	 * Updates the history with a new entry.	 * @method updateHistory	 * @param String type The action type for history entry.	 * @param String action A free text description for the entry.	 * @param Boolean instantSave (Boolean) When true the document will be saved with the new entry now, otherwise it will not happen until the user saves the form.  Defaults to false.	 */	updateHistory: function(type, action, instantSave) {		if(instantSave) {			Core.addRequest("POST", this.getAgentUrl("updateHistory")+"&id="+this.getDocId()+"&user="+this.getValue("currentUserCN")+"&type="+type, action, this.updateHistoryListener.bind(this));		} else {			var oldHistory = this.getValue("history").trim();			this.setValue("history", (oldHistory=="" ? "" : oldHistory+",")+"{user: \"" + this.getValue("currentUserCN") + "\", type: \""+type+"\", action: \"" + action + "\", date: \"" + (new Date).format("dd-mmm-yyyy hh:nn:ss") + "\"}");		}	},	/* private	 * Called when the save history comment ajax request completes, it re-syncs the history and removes the field %%ModDate to avoid replication save conflicts.	 * @method updateHistoryListener	 * @param Object event The event information object for ajax response.	 */	updateHistoryListener: function(event) {		var xml = event.request.responseXML;					var agentNodes = xml.getElementsByTagName("agent");		if(agentNodes.length>0) {			if(agentNodes[0].getAttribute("status")=="ok") {				var historyNodes = agentNodes[0].getElementsByTagName("history");				if(historyNodes.length>0) {					this.setValue("history", historyNodes[0].firstChild.nodeValue);				}			}		}				if(this.hasInput("%%ModDate")) {			this.getElement("%%ModDate").remove();		}	},		/*	 * Creates and returns a text represntaion of this domino page	 * @method toString	 * @return {String}	 */	toString : function() {		return "[Core.Domino.Document "+this.getDocUrl()+"]";	}});/** * A data object used to load notes documents, each row will be a document and each cell will be an item form that document. * @class Core.Domino.DocumentData *@extends Core.Data.XML */Core.Domino.DocumentData = Core.create(Core.Data.XML, {	/**	 * Creates a new documentData object	 * @method init	 * @constructor	 * @param {Core.Domino.Page} dominoPage The current domino page.	 * @param {String} id (Optional) A comma seperated list of document ids which should be loaded.	 */	init: function(dominoPage, id) {		Core.Data.XML.prototype.init.call(this, {			rootNode: "documents",			rowNode: "document",			positionAttribute: "unid",			cellAttributes: ["unid"],			cellNode: "item",			cellNodeNameAttribute: "name",			cellNodeValueAttribute: "value",			cellNodeTypeAttribute: "type"		});				this.url = dominoPage.getAgentUrl("readDocuments");				if(id && id!="") {			this.load(id);		}	},	/**	 * Loads data in to this object.	 * @method load	 * @param {String} id A comma seperated list of document ids which should be loaded.	 */	load: function(id) {		Core.addRequest("GET", this.url + "&ids=" + id, "", this.loadData.bind(this, [true, true]));	}});/* ############################## History ############################## *//** * A data object used to load a documents history. * @class Core.Domino.HistoryData * @extends Core.Data */Core.Domino.HistoryData = Core.create(Core.Data, {	/**	 * Create a  new document history object.	 * @method init	 * @constructor	 * @param {Core.Domino.Document} dominoDoc The document for which you are loading the history.	 * @param {String} fieldName (Optional) The field where the histroy is stored, defaults to "history".	 * @param Boolean descending (Optional) When true the history is shown in reverse chronological order, defalts to false.	 */	init: function(dominoDoc, fieldName, descending) {		Core.Data.prototype.init.call(this);				this.dominoDoc = dominoDoc;		this.fieldName = typeof(fieldName)=="undefined" ? "history" : fieldName;		this.descending = typeof(descending)=="undefined" ? false : descending;	},	/**	 * Loads the history data	 * @method load	 */	load: function() {		this.resetData();				// get history		var history = Core.JSON.parse("["+this.dominoDoc.getValue(this.fieldName)+"]");				for(var line=this.descending ? history.length-1 : 0; this.descending ? line>=0 : line<history.length; this.descending ? line-- : line++) {			var row = this.addRow();			for(var property in history[line]) {				var value = history[line][property].trim();				if(value=="") {					value="&nbsp;";				}				row.addCell(Core.Data.STRING, property, value);			}		}				this.fireUpdateEvent();	}	});/** * The history dialog extends the normal functionality of the the dialog class by automaticly  * setting up the dialog and adding a filter button (that doesn't close the dialog). * @class Core.Domino.HistoryDialog * @extends Core.Ui.Dialog */Core.Domino.HistoryDialog = Core.create(Core.Ui.Dialog, {    /**	 * Creates a new history dialog. The Dialog is initially craeted hidden.	 * @method init	 * @constructor	 * @param {Core.Domino.Document} dominoDoc The document for which you are loading the history.	 * @param {String} field (Optional) The field which contains the history, defaults to "history"	 */	init : function(dominoDoc, field) {		Core.Ui.Dialog.prototype.init.call(this, true, false, "historyDialog", "History", "", ["Close"]);		var formDiv = this.getContentElement().createChild({tag: "div", klass: "form"});		var viewDiv = formDiv.createChild({tag: "div", klass: "view"});		this.view = dominoDoc.createHistoryView({element: viewDiv, header: true, footer: false, expandColumn: "action", filterColumn: "action", filterValue: "Document saved", filterPolarity: false}, [true, true, true, true], field);	},	/**	 * Makes the popup visible.	 * @method show	 * @param Object event (Optional) If the show opertaion is triggered by an event, this should be the event information object of that event.	 */	show: function(event) {		this.view.reload();		Core.Ui.Dialog.prototype.show.call(this, event);	},	/**	 * Sets the actions (buttons) for this dialog. With the extra filter button	 * @method setActions	 * @param {Array} actions An array of strings, each represnting a button to be displayed.  	 */	setActions: function(actions) {		this.footElement.clear();		var actionBar = this.footElement.createChild({tag:"div",  klass:"actionBar"});		var actionGroup = actionBar.createChild({tag:"div",  klass:"actionGroup"});		this.filterButton = Core.Ui.addButton(actionGroup, "Show All", true, this.filterListener.bind(this));				if(actions.length>0) {			if(typeof(actions[0])=="string") {				actionGroup = actionBar.createChild({tag:"div",  klass:"actionGroup actionGroupRight"});				for(var action=0; action<actions.length; action++) {					Core.Ui.addButton(actionGroup, actions[action], action==0, this.actionListener.bind(this, [actions[action]]));				}			} else {							for(var group=0; group<actions.length; group++) {					actionGroup = actionBar.createChild({tag:"div",  klass:"actionGroup actionGroupRight"});					for(var action=0; action<actions[group].length; action++) {						Core.Ui.addButton(actionGroup, actions[group][action], action==0, this.actionListener.bind(this, [actions[group][action]]));					}							}			}		}	},	/**	 * Listens to the filter button	 * @method filterListener	 * @param Object event The event	 */	filterListener: function(event) {		if(this.filterButton.getHtml()=="Show All") {			this.filterButton.setHtml("Filter")			this.view.setFilter("action", "", false);		} else {			this.filterButton.setHtml("Show All")			this.view.setFilter("action", "Document saved", false);		}	}});/* ############################## DB Lookup Dialog ############################## *//** * A data object used with dbLookups * @class Core.Domino.DbLookupData * @extends Core.Data.SimpleArray */Core.Domino.DbLookupData = Core.create(Core.Data.SimpleArray, {	/**	 * Create a  new document history object.	 * @method init	 * @constructor	 * @param {Core.Domino.Page} page The current page	 * @param {String} database The setting profile name of the database to perform the lookup in, use a blank string to specifiy the current database.	 * @param {String} view The view name to perform the lookup on.	 * @param {String} key What to look for in the view.	 * @param Number/{String} column The column number or field name to return.	 */	init: function(dominoDoc, database, view, key, column) {		Core.Data.prototype.init.call(this);				dominoDoc.dbLookup(database, view, key, column, this.load.bind(this));	}});/** * Uses a table dialog to display the results of a DB lookup for making a selection. * @class Core.Domino.DbLookupDialog * @extends Core.Ui.TableDialog */Core.Domino.DbLookupDialog = Core.create(Core.Ui.TableDialog, {	/**	 * This creates the view dialog.	 * @method init	 * @constructor	 * @param {Core.Domino.Page} page The current page	 * @param {String} database The setting profile name of the database to perform the lookup in, use a blank string to specifiy the current database.	 * @param {String} view The view name to perform the lookup on.	 * @param {String} key What to look for in the view.	 * @param Number/{String} column The column number or field name to return.	 * @param Boolean selectMany When true the user will be able to select multiple rows.	 * @param {String} dialogTitle The title for the dialog.	 */	init: function(page, database, view, key, column, selectMany, dialogTitle) {		Core.Ui.TableDialog.prototype.init.call(			this, 			new Core.Data.Array([				{					name: "data",					title: "Data",					width: "auto"				}			]), 			new Core.Domino.DbLookupData(page, database, view, key, column), 			"data", 			selectMany, 			dialogTitle		);	}});/** * A DB lookup dialog field converts a normal html input elment into a field with a DB lookup dialog popup. * @class Core.Domino.DbLookupDialogField * @extends Core.Ui.DialogField * @see Core.Domino.DbLookupDialog */Core.Domino.DbLookupDialogField = Core.create(Core.Ui.DialogField, {	/**	 * Creates a new new view dialog field	 * @method init	 * @constructor	 * @param {Core.Domino.Page} dominoPage The current domino page.	 * @param {String}/HTMLInput/{Core.Element} field The name, HTML input or Core.Element for the input to replace.	 * @param {String} database The setting profile name of the database to perform the lookup in, use a blank string to specifiy the current database.	 * @param {String} view The view name to perform the lookup on.	 * @param {String} key What to look for in the view.	 * @param Number/{String} column The column number or field name to return.	 * @param Boolean selectMany When true the user will be able to select multiple rows.	 * @param {String} dialogTitle The title for the dialog.	 */	init: function(dominoPage, field, database, view, key, column, selectMany, dialogTitle) {		Core.Ui.DialogField.prototype.init.call(this, dominoPage, field, new Core.Domino.DbLookupDialog(dominoPage, database, view, key, column, selectMany, dialogTitle));	},	toString : function() {		return "[Core.Domino.DbLookupDialogField]"	}});/* ############################## View ############################## *//** * A data object used to load the design of a view, this is then mapped in to a format  * reconginsed Core.Ui.Table.  You would not normaly create a view design object * directly as one is created by a ViewData object. * @class Core.Domino.ViewDesign * @extends Core.Data.XML * @see Core.Domino.ViewData */Core.Domino.ViewDesign = Core.create(Core.Data.XML, {	/**	 * Creates a new view design object.	 * @method init	 * @constructor	 * @param {String} url The url for the view.	 * @param Boolean singleCategory Specify as true when loading data for a single category view.	 */	init: function(url, singleCategory) {		Core.Data.XML.prototype.init.call(this, {			url: url,			rootNode: "viewdesign",			rowNode: "column",			cellAttributes: ["name", "columnnumber", "title", "width", "icon", "align", "sortcategorize"],			cellMap: {				"name": "fieldName",				"columnnumber": "name",				"sortcategorize": "categorise"			}		});				this.singleCategory = singleCategory;	},	/* over ridden to ignore the first column on single category views */	loadRow: function(parent, rowNode) {		if(!this.singleCategory || Core.XML.getAttribute(rowNode, "columnnumber", null)!=0) {			return Core.Data.XML.prototype.loadRow.call(this, parent, rowNode);		} else {			return null;		}	},	/* over ridden to is decrease the column number by one in single category views */	loadCellAttribute: function(row, rowNode, attributeName) {		var value = Core.XML.getAttribute(rowNode, attributeName, null);		if(value!=null) {			if(this.singleCategory && attributeName=="columnnumber") {				value = ""+(parseInt(value)-1);			} else if(attributeName=="align") {				if(value==1) {					value="right";				} else {					value="left";				}			}			row.addCell(Core.Data.STRING, this.getCellName(attributeName), value);		}	}});/** * <p>A data object used to load a view.  The view data object will also create a view design object which can be a accessed via the getDesignData method.</p> * <p>You can confugure what and how the data is loaded by passing an options object to the construtor. The options object can have the following parameters:</p> * <table cellspacing="0" cellpadding="0" > * <tr><th style="width: 120px;">Option Name</th><th style="width: 120px;">Type</th><th>Description</th></tr> * <tr><td>viewName</td><td>String</td><td>The name of the view is this database to load.  Either viewName or viewUrl must be defined.</td></tr> * <tr><td>viewUrl</td><td>String</td><td>The url of the view to load, this should not include any parameters such as <code>?OpenView</code>.  Either viewName or viewUrl must be defined.</td></tr> * <tr><td>urlParameters</td><td>String</td><td>Any additional parameters you want ot add to the end of the url e.g. <code>&currency=usd</code>.</td></tr> * <tr><td>category</td><td>String</td><td>(Optional) When specified the view will only load a single category equal to this.</td></tr> * <tr><td>categoryTotals</td><td>Boolean</td><td>(Optional) When true category totals rows will be displayed, defaults to true.</td></tr> * <tr><td>deapthFirst</td><td>Boolean</td><td>(Optional) When true view rows will be loaded deapth first, when false rows will be loaded level at a time.  Defaults to false.</td></tr> * <tr><td>step</td><td>Number</td><td>(Optional) The number of rows to load in each ajax request. Defaults to 100</td></tr> * <tr><td>entriesPerPage</td><td>Number</td><td>(Optional) The number of rows to load for each page.  -1 means there is no limt.  Defaults to 500</td></tr> * <tr><td>nav</td><td>Core.Domino.ViewData.Navigator</td><td>(Optional) The navigator for this data object.</td></tr> * <tr><td>firstLoadCmd</td><td>String</td><td>(Optional) When specified the parameters that should be used in the first view load, for example "StartKey=A&UntilKey=A_".  A cookie would override this.</td></tr> * <tr><td>cookieName</td><td>String</td><td>(Optional) The state of the data source is saved in a cookie, this defines the name of the cookie.  Defaults to "", which means no cookie will be created.</td></tr> * <tr><td>cookiePath</td><td>String</td><td>(Optional) The state of the data source is saved in a cookie, this defines the path of the cookie.  Defaults to "/".</td></tr> * </table> * @class Core.Domino.ViewData *@extends Core.Data.XML */Core.Domino.ViewData = Core.create(Core.Data.XML, {	/**	 * Creates a new view data object.	 * @method init	 * @constructor	 * @param {Core.Domino.Page} dominoPage The current domino page.	 * @param Object options The options for this view data object as described above.	 */	init: function(dominoPage, options) {		Core.Data.XML.prototype.init.call(this, {			rootNode: "viewentries",			rowNode: "viewentry",			positionAttribute: "position",			siblingsAttribute: "siblings",			childrenAttribute: "children",			cellAttributes: ["unid"],			cellNode: "entrydata"		});				// view and url		this.dominoPage = dominoPage;		Core.copyNew(options, Core.Options);				this.viewName = options.readOption("viewName", "");		this.viewUrl = options.readOption("viewUrl", dominoPage.getDbUrl() + "/" + this.viewName);		this.category = options.readOption("category", "");		this.category = this.category=="" ? "" : "&RestrictToCategory="+this.category;		this.categoryTotals = options.readBooleanOption("categoryTotals", true);		this.timeStamp = "&time=" + (new Date()).getTime();		this.urlParameters = options.readOption("urlParameters", "");				// flags and counters		this.deapthFirst = options.readBooleanOption("deapthFirst", false);		this.working = false;		this.cancel = false;		this.entriesLoaded = 0;		this.step = options.readIntOption("step", 100);				this.entriesPerPage = options.readIntOption("entriesPerPage", 500);				// navigation		this.nav = options.readOption("nav", null);		if(this.nav!=null) {			this.nav.setData(this, this.entriesPerPage);		}		this.startCmd = "";		this.lastStartCmd = "";		this.firstStartCmd = options.readOption("firstLoadCmd", "");						// data		this.columnCount = 0;		this.rows = new Array();		 		// cookie		this.cookieName = options.readOption("cookieName", "");		this.cookiePath = options.readOption("cookiePath", "/");		if(this.cookieName!="") {			var cookieValue = Core.readCookie("viewDataStartCmd_"+this.cookieName);			if(cookieValue!=null) {				this.firstStartCmd = cookieValue;			}		}				// design data		this.designData = new Core.Domino.ViewDesign(this.viewUrl + "?ReadDesign" + this.getUrlSuffix(), this.category!="");		this.designData.load();	},		/**	 * If the view data was initially created with a category, this alows you to change the category with out re-loading the page.  Calling this will reload the data.	 * @method changeCategory	 * @constructor	 * @param {String} category The new category.	 */	changeCategory: function(category) {		if(this.category!="" && category!="" && this.category!= "&RestrictToCategory="+category) {			this.category = "&RestrictToCategory="+category;			this.load();		}	},		/* private: used by row to get the database url */	getDbUrl: function() {		return this.dominoPage.getDbUrl();	},	/* private: used by row to get the view url */	getUrl: function() {		return this.viewUrl;	},	/* private: the category and time stamp parts of the view url */	getUrlSuffix: function() {		return this.category + this.timeStamp + this.urlParameters;	},	/* private: used to get the prefix for link urls */	getLinkUrlPrefix: function() {		return this.dominoPage.getDbUrl()+"/"+this.dominoPage.getViewName();	},	/* private: returns true when there is a ajax request in progress */	isWorking: function() {		return this.working;	},	/* private: changes working status */	setWorking: function(working) {		this.working = working;		if(working) {			Core.Ui.showStatusMessage("Loading view data...", false, 500);		} else {			Core.Ui.hideStatusMessage("Loading view data...");		}	},	/* private: true when an operation has been canceled */	isCancel: function() {		return this.cancel;	},	/* private: changes the cancel value */	setCancel: function(cancel) {		this.cancel = cancel;	},	/* private: get howmany documents to load in one ajax request */	getStep: function() {		return this.step;	},	/**	 * @method getDesignData	 * @return {Core.Domino.ViewDesign} The design for this view.	 */	getDesignData: function() {		return this.designData;	},	/* overridden so Core.Domino.ViewData.Row objects are created for rows in the data tree */	createRow: function(parent, position) {		return new Core.Domino.ViewData.Row(this, parent, position); 	},	/**	 * Loads the data from the view.	 * @method load	 * @param {String}/{Number} start (Optional) Specifies which data to load.  When unspecifed the data is loaded from the start of the view. When the start is a number, data is loaded from this row number.  When start is a string the data is loaded from the first enty that starts with that text.	 */	load: function(start) {		if(typeof(start)=="string") {			this.loadPage("StartKey="+start);		} else if(typeof(start)=="number") {			this.loadPage("Start="+start);		} else if(this.firstStartCmd!="") {			this.loadPage(this.firstStartCmd);			this.firstStartCmd="";		} else {			this.timeStamp = "&time=" + (new Date()).getTime();			this.lastStartCmd = "";			this.loadPage("Start=1");		}			},	/*	 * Triggers a load, if there is an ajax request currenlty is progress, this is canceled first.  If the startCmd is the same as the last run command it is ignored.	 * @method loadPage	 * @param {String} startCmd 	 */	loadPage: function(startCmd) {		if(startCmd && startCmd!="") {			this.startCmd=startCmd;		}		if(this.startCmd!=this.lastStartCmd) {			if(this.isWorking()) {				this.setCancel(true);				window.setTimeout(this.loadPage.bind(this), 200);			} else {				this.setWorking(true);							this.entriesLoaded = this.entriesPerPage<0 ? -2 : 0;				this.lastStartCmd = this.startCmd;				Core.createCookie("viewDataStartCmd_"+this.cookieName, this.startCmd, null, this.cookiePath);				if(this.deapthFirst) {					Core.addRequest("GET", this.viewUrl + "?ReadViewEntries&" + this.startCmd + "&Count=" + this.step + "&ExpandView" + this.getUrlSuffix(), "", this.loadData.bind(this, [true]));				} else {					Core.addRequest("GET", this.viewUrl + "?ReadViewEntries&" + this.startCmd + "&Count=" + this.step + "&CollapseView" + this.getUrlSuffix(), "", this.loadData.bind(this, [true]));				}			}		}	},	/*	 * Loads all the direct children of a row	 * @method loadChildren	 * @param {String} startCmd 	 */	loadChildren: function(row) {		if(this.isWorking()) {			window.setTimeout(this.loadChildren.bind(this, [row], true), 200);		} else {			this.setWorking(true);						this.entriesLoaded = -2;			var start = row.getFullPosition().join(".");			var count = this.step>0 ? Math.min(this.step, row.getChildren()) : row.getChildren();			Core.addRequest("GET", this.viewUrl + "?ReadViewEntries&Start=" + start + ".1&Count=" + count + "&Expand=" + this.getUrlSuffix(), "", this.loadData.bind(this, [false]));		}	},	/* over ridden so the navigation is updated each time data is loaded and to work out if anouther set of data should be loaded */	loadData: function(event, reset) {		var rowsLoaded = Core.Data.XML.prototype.loadData.call(this, event, reset, false);				// update nav		if(reset && rowsLoaded>0 && this.nav!=null) {			var firstRow = this.getRows()[0];			this.nav.updateNav(parseInt(firstRow.getPosition()), firstRow.getSiblings(), firstRow, this.lastStartCmd);		}						// load next set		var finished = true;					if(rowsLoaded>0 && this.step>0 && this.entriesLoaded<this.entriesPerPage && !this.isCancel()) {			if(this.deapthFirst) {				var row = this.getLastRow();								var start = row.getFullPosition().join(".")+".1";				finished = false;				Core.addRequest("GET", this.viewUrl + "?ReadViewEntries&Start=" + start + "&Count=" + this.step + "&ExpandView" + this.getUrlSuffix(), "", this.loadData.bind(this, [false]));			} else {				var start = parseInt(this.getLastRow().getPosition())+1;				if(start<this.getLastRow().getSiblings()) {					finished = false;					var fullStart = this.getLastRow().getFullPosition();					fullStart[fullStart.length-1]=start;					var count = Math.min(this.step, this.getLastRow().getSiblings()-start+1);					Core.addRequest("GET", this.viewUrl + "?ReadViewEntries&Start=" + fullStart.join(".") + "&Count=" + count + "&Expand=" + this.getUrlSuffix(), "", this.loadData.bind(this, [false]));				}			}		}								if(finished) {			this.setWorking(false);			if(this.isCancel()) {				this.setCancel(false);			}		}				// fire update event		this.fireUpdateEvent();	},	/* overridden to add the unid and url for the document as cells in the row.  Also to track the total number of rows loaded. */	loadRow: function(parent, rowNode) {		if(this.categoryTotals || Core.XML.getAttribute(rowNode, "categorytotal", "")!="true") {			var row = Core.Data.XML.prototype.loadRow.call(this, parent, rowNode);			var unid = Core.XML.getAttribute(rowNode, "unid", "");			if(unid!="") {				row.addCell(Core.Data.STRING, "url", this.getLinkUrlPrefix() + "/" + unid + "?OpenDocument");			}					if(this.entriesLoaded>=0) {				this.entriesLoaded++;			}						return row;		} else {			return null;		}	},	/* overridden to extract the value for the cell and deal with the various data types */	loadCell: function(row, cellNode) {		var type = Core.Data.STRING;		var name = Core.XML.getAttribute(cellNode, "columnnumber", "0");		var value = "&nbsp;";		var indent = parseInt(Core.XML.getAttribute(cellNode, "indent", 0));		// get design info		var column = this.designData.getRow(name);					// find data		var dataNode = cellNode.firstChild;		while(dataNode!=null && dataNode.nodeType!=1) {			dataNode = dataNode.nextSibling;		}								// get value		if(dataNode!=null) {			if(dataNode.nodeName=="text") {				if(dataNode.childNodes.length>0) {					value = dataNode.firstChild.nodeValue;				}			} else if(dataNode.nodeName=="textlist") {				var textNodes = dataNode.getElementsByTagName("text");				var values = new Array();				for(var index=0; index<textNodes.length; index++) {					if(textNodes[index].childNodes.length>0) {						values.push(textNodes[index].firstChild.nodeValue);					}				}				value = values.join(", ");			} else if(dataNode.nodeName=="number") {				type=Core.Data.NUMBER;				if(dataNode.childNodes.length>0) {					value = dataNode.firstChild.nodeValue;				}				if(column) {					if(column.hasCell("icon") && column.getValue("icon")=="true") {						type=Core.Data.ICON;					}				}			} else if(dataNode.nodeName=="datetime") {				type=Core.Data.DATE;				if(dataNode.childNodes.length>0) {					var text = dataNode.firstChild.nodeValue.substr(0,8);					var date = new Date();					date.setFullYear(parseInt(text.substr(0,4), 10), parseInt(text.substr(4,2), 10)-1, parseInt(text.substr(6,2), 10));					value = date;				}			} else {				value = "Unknown Data Type: "+dataNode.nodeName;			}		}				// create cell		//row.addCell(Core.Data.CUSTOM, name, new Core.Domino.ViewData.Cell(type, name, value, indent));		var cell = row.addCell(type, name, value);		cell.setIndent(indent);			}});/* private class: Represents a row in a view. Overridden a row can load it children.*/Core.Domino.ViewData.Row = Core.create(Core.Data.Row, {	init: function(table, parent, position) {		Core.Data.Row.prototype.init.call(this, table, parent, position);				this.needToLoad = true;	},	addRow: function(position) {		this.needToLoad = false;		return Core.Data.Row.prototype.addRow.call(this, position);			},	loadChildRows: function() {		if(this.needToLoad) {			this.needToLoad = false;			this.table.loadChildren(this);		}	}});/* private class: Represents a cell in a view. Overridden to support indents.*//*Core.Domino.ViewData.Cell = Core.create(Core.Data.Cell, {	init: function(type, name, value, indent) {		Core.Data.Cell.prototype.init.call(this, type, name, value);		this.indent = indent;	},	getIndent: function() {		return this.indent;	}});*//* ############################## View Navigation ############################## *//** * This is an super class for view navigation.  The Core.Domino.ViewData object breaks up data  * into "pages" each page showing the number of entires as specified by the <code>entriesPerPage *</code> option.  This super class provides the methods and properties to track the data loaded and * to perform simple "scroll to" searching on the view.  The default view navigation does not have footer  * navigation so the footer of the table will not be updated. * @class Core.Domino.ViewData.Navigator * @extends Core.Ui.Table.Navigator * @see Core.Domino.ViewData */Core.Domino.ViewData.Navigator = Core.create(Core.Ui.Table.Navigator, {	/**	 * Creates a new view navigator.	 * @method init	 * @constructor	 */	init: function() {		this.viewData = null;		this.table = null;				this.postion = 1;		this.topLevelEntries = 1;		this.entriesPerPage = 500;					this.searchText="";				this.submitSearch=this.submitSearch.bind(this);		this.searchTimer=null;	},	/**	 * Called by the Core.Domino.ViewData constuctor to set a link to the data object.	 * @method setData	 * @param {Core.Domino.ViewData} data The data object.	 * @param {Number} entriesPerPage The entries per page.	 */	setData: function(data, entriesPerPage) {		this.data = data;		this.entriesPerPage = entriesPerPage	},	/**	 * Called by the Core.Domino.ViewData when ever the the data is updated from an ajax	 * request.  It is to inform the navigator of howmany top level entries there are and which	 * ones are currently displayed.	 * @method updateNav	 * @param {Number} position The position for the first entry in the data source	 * @param {Number} topLevelEntries The number of top level entries in this view.	 * @param {Core.Data.Row} firstRow The first row.	 * @param {String} startCmd The start value used to load this data	 */	updateNav: function(position, topLevelEntries, firstRow, startCmd) {		this.position = position;		this.topLevelEntries = topLevelEntries;	},	/**	 * This is called from the table to regiter it with the navigation.	 * @method registerTable	 * @param {Core.Ui.Table} tabel This is the table.	 */	registerTable: function(table) {		this.table = table;	},	/**	 * This is called from the table to draw the navigation in the footer of the table.  The default	 * navigation does not have footer navigation so this does nothing.	 * @method craeteFooterNavigation	 * @param {Core.Element} footerElement This is the footer element for the table.	 */	createFooterNavigation: function(footerElement) {		// do nothing	},	/**	 * This is a listener for a search fields in the navigation or else where on the form, 	 * it should be added as the "keyup" listener for the input.  When the input is updated	 * the text entered is passed to the <code>search</code> method.  If the last key	 * pressed was Enter, <code>search</code> is called with <code>now</code> set	 * to true.	 * @method searchTextEntered	 * @param {Object} event The keyboard event generated by the input.	 */	searchTextEntered: function(event) {		var characterCode = event.keyCode;			this.search(event.target.value, characterCode==13);	},	/**	 * Performs a "scoll to" search on the view, essetially loading the view with the 	 * startKey set to the searchText.	 * @method search	 * @param {String} searchText The text the first entry in the view sould try to match.	 * @param Boolean now (Optional) When true the search is started now.  When false the search is delayed for half a second, this is useful when listening to an input for the search text, the result being the search is not started until the user pauses.  The default is false.	 */	search: function(searchText, now) {		if(typeof(searchText)=="string") {			window.clearTimeout(this.searchTimer);			this.searchText=searchText;						if(now){				this.submitSearch();			} else {				this.searchTimer = window.setTimeout(this.submitSearch, 500);			}		}	},	/* private called by seach to submit the search */	submitSearch: function() {		if(this.table!=null) {			this.table.clearState();		}		this.data.load(this.searchText);	},		toString : function() {		return "[Core.Domino.ViewData.Navigator]";	}});/** * Exapands on the default navigation by adding basic page navigation to the footer. * The footer navigation has previous and next page buttons with a message to  * display which page you are currently viewing and howmany pages there are in total. * @class Core.Domino.ViewData.BasicNavigator * @extends Core.Domino.ViewData.Navigator */Core.Domino.ViewData.BasicNavigator = Core.create(Core.Domino.ViewData.Navigator, {	/**	 * Creates a new view navigator.	 * @method init	 * @constructor	 */	init: function() {		Core.Domino.ViewData.Navigator.prototype.init.call(this);		this.prev = null;		this.next = null;		this.text = null;	},	/**	 * This is called from the table to draw the navigation in the footer of the table.  The 	 * footer navigation has previous and next page buttons with a message to display 	 * which page you are currently viewing and howmany pages there are in total.	 * @method craeteFooterNavigation	 * @param {Core.Element} footerElement This is the footer element for the table.	 */	createFooterNavigation: function(footerElement) {		var navElement = footerElement.createChild({tag: "div", klass: "nav"});				this.next = navElement.createChild({tag: "div", klass: "pageList", html: "<a>&gt;</a>"});		this.next.addListener("click", this.pageListClicked.bind(this));		this.next.addListener("mouseover", Core.Element.highlight.bind(["highlight", true]));		this.next.addListener("mouseout", Core.Element.highlight.bind(["highlight", false]));				var textDiv = navElement.createChild({tag: "div", klass: "pageNumber"});		this.text = textDiv.createChild({tag: "span", html: "1 of 1"});				this.prev = navElement.createChild({tag: "div", klass: "pageList", html: "<a>&lt;</a>"});		this.prev.addListener("click", this.pageListClicked.bind(this));		this.prev.addListener("mouseover", Core.Element.highlight.bind(["highlight", true]));		this.prev.addListener("mouseout", Core.Element.highlight.bind(["highlight", false]));	},	/**	 * Called by the Core.Domino.ViewData when ever the the data is updated from an ajax	 * request.  It is to inform the navigator of howmany top level entries there are and which	 * ones are currently displayed.  This data is used to update the display in the footer 	 * hiding any irrelevant buttons, updating the current page and updating the total number	 * of pages.	 * @method updateNav	 * @param {Number} position The position for the first entry in the data source	 * @param {Number} topLevelEntries The number of top level entries in this view.	 * @param {Core.Data.Row} firstRow The first row.	 * @param {String} startCmd The start value used to load this data	 */	updateNav: function(position, topLevelEntries, firstRow, startCmd) {		if(this.text!=null) {			this.position = position;			this.topLevelEntries = topLevelEntries;						if(this.position>1) {				this.prev.show();			} else {				this.prev.hide();			}			if(this.position<this.topLevelEntries-this.entriesPerPage) {				this.next.show();			} else {				this.next.hide();			}			this.text.setHtml("Displaying "+position+" to "+(position+this.entriesPerPage-1)+" of "+topLevelEntries);		}	},	/* private, triggered when a button is pressed, it causes the page to change */	pageListClicked: function(event) {		if(event && event.target && event.target.innerHTML) {			var action = event.target.innerHTML;			var start = this.position;						if(action=="&lt;") {				start = Math.max(start-this.entriesPerPage, 1);			} else if(action=="&gt;") {				start = Math.min(start+this.entriesPerPage, this.topLevelEntries-this.entriesPerPage);			} 						if(this.table!=null) {				this.table.clearState();			}			this.data.load(start);		}			},		toString : function() {		return "[Core.Domino.BasicViewNavigator]";	}});/** * Exapands on the default navigation by adding search and a page list to the footer. * The footer navigation has a "Starts with" text box for searching.  A first, previous, next  * and last page buttons to switch page.  A list of page numbers with up to four pages  * in both direction. A text input with the current page number which can be changed  * by the user and howmany pages there are in total. * @class Core.Domino.ViewData.PageListNavigator * @extends Core.Domino.ViewData.Navigator */Core.Domino.ViewData.PageListNavigator = Core.create(Core.Domino.ViewData.Navigator, {	/**	 * Creates a new page list view navigator.	 * @method init	 * @constructor	 * @param Boolean search (Optional) When true the "Starts with" search box is added to the footer, defaults to true.	 */	init: function(search) {		Core.Domino.ViewData.Navigator.prototype.init.call(this);		this.pageList = null;		this.pageNumberInput = null;		this.pageCountSpan = null;				this.addSearch = typeof(search)=="boolean" ? search : true;				this.pageNumber = 1;		this.pageCount = 1;	},	/**	 * This is called from the table to draw the navigation in the footer of the table.  See	 * above for what is included in the navigation.	 * @method craeteFooterNavigation	 * @param {Core.Element} footerElement This is the footer element for the table.	 */	createFooterNavigation: function(footerElement) {		if(this.addSearch) {			var searchBar = footerElement.createChild({tag: "div", klass: "search"});			searchBar.createChild({tag: "span", html: "Starts with: "});			this.searchInput = searchBar.createChild({tag: "input", type: "text", value: ""});			this.searchInput.addListener("keyup", this.searchTextEntered.bind(this));		}			var navPageNumber = footerElement.createChild({tag: "div", klass: "pageNumber"});		navPageNumber.createChild({tag: "span", html: "Page"});		this.pageNumberInput = navPageNumber.createChild({tag: "input", type: "text", value: "1"});		this.pageNumberInput.addListener("keypress", this.pageNumberEntered.bind(this));		this.pageNumberInput.addListener("click", this.inputFocused);		this.pageNumberInput.addListener("focus", this.inputFocused);		this.pageCountSpan = navPageNumber.createChild({tag: "span", html: "of 1"});				this.pageList = footerElement.createChild({tag: "div", klass: "pageList"});		this.pageList.addListener("click", this.pageListClicked.bind(this));		this.pageList.addListener("mouseover", Core.Element.highlight.bind(["highlight", true]));		this.pageList.addListener("mouseout", Core.Element.highlight.bind(["highlight", false]));	},	/**	 * Called by the Core.Domino.ViewData when ever the the data is updated from an ajax	 * request.  It is to inform the navigator of howmany top level entries there are and which	 * ones are currently displayed.  This data is used to update the display in the footer 	 * hiding any irrelevant buttons, updating the current page and updating the total number	 * of pages.	 * @method updateNav	 * @param {Number} position The position for the first entry in the data source	 * @param {Number} topLevelEntries The number of top level entries in this view.	 * @param {Core.Data.Row} firstRow The first row.	 * @param {String} startCmd The start value used to load this data	 */	updateNav: function(position, topLevelEntries, firstRow, startCmd) {		if(this.pageList!=null) {			this.position = position;			this.topLevelEntries = topLevelEntries;			this.pageNumber = Math.ceil((parseInt(position)-1) / this.entriesPerPage)+1;			this.pageCount = this.pageNumber + Math.ceil((topLevelEntries-position) / this.entriesPerPage) -1;							var links = "";			var start = Math.max(this.pageNumber-4, 1);			var end = Math.min(this.pageNumber+4, this.pageCount);			if(start>1) links += "<a class=\"text\">First</a>";			if(this.pageNumber>1) links += "<a>&lt;</a>";			for(var index=start; index<=end; index++) {				var klass = index==this.pageNumber ? " class=\"current\"" : "";				links += "<a"+klass+">"+new String(index);+"</a>";				}			if(this.pageNumber<this.pageCount) links += "<a>&gt;</a>";			if(end<this.pageCount) links += "<a class=\"text\">Last</a>";			this.pageList.setHtml(links);							this.pageNumberInput.getDom().value=this.pageNumber;			this.pageCountSpan.setHtml("of "+this.pageCount);		}		if(this.searchInput!=null) {			if(this.searchInput.getDom().value=="" && startCmd.substr(0, 9)=="StartKey=") {				this.searchInput.getDom().value=startCmd.substr(9);			}		}	},	/* private triggered when a button in the page list is clicked, changes to the relevent page */	pageListClicked: function(event) {		if(event && event.target && event.target.innerHTML) {			var action = event.target.innerHTML;			var start = this.position;						if(action=="First") {				start = 1;			} else if(action=="&lt;") {				if(this.pageNumber>1) {					start-=this.entriesPerPage;				}			} else if(action=="&gt;") {				if(this.pageNumber<this.pageCount) {					start+=this.entriesPerPage;				}			}  else if(action=="Last") {				start+= (this.pageCount-this.pageNumber)*this.entriesPerPage;			} else {				var newPageNumber = parseInt(action);				start+= (newPageNumber-this.pageNumber)*this.entriesPerPage;			}									start=Math.max(1, Math.min(start, this.topLevelEntries));			if(this.table!=null) {				this.table.clearState();			}			this.data.load(start);		}			},	/* private when the page number box recives focus, causes the text to be selected */	inputFocused: function(event) {		event.target.select();	},	/* private called when the user enters data in to the page number box, when enter is pressed the page is loaded */	pageNumberEntered: function(event) {		var characterCode = event.keyCode;				if(characterCode == 13){ // ascii 13 is enter key			Core.EventManager.catchEvent(event);			var newPage = parseInt(this.pageNumberInput.getDom().value);			if(!isNaN(newPage) && newPage>0 && newPage<=this.pageCount) {				var start = (newPage-1) * this.entriesPerPage + 1;				if(this.table!=null) {					this.table.clearState();				}				this.data.load(start);			}		}	},		toString : function() {		return "[Core.Domino.DefaultViewNavigator]";	}});/** * Exapands on the default navigation by adding an alphabet picker to the footer. * @class Core.Domino.ViewData.AlphabetNavigator * @extends Core.Domino.ViewData.Navigator */Core.Domino.ViewData.AlphabetNavigator = Core.create(Core.Domino.ViewData.Navigator, {	/**	 * Creates a new alphabet view navigator.	 * @method init	 * @constructor	 */	init: function() {		Core.Domino.ViewData.Navigator.prototype.init.call(this);		this.alphabetList = null;	},	/**	 * This is called from the table to draw the navigation in the footer of the table.  See	 * above for what is included in the navigation.	 * @method craeteFooterNavigation	 * @param {Core.Ui.Table} tabel This is the table.	 * @param {Core.Element} footerElement This is the footer element for the table.	 */	createFooterNavigation: function(footerElement) {		// alpahbet		this.alphabetList = footerElement.createChild({tag: "div", klass: "alphabetList"});		this.links = new Array();		this.links[27] = this.alphabetList.createChild({tag: "a", klass: "text", html: "All"});		this.links[0] = this.alphabetList.createChild({tag: "a", klass: "text", html: "0-9"});		for(var index=1; index<=26; index++) {			this.links[index] = this.alphabetList.createChild({tag: "a", html: String.fromCharCode(index+64)});		}		this.alphabetList.addListener("click", this.alphabetListClicked.bind(this));		this.alphabetList.addListener("mouseover", Core.Element.highlight.bind(["highlight", true]));		this.alphabetList.addListener("mouseout", Core.Element.highlight.bind(["highlight", false]));	},	/**	 * Called by the Core.Domino.ViewData when ever the the data is updated from an ajax	 * request.  It is to inform the navigator of howmany top level entries there are and which	 * ones are currently displayed.  This data is used to update the display in the footer 	 * hiding any irrelevant buttons, updating the current page and updating the total number	 * of pages.	 * @method updateNav	 * @param {Number} position The position for the first entry in the data source	 * @param {Number} topLevelEntries The number of top level entries in this view.	 * @param {Core.Data.Row} firstRow The first row.	 * @param {String} startCmd The start value used to load this data	 */	updateNav: function(position, topLevelEntries, firstRow, startCmd) {		if(this.links!=null) {			for(var index=0; index<=27; index++) {				this.links[index].removeClass("current");			}			if(startCmd=="Start=1") {				this.links[27].addClass("current");			} else {					var charCode = firstRow.getValue("0").toUpperCase().charCodeAt(0);				if(charCode>=65 && charCode<=90) {					this.links[charCode-64].addClass("current");				} else {					this.links[0].addClass("current");				}			}					}	},	/* private triggered when a button in the page list is clicked, changes to the relevent page */	alphabetListClicked: function(event) {		if(event && event.target && event.target.innerHTML) {			for(var index=0; index<=27; index++) {				this.links[index].removeClass("current");			}			var action = event.target.innerHTML;			var charCode = action.toUpperCase().charCodeAt(0);						if(action=="All") {				if(this.table!=null) {					this.table.clearState();				}				this.data.load(1);				this.links[27].addClass("current");			} else if(action=="0-9") {				if(this.table!=null) {					this.table.clearState();				}				this.data.load("0&UntilKey=9_");				this.links[0].addClass("current");			} else {				if(this.table!=null) {					this.table.clearState();				}				this.data.load(action+"&UntilKey="+action+"_");				this.links[charCode-64].addClass("current");			}		}			},		toString : function() {		return "[Core.Domino.DefaultViewNavigator]";	}});/* ############################## View Actions ############################## *//** * A class used to wrap actions for views rendered using $$ViewTemplateDefault * @class Core.Domino.ViewAction */Core.Domino.ViewAction = Core.create({	/**	 * Creates a new Core.Domino.ViewAction object.  Normally actions would be created via the static <code>create</code> method.	 * @method init	 * @constructor	 * @param {String}/{Array} viewNames A string or array of strings containing the view names this function should be used on.  The name should be the last alias.	 * @param {String} title The title to be used in the button for this action.	 * @param {Number} groupNumber (Optional) The action group to add the action to.  The first group is 0.  Defaults to 0.	 * @param Boolean right (Optional) Specifies if this action should be added to a group on the right.  Default is false (left).	 */	init: function(viewNames, title, groupNumber, right) {		if(typeof(viewNames)=="string") {			this.viewNames = new Array();			this.viewNames.push(viewNames);		} else {			this.viewNames = viewNames;		}			this.title= typeof(title)=="string" ? title : "";		this.groupNumber = typeof(groupNumber)=="number" ? groupNumber : 0;		this.rightGroup = right==true;				Core.Domino.ViewAction.register(this);	},		/**	 * Called by addActions to check if this action should be added to the view.	 * @method isOnView	 * @param {String} viewName The view name of the view to check.	 * @return Boolean True if it should be added.	 */	isOnView: function(viewName) {		return this.viewNames.indexOf("*")>=0 || this.viewNames.indexOf(viewName)>=0;	},	/**	 * Returns the title to be used in the button for this action.	 * @method getTitle	 * @return {String}	 */	getTitle: function() {		return this.title;	},	/**	 * Returns the action group number for this action	 * @method getGroupNumber	 * @return {Number}	 */	getGroupNumber: function() {		return this.groupNumber;	},	/**	 * Checks to see if the action should be added to an action group on the right.	 * @method isRightGroup	 * @return Boolean True when the action should be added to the right.	 */	isRightGroup: function() {		return this.rightGroup;	},	/**	 * This is what is called when the action is triggered, it should be overridden by implementing classes	 * @method click	 * @param Core.Ui.Table view The view the action belongs to	 */	click: function() {		// does nothing in the base class	},	toString : function() {		return "[Core.Domino.ViewAction : "+this.viewNames+", "+this.title+"]"	}});Core.copy(Core.Domino.ViewAction, {	/**	 * This defines an action and bootstraps it so it created when the page is loaded.	 * @method create	 * @static	 * @param {String}/{Array} viewNames A string or array of strings containing the view names this function should be used on.  The name should be the last alias.	 * @param {String} title The title to be used in the button for this action.	 * @param {Number} groupNumber (Optional) The action group to add the action to.  The first group is 0.  Defaults to 0.	 * @param Boolean right (Optional) Specifies if this action should be added to a group on the right.  Default is false (left).	 * @param Object methods The methods this actions should have, click should be defined.	 */	create: function(viewNames, title, groupNumber, right, methods) {		if(arguments.length>2) {			if(arguments.length==3) {				methods = groupNumber;				groupNumber = 0;				right=false;			} else if(arguments.length==4) {				methods = right;				right=false;			}						var action = Core.create(Core.Domino.ViewAction, {				init: function() {					Core.Domino.ViewAction.prototype.init.call(this, viewNames, title, groupNumber, right);				}			});			Core.copy(action.prototype, methods);			Core.EventManager.bootstrap(action);		}	},	/* - private -	 * Is called when a view action is created.  This registers the view action so it can be instantiated if required.	 * @method register	 * @static	 * @param Core.Domino.ViewAction action The action to register	 */	register: function(action) {		if(!this.actions) {			this.actions = new Array();		}		this.actions.push(action)	},	/**	 * This is called to add default actions to a view.  When usgin the {Core.Domino.Page#createView} if the 	 * option <code>addDefaultActions</code> is set to true, this will be called for that view.  The 	 * $$ViewTemplateDefault form has this parameter set to true.	 * @method addActions	 * @static	 * @param Core.Domino.Page page The domino page this table is on	 * @param Core.Element tableElement The element where the table is, the actions will be added above this.	 * @param Core.Ui.Table table The table	 * @param String viewName The name of the view.	 */	addActions: function(page, tableElement, table, viewName) {		if(this.actions && tableElement!=null && viewName!="") {			var actionBar = null;			var leftActionGroups =null;			var rightActionGroups = null;						for(var index=0; index<this.actions.length; index++) {				var action = this.actions[index];				if(action.isOnView(viewName)) {					// create bar if required					if(actionBar==null) {						actionBar = tableElement.createSibling({tag:"div",  klass:"actionBar"}, false);						leftActionGroups = new Array();						rightActionGroups = new Array();					}										// add action groups					var actionGroups;					if(!action.isRightGroup()) {						actionGroups=leftActionGroups;						while(actionGroups.length<=action.getGroupNumber()) {							actionGroups.push(actionBar.createChild({tag:"div",  klass:"actionGroup"}));						}					} else {						actionGroups=rightActionGroups;						while(actionGroups.length<=action.getGroupNumber()) {							actionGroups.push(actionBar.createChild({tag:"div",  klass:"actionGroup actionGroupRight"}));						}					}															// add action					var first = actionGroups[action.getGroupNumber()].getHtml()=="";										Core.Ui.addButton(actionGroups[action.getGroupNumber()], action.getTitle(), first, action.click.bind(action, [page, table], true));				}			}						// remove blank groups			if(actionBar!=null) {				for(var index=0; index<leftActionGroups.length; index++) {					if(leftActionGroups[index].getHtml()=="") {						leftActionGroups[index].remove();					}				}				for(var index=0; index<rightActionGroups.length; index++) {					if(rightActionGroups[index].getHtml()=="") {						rightActionGroups[index].remove();					}				}			}		}	}});/* ############################## Export View Dialog ############################## *//** * A dialog for exporting documents from this view. * @class Core.Domino.ExportDialog * @extends Core.Ui.Dialog */Core.Domino.ExportDialog = Core.create(Core.Ui.Dialog, {	/**	 * Creates an export dialog.  The fields parameter should be in the following format	 * <pre><code>[	{group: "Common Fields", fields: [		{field:"common_companyType", display:"Company Type"},		{field:"common_companyName", display:"Company Name", checked: true},		{field:"common_office", display:"Office", checked: true},		{field:"common_familyName", display:"Family Name"},		{field:"common_givenName", display:"Given Name"},		{field:"common_familiarName", display:"Familiar Name"},		{field:"common_nameFormat", display:"Name Format"},		{field:"common_userName", display:"User Name", checked: true, link: true}	]},	{group: "DEK Specific Fields", fields: [		{field:"dek_basedInOffice", display:"Based in a Office"},		{field:"dek_nonOfficeLocation", display:"Non-Office Location"},		{field:"dek_responsibilities", display:"Additional Responsibilities"}	]}];</code></pre>	 * @method init	 * @constructor	 * @param {Core.Domino.Page} dominoPage The current page.	 * @param {Core.Ui.Table} table The table object for for the view to export from. 	 * @param {String} viewName The name of the view to export from. 	 * @param {String} category (Optional) The single category to restrict the view to.	 * @param {String} fileName The name of the file exported from this view.	 * @param {Array} fields An array of fields which should be avalible as options for this export dialog.	 * @param {Boolean} allOnly (Optional) When true the document selection options are not shown and all doucment are selected by defualt.	 */	init : function(dominoPage, table, viewName, category, fileName, fields, allOnly) {		Core.Ui.Dialog.prototype.init.call(this, true, true, "exportDialog", "Export", "", ["Export", "Cancel"]);		this.closeEvent.addListener(this.exportListener.bind(this));				if(!fields || typeof(fields)==="boolean") {			allOnly = fields;			fields = fileName;			fileName = category;			category = "";		}		this.allOnly = typeof(allOnly)!="undefined"?allOnly : false;		this.fields = fields;				content = "";		content +="<input type=\"hidden\" name=\"fileName\" value=\""+fileName+"\"/>";		content +="<input type=\"hidden\" name=\"viewName\" value=\""+viewName+"\"/>";		content +="<input type=\"hidden\" name=\"unids\"/>";		content +="<input type=\"hidden\" name=\"category\" value=\""+category+"\"/>";		content +="<input type=\"hidden\" name=\"filterItem\"/>";		content +="<input type=\"hidden\" name=\"filterValue\"/>";		if(!this.allOnly) {			content +="<div class=\"fields\">";			content +="<h4>Documents</h4>";			content +="<table cellpadding=\"0\" cellspacing=\"0\">";			content +="<tr class=\"first\"><th>Documents:</th><td><input type=\"radio\" class=\"radio\" name=\"range\" value=\"all\"/>Export all documents<br><input type=\"radio\" class=\"radio\" name=\"range\" value=\"selected\" checked=\"checked\"/>Export selected documents</td></tr>";			content +="</table>";			content +="</div>";		} else {			content +="<input type=\"hidden\" name=\"range\" value=\"all\"/>";		}		content +="<div class=\"fields\">";		content +="<h4>Fields</h4>";		content +="<table cellpadding=\"0\" cellspacing=\"0\">";		for(var group=0; group<fields.length; group++) {			content +="<tr"+(group==0?" class=\"first\"":"")+"><th>"+fields[group].group+":<br/><span class=\"link\" id=\"exportDialogAll"+group+"\">All</span> / <span class=\"link\" id=\"exportDialogNone"+group+"\">None</span></th><td>";			for(var field=0; field<fields[group].fields.length; field++) {				content +="<input type=\"checkbox\" class=\"check\" name=\"fields\" value=\""+fields[group].fields[field].field+"|"+fields[group].fields[field].display+"|"+(fields[group].fields[field].link? "link" : "normal")+"\""+(fields[group].fields[field].checked?" checked=\"checked\"":"")+"/>"+fields[group].fields[field].display+"<br/>";			}			content +="</td></tr>";			}		content +="</table>";		content +="</div>";					var contentElement = this.getContentElement();		var formElement = contentElement.createChild({tag: "form", method: "POST", action: dominoPage.getAgentUrl("exportToExcel"), target: "_blank", html:content});		this.form = new Core.Form(formElement.getDom());		this.view = table;				for(var group=0; group<fields.length; group++) {			var link = Core.get("exportDialogAll"+group);			link.addListener("click", this.selectAllFields.bind(this, [group], true));			link = Core.get("exportDialogNone"+group);			link.addListener("click", this.selectNoFields.bind(this, [group], true));			}	},	selectAllFields : function(group) {		for(var field=0; field<this.fields[group].fields.length; field++) {			this.form.addValue("fields", this.fields[group].fields[field].field+"|"+this.fields[group].fields[field].display+"|"+(this.fields[group].fields[field].link? "link" : "normal"));		}	},	selectNoFields : function(group) {		for(var field=0; field<this.fields[group].fields.length; field++) {			this.form.removeValue("fields", this.fields[group].fields[field].field+"|"+this.fields[group].fields[field].display+"|"+(this.fields[group].fields[field].link? "link" : "normal"));		}	},	/* private, listes to dialog and fires a call to the export agent */	exportListener: function(event) {		if(event.result=="Export") {			// validate			var selected = this.view.getSelectedValues();			if(this.form.getValue("range")!="all" && selected.length==0) {				Core.Ui.msgbox("Export", "Please select documents to export", ["OK"]);			} else if(this.form.getValue("fields").length==0) {				Core.Ui.msgbox("Export", "Please select fields to export", ["OK"], this.show.bind(this));			} else {				// submit				//this.form.setValue("filterValue", this.filterName=="All" ? "" : this.filterName);				this.form.setValue("unids", this.view.getSelectedValues().join(","));				this.form.submit();			}		}	},	setCategory: function(category) {		this.form.setValue("category", category);	},	/*	 * Creates and returns a text represntaion of this Popup	 * @method toString	 * @return {String}	 */	toString : function() {		return "[Core.Domino.ExportDialog]";	}});/* ############################## Outline ############################## *//** * A data object used to load a notes outlines, each row will be an entry in the outline.  This  * is mapped in to a format reconginsed Core.Ui.Tree. * @class  Core.Domino.OutlineData * @extends Core.Data.XML */ Core.Domino.OutlineData = Core.create(Core.Data.XML, { 	/**	 * Creates a new OutlineData object	 * @method init	 * @constructor	 * @param {String} url The url of the outline to be loaded.	 */	init: function(url) {		Core.Data.XML.prototype.init.call(this, {			url: url + "&time=" + (new Date()).getTime(),			rootNode: "outlinedata",			rowNode: "outlineentry",			positionAttribute: "position",			cellAttributes: ["title", "type", "url", "expandable"],			cellMap: {				expandable: "expanded"						}		});	}});/** * <p>A data object used to load a notes view and reformat the data so it is reconsied  * by a Core.Ui.Tree.  All the visible columns in the view are concatinated to form the  * text for the tree node.  This is the data object used by {Core.Domino.Page#createViewOutline}</p> * <p>You can confugure what and how the data is loaded by passing an options object to the construtor. The options object can have the following parameters:</p> * <table cellspacing="0" cellpadding="0" > * <tr><th style="width: 120px;">Option Name</th><th style="width: 120px;">Type</th><th>Description</th></tr> * <tr><td>viewName</td><td>String</td><td>The name of the view is this database to load.  Either viewName or viewUrl must be defined.</td></tr> * <tr><td>viewUrl</td><td>String</td><td>The url of the view to load, this should not include any parameters such as <code>?OpenView</code>.  Either viewName or viewUrl must be defined.</td></tr> * <tr><td>category</td><td>String</td><td>(Optional) When specified the view will only load a single category equal to this.</td></tr> * <tr><td>deapthFirst</td><td>Boolean</td><td>(Optional) When true view rows will be loaded deapth first, when false rows will be loaded level at a time.  Defaults to false.</td></tr> * <tr><td>step</td><td>Number</td><td>(Optional) The number of rows to load in each ajax request. Defaults to 100</td></tr> * </table> * @class Core.Domino.ViewOutlineData * @extends Core.Domino.ViewData */Core.Domino.ViewOutlineData = Core.create(Core.Domino.ViewData, {	/**	 * Creates a new ViewOutlineData object.	 * @method init	 * @constructor	 * @param {Core.Domino.Page} dominoPage The current domino page.	 * @param Object options The options for this view data object as described above.	 */	init: function(dominoPage, options) {		options.entriesPerPage=-1;				Core.Domino.ViewData.prototype.init.call(this, dominoPage, options);	},	/* over ridden to format the data for a tree */	loadRow: function(parent, rowNode) {		var row = Core.Domino.ViewData.prototype.loadRow.call(this, parent, rowNode);				var title = "";		for(var column=0; column<10 && title==""; column++) {			title = row.getValue(""+column);		}		row.addCell(Core.Data.STRING, "title", title);		row.addCell(Core.Data.STRING, "type", row.hasCell("url") ? Core.Ui.Tree.PAGE : Core.Ui.Tree.FOLDER);		row.addCell(Core.Data.STRING, "expanded", "false");				return row;	}});/* ############################## View Dialog ############################## *//** * A view dialog is a dialog with a view used for making a selection.  For example to select a  * person from the address book.  It uses a view as a data source and adds a search field to the * Core.Ui.TableDialog. * @class Core.Domino.ViewDialog * @extends Core.Ui.TableDialog */Core.Domino.ViewDialog = Core.create(Core.Ui.TableDialog, {	/**	 * This creates the view dialog.	 * @method init	 * @constructor	 * @param {Core.Domino.Page} dominoPage The current domino page.	 * @param {String} database The url for the database containing the view, use "" to specify the current database.	 * @param {String} view The name of the view to display.	 * @param Boolean shortView When true only 16 entries will be shown at a time, the user will the use a "Starts with" search box to find the entry they require.	 * @param {Number} column The column number to use as a value for the selected row.  Column numbers start at 1.	 * @param Boolean selectMany When true the user will be able to select multiple rows.	 * @param {String} dialogTitle The title for the dialog.	 */	init: function(dominoPage, database, view, shortView, column, selectMany, dialogTitle) {		// create dialog		Core.Ui.SelectionDialog.prototype.init.call(this, dialogTitle);				var fieldsElement = this.dialog.getContentElement().createChild({tag:"div", klass:"fields", html:"<table cellpadding=\"0\" cellspacing=\"0\"><tr class=\"first\"><th style=\"width: 80px;\">Starts with:</th><td><input type=\"text\" class=\"full\"></td></tr></table>"});		this.inputElement = Core.get(fieldsElement.getDom().getElementsByTagName("input")[0]);		var viewElement = this.dialog.getContentElement().createChild({tag:"div", klass:"view"});				// create view		var url = (database=="" ? dominoPage.getDbUrl() : database)+"/"+view;		this.viewNav = shortView ? new Core.Domino.ViewData.BasicNavigator() : new Core.Domino.ViewData.PageListNavigator(false);		this.inputElement.addListener("keyup", this.viewNav.searchTextEntered.bind(this.viewNav));				var viewDataOptions = {			viewUrl: url,			nav: this.viewNav		};			if(shortView) {			viewDataOptions.step=16;			viewDataOptions.entriesPerPage=16;		}		this.viewData = new Core.Domino.ViewData(dominoPage, viewDataOptions);				this.table = new Core.Ui.Table({			data: this.viewData,			design: this.viewData.getDesignData(),			element: viewElement,			header: false,			fitContent: shortView,			tableClass: shortView ? "nowrap" : "",			selectMany: selectMany,			checkBoxes: selectMany,			boundColumn: isNaN(column) ? column : column==0 ? "unid" : column-1,			clickAction: Core.Ui.Table.SELECT,doubleClickAction: this.viewDoubleClickListener.bind(this),			nav: this.viewNav		});	},	/**	 * Makes the dialog visable	 * @method show	 * @param {Object} event If the show opereation is triggered by an event, this should be the event information object 	 */	show: function(event) {		this.viewData.load();		this.inputElement.getDom().value="";		this.dialog.show(event);		this.inputElement.getDom().focus();	},	toString : function() {		return "[Core.Domino.ViewDialog]"	}});/** * A view dialog field converts a normal html input elment into a field with a view dialog popup. * @class Core.Domino.ViewDialogField * @extends Core.Ui.DialogField * @see Core.Domino.ViewDialog */Core.Domino.ViewDialogField = Core.create(Core.Ui.DialogField, {	/**	 * Creates a new new view dialog field	 * @method init	 * @constructor	 * @param {Core.Domino.Page} dominoPage The current domino page.	 * @param {String}/HTMLInput/{Core.Element} field The name, HTML input or Core.Element for the input to replace.	 * @param {String} database The url for the database containing the view, use "" to specify the current database.	 * @param {String} view The name of the view to display.	 * @param Boolean shortView When true only 16 entries will be shown at a time, the user will the use a "Starts with" search box to find the entry they require.	 * @param {Number} column The column number to use as a value for the selected row.  Column numbers start at 1.	 * @param Boolean selectMany When true the user will be able to select multiple rows.	 * @param {String} dialogTitle The title for the dialog.	 */	init: function(dominoPage, field, database, view, shortView, column, selectMany, dialogTitle) {		Core.Ui.DialogField.prototype.init.call(this, dominoPage, field, new Core.Domino.ViewDialog(dominoPage, database, view, shortView, column, selectMany, dialogTitle));	},	toString : function() {		return "[Core.Domino.ViewDialogField]"	}});/* ############################## Mass Selection View Dialog ############################## *//** * A table dialog is a dialog with a table used for making a selection. * @class Core.Domino.MassSelectionViewDialog * @extends Core.Ui.SelectionDialog */Core.Domino.MassSelectionViewDialog = Core.create(Core.Ui.SelectionDialog, {	/**	 * This creates the view dialog.	 * @method init	 * @constructor	 * @param {Core.Domino.Page} dominoPage The current domino page.	 * @param {String} database The url for the database containing the view, use "" to specify the current database.	 * @param {String} view The name of the view to display.	 * @param Boolean shortView When true only 16 entries will be shown at a time, the user will the use a "Starts with" search box to find the entry they require.	 * @param {Number} column The column number to use as a value for the selected row.  Column numbers start at 1.	 * @param Boolean otherEntries When true the user will be able to enter data, not it the list.  (Not implemented)	 * @param Boolean sortable When true the user will be able to sort the selected entries.	 * @param {String} dialogTitle The title for the dialog.	 */	init: function(dominoPage, database, view, shortView, column, otherEntries, sortable, dialogTitle) {		this.sortable = sortable;			// create dialog		Core.Ui.SelectionDialog.prototype.init.call(this, dialogTitle, "massSelectionDialog");				// --- Left ---		var leftElement = this.dialog.getContentElement().createChild({tag:"div", klass:"left"});				var fieldsElement = leftElement.createChild({tag:"div", klass:"fields", html:"<table cellpadding=\"0\" cellspacing=\"0\"><tr class=\"first\"><th style=\"width: 80px;\">Starts with:</th><td><input type=\"text\" class=\"full\"></td></tr></table>"});		this.inputElement = Core.get(fieldsElement.getDom().getElementsByTagName("input")[0]);		var viewElement = leftElement.createChild({tag:"div", klass:"view"});				// create view		var url = (database=="" ? dominoPage.getDbUrl() : database)+"/"+view;		this.viewNav = shortView ? new Core.Domino.ViewData.BasicNavigator() : new Core.Domino.ViewData.PageListNavigator(false);		this.inputElement.addListener("keyup", this.viewNav.searchTextEntered.bind(this.viewNav));				var viewDataOptions = {			viewUrl: url,			nav: this.viewNav		};			if(shortView) {			viewDataOptions.step=16;			viewDataOptions.entriesPerPage=16;		}		this.viewData = new Core.Domino.ViewData(dominoPage, viewDataOptions);				this.optionsTable = new Core.Ui.Table({			data: this.viewData,			design: this.viewData.getDesignData(),			element: viewElement,			header: false,			height: 300,			tableClass: shortView ? "nowrap" : "",			selectMany: false,			checkBoxes: false,			boundColumn: isNaN(column) ? column : column==0 ? "unid" : column-1,			clickAction: Core.Ui.Table.SELECT,			doubleClickAction: this.addListener.bind(this),			nav: this.viewNav		});		// --- mid ---		var midElement = this.dialog.getContentElement().createChild({tag:"div", klass:"mid"});		var action = midElement.createChild({tag:"div", klass: "action", html:"Add"});		Core.Ui.initButton(action, this.addListener.bind(this));		action = midElement.createChild({tag:"div", klass: "action", html:"Remove"});		Core.Ui.initButton(action, this.removeListener.bind(this));		action = midElement.createChild({tag:"div", klass: "action", html:"Remove All"});		Core.Ui.initButton(action, this.removeAllListener.bind(this));		if(this.sortable) {			midElement.createChild({tag:"div", klass: "spacer"});			action = midElement.createChild({tag:"div", klass: "action", html:"Move Up"});			Core.Ui.initButton(action, this.moveUpListener.bind(this));			action = midElement.createChild({tag:"div", klass: "action", html:"Move Down"});			Core.Ui.initButton(action, this.moveDownListener.bind(this));		}				// --- right ---		var rightElement = this.dialog.getContentElement().createChild({tag:"div", klass:"right"});				viewElement = rightElement.createChild({tag:"div", klass:"view"});				// create table		this.selected = [];		this.selectionData = new Core.Data.Array(this.selected);				this.selectionTable = new Core.Ui.Table({			data: this.selectionData,			design: new Core.Data.Array([				{					name: "0",					title: "Selection",					width: "auto"				}			]),			element: viewElement,			header: false,			footer: false,			height: 345,			selectMany: false,			checkBoxes: false,			boundColumn: "0",			clickAction: Core.Ui.Table.SELECT,			doubleClickAction: this.removeListener.bind(this)		});				// --- clear float ---		this.dialog.getContentElement().createChild({tag:"div", klass:"clear"});	},	/**	 * Set the values that should be selected in the table.	 * @method setSelectedValues	 * @param {Array} selected An array of strings with the values to select.	 */	setSelectedValues: function(selected) {		this.selected=[];		if(selected && selected[0] && selected[0]!=="") {			for(var index=0; index<selected.length; index++) {				this.selected[index]={"0":selected[index]};			}		}		this.selectionData.load(this.selected);	},	/**	 * Gets the values that are selected in the table.	 * @method getSelectedValues	 * @return {Array} An array of strings with the values selected.	 */	getSelectedValues: function() {		var rv = [];		for(var index=0; index<this.selected.length; index++) {			rv[index] = this.selected[index]["0"];		}		return rv;	},	/* private:  */	addListener : function(event) {		var selected = this.optionsTable.getSelectedValues();		if(selected && selected[0]) {			var toAdd = selected[0];			if(toAdd!=="") {				for(var index=0; index<this.selected.length; index++) {					if(this.selected[index]["0"]===toAdd) {						return;					} else if(!this.sortable && this.selected[index]["0"]>toAdd) {						this.selected.splice(index, 0, {"0":toAdd});						this.selectionData.load(this.selected);						return;					}				}				this.selected[this.selected.length]={"0":toAdd};				this.selectionData.load(this.selected);			}		}	},	removeListener : function(event) {		var selected = this.selectionTable.getSelectedValues();		if(selected && selected[0]) {			var toRemove = selected[0];			if(toRemove!=="") {				for(var index=0; index<this.selected.length; index++) {					if(this.selected[index]["0"]===toRemove) {						this.selected.splice(index, 1);						this.selectionData.load(this.selected);						return;					}				}			}		}	},	removeAllListener : function(event) {		this.selected=[];		this.selectionData.load(this.selected);	},	moveUpListener : function(event) {		var selected = this.selectionTable.getSelectedValues();		if(selected && selected[0]) {			var toMove = selected[0];			if(toMove!=="") {				for(var index=1; index<this.selected.length; index++) {					if(this.selected[index]["0"]===toMove) {						var temp = this.selected[index-1];						this.selected[index-1] = this.selected[index];						this.selected[index] = temp;						this.selectionData.load(this.selected);						return;					}				}			}		}	},	moveDownListener : function(event) {		var selected = this.selectionTable.getSelectedValues();		if(selected && selected[0]) {			var toMove = selected[0];			if(toMove!=="") {				for(var index=0; index<this.selected.length-1; index++) {					if(this.selected[index]["0"]===toMove) {						var temp = this.selected[index+1];						this.selected[index+1] = this.selected[index];						this.selected[index] = temp;						this.selectionData.load(this.selected);						return;					}				}			}		}	},	/**	 * Makes the dialog visable	 * @method show	 * @param {Object} event If the show opereation is triggered by an event, this should be the event information object 	 */	show: function(event) {		this.viewData.load();		this.inputElement.getDom().value="";		this.dialog.show(event);		this.inputElement.getDom().focus();	},	toString : function() {		return "[Core.Domino.ViewDialog]"	}});/** * A view dialog field converts a normal html input elment into a field with a view dialog popup. * @class Core.Domino.MassSelectionViewDialogField * @extends Core.Ui.DialogField * @see Core.Domino.MassSelectionViewDialog */Core.Domino.MassSelectionViewDialogField = Core.create(Core.Ui.DialogField, {	/**	 * Creates a new new view dialog field	 * @method init	 * @constructor	 * @param {Core.Domino.Page} dominoPage The current domino page.	 * @param {String}/HTMLInput/{Core.Element} field The name, HTML input or Core.Element for the input to replace.	 * @param {String} database The url for the database containing the view, use "" to specify the current database.	 * @param {String} view The name of the view to display.	 * @param Boolean shortView When true only 16 entries will be shown at a time, the user will the use a "Starts with" search box to find the entry they require.	 * @param {Number} column The column number to use as a value for the selected row.  Column numbers start at 1.	 * @param Boolean otherEntries When true the user will be able to enter data, not it the list.  (Not implemented)	 * @param Boolean sortable When true the user will be able to sort the selected entries.	 * @param {String} dialogTitle The title for the dialog.	 */	init: function(dominoPage, field, database, view, shortView, column, otherEntries, sortable, dialogTitle) {		Core.Ui.DialogField.prototype.init.call(this, dominoPage, field, new Core.Domino.MassSelectionViewDialog(dominoPage, database, view, shortView, column, otherEntries, sortable, dialogTitle));	},	toString : function() {		return "[Core.Domino.MassSelectionViewDialogField]"	}});/* ############################## Attachments ############################## */Core.Domino.AttachmentData = Core.create(Core.Data.XML, {	init: function(url) {		Core.Data.XML.prototype.init.call(this, {			url: url,			rootNode: "document",			rowNode: "attachment",			cellAttributes: ["name", "size", "modified", "url"],			cellAttributeTypes: [Core.Data.UTF8, Core.Data.STRING, Core.Data.STRING, Core.Data.UTF8]		});	}});/** * The Attachment Table is a wrapper for a Core.Ui.Table designed to show the attachmets added * or being added to a document. * @class Core.Domino.AttachmentTable */Core.Domino.AttachmentTable = Core.create({	/**	 * Creates a new attachment table	 * @method init	 * @constructor	 * @param {Core.Domino.Document} doc The document containing the attachments.	 * @param {String} field The name of the field that contains the attachments	 * @param {String}/HTMLElement/{Core.Element} element The id, html node or Core.Element for where the attachment table should be added.	 * @param {String} title (Optional) The title for the attachment table.	 * @param Number height (Optional) The height of this table, defaults to 150.	 */	init: function(doc, field, element, title, height) {		this.doc = doc;		this.field = field;		this.element = Core.get(element);		height = isNaN(height) ? 150 : height;						var columns = new Core.Data.Array([			{				name: "name",				title: "Name",				width: "300px"			},			{				name: "size",				title: "Size (KB)",				width: "125px"			},			{				name: "modified",				title: "Modified",				width: "125px"			} ]);						this.data = new Core.Domino.AttachmentData(this.doc.getAgentUrl("getAttachments")+"&id="+this.doc.getDocId()+"&field="+this.field);		this.data.addListeners(this.dataRefresh.bind(this), this.dataRefresh.bind(this));		this.table = new Core.Ui.Table({data: this.data, design: columns, footer: false, element: element, title: title, height: height, checkBoxes: doc.isDocBeingEdited(), selectMany: true, boundColumn: "name", title: title, target: "_blank",  linkColumn: "name"});		this.refresh();				this.uploads = {};	},	/*	 * Listener for the upload control's start event	 * @method startedUpload	 * @param {Object} event The upload event	 */	startedUpload: function(event) {		var cell = new Core.Domino.AttachmentTable.UploadCell(this.doc.getDbUrl()+"/progressBar.png", 122)		this.uploads[event.fileName] = cell;		this.refresh();	},	/*	 * Listener for the upload control's progress event	 * @method startedUpload	 * @param {Object} event The upload event	 */	progressUpload: function(event) {		this.uploads[event.fileName].updateProgress(event.progress);	},	/*	 * Listener for the upload control's completed event	 * @method startedUpload	 * @param {Object} event The upload event	 */	completedUpload: function(event) {		this.uploads[event.fileName].complete();		delete this.uploads[event.fileName];		this.refresh();	},	/* private: called after the upload data is returned so current uploads can be added */	dataRefresh: function(event) {		for(var fileName in this.uploads) {			var row = this.data.addRow();			row.addCell(Core.Data.STRING, "name", fileName);			row.addCell(Core.Data.CUSTOM, "size", this.uploads[fileName]);		}	},	/**	 * Causes the table to be refreshed	 * @method refresh	 */	refresh: function(event) {		this.data.load();	},	/**	 * Returns a list of attachments which have been selected in the table.	 * @method getAttachmentsForRemoval	 * @return {Array} An array of attachment names which have been selected.	 */	getAttachmentsForRemoval: function(event) {		return this.table.getSelectedValues();	}});/* * This is a custom cell used to represent the progress bar for current uploads * @class Core.Domino.AttachmentTable.UploadCell */Core.Domino.AttachmentTable.UploadCell = Core.create(Core.Data.Cell, {	init: function(pictureUrl, pictureSize) {		Core.Data.Cell.prototype.init.call(this, Core.Data.PROGRESS, "size", 0);		this.setDynamic(true);		this.pictureUrl = pictureUrl;		this.pictureSize = pictureSize;		this.timer = null;	},	updateProgress: function(progress) {		this.setValue(progress);		if(progress>=80 && this.timer==null) {			this.timer = setInterval(this.incrementProgress.bind(this), 1000);		}	},	incrementProgress: function() {		var progress = this.getValue();		if(progress<100) {			progress+=0.5;			this.setValue(progress);		} else {			clearInterval(this.timer);		}	},	complete: function() {		this.setValue(100);		if(this.timer!=null) {			clearInterval(this.timer);		}	},	render: function(element) {		element.setHtml("<img src=\""+this.pictureUrl+"\" alt=\""+this.getValue()+"%\" class=\"progressBar\" style=\"background-position: -"+(this.pictureSize-(this.getValue()/100*this.pictureSize))+"px 50%;\" />");	}});/** * A dialog showing the progress of an upload * @class Core.Domino.UploadDialog */Core.Domino.UploadDialog = Core.create({	/**	 * Creates a new upload dialog.	 * @method init	 * @constructor	 * @param {Core.Domino.Document} doc The document containing the attachments.	 * @param {String} dialogTitle (Optional) The title for the dialog.	 */	init: function(doc, dialogTitle) {		this.pictureUrl = doc.getDbUrl()+"/bigProgressBar.png";		this.pictureSize = 252;		dialogTitle = typeof(dialogTitle)=="string" ? dialogTitle : "";		this.timer = null;					// create dialog		this.dialog=new Core.Ui.Dialog(true, true, "progress", dialogTitle);		var text=this.dialog.getContentElement().createChild({tag: "div", klass: "text"});		this.message=text.createChild({tag: "p"});		this,progress = 0;		this.progressBar=text.createChild({tag: "img", klass: "bigProgressBar", src: this.pictureUrl, alt: "0%", style: "background-position: -"+this.pictureSize+"px 50%;"});	},		/*	 * Listener for the upload control's start event	 * @method startedUpload	 * @param {Object} event The upload event	 */	startedUpload: function(event) {		this.message.setHtml("Uploading "+event.fileName);		this.progressBar.getDom().alt = "0%";		this.progressBar.getDom().style.backgroundPosition = "-"+this.pictureSize+"px 50%";		this.dialog.show();	},	/*	 * Listener for the upload control's progress event	 * @method startedUpload	 * @param {Object} event The upload event	 */	progressUpload: function(event) {		this.progress = event.progress;		this.progressBar.getDom().alt = this.progress+"%";		this.progressBar.getDom().style.backgroundPosition = "-"+(this.pictureSize-(this.progress/100*this.pictureSize))+"px 50%";		if(this.progress>=80 && this.timer==null) {			this.timer = setInterval(this.incrementProgress.bind(this), 1000);		}	},	incrementProgress: function() {		if(this.progress<100) {			this.progress+=0.5;			this.progressBar.getDom().alt = this.progress+"%";			this.progressBar.getDom().style.backgroundPosition = "-"+(this.pictureSize-(this.progress/100*this.pictureSize))+"px 50%";		} else {			clearInterval(this.timer);			this.timer = null;		}	},	/*	 * Listener for the upload control's completed event	 * @method startedUpload	 * @param {Object} event The upload event	 */	completedUpload: function(event) {		this.dialog.hide();	}});/** * This represent the button on the screen to add an attachment, it uses a flash control to  * select the file and trigger the upload. * @class Core.Domino.UploadControl */Core.Domino.UploadControl = Core.create({	/**	 * Creates a new Upload Control	 * @method init	 * @constructor	 * @param {Core.Domino.Document} doc The document containing the attachments.	 * @param {String} field The name of the field that contains the attachments	 * @param {String}/HTMLElement/{Core.Element} button The id, html node or Core.Element for the button.	 * @param {Core.Domino.AttachmentTable} display A reference to the object that is representing the current attachments/uploads.	 * @param Boolean multi (Optional) When true multiple attachments can be added to the document, defaults to true.	 * @param String filter (Optional) A list of file extensions that are valid, e.g. "Images:*.jpg;*.gif;*.png:Documents:*.pdf;*.doc;*.txt".  Defaults to "" which is all file types.	 * @param String agent (Optional) If specifed an alternate agent will be called to process the attachment when the upload is complete.  Defaults to "".	 */	init: function(doc, field, button, view, multi, filter, agent) {		this.doc = doc;		this.docId = doc.getDocId ? doc.getDocId() : "0";		this.field = field;		this.button = Core.get(button);		this.multi = typeof(multi)=="boolean" ? multi : true;		filter = typeof(filter)=="string" ? filter : "";		this.agent = typeof(agent)=="string" ? agent : "";				Core.Domino.UploadControl.register(this.field, this);		this.jobs = {};				this.button.createChild({tag: "div", klass: "uploadControl", html: "<div id=\""+field+"_flashDiv\"></div>"});		var params = {			dbUrl: this.doc.getDbUrl(),			//docId: this.docId,			field: this.field,			multi: this.multi,			fucName: this.doc.getValue("fucId"),			filter: filter		};		swfobject.embedSWF(this.doc.getDbUrl()+"/uploader.swf", this.field+"_flashDiv", "100%", "100%", "10.0.0", false, params, {wmode: "transparent"});				// events		/**		 * Fires when an upload starts		 * @event uploadStartedEvent		 * @param {Core.Domino.UploadControl} uploadControl A referrence to the upload control used to upload the file.		 * @param {String} fileName The file name of the file being uploaded.		 */		this.uploadStartedEvent = new Core.Event("Core.Domino.UploadControl:uploadStarted");				/**		 * Fires when an upload progresses		 * @event uploadProgressEvent		 * @param {Core.Domino.UploadControl} uploadControl A referrence to the upload control used to upload the file.		 * @param {String} fileName The file name of the file being uploaded.		 * @param {Number} progress The progress for this upload as a percentage.		 */		this.uploadProgressEvent = new Core.Event("Core.Domino.UploadControl:uploadProgress");				/**		 * Fires when an upload completes		 * @event uploadCompletedEvent		 * @param {Core.Domino.UploadControl} uploadControl A referrence to the upload control used to upload the file.		 * @param {String} fileName The file name of the file being uploaded.		 */		this.uploadCompletedEvent = new Core.Event("Core.Domino.UploadControl:uploadCompleted");				if(view) {			this.uploadStartedEvent.addListener(view.startedUpload.bind(view));			this.uploadProgressEvent.addListener(view.progressUpload.bind(view));			this.uploadCompletedEvent.addListener(view.completedUpload.bind(view));		}	},	startUpload: function(fileName) {		Core.addRequest("GET", this.doc.getAgentUrl("upload")+"&mode=stub&docId="+this.docId+"&field="+this.field+"&multi="+this.multi, "", this.stubCreated.bind(this, [fileName]));	},	stubCreated: function(event, fileName) {		var xml = event.request.responseXML;			var rootNode = Core.XML.findFirstChild(xml, "agent");		if(rootNode!=null) {			if(Core.XML.getAttribute(rootNode, "status")=="stub") {				this.uploadStartedEvent.fire({uploadControl: this, fileName: fileName});								var flashControl = Core.get(this.field+"_flashDiv").getDom();				flashControl.stubCreated(fileName, Core.XML.getAttribute(rootNode, "stubId"));									}				}	},	progressUpload: function(fileName, progress) {		this.uploadProgressEvent.fire({uploadControl: this, fileName: fileName, progress: progress});	},	completedUpload: function(fileName, stubId) {		//Core.addRequest("GET", this.doc.getAgentUrl("uploadAttachment")+"&unid="+this.doc.getDocId()+"&field="+this.field+"&fileName="+fileName+"&multi="+(this.multi ? "true" : "false"), "", this.fileAdded.bind(this, [fileName]));		if(this.agent=="") {			Core.addRequest("GET", this.doc.getAgentUrl("upload")+"&mode=move&stubId="+stubId, "", this.fileAdded.bind(this, [fileName]));		} else {			Core.addRequest("GET", this.doc.getAgentUrl(this.agent)+"&stubId="+stubId, "", this.fileAdded.bind(this, [fileName]));		}	},	fileAdded: function(event, fileName) {		if(this.doc.hasInput("%%ModDate")) {			this.doc.getElement("%%ModDate").remove();		}		this.uploadCompletedEvent.fire({uploadControl: this, fileName: fileName, agentResult: event});	},	toString : function() {		return "[Core.Domino.UploadControl]";	}});Core.copy(Core.Domino.UploadControl, {	/* private, called by the constructors to register a new new control. */	register: function(field, control) {		if(!Core.Domino.UploadControl.uploadControlList) {			Core.Domino.UploadControl.uploadControlList = {};		}		Core.Domino.UploadControl.uploadControlList[field] = control;	},	/*	 * This is called by the flash uploader to start a new upload.  It finds the upload 	 * control for the specifed field and passes it the information.	 * @method startUpload	 * @static	 * @param {String} field The field name this attachment should be added to.	 * @param {String} fileName The file name of the new file.	 */	startUpload: function(field, fileName) {		if(Core.Domino.UploadControl.uploadControlList[field]) {			Core.Domino.UploadControl.uploadControlList[field].startUpload(fileName);		}	},	/*	 * This is called by the flash uploader to start a new upload.  It finds the upload 	 * control for the specifed field and passes it the information.	 * @method progressUpload	 * @static	 * @param {String} field The field name this attachment should be added to.	 * @param {String} fileName The file name of the new file.	 * @param {Number} progress The progress for this upload as a percentage.	 */	progressUpload: function(field, fileName, progress) {		if(Core.Domino.UploadControl.uploadControlList[field]) {			Core.Domino.UploadControl.uploadControlList[field].progressUpload(fileName, progress);		}	},	/*	 * This is called by the flash uploader to start a new upload.  It finds the upload 	 * control for the specifed field and passes it the information.	 * @method completedUpload	 * @static	 * @param {String} field The field name this attachment should be added to.	 * @param {String} fileName The file name of the new file.	 */	completedUpload: function(field, fileName, stubId) {		if(Core.Domino.UploadControl.uploadControlList[field]) {			Core.Domino.UploadControl.uploadControlList[field].completedUpload(fileName, stubId);		}	},	/*	 * This is called by the flash uploader when the file is too big (above 25Mb).	 * @method uploadTooLarge	 * @static	 * @param {String} field The field name this attachment should be added to.	 */	uploadTooLarge: function(field) {		Core.Ui.msgbox("Uploads", "The selected file is above the maximum 25MB.");	}});/** * This represent a button to remove attachments from a document. * @class Core.Domino.DetachButton */Core.Domino.DetachButton = Core.create({	/**	 * Creates a new Detach Button	 * @method init	 * @constructor	 * @param {Core.Domino.Document} doc The document containing the attachments.	 * @param {String} field The name of the field that contains the attachments	 * @param {String}/HTMLElement/{Core.Element} button The id, html node or Core.Element for the button.	 * @param {Core.Domino.AttachmentTable} display A reference to the object that is representing the current attachments/uploads.	 */	init: function(doc, field, button, view) {		this.doc = doc;		this.field = field;		Core.Ui.initButton(button, this.detach.bind(this));		this.view = view;			},	/* private called when the button is pressed */	detach: function(event) {		var attachments = this.view.getAttachmentsForRemoval();		if(attachments.length>0) {			Core.addRequest("GET", this.doc.getAgentUrl("deleteAttachments")+"&id="+this.doc.getDocId()+"&field="+this.field+"&fileNames="+attachments, "", this.view.refresh.bind(this.view));			if(this.doc.hasInput("%%ModDate")) {				this.doc.getElement("%%ModDate").remove();			}		}		}});/* ############################## Domino Text edior ############################## *//** * <p>The Core.Domino.TextEditor is an implmentation of a cross browser rich text editor.  It currently  * supports IE and Firefox.  The editor replaces a text area and presents the user with up to  * three ways to enter data, in plain text, in rich text and in HTML.  The rich text part can be  * configure by passing the following options object to the constructor and the HTML editor can  * be disabled.</p> * <p>The Core.Domino.TextEditor adds support for inserting images from attachments over the  * Core.Ui.TextEditor</p> * <table cellspacing="0" cellpadding="0" > * <tr><th style="width: 120px;">Option Name</th><th style="width: 120px;">Type</th><th>Description</th></tr> * <tr><td>cssFile</td><td>String</td><td>(Optional) The url for a CSS file to be applied to the rich text area.</td></tr> * <tr><td>ieCssFile</td><td>String</td><td>(Optional) The url for a CSS file to be applied to the rich text area in addition to the <code>cssFile</code> if the browser is IE.</td></tr> * <tr><td>styles</td><td>Boolean</td><td>(Optional) When true the styles drop down will be shown.  Defaults to true.</td></tr> * <tr><td>links</td><td>Boolean</td><td>(Optional) When true the insert link button will be shown.  Defaults to true.</td></tr> * <tr><td>images</td><td>Boolean</td><td>(Optional) When true the insret image button will be shown.  Defaults to true.</td></tr> * <tr><td>tables</td><td>Boolean</td><td>(Optional) When true the insert table button will be shown.  Defaults to true.</td></tr>  * <tr><td>pictureAttachments</td><td>Boolean</td><td>(Optional) When specified, users will be able to select pictures from the attachments in this field.</td></tr> * </table> * @class Core.Domino.TextEditor * @extends Core.Ui.TextEditor */Core.Domino.TextEditor = Core.create(Core.Ui.TextEditor, {	/**	 * Creates a new TextEditor	 * @method init	 * @constructor	 * @param {Core.Domino.Document} doc The domino document containging the data and type fields.	 * @param {String}dataField The name of the field should store the text to be edited.	 * @param {String}typeField The name of the field that will identify the type of text in the field, valid options are "plain", "rich" and "html"	 * @param {String} title (Optional) A title to display at the top of the screen, an empty string mean no title, defaults to an empty string.	 * @param Object richtextOptions (Optional) An options object including the following options, 	 * @param boolean html (Optional) When true, html is a valid input option, defaults to false.	 * @param boolean plain (Optional) When true, plain is a valid input option, defaults to true.	 */	 	/**	 * Creates the rich text field for use in this editor.  The Core.Domino.TextEditor changes the implmentation of this so it returns a Core.Domino.RichTextField, over ride this again if you want a custom rich text field.	 * @method createRichTextFeild	 * @param {Core.Form} form The form containging the data and type fields.	 * @param {String} dataField The name of the field should store the text to be edited.	 * @param {String}/HTMLElement/{Core.Element} element The id, DOM node or Core.Element for where to place the rich text field and its action bar.	 * @param Object options (Optional) An options object including the above options, 	 * @return {Core.Ui.RichTextField} Arich text field.	 */	createRichTextField: function(form, dataField, element, options) {		return new Core.Domino.RichTextField(form, dataField, element, options);	}});/** * <p>The Core.Ui.RichTextField is an implmentation of a cross browser rich text field.  It currently  * supports IE and Firefox.</p> * <table cellspacing="0" cellpadding="0" > * <tr><th style="width: 120px;">Option Name</th><th style="width: 120px;">Type</th><th>Description</th></tr> * <tr><td>cssFile</td><td>String</td><td>(Optional) The url for a CSS file to be applied to the rich text area.</td></tr> * <tr><td>ieCssFile</td><td>String</td><td>(Optional) The url for a CSS file to be applied to the rich text area in addition to the <code>cssFile</code> if the browser is IE.</td></tr> * <tr><td>styles</td><td>Boolean</td><td>(Optional) When true the styles drop down will be shown.  Defaults to true.</td></tr> * <tr><td>links</td><td>Boolean</td><td>(Optional) When true the insert link button will be shown.  Defaults to true.</td></tr> * <tr><td>images</td><td>Boolean</td><td>(Optional) When true the insret image button will be shown.  Defaults to true.</td></tr> * <tr><td>tables</td><td>Boolean</td><td>(Optional) When true the insert table button will be shown.  Defaults to true.</td></tr> * <tr><td>pictureAttachments</td><td>Boolean</td><td>(Optional) When specified, users will be able to select pictures from the attachments in this field.</td></tr> * </table> * @class Core.Domino.RichTextField */Core.Domino.RichTextField = Core.create(Core.Ui.RichTextField, {	/**	 * Creates a new TextEditor	 * @method init	 * @constructor	 * @param {Core.Domino.Document} doc The domino document containging the data and type fields.	 * @param {String} dataField The name of the field should store the text to be edited.	 * @param {String}/HTMLElement/{Core.Element} element The id, DOM node or Core.Element for where to place the rich text field and its action bar.	 * @param Object options (Optional) An options object including the above options, 	 */	 	insertImage: function(event) {		if(!this.imageDialog) {			this.imageDialogId = Core.unique();					content = "";			content +="<div class=\"fields\">";			content +="<table cellpadding=\"0\" cellspacing=\"0\">";			if(this.options.hasOption("pictureAttachments")) {				if(!this.form.isNewDoc()) {					content +="<tr class=\"first\"><th>Type</th><td><input type=\"radio\" name=\"type\" value=\"url\" class=\"radio\"/>URL<input type=\"radio\" name=\"type\" value=\"attachment\" class=\"radio\"/>Attachment</td></tr>";					content +="<tr id=\""+this.imageDialogId+"UrlRow\"><th>URL</th><td><input type=\"text\" name=\"url\" class=\"full\"/></td></tr>";					content +="<tr id=\""+this.imageDialogId+"AttachmentRow\"><th>Attachment</th><td><select name=\"attachment\" class=\"full\"/></select>";					content +="<br/><div class=\"actionBar\" style=\"margin-top: 5px; margin-left: 0px;\"><div class=\"actionGroup\"><div class=\"first\" id=\""+this.imageDialogId+"UploadButton\">Browse</div></div></div></td></tr>";				} else {					content +="<tr class=\"first\"><th>Type</th><td><input type=\"radio\" name=\"type\" value=\"url\" class=\"radio\"/>URL<input type=\"radio\" name=\"type\" value=\"attachment\" class=\"radio\"/>Attachment</td></tr>";					content +="<tr id=\""+this.imageDialogId+"UrlRow\"><th>URL</th><td><input type=\"text\" name=\"url\" class=\"full\"/></td></tr>";					content +="<tr id=\""+this.imageDialogId+"AttachmentRow\"><th>Attachment</th><td>You will need to save this document before you can use attachments.</span></td></tr>";				}			} else {				content +="<tr class=\"first\"><th>URL</th><td><input type=\"text\" name=\"url\" class=\"full\"/></td></tr>";			}			content +="</table>";			content +="</div>";						this.imageDialog = new Core.Ui.Dialog(true, true, "", "Insert Image", "", ["Insert", "Cancel"]);			this.imageDialog.closeEvent.addListener(this.imageDialogListener.bind(this));			var contentElement = this.imageDialog.getContentElement();			var formElement = contentElement.createChild({tag: "form", html:content});			this.imageForm = new Core.Form(formElement.getDom());			if(this.options.hasOption("pictureAttachments")) {				this.imageForm.addListener("type", "click", this.imageDialogTypeListener.bind(this));				this.imageDialogAttachmentData = new Core.Domino.AttachmentData(this.form.getAgentUrl("getAttachments")+"&id="+this.form.getDocId()+"&field="+this.options.readOption("pictureAttachments"));				this.imageDialogAttachmentData.addListeners(this.imageDialogUpdateAttachmentOptions.bind(this), this.imageDialogUpdateAttachmentOptions.bind(this));								if(!this.form.isNewDoc()) {					var uploadDialog = new Core.Domino.UploadDialog(this.form);					var uploadControl = new Core.Domino.UploadControl(this.form, this.options.readOption("pictureAttachments"), this.imageDialogId+"UploadButton", uploadDialog,  true);					uploadControl.uploadCompletedEvent.addListener(this.imageDialogAddUpload.bind(this));				}			}		}				this.saveSelection();		this.imageForm.setValue("url", "");		if(this.options.hasOption("pictureAttachments")) {			this.imageForm.setValue("type", "attachment");			this.imageDialogTypeListener();			this.imageDialogAttachmentData.load();		}		this.imageDialog.show();	},	imageDialogListener : function(event) {		if(event.result=="Insert") {			try {				this.rteWindow.focus();							// reselect range				this.restoreSelection();								// create image				var url = "";				if(this.options.hasOption("pictureAttachments") && this.imageForm.getValue("type")=="attachment") {					url = this.form.getDocId()+"/$file/"+this.imageForm.getValue("attachment");				} else {					url = this.imageForm.getValue("url");				}				if(url!="") {					this.rte.execCommand("insertImage", false, url);				}				this.rteWindow.focus();			} catch (e) {				//alert(e);			}		}	},	imageDialogTypeListener: function(event) {		if(this.imageForm.getValue("type")=="url") {			Core.get(this.imageDialogId+"UrlRow").show();			Core.get(this.imageDialogId+"AttachmentRow").hide();		} else {			Core.get(this.imageDialogId+"UrlRow").hide();			Core.get(this.imageDialogId+"AttachmentRow").show();		}	},	imageDialogUpdateAttachmentOptions: function(event) {		var oldValue = this.imageForm.getValue("attachment");		var element = this.imageForm.getElement("attachment");		var data = this.imageDialogAttachmentData.getRows();					if(data.length>0) {			for(var index=0; index<data.length; index++) {				var value = data[index].getValue("name")				var selected = value==oldValue;							element.getDom().options[index] = new Option(value, value, false, selected);			}		} else {			element.getDom().options[0] = new Option("", "", false, true);		}	},	imageDialogAddUpload: function(event) {		var element = this.imageForm.getElement("attachment");		element.getDom().options[element.getDom().options.length] = new Option(event.fileName, event.fileName, false, true);	}});/* ############################## Search  ############################## *//** * This creates a search button  * @class Core.Domino.SearchBox */Core.Domino.SearchBox = Core.create({    /**	 * Creates a search button	 * @method init	 * @constructor	 * @param {Core.Domino.Page} dominoPage The current page.	 * @param {String}/HTMLElement/{Core.Element} header The id, html node or Core.Element for the header in which the search button should be placed.	 * @parma {String} search (Optional) A sting with an intial search string in it.	 * @param {Array} profiles (Optional) A list of search profiles, by default uses the values in "searchProfiles" field.	 * @param {String} defaultProfile (Optional) The profile to be selected by default, this is only used if the user has not used a cookie to switch the search.  When not specified and no cookie exsitits the first entry will be used.	 * @param {String} cookiePrefix (Optional) The prefix to use on cookie names, defailts to "search" for "searchProfile" and "searchString".	 */	init : function(page, header, searchString, profiles, defaultProfile, cookiePrefix) {		this.page = page;		header = Core.get(header);		this.cookiePrefix = cookiePrefix?cookiePrefix:"search";				// search box		this.searchBox = header.createChild({tag: "div", klass: "search"});		this.selectorButton = this.searchBox.createChild({tag: "div", klass: "selector", html: "<img src=\""+this.page.getDbUrl()+"/search.png\"/>"});				this.input = this.searchBox.createChild({tag: "input", value: (searchString?searchString:"")});		var button = this.searchBox.createChild({tag: "div", klass: "button", html: "Search"});		button.preventTextSelection();				// selector		profiles = profiles?profiles:page.getValue("searchProfiles").split(", ");		this.selectedProfile = Core.readCookie(this.cookiePrefix+"Profile");		if(this.selectedProfile==null) {			if(defaultProfile) {				this.selectedProfile=defaultProfile;			} else {				this.selectedProfile=profiles[0];			}		}		var profilesObject = {}		for(var index=0; index<profiles.length; index++) {			profilesObject[profiles[index]] = profiles[index];		}						this.selectorPopup = new Core.Ui.ComboListPopup(profilesObject, "searchListPopup");		this.selectorPopup.setSelected([this.selectedProfile]);		this.selectorPopup.selectionEvent.addListener(this.selectorSelect.bind(this));		this.selectorPopup.hideEvent.addListener(this.selectorHide.bind(this));						// events		this.selectorButton.addListener("click", this.selectorClick.bind(this));		this.input.addListener("keypress", this.searchTextEntered.bind(this));		button.addListener("click", this.search.bind(this));	},	searchTextEntered: function(event) {		var characterCode = event.keyCode;			if(characterCode==13) {			Core.EventManager.catchEvent(event);				this.search();		}	},	search: function() {		Core.eraseCookie(this.cookiePrefix+"String", this.page.getDbUrl());		var searchString = this.input.getDom().value.trim();		if(searchString!="") {			location.href = this.page.getDbUrl()+"/searchForm?OpenForm&searchString="+escape(searchString);		} else {			location.href = this.page.getDbUrl()+"/searchForm?OpenForm";		}	},	selectorClick: function(event) {		if(this.selectorPopup.isVisible()) {			this.selectorHide(event);		} else {			this.selectorShow(event);		}	},	selectorShow: function(event) {		this.selectorButton.addClass("pressed");				var pos = this.searchBox.getTotalOffset().add(new Core.Point(0, this.searchBox.getSize().y));				this.selectorPopup.setOffset(pos);		this.selectorPopup.show(event);	},	selectorHide: function(event) {		this.selectorButton.removeClass("pressed");		this.selectorPopup.hide();	},	selectorSelect: function(event) {		if(event && event.value) {			this.selectedProfile = event.value;			this.selectorPopup.setSelected([this.selectedProfile]);			Core.createCookie(this.cookiePrefix+"Profile", this.selectedProfile, 0, this.page.getDbUrl());		}	},	getCurrentProfile: function() {		return this.selectedProfile;	}});/** * A data object used to load search results. * @class Core.Domino.SearchData *@extends Core.Data.XML */Core.Domino.SearchData = Core.create(Core.Data.XML, {	/**	 * Creates a new SearchData object	 * @method init	 * @constructor	 * @param {Core.Domino.Page} dominoPage The current domino page.	 * @param {String} searchString (Optional) The string to search for.	 * @param {String} profileName (Optional) The search profile to use.	 */	init: function(dominoPage, searchString, profileName) {		Core.Data.XML.prototype.init.call(this, {			rootNode: "viewentries",			rowNode: "viewentry",			positionAttribute: "position",			siblingsAttribute: "siblings",			cellAttributes: ["unid", "url", "form", "score"],			cellNode: "entrydata"		});				this.dominoPage = dominoPage;		this.url = dominoPage.getAgentUrl("ftSearch");		this.profile = profileName;				if(searchString && searchString!="") {			this.load(searchString);		}	},	/**	 * Loads data in to this object.	 * @method load	 * @param {String} searchString The string to search for.	 * @param {String} profileName (Optional) The search profile to use.	 */	load: function(searchString, profileName) {		if(profileName) {			this.profileName = profileName;		}					Core.Ui.showStatusMessage("Searching...", false, 250);		Core.addRequest("GET", this.url + "&profile="+this.profileName+"&searchString="+searchString, "", this.loadData.bind(this, [true, true]));	},	/* overridden to hide loading message */	loadData: function(event, reset, fire) {		var rowsLoaded = Core.Data.XML.prototype.loadData.call(this, event, reset, fire);		Core.Ui.hideStatusMessage();	},		/* overridden to extract the value for the cell and deal with the various data types */	loadCell: function(row, cellNode) {		var type = Core.Data.STRING;		var name = Core.XML.getAttribute(cellNode, "columnnumber", "0");		var value = "&nbsp;";			// find data		var dataNode = cellNode.firstChild;		while(dataNode!=null && dataNode.nodeType!=1) {			dataNode = dataNode.nextSibling;		}								// get value		if(dataNode!=null) {			if(dataNode.nodeName=="text") {				if(dataNode.childNodes.length>0) {					value = dataNode.firstChild.nodeValue;				}			} else if(dataNode.nodeName=="textlist") {				var textNodes = dataNode.getElementsByTagName("text");				var values = new Array();				for(var index=0; index<textNodes.length; index++) {					if(textNodes[index].childNodes.length>0) {						values.push(textNodes[index].firstChild.nodeValue);					}				}				value = values.join(", ");			} else if(dataNode.nodeName=="number") {				type=Core.Data.NUMBER;				if(dataNode.childNodes.length>0) {					value = dataNode.firstChild.nodeValue;				}			} else if(dataNode.nodeName=="datetime") {				type=Core.Data.DATE;				if(dataNode.childNodes.length>0) {					var text = dataNode.firstChild.nodeValue.substr(0,8);					var date = new Date();					date.setFullYear(parseInt(text.substr(0,4), 10), parseInt(text.substr(4,2), 10)-1, parseInt(text.substr(6,2), 10));					value = date;				}			} else {				value = "Unknown Data Type: "+dataNode.nodeName;			}		}				// create cell		var cell = row.addCell(type, name, value);	}});
