1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 
 25 /**
 26  * @constructor
 27  * @class
 28  * 
 29  * @param attributes
 30  * @param {XModel}	model		the model
 31  * @param {Object}	instance  the data instance
 32  * @param {DwtComposite}	dwtContainer 	the container
 33  * 
 34  * @private
 35  */
 36 XForm = function(attributes, model, instance, dwtContainer, contextId) {
 37 	if (attributes) {
 38 		for (var prop in attributes) {
 39 			this[prop] = attributes[prop];	
 40 		}
 41 	}
 42 
 43 	// get a unique id for this form
 44 	this.assignGlobalId(this, this.id || contextId ||  "_XForm");
 45 	DwtComposite.call(this, dwtContainer, "DWTXForm");
 46 	
 47 	if (this.itemDefaults) {
 48 		XFormItemFactory.initItemDefaults(this, this.itemDefaults);
 49 	}
 50 
 51 	// if they didn't pass in a model, make an empty one now
 52 	if (model) {
 53 		this.setModel(model);
 54 	} else {
 55 		this.xmodel = new XModel();
 56 	}
 57 	if (instance) this.setInstance(instance);
 58 
 59 	this.__idIndex = {};
 60 	this.__externalIdIndex = {};
 61 	this.__itemsAreInitialized = false;
 62 	this.tabIdOrder = [];
 63 	this.tabGroupIDs = [];
 64 }
 65 XForm.prototype = new DwtComposite;
 66 XForm.prototype.constructor = XForm;
 67 XForm.FONT_WIDTH1 = 7;
 68 XForm.FONT_WIDTH2 = 8;
 69 XForm.toString = function() {	return "[Class XForm]";	}
 70 XForm.prototype.toString = function() {	return "[XForm " + this.__id + "]";	}
 71 XForm.prototype.getId = function () {	return this.__id;	}
 72 
 73 /**
 74 * A global handler for setTimeout/clearTimeout. This handler is used by onKeyPress event of all input fields.
 75 **/
 76 XForm.keyPressDelayHdlr = null;
 77 
 78 
 79 /**
 80 * FORM DEFAULTS
 81 **/
 82 XForm.prototype.numCols = 2;
 83 XForm.prototype.defaultItemType = "output";
 84 XForm.prototype._isDirty = false;
 85 XForm._showBorder = false;		// if true, we write a border around form cells for debugging
 86 
 87 //
 88 //	FORM CONSTANTS
 89 //
 90 
 91 // generic script constants
 92 var _IGNORE_CACHE_ = "IGNORE_CACHE";
 93 var _UNDEFINED_;
 94 
 95 var _UNDEFINED_;
 96 var _ALL_ = "all";
 97 
 98 // possible values for "labelDirection", "align" and "valign"
 99 var _NONE_ = "none";
