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