1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 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, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 
 25 //
 26 //	Factory to create XModelItems from simple attributes (eg: from JS object literals or XML)
 27 //
 28 
 29 /**
 30  * 
 31  * This class is never instantiated.
 32  * 
 33  * @private
 34  */
 35 XModelItemFactory = function() {}
 36 
 37 XModelItemFactory.createItem = function (attributes, parentItem, xmodel) {
 38 	// assign a modelItem to the item
 39 	var type = attributes.type;
 40 	var constructor = this.getItemTypeConstructor(type || _UNTYPED_);
 41 
 42 	var item = new constructor();
 43 	item._setAttributes(attributes);
 44 	if (item.id != null && item.ref == null) item.ref = item.id;
 45 
 46 	// idPath is mostly used for debugging...
 47 	var idPath = this.getIdPath(attributes, parentItem);
 48 	item.__idPath = idPath;
 49 
 50 	item.__xmodel = xmodel;
 51 	item.__parentItem = parentItem;
 52 
 53 //DBG.println("XModelItemFactory.createItem(", attributes.id, ") idPath='", idPath, "' type='", item.type,"'");
 54 	item.initModelItem();
 55 
 56 	// add the item to its model's index
 57 	xmodel.indexItem(item, idPath);
 58 
 59 	return item;
 60 }
 61 
 62 XModelItemFactory.getIdPath = function (attributes, parentItem) {
 63 	if (attributes.path) return attributes.path;
 64 	return this.getFullPath(attributes.id, (parentItem ? parentItem.getIdPath() : ""));
 65 }
 66 
 67 XModelItemFactory.getFullPath = function (itemPath, parentPath) {
 68 	if (itemPath == null) return null;
 69 	if (parentPath == null) parentPath = "";
 70 	
 71 	var path = itemPath;
 72 	if (itemPath == ".") {
 73 		path = parentPath;
 74 
 75 	} else if (itemPath == "..") {
 76 		parentPath = parentPath.split("/");
 77 		path = parentPath.slice(0, parentPath.length - 1).join("/");
 78 
 79 	} else if (parentPath == "") {
 80 		path = itemPath;
 81 
 82 	} else {
 83 		path = parentPath + "/" + itemPath;
 84 	}
 85 	return path;
 86 }
 87 
 88 
 89 
 90 XModelItemFactory.typeConstructorMap = {};
 91 
 92 XModelItemFactory.createItemType = function (typeConstant, typeName, constructor, superClassConstructor) {
 93 	if (constructor == null) constructor = new Function();
 94 	if (typeof superClassConstructor == "string") superClassConstructor = this.getItemTypeConstructor(superClassConstructor);
 95 	if (superClassConstructor == null) superClassConstructor = XModelItem;
 96 
 97 	// initialize the constructor
 98 	constructor.prototype = new superClassConstructor();	
 99 
100 	constructor.prototype.type = typeName;
101 	constructor.prototype.constructor = constructor;
102 	constructor.prototype.toString = new Function("return '[XModelItem:" + typeName + " path=\"' + this.getIdPath() + '\"]'");
103 	constructor.toString = new Function("return '[Class XModelItem:" + typeName + "]'");
104 	
105 	// put the item type into the typemap
106 	this.registerItemType(typeConstant, typeName, constructor);
107 
108 	// return the prototype
109 	return constructor;
110 }
111 
112 XModelItemFactory.registerItemType = function(typeConstant, typeName, constructor) {
113 	// assign the type constant to the window so everyone else can use it
114 	window[typeConstant] = typeName;
115 	this.typeConstructorMap[typeName] = constructor;	
116 }
117 
118 
119 XModelItemFactory.getItemTypeConstructor = function (typeName) {
120 	var typeConstructor = this.typeConstructorMap[typeName];
121 	if (typeConstructor == null) typeConstructor = this.typeConstructorMap["string"];
122 	return typeConstructor;
123 }
124 
125 
126 
127 
128 
129 
130 
131 XModelItem = function() {}
132 XModelItemFactory.createItemType("_UNTYPED_", "untyped", XModelItem, Object);
133 
134 // define the base class as the "object" class -- it works, but no type logic is applied
135 XModelItemFactory.registerItemType("_OBJECT_", "object", XModelItem);
136 
137 
138 // set base class defaults
139 
140 XModelItem.prototype.__isXModelItem = true;
141 XModelItem.prototype.getterScope = _INHERIT_;
142 XModelItem.prototype.setterScope = _INHERIT_;
143 
144 // methods
145 XModelItem.prototype._setAttributes = function (attributes) {
146 	this._attributes = attributes;
147 	for (var prop in attributes) {
148 		this[prop] = attributes[prop];
149 	}
150 }
151 
152 
153 XModelItem.prototype.initModelItem = function() {
154 	this._eventMgr = new AjxEventMgr();
155 }
156 
157 
158 
159 // initialize sub-items for this item
160 XModelItem.prototype.initializeItems = function () {
161 	var items = this.getItems();
162 	if (items != null) {
163 		this.items = this.getModel().initItemList(items, this);
164 	}
165 }
166 
167 
168 
169 //
170 //	accessors
171 //
172 XModelItem.prototype.getModel = function() 		{		return this.__xmodel;		}
173 XModelItem.prototype.getParentItem = function() 	{		return this.__parentItem;	}
174 XModelItem.prototype.getIdPath = function()	 	{		return this.__idPath;		}
175 
176 
177 XModelItem.prototype.getItems = function () 		{		return this.items;			}
178 XModelItem.prototype.addItem = function (item) {
179 	if (!item.__isXModelItem) item = this.xmodel.initItem(item, this);
180 	if (this.items == null) this.items = [];
181 	this.items.push(item);
182 }
183 
184 
185 XModelItem.prototype.getConstraints = function()	{		return this.constraints;	}
186 XModelItem.prototype.getRequired = function()		{		return this.required;		}
187 XModelItem.prototype.getReadonly = function()		{		return this.readonly;		}
188 XModelItem.prototype.getReadOnly = XModelItem.prototype.getReadonly;
189 
190 
191 XModelItem.prototype.getDefaultValue = function () {return new Object() };
192 
193 
194 //
195 //	validate this value (i.e. when a formitem that refers to it has changed)
196 //
197 
198 XModel.registerErrorMessage("valueIsRequired", AjxMsg.valueIsRequired);
199 XModelItem.prototype.validate = function (value, form, formItem, instance) {
200 
201 	// see if it's required
202 	if (value == null || value === "") {
203 		if (this.getRequired()) {
204 			throw this.getModel().getErrorMessage("valueIsRequired", value);
205 		}
206     }
207     
208 	// next validate the type
209 	//	this will throw an exception if something went wrong
210 	//	also, value may be coerced to a particular type by the validator
211 	else {
212 		value = this.validateType(value);
213 	}
214 	
215 	// if they defined any constraints, 
216 	var constraints = this.getConstraints();
217 	if (constraints == null) return value;
218 
219 	if (! (AjxUtil.isInstance(constraints, Array))) constraints = [constraints];
220 	for (var i = 0; i < constraints.length; i++) {
221 		var constraint = constraints[i];
222 		if (constraint.type == "method") {
223 			// The constraint method should either return a value, or throw an
224 			// exception.
225 			value = constraint.value.call(this, value, form, formItem, instance);
226 		}
227 // 		if (isValid == false) {
228 // 			throw this.getModel().getErrorMessage(constraint.errorMessageId, value);
229 // 		}
230 	}
231 	return value;
232 }
233 
234 XModelItem.prototype.getDefaultErrorMessage = function () {
235 	return this.errorMessage;
236 }
237 
238 
239 // generic validators for different data types
240 //	we have them here so we can use them in the _LIST_ data type
241 
242 
243 XModelItem.prototype.validateType = function(value) {	return value;		}
244 
245 
246 
247 
248 //
249 //	for validating strings
250 //
251 
252 /**
253  * Datatype facet: length. If not null, the length of the data
254  * value must be equal to this value. Specifying this attribute
255  * ignores the values for {@link XModelItem.prototype.minLength}
256  * and {@link XModelItem.prototype.maxLength}.
257  */
258 XModelItem.prototype.length = null;
259 
260 /**
261  * Datatype facet: minimum length. If not null, the length of
262  * the data value must not be less than this value.
263  */
264 XModelItem.prototype.minLength = null;
265 
266 /**
267  * Datatype facet: maximum length. If not null, the length of
268  * the data value must not exceed this value.
269  */
270 XModelItem.prototype.maxLength = null;
271 
272 /**
273  * Datatype facet: pattern. If not null, specifies an array of
274  * <code>RegExp</code> objects. The data value must match one of
275  * the patterns or an error is signaled during validation.
276  */
277 XModelItem.prototype.pattern = null;
278 
279 /**
280  * Datatype facet: enumeration. If not null, specifies an array of
281  * literal string values. The data value must match one of the
282  * literals or an error is signaled during validation.
283  */
284 XModelItem.prototype.enumeration = null;
285 
286 /**
287  * Datatype facet: white space. If not null, specifies how white
288  * space in the value should be processed before returning the
289  * final value. Valid values are:
290  * <ul>
291  * <li>"preserve": leaves whitespace as-is (default)
292  * <li>"replace": replaces tabs, newlines, and carriage-returns with a space
293  * <li>"collapse": same as "replace"  but also trims leading and trailing whitespace and replaces sequences of spaces with a single space
294  * </ul>
295  */
296 XModelItem.prototype.whiteSpace = null;
297 
298 XModelItem.prototype.getLength = function() 		{ return this.length; }
299 XModelItem.prototype.getMinLength = function () 	{	return this.minLength;				}
300 XModelItem.prototype.getMaxLength = function () 	{	return this.maxLength;				}
301 XModelItem.prototype.getPattern = function() {
302 	if (this.pattern != null && this.pattern.checked == null) {
303 		if (AjxUtil.isString(this.pattern)) {
304 			this.pattern = [ new RegExp(this.pattern) ];
305 		}
306 		else if (AjxUtil.isInstance(this.pattern, RegExp)) {
307 			this.pattern = [ this.pattern ];
308 		}
309 		else if (AjxUtil.isArray(this.pattern)) {
310 			for (var i = 0; i < this.pattern.length; i++) {
311 				var pattern = this.pattern[i];
312 				if (AjxUtil.isString(pattern)) {
313 					this.pattern[i] = new RegExp(this.pattern[i]);
314 				}
315 			}
316 		}
317 		else {
318 			// REVISIT: What to do in this case? Do we just
319 			//          assume that it was specified correctly?
320 		}
321 		this.pattern.checked = true;
322 	}
323 	return this.pattern;
324 }
325 XModelItem.prototype.getEnumeration = function() { return this.enumeration; }
326 XModelItem.prototype.getWhiteSpace = function() { return this.whiteSpace; }
327 
328 XModel.registerErrorMessage("notAString",		AjxMsg.notAString);
329 XModel.registerErrorMessage("stringLenWrong",   AjxMsg.stringLenWrong);
330 XModel.registerErrorMessage("stringTooShort", 	AjxMsg.stringTooShort);
331 XModel.registerErrorMessage("stringTooLong",	AjxMsg.stringTooLong);
332 XModel.registerErrorMessage("stringMismatch",   AjxMsg.stringMismatch);
333 
334 XModelItem.prototype.validateString = function(value) {
335 	if (value == null) return;
336 	
337 	if (!AjxUtil.isString(value)) {
338 		throw this.getModel().getErrorMessage("notAString", value);
339 	}
340 
341 	value = this._normalizeAndValidate(value);
342 
343     var length = this.getLength();
344     if (length !== null) {
345         if (value.length !== length) {
346             throw this.getModel().getErrorMessage("stringLenWrong", length);
347         }
348     }
349     else {
350 		var maxLength = this.getMaxLength();
351 		if (maxLength !== null && value.length > maxLength) {
352 			throw this.getModel().getErrorMessage("stringTooLong", maxLength);
353 		}
354 	
355 		var minLength = this.getMinLength();
356 		if (minLength !== null && value.length < minLength) {
357 			throw this.getModel().getErrorMessage("stringTooShort", minLength);
358 		}
359     }
360     
361     return value;
362 }
363 
364 XModel.registerErrorMessage("invalidEmailAddr",   AjxMsg.invalidEmailAddr);
365 XModelItem.prototype.validateEmailAddress = function(value) {
366 	if (value == null) return;
367 	
368 	if (!AjxUtil.isString(value)) {
369 		throw this.getModel().getErrorMessage("notAString", value);
370 	}
371 
372 	value = this._normalizeAndValidate(value);
373 
374     var length = this.getLength();
375     if (length !== null) {
376         if (value.length !== length) {
377             throw this.getModel().getErrorMessage("stringLenWrong", length);
378         }
379     } else {
380 		var maxLength = this.getMaxLength();
381 		if (maxLength !== null && value.length > maxLength) {
382 			throw this.getModel().getErrorMessage("stringTooLong", maxLength);
383 		}
384 	
385 		var minLength = this.getMinLength();
386 		if (minLength !== null && value.length < minLength) {
387 			throw this.getModel().getErrorMessage("stringTooShort", minLength);
388 		}
389 	    var parts = value.split('@');
390 		if (!parts || parts[0] == null || parts[0] == ""){
391 		   // set the name, so that on refresh, we don't display old data.
392 			throw this.getModel().getErrorMessage("invalidEmailAddr");
393 		 } else {
394 			if(!AjxUtil.isValidEmailNonReg(value)) {
395 			   throw this.getModel().getErrorMessage("invalidEmailAddr");
396 			}
397 	  	 }
398 	}
399     return value;
400 }
401 
402 /**
403  * Normalizes value against whiteSpace facet and then validates 
404  * against pattern and enumeration facets.
405  * @private
406  */
407 XModelItem.prototype._normalizeAndValidate = function(value) {
408 
409     var whiteSpace = this.getWhiteSpace();
410     if (whiteSpace !== null) {
411     	if (whiteSpace === "replace" || whiteSpace === "collapse") {
412     		value = value.replace(/[\t\r\n]/g, " ");
413     	}
414     	if (whiteSpace === "collapse") {
415     		value = value.replace(/^\s+/,"").replace(/\s+$/,"").replace(/[ ]+/, " ");
416     	}
417     }
418 	
419     var pattern = this.getPattern();
420     if (pattern != null) {
421     	var matched = false;
422     	for (var i = 0; i < pattern.length; i++) {
423     		if (pattern[i].test(value)) {
424     			matched = true;
425     			break;
426     		}
427     	}
428 		if (!matched) {
429 			throw this.getModel().getErrorMessage("stringMismatch", value);
430 		}    	
431     }
432     
433     var enumeration = this.getEnumeration();
434     if (enumeration !== null) {
435     	var matched = false;
436     	for (var i = 0; i < enumeration.length; i++) {
437     		if (enumeration[i] === value) {
438     			matched = true;
439     			break;
440     		}
441     	}
442     	if (!matched) {
443 			throw this.getModel().getErrorMessage("stringMismatch", value);
444     	}
445     }
446     
447 	return value;
448 }
449 
450 
451 //
452 //	for validating numbers
453 //
454 
455 /**
456  * Datatype facet: total digits. If not null, the number of
457  * digits before the decimal point in the data value must not
458  * be greater than this value.
459  */
460 XModelItem.prototype.totalDigits = null;
461  
462 /** 
463  * Datatype facet: fraction digits. If not null, the number of
464  * digits after the decimal point in the data value must not be
465  * greater than this value.
466  */
467 XModelItem.prototype.fractionDigits = null;
468 
469 /** 
470  * Datatype facet: maximum value (inclusive). If not null, the
471  * data value must be less than or equal to this value.
472  */
473 XModelItem.prototype.maxInclusive = null;
474 
475 /** 
476  * Datatype facet: maximum value (exclusive). If not null, the
477  * data value must be less than this value.
478  */
479 XModelItem.prototype.maxExclusive = null;
480 
481 /** 
482  * Datatype facet: minimum value (inclusive). If not null, the
483  * data value must be greater than or equal to this value.
484  */
485 XModelItem.prototype.minInclusive = null;
486 
487 /** 
488  * Datatype facet: minimum value (exclusive). If not null, the
489  * data value must be greater than or equal to this value.
490  */
491 XModelItem.prototype.minExclusive = null;
492 
493 
494 XModelItem.prototype.getTotalDigits = function() { return this.totalDigits; }
495 XModelItem.prototype.getFractionDigits = function () 	{	return this.fractionDigits;			}
496 XModelItem.prototype.getMinInclusive = function () 			{	return this.minInclusive;				}
497 XModelItem.prototype.getMinExclusive = function() { return this.minExclusive; }
498 XModelItem.prototype.getMaxInclusive = function () 			{	return this.maxInclusive;				}
499 XModelItem.prototype.getMaxExclusive = function() { return this.maxExclusive; }
500 
501 /**
502  * Registers a listener with the control. The listener will be call when events
503  * of type <code>eventType</code> fire
504  *
505  * @param {String} eventType Event type for which to listen (required)
506  * @param {AjxListener} listener Listener to be registered (required)
507  * @param index		[int]*			index at which to add listener
508  *
509  * @see DwtEvent
510  * @see AjxListener
511  * @see #removeListener
512  * @see #removeAllListeners
513  * @see #notifyListeners
514  */
515 XModelItem.prototype.addListener =
516 function(eventType, listener, index) {
517 	return this._eventMgr.addListener(eventType, listener, index);
518 };
519 
520 /**
521  * Removes a listener from the control.
522  *
523  * @param {String} eventType Event type for which to remove the listener (required)
524  * @param {AjxListener} listener Listener to be removed (required)
525  *
526  * @see DwtEvent
527  * @see AjxListener
528  * @see #addListener
529  * @see #removeAllListeners
530  */
531 XModelItem.prototype.removeListener =
532 function(eventType, listener) {
533 	return this._eventMgr.removeListener(eventType, listener);
534 };
535 
536 
537 /**
538  * Removes all listeners for a particular event type.
539  *
540  * @param {String} eventType Event type for which to remove listeners (required)
541  *
542  * @see DwtEvent
543  * @see AjxListener
544  * @see #addListener
545  * @see #removeListener
546  */
547 XModelItem.prototype.removeAllListeners =
548 function(eventType) {
549 	return this._eventMgr.removeAll(eventType);
550 };
551 
552 /**
553  * Queries to see if there are any listeners registered for a particular event type
554  *
555  * @param {String} eventType Event type for which to check for listener registration (required)
556  *
557  * @return True if there is an listener registered for the specified event type
558  *
559  * @see DwtEvent
560  */
561 XModelItem.prototype.isListenerRegistered =
562 function(eventType) {
563 	return this._eventMgr.isListenerRegistered(eventType);
564 };
565 
566 /**
567  * Notifys all listeners of type <code>eventType</code> with <code>event</code>
568  *
569  * @param {String} eventType Event type for which to send notifications (required)
570  * @param {DwtEvent} event Event with which to notify. Typically a subclass of
571  * 		DwtEvent
572  *
573  * @see DwtEvent
574  */
575 XModelItem.prototype.notifyListeners =
576 function(eventType, event) {
577 	return this._eventMgr.notifyListeners(eventType, event);
578 };
579 
580 XModel.registerErrorMessage("notANumber",		 AjxMsg.notANumber);
581 XModel.registerErrorMessage("numberTotalExceeded", AjxMsg.numberTotalExceeded);
582 XModel.registerErrorMessage("numberFractionExceeded", AjxMsg.numberFractionExceeded);
583 XModel.registerErrorMessage("numberMoreThanMax", AjxMsg.numberMoreThanMax);
584 XModel.registerErrorMessage("numberMoreThanEqualMax", AjxMsg.numberMoreThanEqualMax);
585 XModel.registerErrorMessage("numberLessThanMin", AjxMsg.numberLessThanMin);
586 XModel.registerErrorMessage("numberLessThanEqualMin", AjxMsg.numberLessThanEqualMin);
587 
588 XModelItem.prototype.validateNumber = function(value) {
589 	value = this._normalizeAndValidate(value);
590 
591 	var nvalue = parseFloat(value);
592 
593 	if (isNaN(nvalue) || !AjxUtil.FLOAT_RE.test(value)) {
594 		throw this.getModel().getErrorMessage("notANumber", value);
595 	}
596 
597 	var totalDigits = this.getTotalDigits();
598 	if (this.totalDigits !== null) {
599 		var wholePart = Math.floor(nvalue);
600 		if (wholePart.toString().length > totalDigits) {
601 			throw this.getModel().getErrorMessage("numberTotalExceeded", value, totalDigits);
602 		}
603 	}
604 
605 	var fractionDigits = this.getFractionDigits();
606 	if (this.fractionDigits !== null) {
607 		var fractionPart = String(nvalue - Math.floor(nvalue));
608 		if (fractionPart.indexOf('.') != -1 && fractionPart.replace(/^\d*\./,"").length > fractionDigits) {
609 			throw this.getModel().getErrorMessage("numberFractionExceeded", value, fractionDigits);
610 		}
611 	}
612 
613 	var maxInclusive = this.getMaxInclusive();
614 	if (maxInclusive !== null && nvalue > maxInclusive) {
615 		throw this.getModel().getErrorMessage("numberMoreThanMax", maxInclusive);
616 	}
617 	
618 	var maxExclusive = this.getMaxExclusive();
619 	if (maxExclusive !== null && nvalue >= maxExclusive) {
620 		throw this.getModel().getErrorMessage("numberMoreThanEqualMax", maxExclusive);
621 	}
622 
623 	var minInclusive = this.getMinInclusive();
624 	if (minInclusive !== null && nvalue < minInclusive) {
625 		throw this.getModel().getErrorMessage("numberLessThanMin",  minInclusive);
626 	}
627 	
628 	var minExclusive = this.getMinExclusive();
629 	if (minExclusive !== null && nvalue <= minExclusive) {
630 		throw this.getModel().getErrorMessage("numberLessThanEqualMin", minExclusive);
631 	}
632 
633 	return nvalue;
634 }
635 
636 XModel.registerErrorMessage("notAnInteger",		 AjxMsg.notAnInteger);
637 XModelItem.prototype.validateInt = function (value) {
638     var fvalue = this.validateNumber (value) ; //parseFloat value
639     var nvalue = parseInt (value) ;   //parseInt value
640     if (nvalue != fvalue ) {
641         throw this.getModel().getErrorMessage("notAnInteger", value) ;        
642     }
643 
644     return nvalue ;
645 }
646 
647 
648 //
649 //	for validating dates and times
650 //
651 
652 XModelItem.prototype.msecInOneDay = (1000 * 60 * 60 * 24);
653 XModel.registerErrorMessage("invalidDateString", AjxMsg.invalidDateString);
654 
655 // methods
656 XModelItem.prototype.validateDate = function(value) {
657 	
658 	if (AjxUtil.isInstance(value, Date)) return value;
659 	if (AjxUtil.isString(value)) {
660 		value = value.toLowerCase();
661 		var date = new Date();
662 
663 		if (value.indexOf("/") > -1) {
664 			var dateStrs = value.split("/");
665 			if (dateStrs.length == 3){
666 				var month = dateStrs[0];
667 				var day = dateStrs[1];
668 				var year = dateStrs[2];							
669 					
670 				if (month.length <= 2 && day.length <= 2 && year.length == 4) {
671 					//remove the preceeding 0 of the date value,
672 					//otherwise parseInt will evaluate it as 0
673 					month = parseInt(XModel.removePreceedingZero(month));
674 					day = parseInt(XModel.removePreceedingZero(day));
675 					year = parseInt(XModel.removePreceedingZero(year));							
676 					
677 					if (!isNaN(month) && !isNaN(day) && !isNaN(year)) {
678 						month -= 1;
679 						date.setFullYear(year, month, day);
680 						date.setHours(0,0,0,0);
681 						return date; 
682 											
683 						/*
684 						month -= 1;
685 						if (year < 1900) {
686 							if (year < 50) year += 2000;
687 							year += 1900;
688 						}
689 						date.setFullYear(year, month, day);
690 						date.setHours(0,0,0,0);
691 						return date; */
692 					}
693 				}
694 			}
695 		} else {
696 			// set to midnight today according to local time
697 			date.setHours(0,0,0,0);
698 			
699 			if (value == AjxMsg.today) {
700 				return date;
701 			} else if (value == AjxMsg.yesterday) {
702 				date.setTime(date.getTime() - this.msecInOneDay);
703 				return date;
704 			} else if (value == AjxMsg.tomorrow) {
705 				date.setTime(date.getTime() + this.msecInOneDay);
706 				return date;
707 			}
708 		}
709 	}
710 	throw this.getModel().getErrorMessage("invalidDateString", value);
711 	return value;
712 }
713 
714 //remove the preceeding zero of a string, it is useful when evaluate a date item
715 XModel.removePreceedingZero =
716 function (dStr){
717 	var pattern = /^[0]*(.*)$/ ;
718 	var result = dStr.match(pattern) ;
719 	if (result != null) {
720 		return result[1];
721 	}else{
722 		return dStr ;
723 	}
724 }
725 
726 
727 XModel.registerErrorMessage("invalidTimeString",		 AjxMsg.invalidTimeString);
728 // time is returned as a number of milliseconds since
729 XModelItem.prototype.validateTime = function (value) {
730 
731 	if (AjxUtil.isNumber(value)) return value;
732 	
733 	if (AjxUtil.isInstance(value, Date)) {
734 		return ((value.getHours() * 360) + (value.getMinutes() * 60) + value.getSeconds()) * 1000;
735 	}
736 	
737 	if (AjxUtil.isString(value)) {
738 		value = value.toLowerCase();
739 		if (value.indexOf(":") > -1) {
740 			value = value.split(":");
741 
742 			var isPM = false;
743 			var lastPiece = value[value.length - 1];
744 			isPM = (lastPiece.indexOf(I18nMsg.periodPm.toLowerCase()) > -1);
745 
746 			var hour = parseInt(value[0]);
747 			var min = parseInt(value[1]);
748 			var sec = (value.length == 3 ? parseInt(value[2]) : 0);
749 			if (!isNaN(hour) && !isNaN(min) && !isNaN(sec)) {
750 				hour -= 1;
751 				if (isPM && hour > 11) hour += 12;
752 				
753 				return ((hour * 360) + (min * 60) + sec) * 1000;
754 			}
755 		}
756 	}
757 	throw this.getModel().getErrorMessage("invalidTimeString", value);
758 }
759 
760 
761 XModel.registerErrorMessage("invalidDatetimeString",		 AjxMsg.invalidDatetimeString);
762 XModelItem.prototype.validateDateTime = function (value) {
763 
764 	if (AjxUtil.isInstance(value, Date)) return value;
765 	if (AjxUtil.isNumber(value)) return value;
766 	if (AjxUtil.isString(value)) {
767 		// try to get the value as a date
768 		//  (this will ignore time fields, and will throw an exeception if we couldn't parse a date)
769 		var date = this.validateDate(value);
770 		
771 		// if it has a time component
772 		if (value.indexOf(":") > -1) {
773 			var time = value.split(" ")[1];
774 			// this will validate the time string and will throw an exception if it doesn't match
775 			time = this.validateTimeString(time);
776 			
777 			date.setTime(date.getTime() + time);
778 		}
779 		return date;
780 	}
781 	// probably should never get here...
782 	throw this.getModel().getErrorMessage("invalidDatetimeString", value);
783 }
784 
785 
786 
787 
788 
789 
790 //
791 //	XModelItem class: "string"
792 //
793 String_XModelItem = function(){}
794 XModelItemFactory.createItemType("_STRING_", "string", String_XModelItem)  ;
795 String_XModelItem.prototype.validateType = XModelItem.prototype.validateString;
796 String_XModelItem.prototype.getDefaultValue = function () {	return ""; };
797 
798 
799 //
800 //	XModelItem class: "number"
801 //
802 Number_XModelItem = function(){}
803 XModelItemFactory.createItemType("_NUMBER_", "number", Number_XModelItem);
804 Number_XModelItem.prototype.validateType = XModelItem.prototype.validateNumber;
805 Number_XModelItem.prototype.getDefaultValue = function () {	return 0; };
806 
807 //XModelItem class: "int"
808 Integer_XModelItem = function(){}
809 XModelItemFactory.createItemType("_INT_", "int", Integer_XModelItem);
810 Integer_XModelItem.prototype.validateType = XModelItem.prototype.validateInt;
811 Integer_XModelItem.prototype.getDefaultValue = function () {	return 0; };
812 
813 
814 
815 
816 //
817 //	XModelItem class: "date"
818 //
819 Date_XModelItem = function(){}
820 XModelItemFactory.createItemType("_DATE_", "date", Date_XModelItem);
821 Date_XModelItem.prototype.validateType = XModelItem.prototype.validateDate;
822 Date_XModelItem.prototype.getDefaultValue = function () {	return new Date(); };
823 
824 
825 
826 
827 //
828 //	XModelItem class: "time"
829 //
830 Time_XModelItem = function(){}
831 XModelItemFactory.createItemType("_TIME_", "time", Time_XModelItem);
832 Time_XModelItem.prototype.validateType = XModelItem.prototype.validateTime;
833 Time_XModelItem.prototype.getDefaultValue = function () {	return new Date(); };
834 
835 
836 
837 
838 
839 //
840 //	XModelItem class: "datetime"
841 //
842 Datetime_XModelItem = function(){}
843 XModelItemFactory.createItemType("_DATETIME_", "datetime", Datetime_XModelItem);
844 Datetime_XModelItem.prototype.validateType = XModelItem.prototype.validateDateTime;
845 Datetime_XModelItem.prototype.getDefaultValue = function () {	return new Date(); };
846 
847 
848 
849 
850 
851 //
852 //	XModelItem class: "list"
853 //
854 List_XModelItem = function(){}
855 XModelItemFactory.createItemType("_LIST_", "list", List_XModelItem);
856 List_XModelItem.prototype.getDefaultValue = function () {	return new Array(); };
857 
858 // type defaults and accessors
859 List_XModelItem.prototype.outputType = _LIST_;	// 	_STRING_ == convert to a string
860 													//	_LIST_ == convert to an array
861 List_XModelItem.prototype.itemDelimiter = ","; 		//	delimiter for converting string values to arrays
862 List_XModelItem.prototype.inputDelimiter = /[\s,\r\n]+/;		//	delimiter for converting string values to arrays
863 List_XModelItem.prototype.listItem = {type:_UNTYPED_};
864 
865 List_XModelItem.prototype.getOutputType = function () 	{	return this.outputType;			}
866 List_XModelItem.prototype.getItemDelimiter = function() {	return this.itemDelimiter		}
867 List_XModelItem.prototype.getInputDelimiter = function() {	return this.inputDelimiter		}
868 List_XModelItem.prototype.getListItem = function () 	{	return this.listItem;			}
869 List_XModelItem.prototype.getterScope = _MODELITEM_;
870 List_XModelItem.prototype.setterScope = _MODELITEM_;
871 List_XModelItem.prototype.getter = "getValue";
872 List_XModelItem.prototype.setter = "setValue";
873 
874 
875 
876 //	methods
877 List_XModelItem.prototype.getValue =  function(ins, current, ref) {
878 	var value = eval("ins."+ref);
879 	if(value && this.getOutputType() ==_STRING_ && value instanceof Array) {
880 		return value.join(this.getItemDelimiter());
881 	} else {
882 		return value;
883 	}
884 }
885 
886 List_XModelItem.prototype.setValue = function(val, ins, current, ref) {
887 	if(val && this.getOutputType() == _STRING_ && !(val instanceof Array)) {
888 		var value = val.split(this.getInputDelimiter());
889 		eval("ins."+ref+" = value");
890 		return value;
891 	} else {
892         var value = eval("ins."+ref+" = val");
893         return value;
894 	}
895 }
896 
897 List_XModelItem.prototype.initializeItems = function () {
898 	var listItem = this.listItem;
899 	listItem.ref = listItem.id = "#";	
900 	this.listItem = XModelItemFactory.createItem(listItem, this, this.getModel());
901 	this.listItem.initializeItems();
902 }
903 
904 
905 List_XModelItem.prototype.validateType = function (value) {
906 	return value;
907 //XXX REWORK THIS TO USE THE listItem MODEL ITEM FOR EACH SUB-ITEM
908 }
909 
910 
911 
912 
913 
914 
915 
916 
917 //
918 //	XModelItem class: "enum"
919 //
920 Enum_XModelItem = function(){
921     XModel.registerErrorMessage("didNotMatchChoice",	AjxMsg.didNotMatchChoice);
922 
923 }
924 XModelItemFactory.createItemType("_ENUM_", "enum", Enum_XModelItem);
925 //XXXX
926 Enum_XModelItem.prototype.getDefaultValue = function () {	return this.getChoices()[0]; };
927 
928 Enum_XModelItem.prototype.getChoices = function()		 {
929     if (typeof this.choices == "function") {  //due to the i18n complexity, we have to define the choices use the function
930         this.choices = this.choices.call (this) ;
931     }
932     return this.choices;
933 }
934 Enum_XModelItem.prototype.getSelection = function() 	{		return this.selection;		}
935 
936 
937 Enum_XModelItem.prototype.validateType = function (value) {
938 	// if the selection is open, they can enter any value they want
939 	var selectionIsOpen = this.getSelection() == _OPEN_;
940 	if (selectionIsOpen) return value;
941 	
942 	// selection is not open: it must be one of the supplied choices
943 	var choices = this.getChoices();
944 	for (var i = 0; i < choices.length; i++) {
945 		var choice = choices[i];
946 		if (AjxUtil.isInstance(choice, Object)) {
947 			if (choice.value == value) return value;
948 		} else {
949 			if (choice == value) return value;
950 		}
951 	}
952 	
953 	// if we get here, we didn't match any of the choices
954 	throw this.getModel().getErrorMessage("didNotMatchChoice", value);
955 }
956 
957 //
958 // Model Item Class: "bool"
959 // Can only be used with checkbox
960 //
961 Bool_XModelItem = function(){};
962 XModelItemFactory.createItemType("_BOOL_", "bool", Bool_XModelItem);
963 Bool_XModelItem.prototype = new Enum_XModelItem();
964 Bool_XModelItem.prototype.choices = ["FALSE", "TRUE", null];
965 
966 FileSize_XModelItem = function (){}
967 XModelItemFactory.createItemType("_FILE_SIZE_", "file_size", FileSize_XModelItem);
968 FileSize_XModelItem.prototype.validateType = XModelItem.prototype.validateNumber;
969 FileSize_XModelItem.prototype.getterScope = _MODELITEM_;
970 FileSize_XModelItem.prototype.setterScope = _MODELITEM_;
971 FileSize_XModelItem.prototype.getter = "getValue";
972 FileSize_XModelItem.prototype.setter = "setValue";
973 FileSize_XModelItem.prototype.units = AjxUtil.SIZE_MEGABYTES;
974 FileSize_XModelItem.prototype.minInclusive = 0;
975 FileSize_XModelItem.prototype.maxInclusive = 922337203685477;
976 FileSize_XModelItem.prototype.maxLength = 15;
977 
978 FileSize_XModelItem.prototype.getValue =  function(ins, current, ref) {
979 	var value = eval("ins."+ref);
980 	return value ? AjxUtil.formatSizeForUnits(value, AjxUtil.SIZE_KILOBYTES, false, 2) : 1;
981 }
982 
983 FileSize_XModelItem.prototype.setValue = function(val, ins, current, ref) {
984         var value = eval("ins."+ref+" = AjxUtil.parseSize(val, this.units)");
985 	return isNaN(value) ? 1 : value;
986 }
987 
988 HostNameOrIp_XModelItem = function() {}
989 XModelItemFactory.createItemType("_HOSTNAME_OR_IP_", "hostname_or_ip", HostNameOrIp_XModelItem);
990 HostNameOrIp_XModelItem.prototype.validateType = XModelItem.prototype.validateString;
991 HostNameOrIp_XModelItem.prototype.maxLength = 256;
992 HostNameOrIp_XModelItem.prototype.pattern = [
993 	AjxUtil.HOST_NAME_RE,
994 	AjxUtil.IPv4_ADDRESS_RE,
995 	AjxUtil.IPv6_ADDRESS_RE,
996 	AjxUtil.HOST_NAME_WITH_PORT_RE,
997 	AjxUtil.IPv4_ADDRESS_WITH_PORT_RE,
998 	AjxUtil.IPv6_ADDRESS_WITH_PORT_RE
999 ];
1000 
1001 ShortURL_XModelItem = function() {}
1002 XModelItemFactory.createItemType("_SHORT_URL_", "short_url", ShortURL_XModelItem);
1003 ShortURL_XModelItem.prototype.validateType = XModelItem.prototype.validateString;
1004 ShortURL_XModelItem.prototype.maxLength = 256;
1005 ShortURL_XModelItem.prototype.pattern = [AjxUtil.SHORT_URL_RE,AjxUtil.IP_SHORT_URL_RE];
1006 
1007 Port_XModelItem = function() {}
1008 XModelItemFactory.createItemType("_PORT_", "port", Port_XModelItem);
1009 Port_XModelItem.prototype.validateType = XModelItem.prototype.validateNumber;
1010 Port_XModelItem.prototype.minInclusive = 0;
1011 Port_XModelItem.prototype.maxInclusive = 65535;
1012 
1013 Percent_XModelItem = function() {}
1014 XModelItemFactory.createItemType("_PERCENT_", "percent", Percent_XModelItem);
1015 Percent_XModelItem.prototype.validateType = XModelItem.prototype.validateNumber;
1016 Percent_XModelItem.prototype.minInclusive = 0;
1017 Percent_XModelItem.prototype.maxInclusive = 100;
1018 
1019 EmailAddress_XModelItem = function() {}
1020 XModelItemFactory.createItemType("_EMAIL_ADDRESS_", "email_address", EmailAddress_XModelItem);
1021 EmailAddress_XModelItem.prototype.validateType = XModelItem.prototype.validateEmailAddress;
1022 EmailAddress_XModelItem.prototype.maxLength = 256;
1023 
1024 FullUrl_XModelItem = function() {}
1025 XModelItemFactory.createItemType("_FULL_URL_", "full_url", FullUrl_XModelItem);
1026 FullUrl_XModelItem.prototype.validateType = XModelItem.prototype.validateString;
1027 FullUrl_XModelItem.prototype.maxLength = 1024;
1028 FullUrl_XModelItem.prototype.pattern = [AjxUtil.FULL_URL_RE, AjxUtil.IP_FULL_URL_RE];
1029