/* * ***** BEGIN LICENSE BLOCK ***** * Version: ZAPL 1.1 * * The contents of this file are subject to the Zimbra AJAX Public * License Version 1.1 ("License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.zimbra.com/license * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is: Zimbra AJAX Toolkit. * * The Initial Developer of the Original Code is Zimbra, Inc. * Portions created by Zimbra are Copyright (C) 2005 Zimbra, Inc. * All Rights Reserved. * * Contributor(s): * * ***** END LICENSE BLOCK ***** */ /** * @constructor * @class * @param attributes * @param model - instance of XModel * @param instance - data instance * @param dwtContainer - instance of DwtComposite **/ function XForm(attributes, model, instance, dwtContainer) { if (attributes) { for (var prop in attributes) { this[prop] = attributes[prop]; } } // get a unique id for this form this.assignGlobalId(this, this.id || "_XForm"); DwtComposite.call(this, dwtContainer, "DWTXForm"); if (this.itemDefaults) { XFormItemFactory.initItemDefaults(this, this.itemDefaults); } // if they didn't pass in a model, make an empty one now if (model) { this.setModel(model); } else { this.xmodel = new XModel(); } if (instance) this.setInstance(instance); this.__idIndex = {}; this.__externalIdIndex = {}; this.__itemsAreInitialized = false; } XForm.prototype = new DwtComposite; XForm.prototype.constructor = XForm; XForm.toString = function() { return "[Class XForm]"; } XForm.prototype.toString = function() { return "[XForm " + this.__id + "]"; } XForm.prototype.getId = function () { return this.__id; } /** * A global handler for setTimeout/clearTimeout. This handler is used by onKeyPress event of all input fields. * Greg Solovyev 07/19/05 **/ XForm.keyPressDelayHdlr = null; /** * FORM DEFAULTS **/ XForm.prototype.numCols = 2; XForm.prototype.defaultItemType = "output"; XForm.prototype._isDirty = false; XForm._showBorder = false; // if true, we write a border around form cells for debugging // // FORM CONSTANTS // // generic script constants var _IGNORE_CACHE_ = "IGNORE_CACHE"; var _UNDEFINED_; var _UNDEFINED_; var _ALL_ = "all"; // possible values for "labelDirection", "align" and "valign" var _NONE_ = "none"; var _LEFT_ = "left"; var _TOP_ = "top"; var _RIGHT_ = "right"; var _BOTTOM_ = "bottom"; var _CENTER_ = "center"; var _MIDDLE_ = "middle"; // values for "relevantBehavior" var _HIDE_ = "hide"; var _DISABLE_ = "disable"; var _SHOW_DISABLED_ = "show_disabled"; var _PARENT_ = "parent"; // used in error location as well // values for "errorLocation" var _SELF_ = "self"; // var _INHERIT_ = "inherit" -- this is defined in XModel.js // values for "selection" var _OPEN_ = "open"; var _CLOSED_ = "closed"; // possible values for "overflow" var _HIDDEN_ = "hidden"; var _SCROLL_ = "scroll"; var _AUTO_ = "auto"; var _VISIBLE_ = "visible"; // refresh: update the form with new values // NOTE: this will be done automatically if you do a setInstance()... XForm.prototype.refresh = function () { // EMC 07/31/2005 -- I don't think we want to clear errors on every refresh // since this is called from itemChanged. //this.clearErrors(); this.updateScript(); } /** THE FOLLOWING CODE SHOULD BE CONVERTED TO DWT **/ XForm.prototype.getGlobalRefString = function() { return "XFG.cacheGet('" + this.__id + "')"; } XForm.prototype.assignGlobalId = function (object, prefix) { return XFG.assignUniqueId(object, prefix); } XForm.prototype.getUniqueId = function (prefix) { return XFG.getUniqueId(prefix); } XForm.prototype.getElement = function (id) { if (id == null) id = this.getId(); return XFG.getEl(id); } XForm.prototype.showElement = function (id) { if (id == null) id = this.getId(); return XFG.showEl(id); } XForm.prototype.hideElement = function (id) { if (id == null) id = this.getId(); return XFG.hideEl(id); } XForm.prototype.createElement = function (id, parentEl, tagName, contents) { if (id == null) id = this.getId(); return XFG.createEl(id, parentEl, tagName, contents); } /** END DWT CONVERSION NEEDED **/ XForm.prototype.focusElement = function (id) { var el = this.getElement(id); // if this is a div we will have problems. if (el != null) { var tagName = el.tagName; if (tagName != "DIV" && tagName != "TD" && tagName != "TABLE") { el.focus(); this.onFocus(id); } } }; XForm.prototype.focusNext = function(id) { if(this.tabIdOrder && this.tabIdOrder.length > 0) { var cnt = this.tabIdOrder.length; // var nextId = null; for (var i = 0; i < cnt; i++) { if(this.tabIdOrder[i] == id) { if(this.tabIdOrder[i+1]) { // nextId = this.tabIdOrder[i+1]; this.focusElement(this.tabIdOrder[i+1]); } else { // nextId = this.tabIdOrder[0]; this.focusElement(this.tabIdOrder[0]); } break; } } } }; XForm.prototype.getModel = function () { return this.xmodel; } XForm.prototype.setModel = function (model) { this.xmodel = model; } XForm.prototype.getInstance = function () { return this.instance; } XForm.prototype.setInstance = function(instance) { this.setIsDirty(false); this.clearErrors(); this.instance = instance; if (this.__drawn) { this.refresh(); } } XForm.prototype.getController = function () { return this.controller; } XForm.prototype.setController = function(controller) { this.controller = controller; } XForm.prototype.getIsDirty = function () { return this._isDirty; } XForm.prototype.setIsDirty = function(dirty) { this._isDirty = (dirty == true); this.notifyListeners(DwtEvent.XFORMS_FORM_DIRTY_CHANGE, new DwtXFormsEvent(this, null, this._isDirty)); } XForm.prototype.initializeItems = function() { // tell the model to initialize all its items first // (its smart enough to only do this once) this.xmodel.initializeItems(); if (this.__itemsAreInitialized) return; // create a group for the outside parameters and initialize that // XXX SKIP THIS IF THERE IS ONLY ONE ITEM AND IT IS ALREADY A GROUP, SWITCH OR REPEAT??? var outerGroup = { id:"__outer_group__", type:_GROUP_, useParentTable:false, numCols:this.numCols, colSizes:this.colSizes, items:this.items, tableCssClass:this.tableCssClass, tableCssStyle:this.tableCssStyle } this.items = this.initItemList([outerGroup]); // we wait to initialize items until the form is drawn, for speed this.__itemsAreInitialized = false; } XForm.prototype.initItemList = function(itemAttrs, parentItem) { var items = []; for (var i = 0; i < itemAttrs.length; i++) { var attr = itemAttrs[i]; if (attr != null) { items.push(this.initItem(attr, parentItem)); } } this.__nestedItemCount += itemAttrs.length; //DEBUG return items; } XForm.prototype.initItem = function(itemAttr, parentItem) { // if we already have a form item, assume it's been initialized already! if (itemAttr._isXFormItem) return itemAttr; // create the XFormItem subclass from the item attributes passed in // (also links to the model) var item = XFormItemFactory.createItem(itemAttr, parentItem, this); // have the item initialize it's sub-items, if necessary (may be recursive) item.initializeItems(); return item; } // add an item to our index, so we can find it easily later XForm.prototype.indexItem = function(item, id) { //DBG.println("id: "+id); this.__idIndex[id] = item; // Add the item to an existing array, or var exId = item.getExternalId(); if (exId == null || exId == "") return; var arr = this.__externalIdIndex[exId]; if (arr != null) { arr.push(item); } else { arr = [item]; } this.__externalIdIndex[exId] = arr; } // refPath is ignored // This is probably not useful to an Xforms client -- // use getItemsById instead. XForm.prototype.getItemById = function(id) { if (id._isXFormItem) return id; return this.__idIndex[id]; } // This is a method that can be called by an XForms client, but // which doesn't have much internal use. // gets an item by the id or the ref provided in the declaration // This method returns an array, or null; XForm.prototype.getItemsById = function (id) { return this.__externalIdIndex[id]; }; XForm.prototype.get = function(path) { if (path == null) return null; if (path._isXFormItem) path = path.getRefPath(); return this.xmodel.getInstanceValue(this.instance, path); } XForm.prototype.isDrawn = function () { return (this.__drawn == true); } /** * EMC 7/12/2005: I didn't want the extra div that DwtControl writes, * since the xforms engine already has an outer div that we can use as * container. */ XForm.prototype._replaceDwtContainer = function () { var myDiv = document.getElementById(this.__id); var dwtContainer = this.getHtmlElement(); if (dwtContainer.parentNode) dwtContainer.parentNode.replaceChild(myDiv, dwtContainer); this._htmlElId = this.__id; }; /** * actually draw the form in the parentElement * @param parentElement * calls outputForm to generate all the form's HTML **/ XForm.prototype.draw = function (parentElement) { this.initializeItems(); // get the HTML output var formOutput = this.outputForm(); if (parentElement == null) parentElement = this.getHtmlElement(); // if a parentElement was passed, stick the HTML in there and call the scripts // if not, you'll have call put HTML somewhere and call the scripts yourself if (parentElement) { parentElement.innerHTML = formOutput; this._replaceDwtContainer(); if (this.instance != null) { // run the updateScript this.refresh(); // setTimeout(this.getGlobalRef()+ ".refresh()", 1000); } } // notify any listeners that we're "ready" this.notifyListeners(DwtEvent.XFORMS_READY, new DwtXFormsEvent(this)); // remember that we've been drawn this.__drawn = true; // and we're done! } XForm.prototype.getItems = function () { return this.items; } /** * Prints out the form HTML * calls outputItemList **/ XForm.prototype.outputForm = function () { var t0 = new Date().getTime(); var html = new AjxBuffer(); // holds the HTML output var items = this.getItems(); var indent = ""; html.append('
\r' ); this._itemsToInsert = {}; this._itemsToCleanup = []; var updateScript = new AjxBuffer(); // holds the script to populate values and show/hide elements based on relevant attibute // in initializeItems(), we guaranteed that there was a single outer item // and that it is a group that sets certain properties that can be set at // the form level. Just output that (and it will output all children) // output the actual items of the form this.outputItemList(items[0].items, items[0], html, updateScript, indent, this.numCols); this.makeUpdateScript(updateScript); html.append("\r
"); // save the HTML in this.__html (for debugging and such) this.__HTMLOutput = html.toString(); DBG.println("outputForm() took " + (new Date().getTime() - t0) + " msec"); return this.__HTMLOutput; } XForm.prototype.getOutstandingRowSpanCols = function (parentItem) { if (parentItem == null) return 0; var outstandingRowSpanCols = 0; var previousRowSpans = parentItem.__rowSpanItems; if (previousRowSpans) { for (var i = 0; i < previousRowSpans.length; i++) { var previousItem = previousRowSpans[i]; //DBG.println("outputing ", previousItem.__numDrawnCols," rowSpan columns for ", previousItem); outstandingRowSpanCols += previousItem.__numDrawnCols; previousItem.__numOutstandingRows -= 1; if ( previousItem.__numOutstandingRows == 0) { if (previousRowSpans.length == 1) { delete parentItem.__rowSpanItems; } else { parentItem.__rowSpanItems = [].concat(previousRowSpans.slice(0,i), previousRowSpans.slice(i+1)); } } } } return outstandingRowSpanCols; } /** * This method will iterate through all the items (XFormItem) in the form and call outputMethod on each of them. * @param items * @param parentItem * @param html * @param updateScript * @param indent * @param numCols * @param currentCol * @param skipTable **/ XForm.prototype.outputItemList = function (items, parentItem, html, updateScript, indent, numCols, currentCol, skipTable) { if (parentItem.outputHTMLStart) { parentItem.outputHTMLStart(html, updateScript, indent, currentCol); } var drawTable = (parentItem.getUseParentTable() == false && skipTable != true); if (drawTable) { var colSizes = parentItem.getColSizes(); //XXX MOW: appending an elementDiv around the container if we need to style it var outerStyle = parentItem.getCssString(); if (outerStyle != null && outerStyle != "") { parentItem.outputElementDivStart(html, updateScript, indent); } html.append(indent, "\r"); if (colSizes != null) { html.append(indent, " \r"); for (var i = 0; i < colSizes.length; i++) { var size = colSizes[i]; if (size < 1) size = size * 100 + "%"; html.append(indent, " \r"); } html.append(indent, " \r"); } html.append(indent, "\r"); } numCols = Math.max(1, numCols); if (currentCol == null) currentCol = 0; //DBG.println("outputItemList: numCols:",numCols, " currentCol:", currentCol); for (var itemNum = 0; itemNum < items.length; itemNum++) { var item = items[itemNum]; var isNestingItem = (item.getItems() != null); var itemUsesParentTable = (item.getUseParentTable() != false); item.__numDrawnCols = 0; // write the beginning of the update script // (one of the routines below may want to modify it) item.outputUpdateScriptStart(html, updateScript, indent); var label = item.getLabel(); var labelLocation = item.getLabelLocation(); var showLabel = (label != null && (labelLocation == _LEFT_ || labelLocation == _RIGHT_)); var colSpan = item.getColSpan(); if (colSpan == "*") colSpan = Math.max(1, (numCols - currentCol)); var rowSpan = item.getRowSpan(); var totalItemCols = item.__numDrawnCols = parseInt(colSpan) + (showLabel ? 1 : 0); if (rowSpan > 1 && parentItem) { if (parentItem.__rowSpanItems == null) parentItem.__rowSpanItems = []; parentItem.__rowSpanItems.push(item); item.__numOutstandingRows = rowSpan; } // write the label to the left if desired if (label != null && labelLocation == _LEFT_) { //DBG.println("writing label"); item.outputLabelCellHTML(html, updateScript, indent+" ", rowSpan); } var writeElementDiv = item.getWriteElementDiv(); var outputMethod = item.getOutputHTMLMethod(); if (isNestingItem && itemUsesParentTable) { // actually write out the item if (outputMethod) outputMethod.call(item, html, updateScript, indent + " ", currentCol); } else { // write the cell that contains the item // NOTE: this is currently also the container! item.outputContainerTDStartHTML(html, updateScript, indent, colSpan, rowSpan); // begin the element div, if required if (writeElementDiv) item.outputElementDivStart(html, updateScript, indent); // actually write out the item if (outputMethod) outputMethod.call(item, html, updateScript, indent + " ", 0); // end the element div, if required if (writeElementDiv) item.outputElementDivEnd(html, updateScript, indent); // end the cell that contains the item item.outputContainerTDEndHTML(html, updateScript, indent); } currentCol += totalItemCols; // write the label to the right, if desired if (label != null && labelLocation == _RIGHT_) { //DBG.println("writing label"); item.outputLabelCellHTML(html, updateScript, indent+" ", rowSpan); } // now end the update script if necessary item.outputUpdateScriptEnd(html, updateScript, indent); if ( currentCol >= numCols) { html.append(indent, "\r"); currentCol = this.getOutstandingRowSpanCols(parentItem); //DBG.println("creating new row: currentCol is now ", currentCol, (currentCol > 0 ? " due to outstanding rowSpans" : "")); } // if the number of outstanding rows is the same as the number of columns we're to generate // output an empty row for each while (currentCol >= numCols) { DBG.println("outputting empty row because outstandingRowSpanCols >= numCols"); html.append("");//\r"); currentCol = this.getOutstandingRowSpanCols(parentItem); } } if (drawTable) { html.append("\r", indent, "\r", indent,"
\r"); if (outerStyle != null) { parentItem.outputElementDivEnd(html, updateScript, indent); } } if (parentItem.outputHTMLEnd) { parentItem.outputHTMLEnd(html, updateScript, indent, currentCol); } } // // NOTE: properties of individual items moved to XForm_item_properties.js // // CHANGE HANDLING XForm.prototype.onFocus = function(id) { this.__focusObject = id; } XForm.prototype.onBlur = function(id) { this.__focusObject = null; } XForm.prototype.itemChanged = function (id, value, event) { var item = this.getItemById(id); if (item == null) return alert("Couldn't get item for " + id); // EXCEPTION // tell the item that it's display is dirty so it might have to update item.dirtyDisplay(); // validate value var modelItem = item.getModelItem(); if (modelItem != null) { try { value = modelItem.validate(value, this, item, this.getInstance()); item.clearError(); } catch (message) { item.setError(message); var event = new DwtXFormsEvent(this, item, value); this.notifyListeners(DwtEvent.XFORMS_VALUE_ERROR, event); return; } } // if there is an onChange handler, call that var onChangeMethod = item.getOnChangeMethod(); if (typeof onChangeMethod == "function") { DBG.println("itemChanged(", item.ref, ").onChange = ", onChangeMethod); value = onChangeMethod.call(item, value, event, this); } else { item.setInstanceValue(value); } var event = new DwtXFormsEvent(this, item, value); this.notifyListeners(DwtEvent.XFORMS_VALUE_CHANGED, event); this.setIsDirty(true); this.refresh(); } XForm.prototype.getItemsInErrorState = function () { if (this.__itemsInErrorState == null) { this.__itemsInErrorState = new Object(); this.__itemsInErrorState.size = 0; } return this.__itemsInErrorState; }; XForm.prototype.addErrorItem = function ( item ) { var errs = this.getItemsInErrorState(); var oldItem = errs[item.getId()]; errs[item.getId()] = item; if (oldItem == null){ errs.size++; } }; XForm.prototype.removeErrorItem = function ( item ) { if (item != null) { var errs = this.getItemsInErrorState(); var id = item.getId(); var oldItem = errs[id]; if (oldItem != null) { delete errs[id]; errs.size--; } } }; XForm.prototype.hasErrors = function () { var errs = this.getItemsInErrorState(); return (errs != null && errs.size > 0); }; XForm.prototype.clearErrors = function () { var errs = this.getItemsInErrorState(); if (errs.size > 0) { var k; for (k in errs) { if (k == 'size') continue; errs[k].clearError(); delete errs[k]; } errs.size = 0; } } XForm.prototype.onCloseForm = function () { if (this.__focusObject != null) { var item = this.getItemById(this.__focusObject); var element = item.getElement(); //alert("onCloseForm() not implemented"); // this.itemChanged(this.__focusObject, VALUE???) if (element && element.blur) { element.blur(); } this.__focusObject = null; } } // // SCRIPTS: // * updateScript -- sets values of items in the HTML representation // -- CALLED REPEATEDLY: ON INITIAL DISPLAY AND EVERY TIME AN ITEM CHANGES XForm.prototype.makeUpdateScript = function (script) { if (typeof script != "string") script = script.toString(); this.__updateScript = script; this.updateScript = new Function( (this.updateScriptStart != null ? this.updateScriptStart + ";" : '') + this.getUpdateScriptStart() + script + this.getUpdateScriptEnd() + (this.updateScriptEnd != null ? this.updateScriptEnd + ";" : '') ); } XForm.prototype.appendToUpdateScript = function (script) { if (typeof script != "string") script = script.toString(); this.makeUpdateScript(this.__updateScript + script); } // // Writing parts of the update script // XForm.prototype.getUpdateScriptStart = function () { return AjxBuffer.concat( "var t0 = new Date().getTime();\r", "var updateScript;\r", "var _____________________________________________________ = 0;\r", "var form = this;\r", "var model = this.xmodel;\r", "var instance = this.instance;\r", "var item, element, relevant, value, temp;\r", "this.tabIdOrder = new Array();\r", "with (this) {" ); } XForm.prototype.getUpdateScriptEnd = function () { return AjxBuffer.concat( "_____________________________________________________++;\r", "}\r", // end with (this) "var event = new DwtXFormsEvent(form);\r", "this.notifyListeners(DwtEvent.XFORMS_DISPLAY_UPDATED, event);\r", "var t1 = new Date().getTime();\r", "DBG.println('update script took ' + (t1 - t0) + ' msec.');\r" ); } /** @private */ XForm.prototype._reparentDwtObject = function (dwtObj, newParent) { var dwtE = dwtObj.getHtmlElement(); if (dwtE.parentNode) dwtE.parentNode.removeChild(dwtE); newParent.appendChild(dwtE); } // // INSERTING // XForm.prototype.shouldInsertItem = function (item) { return (this._itemsToInsert[item.getId()] != null) } XForm.prototype.insertExternalWidget = function (item) { DBG.println("insertExternalWidget(): inserting ref=", item.ref, " type=", item.type, " id=", item.getId() ); var insertMethod = item.getInsertMethod(); var widget = item.getWidget(); if (widget && widget.insertIntoXForm instanceof Function) { widget.insertIntoXForm(this, item, item.getElement()); } else if (typeof this[insertMethod] == "function") { this[insertMethod](item, item.getElement()); } else { DBG.println("insertExternalWidget(): don't know how to insert item ", item.ref, " type=", item.type); } // take the item out of the list to insert so we don't insert it more than once delete this._itemsToInsert[item.getId()]; }