100 var _LEFT_ = "left";
101 var _TOP_ = "top";
102 var _RIGHT_ = "right";
103 var _BOTTOM_ = "bottom";
104 var _CENTER_ = "center";
105 var _MIDDLE_ = "middle";
106 var _INLINE_ = "inline";
107 
108 
109 // values for "relevantBehavior"
110 var _HIDE_ = "hide";
111 var _BLOCK_HIDE_ = "block_hide";
112 var _DISABLE_ = "disable";
113 var _SHOW_DISABLED_ = "show_disabled";
114 var _PARENT_ = "parent"; // used in error location as well
115 
116 // values for "errorLocation"
117 var _SELF_ = "self";
118 // var _INHERIT_ = "inherit" -- this is defined in XModel.js
119 
120 // values for "selection"
121 var _OPEN_ = "open";
122 var _CLOSED_ = "closed";
123 
124 // possible values for "overflow"
125 var _HIDDEN_ = "hidden";
126 var _SCROLL_ = "scroll";
127 var _AUTO_ = "auto";
128 var _VISIBLE_ = "visible";
129 
130 /**
131 * update the form with new values
132 *  NOTE: this will be done automatically if you do a {@link #setInstance}
133 * This method is costly and should not be called unless the whole form needs to be refreshed.
134 * When a single or several values are changed on a form - use change events.
135 **/
136 XForm.prototype.refresh = function () {
137 	if(this.__drawn)
138 		this.updateElementStates();
139 }
140 
141 
142 // NOTE: THE FOLLOWING CODE SHOULD BE CONVERTED TO DWT 
143 
144 XForm.prototype.getGlobalRefString = function() {
145 	return "XFG.cacheGet('" + this.__id + "')";
146 }
147 
148 XForm.prototype.assignGlobalId = function (object, prefix) {
149 	return XFG.assignUniqueId(object, prefix);
150 }
151 XForm.prototype.getUniqueId = function (prefix) {
152 	return XFG.getUniqueId(prefix);
153 }
154 
155 XForm.prototype.getElement = function (id) {
156 	if (id == null) id = this.getId();
157 	var el = XFG.getEl(id);
158 	if (el == null) {
159 		DBG.println(AjxDebug.DBG2, "getElement(",id,"): no element found");
160 	}
161 	return el;
162 }
163 
164 XForm.prototype.showElement = function (id) {
165 	if (id == null) id = this.getId();
166 	return XFG.showEl(id);
167 }
168 XForm.prototype.hideElement = function (id,isBlock) {
169 	if (id == null) id = this.getId();
170 	return XFG.hideEl(id,isBlock);
171 }
172 
173 XForm.prototype.createElement = function (id, parentEl, tagName, contents) {
174 	if (id == null) id = this.getId();
175 	return XFG.createEl(id, parentEl, tagName, contents);
176 }
177 
178 // NOTE: END DWT CONVERSION NEEDED 
179 
180 XForm.prototype.focusElement = function (id) {
181 	var el = this.getElement(id);
182 	// if this is a div we will have problems.
183 	if (el != null) {
184 		var tagName = el.tagName;
185 		if (tagName != "DIV" && tagName != "TD" && tagName != "TABLE") {
186 			el.focus();		//MOW: el.select() ????
187 			this.onFocus(id);
188 		}
189 	}
190 
191     return el;
192 };
193 
194 //set focus on the first element in the actuve tab group or in the first element in the form
195 XForm.prototype.focusFirst = function(currentTabId) {
196 	var tabIdOrder=null;
197 	if (currentTabId != null ) {
198 		tabIdOrder = this.tabIdOrder[currentTabId];
199 	} else {
200 		for(var a in this.tabIdOrder) {
201 			if(this.getItemById(a).getIsVisible() && this.getItemById(a).getIsEnabled() && this.tabIdOrder[a] && this.tabIdOrder[a].length > 0) {
202 				tabIdOrder = this.tabIdOrder[a];
203 				break;
204 			}
205 		}
206 	}	
207 	if(tabIdOrder) {
208 		var cnt = tabIdOrder.length;
209 		for (var i = 0; i < cnt; i++) {
210 			var nextItem = this.getItemById(tabIdOrder[i]);
211 			if(nextItem && nextItem.focusable && nextItem.getIsVisible() && nextItem.getIsEnabled()) {
212 				return this.focusElement(tabIdOrder[i]);
213 			}
214 		}
215 	}
216     return null;
217 };
218 
219 XForm.prototype.addTabGroup = function(item, tabGroupKeyAttr) {
220 	tabGroupKeyAttr = tabGroupKeyAttr ? tabGroupKeyAttr : "tabGroupKey";
221 	var tabGroupKey = item.getInheritedProperty(tabGroupKeyAttr) ? item.getInheritedProperty(tabGroupKeyAttr) : item.getId();
222 	this.tabGroupIDs[tabGroupKey] = item.getId();
223 }
224 
225 XForm.prototype.focusNext = function(id, currentTabId) {
226 	var myId = id ? id : null;
227 	var tabIdOrder = null ;
228 	if (currentTabId != null ) {
229 		tabIdOrder = this.tabIdOrder[currentTabId];
230 	} else {
231 		tabIdOrder = this.tabIdOrder ;
232 	}
233 	
234 	if(tabIdOrder && tabIdOrder.length > 0) {
235 		var cnt = tabIdOrder.length;
236 		//DBG.println(AjxDebug.DBG1, "TabIdOrder: length = " + tabIdOrder.length + "<br />" + tabIdOrder.toString());
237 		if (myId != null) {
238 			for (var i = 0; i < cnt; i++) {
239 				if(tabIdOrder[i] == myId) {
240 					var elIndex = ((i+1) % cnt);
241 					if(tabIdOrder[elIndex]) {
242 						var nextEl = this.getItemById(tabIdOrder[elIndex]);
243 						if(nextEl.focusable && nextEl.getIsVisible() && nextEl.getIsEnabled()) {
244 							return this.focusElement(tabIdOrder[elIndex]);
245 						} else {
246 							myId=tabIdOrder[elIndex];
247 						}
248 					} 
249 				}
250 			}
251 		}		
252 		return this.focusFirst(currentTabId);
253 	}
254     return null;
255 };
256 
257 XForm.prototype.focusPrevious = function(id, currentTabId) {
258 	var myId = id ? id : null;
259 	var tabIdOrder = null ;
260 	if (currentTabId != null ) {
261 		tabIdOrder = this.tabIdOrder[currentTabId];
262 	} else {
263 		tabIdOrder = this.tabIdOrder ;
264 	}
265 	
266 	if(tabIdOrder && tabIdOrder.length > 0) {
267 		var cnt = tabIdOrder.length-1;
268 		if (myId != null) {
269 			for (var i = cnt; i >= 0; i--) {
270 				if(tabIdOrder[i] == myId) {
271 					var elIndex = ((i-1) % cnt);
272 					if(tabIdOrder[elIndex]) {
273 						var nextEl = this.getItemById(tabIdOrder[elIndex]);
274 						if(nextEl.focusable && nextEl.getIsVisible()  && nextEl.getIsEnabled()) {
275 							return this.focusElement(tabIdOrder[elIndex]);
276 						} else {
277 							myId=tabIdOrder[elIndex];
278 						}
279 					} 
280 				}
281 			}
282 		}		
283 		return this.focusFirst(currentTabId);
284 	}
285     return null;
286 };
287 
288 XForm.prototype.getModel = function () {
289 	return this.xmodel;
290 }
291 XForm.prototype.setModel = function (model) {
292 	this.xmodel = model;
293 }
294 
295 XForm.prototype.getInstance = function () {
296 	return this.instance;
297 }
298 
299 XForm.prototype.updateElementStates = function () {
300 	if(!this.__drawn)
301 		return;
302 		
303 	this.items[0].updateVisibility();
304 	this.items[0].updateEnabledDisabled();
305 	this.items[0].updateElement();
306 }
307 
308 XForm.prototype.setInstance = function(instance) {
309 	this.setIsDirty(false);
310 	this.clearErrors();
311 	this.instance = instance;
312 	if(this.__drawn)
313 		this.updateElementStates();
314 	else
315 		this.__updateStatesDelayed = true;
316 		
317 	if (this.__drawn) {
318 		this.notifyListeners(DwtEvent.XFORMS_INSTANCE_CHANGED, new DwtXFormsEvent(this));
319 	}
320 }
321 
322 XForm.prototype.getInstance = function() {
323 	return this.instance;
324 }
325 
326 XForm.prototype.setInstanceValue = function(val, refPath) {
327 	this.xmodel.setInstanceValue(this.instance,refPath,val);
328 }
329 
330 XForm.prototype.getInstanceValue = function(refPath) {
331 	return this.xmodel.getInstanceValue(this.instance,refPath);
332 }
333 
334 XForm.checkInstanceValue = function(refPath,val) {
335 	return (this.getInstanceValue(refPath) == val);
336 }
337 
338 XForm.checkInstanceValueNot = function(refPath,val) {
339 	return (this.getInstanceValue(refPath) != val);
340 }
341 
342 XForm.checkInstanceValueEmty = function(refPath) {
343 	return AjxUtil.isEmpty(this.getInstanceValue(refPath));
344 }
345 
346 XForm.checkInstanceValueNotEmty = function(refPath) {
347 	return !AjxUtil.isEmpty(this.getInstanceValue(refPath));
348 }
349 
350 XForm.prototype.getController = function () {
351 	return this.controller;
352 }
353 XForm.prototype.setController = function(controller) {
354 	this.controller = controller;
355 }
356 
357 
358 
359 
360 XForm.prototype.getIsDirty = function () {
361 	return this._isDirty;
362 }
363 XForm.prototype.setIsDirty = function(dirty, item) {
364 	this._isDirty = (dirty == true);
365 	//pass the current dirty XFORM item, so the event object can has the information which item is changed
366 	if (typeof item == "undefined") item = null ; //to make it compatible with the previous version. 
367 	this.notifyListeners(DwtEvent.XFORMS_FORM_DIRTY_CHANGE, new DwtXFormsEvent(this, item, this._isDirty));
368 }
369 
370 
371 
372 
373 
374 
375 XForm.prototype.initializeItems = function() {
376 	// tell the model to initialize all its items first
377 	//	(its smart enough to only do this once)
378 	this.xmodel.initializeItems();
379 
380 	if (this.__itemsAreInitialized) return;
381 	
382 	// create a group for the outside parameters and initialize that
383 	// XXX SKIP THIS IF THERE IS ONLY ONE ITEM AND IT IS ALREADY A GROUP, SWITCH OR REPEAT???
384 	var outerGroup = {
385 		id:"__outer_group__",
386 		type:_GROUP_,
387 		useParentTable:false,
388 
389 		numCols:this.numCols,
390 		colSizes:this.colSizes,
391 		items:this.items,
392 		tableCssClass:this.tableCssClass,
393 		tableCssStyle:this.tableCssStyle
394 	}
395 	this.items = this.initItemList([outerGroup]);
396 	
397 	this.__itemsAreInitialized = true;
398 }
399 
400 
401 XForm.prototype.initItemList = function(itemAttrs, parentItem) {
402 	var items = [];
403 	for (var i = 0; i < itemAttrs.length; i++) {
404 		var attr = itemAttrs[i];
405 		if (attr != null) {
406 			items.push(this.initItem(attr, parentItem));
407 		}
408 	}
409 	this.__nestedItemCount += itemAttrs.length;		//DEBUG
410 	return items;
411 }
412 
413 
414 XForm.prototype.initItem = function(itemAttr, parentItem) {
415 	// if we already have a form item, assume it's been initialized already!
416 	if (itemAttr._isXFormItem) return itemAttr;
417 	
418 	// create the XFormItem subclass from the item attributes passed in
419 	//	(also links to the model)
420 	var item = XFormItemFactory.createItem(itemAttr, parentItem, this);
421 	
422 	// have the item initialize it's sub-items, if necessary (may be recursive)
423 	item.initializeItems();
424 	return item;
425 }
426 
427 
428 // add an item to our index, so we can find it easily later
429 XForm.prototype.indexItem = function(item, id) {
430 	//DBG.println("id: "+id);
431 	this.__idIndex[id] = item;
432 	
433 	// Add the item to an existing array, or
434 	var exId = item.getExternalId();
435 	if (exId == null || exId == "") return;
436 
437 	var arr = this.__externalIdIndex[exId];
438 	if (arr != null) {
439 		arr.push(item);
440 	} else {
441 		arr = [item];
442 	}
443 	this.__externalIdIndex[exId] = arr;
444 }
445 // refPath is ignored
446 // This is probably not useful to an Xforms client --
447 // use getItemsById instead.
448 XForm.prototype.getItemById = function(id) {
449 	if (id._isXFormItem) return id;
450 	return this.__idIndex[id];
451 }
452 
453 // This is a method that can be called by an XForms client, but
454 // which doesn't have much internal use.
455 // gets an item by the id or the ref provided in the declaration
456 // This method returns an array, or null;
457 XForm.prototype.getItemsById = function (id) {
458 	return this.__externalIdIndex[id];
459 };
460 
461 XForm.prototype.get = function(path) {
462 	if (path == null) return null;
463 	if (path._isXFormItem) path = path.getRefPath();
464 	return this.xmodel.getInstanceValue(this.instance, path);
465 }
466 
467 
468 XForm.prototype.isDrawn = function () {
469 	return (this.__drawn == true);
470 }
471 
472 /**
473  * EMC 7/12/2005: I didn't want the extra div that DwtControl writes,
474  * since the xforms engine already has an outer div that we can use as 
475  * container.
476  */
477 XForm.prototype._replaceDwtContainer = function () {
478 	var myDiv = document.getElementById(this.__id);
479 	var dwtContainer = this.getHtmlElement();
480 	if (dwtContainer.parentNode) dwtContainer.parentNode.replaceChild(myDiv, dwtContainer);
481 	this._htmlElId = this.__id;
482 };
483 
484 /**
485 * actually draw the form in the parentElement
486 * @param parentElement
487 * calls outputForm to generate all the form's HTML
488 **/
489 XForm.prototype.draw = function (parentElement) {
490 	this.initializeItems();
491 	
492 	// get the HTML output
493 	var formOutput = this.outputForm();
494 	
495 	if (parentElement == null) parentElement = this.getHtmlElement();
496 	// if a parentElement was passed, stick the HTML in there and call the scripts
497 	//	if not, you'll have call put HTML somewhere and call the scripts yourself
498 	if (parentElement) {
499 		parentElement.innerHTML = formOutput;
500 	}
501 	
502 	// notify any listeners that we're "ready"
503 	this.notifyListeners(DwtEvent.XFORMS_READY, new DwtXFormsEvent(this));
504 	
505 	// remember that we've been drawn
506 	this.__drawn = true;
507 	// and we're done!
508 	
509 	if(this.__updateStatesDelayed && this.instance) {
510 		this.updateElementStates();
511 		this.__updateStatesDelayed = false;
512 	}	
513 }
514 
515 
516 XForm.prototype.getItems = function () {
517 	return this.items;
518 }
519 
520 /**
521 * Prints out the form HTML
522 * calls outputItemList
523 **/
524 XForm.prototype.outputForm = function () {
525 	var t0 = new Date().getTime();
526 	
527 	var html = new AjxBuffer();			// holds the HTML output
528 	var items = this.getItems();
529 
530 	
531 	html.append('<div id="', this.__id, '"', 'style="height: 100%;"',
532 				(this.cssClass != null && this.cssClass != '' ? ' class="' + this.cssClass + '"' : ""),
533 				(this.cssStyle != null && this.cssStyle != '' ? ' style="' + this.cssStyle + ';"' : ""),
534 				'>'
535 				);
536 	
537 	this._itemsToInsert = {};
538 	this._itemsToCleanup = [];
539 
540 	
541 	DBG.timePt("starting outputItemList");
542 	// in initializeItems(), we guaranteed that there was a single outer item
543 	//	and that it is a group that sets certain properties that can be set at
544 	//	the form level.  Just output that (and it will output all children)
545 
546 	// output the actual items of the form
547 	this.outputItemList(items[0].items, items[0], html, this.numCols);
548 	DBG.timePt("finished outputItemList");
549 	html.append("</div id=\"", this.__id,"\">");
550 
551 	// save the HTML in this.__html (for debugging and such)
552 	this.__HTMLOutput = html.toString();
553 
554 	//DBG.println("outputForm() took " + (new Date().getTime() - t0) + " msec");
555 
556 	return this.__HTMLOutput;
557 }
558 
559 XForm.prototype.getOutstandingRowSpanCols = function (parentItem) {
560 	if (parentItem == null) return 0;
561 	var outstandingRowSpanCols = 0;
562 	var previousRowSpans = parentItem.__rowSpanItems;
563 	if (previousRowSpans) {
564 /*
565 		for (var i = 0; i < previousRowSpans.length; i++) {
566 			var previousItem = previousRowSpans[i];
567 			//DBG.println("outputing ", previousItem.__numDrawnCols," rowSpan columns for ", previousItem);
568 			outstandingRowSpanCols += previousItem.__numDrawnCols;
569 
570 			previousItem.__numOutstandingRows -= 1;
571 			if ( previousItem.__numOutstandingRows == 0) {
572 				if (previousRowSpans.length == 1) {
573 					delete parentItem.__rowSpanItems;
574 				} else {
575 					parentItem.__rowSpanItems = [].concat(previousRowSpans.slice(0,i), previousRowSpans.slice(i+1));
576 				}
577 			}
578 		}
579 */
580 		for (var i = previousRowSpans.length-1; i >= 0; i--) {
581 			var previousItem = previousRowSpans[i];
582 			//DBG.println("outputing ", previousItem.__numDrawnCols," rowSpan columns for ", previousItem);
583 			previousItem.__numOutstandingRows -= 1;
584 			if ( previousItem.__numOutstandingRows == 0) {
585 				if (previousRowSpans.length == 1) {
586 					delete parentItem.__rowSpanItems;
587 				} else {
588 					parentItem.__rowSpanItems.pop();
589 				}
590 			} else {
591 				outstandingRowSpanCols += previousItem.__numDrawnCols;			
592 			}
593 		}
594 
595 	}
596 	return outstandingRowSpanCols;
597 }
598 
599 /**
600 * This method will iterate through all the items (XFormItem) in the form and call outputMethod on each of them.
601 * @param items
602 * @param parentItem
603 * @param html
604 * @param numCols
605 * @param currentCol
606 * @param skipTable
607 **/
608 XForm.prototype.outputItemList = function (items, parentItem, html,  numCols, currentCol, skipTable, skipOuter) {
609 	if (parentItem.outputHTMLStart) {
610 		parentItem.outputHTMLStart(html, currentCol);
611 	}
612 	var drawTable = (parentItem.getUseParentTable() == false && skipTable != true);
613 	var outerStyle = null;
614 	if(!skipOuter) {
615 		outerStyle = parentItem.getCssString();
616 		if (outerStyle != null && outerStyle != "") {
617 			parentItem.outputElementDivStart(html);
618 		}
619 	}
620 
621 	if (drawTable) {
622 		var colSizes = parentItem.getColSizes();
623 
624 		//XXX MOW: appending an elementDiv around the container if we need to style it
625 		var cellspacing = parentItem.getInheritedProperty("cellspacing");
626 		var cellpadding = parentItem.getInheritedProperty("cellpadding");
627         var border = parentItem.getInheritedProperty("border");
628         if (border == 0 && XForm._showBorder) {
629             border = 1;
630         }
631 		html.append("<table cellspacing=",cellspacing," cellpadding=",cellpadding,
632 				 "  border=" , border,
633 				" id=\"", parentItem.getId(),"_table\" ", parentItem.getTableCssString(),">");
634 		if (colSizes != null) {
635 			html.append( " <colgroup>");
636 			for (var i = 0; i < colSizes.length; i++) {
637 				var size = colSizes[i];
638 				if(!isNaN(size)) {
639 					if (size < 1) 
640 						size = size * 100 + "%";
641 				}
642 				html.append( "<col width='", size, "'>");
643 			}
644 			html.append( "</colgroup>");
645 		}
646 		html.append( "<tbody>");
647 	}
648 
649 	numCols = Math.max(1, numCols);
650 	if (currentCol == null) currentCol = 0;
651 	//DBG.println("outputItemList: numCols:",numCols, " currentCol:", currentCol);
652 
653 
654 	for (var itemNum = 0; itemNum < items.length; itemNum++) {
655 		var item = items[itemNum];
656 		var isNestingItem = (item.getItems() != null);
657 		var itemUsesParentTable = (item.getUseParentTable() != false);
658 		
659 		item.__numDrawnCols = 0;
660 
661 		// write the beginning of the update script
662 		//	(one of the routines below may want to modify it)
663 		
664 		var label = item.getLabel();
665 		var labelLocation = item.getLabelLocation();
666 		var showLabel = (label != null && (labelLocation == _LEFT_ || labelLocation == _RIGHT_));
667 
668 		var colSpan = item.getColSpan();
669 		if (colSpan == "*") colSpan = Math.max(1, (numCols - currentCol));
670 		var rowSpan = item.getRowSpan();
671 
672 		var totalItemCols = item.__numDrawnCols = parseInt(colSpan) + (showLabel ? 1 : 0);
673 		if (rowSpan > 1 && parentItem) {
674 			if (parentItem.__rowSpanItems == null) parentItem.__rowSpanItems  = [];
675 			parentItem.__rowSpanItems.push(item);
676 			item.__numOutstandingRows = rowSpan;
677 		}
678 		//DBG.println("rowSpan = " + rowSpan);
679 		if(currentCol==0)
680 			html.append( "<tr>");
681 		
682 		// write the label to the left if desired
683 		if (label != null && labelLocation == _LEFT_) {
684 			//DBG.println("writing label");
685 			item.outputLabelCellHTML(html, rowSpan, labelLocation);
686 		}
687 
688 		var writeElementDiv = item.getWriteElementDiv();
689 		var outputMethod = item.getOutputHTMLMethod();
690 		if (isNestingItem && itemUsesParentTable) {
691 			// actually write out the item
692 			if (outputMethod) outputMethod.call(item, html, currentCol);
693 
694 		} else {
695 
696 			// write the cell that contains the item 
697 			//	NOTE: this is currently also the container!
698 			item.outputContainerTDStartHTML(html,  colSpan, rowSpan);
699 	
700 			// begin the element div, if required
701 			if (writeElementDiv) 	item.outputElementDivStart(html);
702 			
703 			// actually write out the item
704 			if (outputMethod) outputMethod.call(item, html, 0);
705 
706 	
707 			// end the element div, if required
708 			if (writeElementDiv) 	item.outputElementDivEnd(html);
709 			
710 	
711 			// end the cell that contains the item
712 			item.outputContainerTDEndHTML(html);
713 
714 		}
715 
716 		currentCol += totalItemCols;
717 
718 		// write the label to the right, if desired
719 		if (label != null && labelLocation == _RIGHT_) {
720 			//DBG.println("writing label");
721 			item.outputLabelCellHTML(html, rowSpan);
722 		}
723 		
724 		// now end the update script if necessary
725 
726 		if ( currentCol >= numCols) {
727 			html.append( "</tr>");
728 			currentCol = this.getOutstandingRowSpanCols(parentItem);
729 			//DBG.println("creating new row:  currentCol is now ", currentCol, (currentCol > 0 ? " due to outstanding rowSpans" : ""));
730 		}
731 
732 		// if the number of outstanding rows is the same as the number of columns we're to generate
733 		//	output an empty row for each
734 		while (currentCol >= numCols) {
735 			//DBG.println("outputting empty row because outstandingRowSpanCols >= numCols");
736 			html.append("</tr id='numCols'>");//\r<tr  id='numCols'>");
737 			currentCol = this.getOutstandingRowSpanCols(parentItem);
738 		}
739 		
740 		if(parentItem)
741 			parentItem.registerActiveChild(item);
742 			
743 		item.signUpForEvents();
744 
745 	}
746 	
747 	
748 	if (drawTable) {
749 		html.append("</tbody></table>");
750 	}
751 
752 	if (outerStyle != null && outerStyle != "") {
753 		parentItem.outputElementDivEnd(html);
754 	}
755 
756 
757 	if (parentItem.outputHTMLEnd) {
758 		parentItem.outputHTMLEnd(html, currentCol);
759 	}
760 
761 }
762 
763 
764 
765 
766 
767 
768 //
769 //	NOTE: properties of individual items moved to XForm_item_properties.js
770 //
771 
772 
773 // CHANGE HANDLING
774 
775 
776 XForm.prototype.onFocus = function(id) {
777 	this.__focusObject = id;
778 }
779 
780 XForm.prototype.onBlur = function(id) {
781 	this.__focusObject = null;
782 }
783 
784 XForm.prototype.subItemChanged = function (id, value, event, quite) {
785 	//console.log("XForm.prototype.itemChanged start (" + id +","+value+","+event+")");
786 	var item = this.getItemById(id);
787 	if (item == null) return alert("Couldn't get item for " + id);	// EXCEPTION
788 	
789 	// tell the item that it's display is dirty so it might have to update
790 	item.dirtyDisplay();
791 
792 	// validate value
793 	var modelItem = item.getSubModelItem();
794 	var errorCorrected = false;
795 	if (modelItem != null) {
796 		try {
797 			value = modelItem.validate(value, this, item, this.getInstance());
798 			if(item.hasError()) {
799 				errorCorrected = true;
800 			}
801 			item.clearError();
802 		}
803 		catch (message) {
804 			item.setError(message);
805 			var event = new DwtXFormsEvent(this, item, value);
806 			this.notifyListeners(DwtEvent.XFORMS_VALUE_ERROR, event);
807 			return;
808 		}
809 	}
810 
811 	// if there is an onChange handler, call that
812 	var onChangeMethod = item.cacheInheritedMethod("onSubChange","$onSubChange","value,event,form");
813 
814 	if (typeof onChangeMethod == "function") {
815 //		DBG.println("itemChanged(", item.ref, ").onChange = ", onChangeMethod);
816 		value = onChangeMethod.call(item, value, event, this);
817 	} else {
818 		var oldVal = item.getInstanceValue(item.getInheritedProperty("subRef"));
819 		if(oldVal == value) {
820 			if(errorCorrected && !quite) 
821 				this.notifyListeners(DwtEvent.XFORMS_VALUE_CHANGED, event);
822 				
823 			return;
824 		}	
825 		item.setInstanceValue(value, item.getSubRefPath());
826 	}
827 	
828 	var event = new DwtXFormsEvent(this, item, value);
829 	if(!quite)
830 		this.notifyListeners(DwtEvent.XFORMS_VALUE_CHANGED, event);
831 
832 	this.setIsDirty(true, item);
833 	//console.log("XForm.prototype.itemChanged end (" + id +","+value+","+event+")");
834 }
835 
836 XForm.prototype.itemChanged = function (id, value, event, quite) {
837 	//console.log("XForm.prototype.itemChanged start (" + id +","+value+","+event+")");
838 	var item = this.getItemById(id);
839 	if (item == null) return alert("Couldn't get item for " + id);	// EXCEPTION
840 	
841 	// tell the item that it's display is dirty so it might have to update
842 	item.dirtyDisplay();
843 
844 	// validate value
845 	var modelItem = item.getModelItem();
846 	var errorCorrected = false;
847 	if (modelItem != null) {
848 		try {
849 			value = modelItem.validate(value, this, item, this.getInstance());
850 			if(item.hasError()) {
851 				errorCorrected = true;
852 			}
853 			item.clearError();
854 		}
855 		catch (message) {
856 			item.setError(message);
857 			var event = new DwtXFormsEvent(this, item, value);
858 			this.notifyListeners(DwtEvent.XFORMS_VALUE_ERROR, event);
859 			return;
860 		}
861 	}
862 
863 	// if there is an onChange handler, call that
864 	var onChangeMethod = item.getOnChangeMethod();
865 
866 	if (typeof onChangeMethod == "function") {
867 //		DBG.println("itemChanged(", item.ref, ").onChange = ", onChangeMethod);
868 		value = onChangeMethod.call(item, value, event, this);
869 	} else {
870 		var oldVal = item.getInstanceValue();
871 		if(oldVal == value) {
872 			if(errorCorrected && !quite) 
873 				this.notifyListeners(DwtEvent.XFORMS_VALUE_CHANGED, event);
874 				
875 			return;
876 		}	
877 		item.setInstanceValue(value);
878 	}
879 	
880 	var event = new DwtXFormsEvent(this, item, value);
881 	if(!quite)
882 		this.notifyListeners(DwtEvent.XFORMS_VALUE_CHANGED, event);
883 
884 	this.setIsDirty(true, item);
885 	//console.log("XForm.prototype.itemChanged end (" + id +","+value+","+event+")");
886 }
887 
888 
889 XForm.prototype.getItemsInErrorState = function () {
890 	if (this.__itemsInErrorState == null) {
891 		this.__itemsInErrorState = new Object();
892 		this.__itemsInErrorState.size = 0;
893 	}
894 	return this.__itemsInErrorState;
895 };
896 
897 XForm.prototype.addErrorItem = function ( item ) {
898 	var errs = this.getItemsInErrorState();
899 	var oldItem = 	errs[item.getId()];
900 	errs[item.getId()] = item;
901 	if (oldItem == null){
902 		errs.size++;
903 	}
904 };
905 
906 XForm.prototype.removeErrorItem = function ( item ) {
907 	if (item != null) {
908 		var errs = this.getItemsInErrorState();
909 		var id = item.getId();
910 		var oldItem = errs[id];
911 		if (oldItem != null) {
912 			delete errs[id];
913 			errs.size--;
914 		}
915 	}
916 };
917 
918 XForm.prototype.hasErrors = function () {
919 	var errs = this.getItemsInErrorState();
920 	return (errs != null && errs.size > 0);
921 };
922 
923 XForm.prototype.clearErrors = function () {
924 	var errs = this.getItemsInErrorState();
925 	if (errs.size > 0) {
926 		var k;
927 		for (k in errs) {
928 			if (k == 'size') continue;
929 			errs[k].clearError();
930 			delete errs[k];
931 		}
932 		errs.size = 0;
933 	}
934 }
935 
936 XForm.prototype.onCloseForm = function () {
937 	if (this.__focusObject != null) {
938 		var item = this.getItemById(this.__focusObject);
939 		var element = item.getElement();
940 
941 //alert("onCloseForm() not implemented");
942 //		this.itemChanged(this.__focusObject, VALUE???)
943 		if (element && element.blur) {
944 			element.blur();
945 		}
946 		this.__focusObject = null;
947 	}
948 }
949 
950 //Hack: to fix the cursor on input field not shown in FF
951 //see https://bugzilla.mozilla.org/show_bug.cgi?id=167801#c58
952 XForm.prototype.releaseFocus = function () {
953 	if (this.__focusObject != null) {
954 		var item = this.getItemById(this.__focusObject);
955 		var element = item.getElement();
956 
957 		if (element && element.blur) {
958 			element.blur();
959 		}
960 		this.__focusObject = null;
961 	}
962 }
963 
964 
965 
966 /** @private */
967 XForm.prototype._reparentDwtObject = function (dwtObj, newParent) {
968 	var dwtE = dwtObj.getHtmlElement();
969 	if (dwtE.parentNode) dwtE.parentNode.removeChild(dwtE);
970 	newParent.appendChild(dwtE);
971 }
972 
973 XForm.prototype.shouldInsertItem = function (item) {
974 	return (this._itemsToInsert[item.getId()] != null)
975 }
976 
977 XForm.prototype.insertExternalWidget = function (item) {
978 	DBG.println("insertExternalWidget(): inserting ref=", item.ref,"  type=", item.type, " id=", item.getId());
979 			 
980 	var insertMethod = item.getInsertMethod();
981 
982 	var widget = item.getWidget();
983 	if (widget && widget.insertIntoXForm instanceof Function) {
984 		widget.insertIntoXForm(this, item, item.getElement());
985 		
986 	} else if (typeof this[insertMethod] == "function") {
987 		this[insertMethod](item, item.getElement());
988 		
989 	} else {
990 		DBG.println("insertExternalWidget(): don't know how to insert item ", item.ref,"  type=", item.type);
991 	}
992 
993 	// take the item out of the list to insert so we don't insert it more than once
994 	delete this._itemsToInsert[item.getId()];
995 }
996