1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 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) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Creates a form. 26 * @class 27 * This class represents a form. 28 * 29 * @param {hash} params a hash of parameters 30 * 31 * @extends DwtComposite 32 * 33 * @private 34 */ 35 DwtForm = function(params) { 36 if (arguments.length == 0) return; 37 params = Dwt.getParams(arguments, DwtForm.PARAMS); 38 params.className = params.className || "DwtForm"; 39 DwtComposite.apply(this, arguments); 40 this.setScrollStyle(DwtControl.SCROLL); 41 42 // data 43 this._tabGroup = new DwtTabGroup(this._htmlElId); 44 45 // context 46 this._context = { 47 set: AjxCallback.simpleClosure(this.set, this), 48 get: AjxCallback.simpleClosure(this.get, this) 49 }; 50 51 // construct form 52 this._dirty = {}; 53 this._ignore = {}; 54 this._invalid = {}; 55 this._errorMessages = {}; 56 this.setModel(params.model); 57 this.setForm(params.form); 58 this.reset(); 59 }; 60 DwtForm.prototype = new DwtComposite; 61 DwtForm.prototype.constructor = DwtForm; 62 63 DwtForm.prototype.toString = function() { 64 return "DwtForm"; 65 }; 66 67 // 68 // Constants 69 // 70 71 DwtForm.PARAMS = DwtControl.PARAMS.concat("form", "model"); 72 73 // 74 // Public methods 75 // 76 77 /** 78 * Sets the value. 79 * 80 * @param {string} id the id 81 * @param {string} value the value 82 * @param {boolean} force if <code>true</code>, to force update 83 */ 84 DwtForm.prototype.setValue = function(id, value, force) { 85 if (typeof id != "string") id = String(id); 86 if (id.match(/\./) || id.match(/\[/)) { 87 var parts = id.replace(/\[(\d+)\](\.)?/,".$1$2").split("."); 88 var control = this.getControl(parts[0]); 89 if (Dwt.instanceOf(control, "DwtForm")) { 90 control.setValue(parts.slice(1).join("."), value, force); 91 } 92 return; 93 } 94 var item = this._items[id]; 95 if (!item) return; 96 if (!force && value == item.value) return; 97 this._setModelValue(id, value); 98 this._setControlValue(id, value); 99 }; 100 101 /** 102 * Gets the value. 103 * 104 * @param {string} id the id 105 * @param {string} defaultValue the default value 106 * @return {string} the value 107 */ 108 DwtForm.prototype.getValue = function(id, defaultValue) { 109 if (typeof id !== "string") { 110 id = String(id); 111 } 112 if (id.match(/\./) || id.match(/\[/)) { 113 var parts = id.replace(/\[(\d+)\](\.)?/,".$1$2").split("."); 114 var control = this.getControl(parts[0]); 115 if (Dwt.instanceOf(control, "DwtForm")) { 116 return control.getValue(parts.slice(1).join(".")); 117 } 118 return null; 119 } 120 var item = this._items[id]; 121 if (!item) { 122 return; 123 } 124 if (item.getter) { 125 return this._call(item.getter) || defaultValue; 126 } 127 var value = this._getControlValue(id); 128 if (value == null) { 129 value = item.value; 130 } 131 132 //added <|| ""> because ... if value="" than it always returns defaultValue which could be undefined. 133 return value || defaultValue || ""; 134 }; 135 136 /** 137 * Gets the control for the item. 138 * 139 * @param {string} id the id 140 * @return {DwtControl} the control 141 */ 142 DwtForm.prototype.getControl = function(id) { 143 if (typeof id != "string") id = String(id); 144 var item = this._items[id]; 145 return item && item.control; 146 }; 147 148 /** 149 * Checks if the id is relevant (meaning: is visible and is enabled). 150 * 151 * @param {string} id the id 152 * @return {boolean} <code>true</code> if the item is relevant 153 */ 154 DwtForm.prototype.isRelevant = function(id) { 155 return this.isVisible(id) && this.isEnabled(id); 156 }; 157 158 DwtForm.prototype.getTabGroupMember = function() { 159 return this._tabGroup; 160 }; 161 162 // control methods 163 164 /** 165 * Sets the label. 166 * 167 * @param {string} id the id 168 * @param {string} label the label 169 */ 170 DwtForm.prototype.setLabel = function(id, label) { 171 var item = this._items[id]; 172 if (!item) return; 173 if (label == this.getLabel(id)) return; 174 var control = item.control; 175 if (!control) return; 176 if (control.setLabel) { control.setLabel(label); return; } 177 if (control.setText) { control.setText(label); return; } 178 }; 179 180 /** 181 * Sets the image. 182 * 183 * @param {string} id the id 184 * @param {string} image the image 185 * @param {string} altText alternate text for non-visual users 186 */ 187 DwtForm.prototype.setImage = function(id, image, altText) { 188 var item = this._items[id]; 189 if (!item) { 190 return; 191 } 192 var control = item.control; 193 if (!control) { 194 return; 195 } 196 control.setImage(image, null, altText); 197 }; 198 199 200 /** 201 * Gets the label. 202 * 203 * @param {string} id the id 204 * @return {string} the label 205 */ 206 DwtForm.prototype.getLabel = function(id) { 207 var item = this._items[id]; 208 var control = item && item.control; 209 if (control) { 210 if (control.getLabel) return control.getLabel(); 211 if (control.getText) return control.getText(); 212 } 213 return ""; 214 }; 215 216 DwtForm.prototype.setVisible = function(id, visible) { 217 // set the form's visibility 218 if (arguments.length == 1) { 219 DwtComposite.prototype.setVisible.call(this, arguments[0]); 220 return; 221 } 222 // set control's visibility 223 var item = this._items[id]; 224 var control = item && item.control; 225 if (!control) return; 226 if (control.setVisible) { 227 control.setVisible(visible); 228 } 229 else { 230 Dwt.setVisible(control, visible); 231 } 232 // if there's a corresponding "*_row" element 233 var el = document.getElementById([this._htmlElId, id, "row"].join("_")); 234 if (el) { 235 Dwt.setVisible(el, visible); 236 } 237 }; 238 239 DwtForm.prototype.isVisible = function(id) { 240 // this form's visibility 241 if (arguments.length == 0) { 242 return DwtComposite.prototype.isVisible.call(this); 243 } 244 // control's visibility 245 var item = this._items[id]; 246 var control = item && item.control; 247 if (!control) return false; 248 if (control.getVisible) return control.getVisible(); 249 if (control.isVisible) return control.isVisible(); 250 return Dwt.getVisible(control); 251 }; 252 253 /** 254 * Sets the enabled flag. 255 * 256 * @param {string} id the id 257 * @param {boolean} enabled if <code>true</code>, the item is enabled 258 */ 259 DwtForm.prototype.setEnabled = function(id, enabled) { 260 // set the form enabled 261 if (arguments.length == 1) { 262 DwtComposite.prototype.setEnabled.call(this, arguments[0]); 263 return; 264 } 265 // set the control enabled 266 var item = this._items[id]; 267 var control = item && item.control; 268 if (!control) return; 269 if (control.setEnabled) { 270 control.setEnabled(enabled); 271 } 272 else { 273 control.disabled = !enabled; 274 } 275 }; 276 277 /** 278 * Checks if the item is enabled. 279 * 280 * @param {string} id the id 281 * @return {boolean} <code>true</code> if the item is enabled 282 */ 283 DwtForm.prototype.isEnabled = function(id) { 284 // this form enabled? 285 if (arguments.length == 0) { 286 return DwtComposite.prototype.isEnabled.call(this); 287 } 288 // the control enabled? 289 var item = this._items[id]; 290 var control = item && item.control; 291 if (!control) return false; 292 if (control.isEnabled) return control.isEnabled(); 293 if (control.getEnabled) return control.getEnabled(); 294 return !control.disabled; 295 }; 296 297 /** 298 * Sets the valid flag. 299 * 300 * @param {string} id the id 301 * @param {boolean} valid if <code>true</code>, the item is valid 302 */ 303 DwtForm.prototype.setValid = function(id, valid) { 304 if (typeof(id) == "boolean") { 305 valid = arguments[0]; 306 for (id in this._items) { 307 this.setValid(id, valid); 308 } 309 return; 310 } 311 if (valid) { 312 delete this._invalid[id]; 313 } 314 else { 315 this._invalid[id] = true; 316 } 317 }; 318 319 /** 320 * Checks if the item is valid. 321 * 322 * @param {string} id the id 323 * @return {boolean} <code>true</code> if the item is valid 324 */ 325 DwtForm.prototype.isValid = function(id) { 326 if (arguments.length == 0 || AjxUtil.isUndefined(id)) { 327 for (var id in this._invalid) { 328 return false; 329 } 330 return true; 331 } 332 return !(id in this._invalid); 333 }; 334 335 /** 336 * Sets the error message. 337 * 338 * @param {string} id the id 339 * @param {string} message the message 340 */ 341 DwtForm.prototype.setErrorMessage = function(id, message) { 342 if (!id || id == "") { 343 this._errorMessages = {}; 344 return; 345 } 346 if (!message) { 347 delete this._errorMessages[id]; 348 } else { 349 this._errorMessages[id] = message; 350 } 351 }; 352 353 /** 354 * Gets the error message. 355 * 356 * @param {string} id the id 357 * @return {string|array} the message(s) 358 */ 359 DwtForm.prototype.getErrorMessage = function(id) { 360 if (arguments.length == 0) { 361 var messages = {}; 362 for (var id in this._invalid) { 363 messages[id] = this._errorMessages[id]; 364 } 365 return messages; 366 } 367 return this._errorMessages[id]; 368 }; 369 370 DwtForm.prototype.getInvalidItems = function() { 371 return AjxUtil.keys(this._invalid); 372 }; 373 374 DwtForm.prototype.setDirty = function(id, dirty, skipNotify) { 375 if (typeof id == "boolean") { 376 dirty = arguments[0]; 377 for (id in this._items) { 378 this.setDirty(id, dirty, true); 379 } 380 if (!skipNotify && this._ondirty) { 381 this._call(this._ondirty, ["*"]); 382 } 383 return; 384 } 385 if (dirty) { 386 this._dirty[id] = true; 387 } 388 else { 389 delete this._dirty[id]; 390 } 391 if (!skipNotify && this._ondirty) { 392 var item = this._items[id]; 393 if (!item.ignore || !this._call(item.ignore)) { 394 this._call(this._ondirty, [id]); 395 } 396 } 397 }; 398 DwtForm.prototype.isDirty = function(id) { 399 if (arguments.length == 0) { 400 for (var id in this._dirty) { 401 var item = this._items[id]; 402 if (item.ignore && this._call(item.ignore)) { 403 continue; 404 } 405 return true; 406 } 407 return false; 408 } 409 var item = this._items[id]; 410 return item.ignore && this._call(item.ignore) ? false : id in this._dirty; 411 }; 412 DwtForm.prototype.getDirtyItems = function() { 413 // NOTE: This avoids needing a closure 414 DwtForm.__acceptDirtyItem.form = this; 415 return AjxUtil.keys(this._dirty, DwtForm.__acceptDirtyItem); 416 }; 417 DwtForm.__acceptDirtyItem = function(id) { 418 var form = arguments.callee.form; 419 var item = form._items[id]; 420 return !item.ignore || !form._call(item.ignore); 421 }; 422 423 DwtForm.prototype.setIgnore = function(id, ignore) { 424 if (typeof id == "boolean") { 425 this._ignore = {}; 426 return; 427 } 428 if (ignore) { 429 this._ignore[id] = true; 430 return; 431 } 432 delete this._ignore[id]; 433 }; 434 DwtForm.prototype.isIgnore = function(id) { 435 return id in this._ignore; 436 }; 437 438 // convenience control methods 439 440 DwtForm.prototype.set = function(id, value) { 441 this.setValue(id, value, true); 442 this.update(); 443 }; 444 DwtForm.prototype.get = DwtForm.prototype.getValue; 445 446 // properties 447 448 DwtForm.prototype.setModel = function(model, reset) { 449 this._context.model = this.model = model; 450 }; 451 452 DwtForm.prototype.setForm = function(form) { 453 this._context.form = this.form = form; 454 this._createHtml(form.template); 455 }; 456 457 // form maintenance 458 459 DwtForm.prototype.validate = function(id) { 460 if (arguments.length == 0) { 461 this.setValid(true); 462 for (var id in this._items) { 463 this._validateItem(id); 464 } 465 return this.isValid(); 466 } 467 return this._validateItem(id); 468 }; 469 470 DwtForm.prototype._validateItem = function(id) { 471 if (!id) return true; 472 var item = this._items[id]; 473 if (!item) return true; 474 try { 475 var value = this.getValue(id); 476 var outcome = item.validator ? item.validator(value) : ((item.control && item.control.validator) ? item.control.validator(value) : true); 477 // the validator may return false to signify that the validation failed (but preferably throw an error with a message) 478 // it may return true to signify that the field validates 479 // It also may return a string or hash (truthy) that we may put into the value field (for normalization of data; e.g. if 13/10/2009 is transformed to 1/10/2010 by the validator) 480 this.setValid(id, Boolean(outcome) || outcome === ""); 481 if (AjxUtil.isString(outcome) || AjxUtil.isObject(outcome)) { 482 this._setControlValue(id, outcome); // Set display value 483 item.value = item.setter ? this._call(item.setter, [outcome]) : outcome; // Set model value 484 var dirty = !Boolean(this._call(item.equals, [item.value,item.ovalue])); 485 this.setDirty(id, dirty); 486 } 487 } 488 catch (e) { 489 this.setErrorMessage(id, AjxUtil.isString(e) ? e : e.message); 490 this.setValid(id, false); 491 } 492 return !(id in this._invalid); 493 }; 494 495 DwtForm.prototype.reset = function(useCurrentValues) { 496 // init state 497 this._dirty = {}; 498 this._ignore = {}; 499 this._invalid = {}; 500 for (var id in this._items) { 501 var item = this._items[id]; 502 if (item.control instanceof DwtForm) { 503 item.control.reset(useCurrentValues); 504 } 505 var itemDef = this._items[id].def; 506 if (!itemDef) continue; 507 this._initControl(itemDef, useCurrentValues); 508 } 509 // update values 510 this.update(); 511 for (var id in this._items) { 512 var item = this._items[id]; 513 item.ovalue = item.value; 514 } 515 // clear state 516 this.validate(); 517 this.setDirty(false); 518 // call handler 519 if (this._onreset) { 520 this._call(this._onreset); 521 } 522 }; 523 524 DwtForm.prototype.update = function() { 525 // update all the values first 526 for (var id in this._items) { 527 var item = this._items[id]; 528 if (item.control instanceof DwtForm) { 529 item.control.update(); 530 } 531 if (item.getter) { 532 this.setValue(id, this._call(item.getter)); 533 } 534 } 535 // now set visible/enabled/ignore based on values 536 for (var id in this._items) { 537 var item = this._items[id]; 538 if (item.visible) { 539 this.setVisible(id, Boolean(this._call(item.visible))); 540 } 541 if (item.enabled) { 542 this.setEnabled(id, Boolean(this._call(item.enabled))); 543 } 544 if (item.ignore) { 545 this.setIgnore(id, Boolean(this._call(item.ignore))); 546 } 547 } 548 // call handler 549 if (this._onupdate) { 550 this._call(this._onupdate); 551 } 552 }; 553 554 // 555 // Protected methods 556 // 557 558 DwtForm.prototype._setModelValue = function(id, value) { 559 var item = this._items[id]; 560 item.value = item.setter ? this._call(item.setter, [value]) : value; 561 var dirty = !Boolean(this._call(item.equals, [item.value,item.ovalue])); 562 this.setDirty(id, dirty); 563 this.validate(id); 564 return dirty; 565 }; 566 567 DwtForm.prototype._setControlValue = function(id, value) { 568 var control = this._items[id].control; 569 if (control) { 570 // TODO: display value 571 if (control instanceof DwtCheckbox || control instanceof DwtRadioButton) { 572 control.setSelected(value); 573 return; 574 } 575 if (control instanceof DwtMenuItem && control.isStyle(DwtMenuItem.CHECK_STYLE)) { 576 control.setChecked(value, true); 577 return; 578 } 579 if (control.setSelectedValue) { control.setSelectedValue(value); return; } 580 if (control.setValue) { control.setValue(value); return; } 581 if (control.setText && !(control instanceof DwtButton)) { control.setText(value); return; } 582 if (!(control instanceof DwtControl)) { 583 // TODO: support other native form elements like select 584 if (control.type == "checkbox" || control == "radio") { 585 control.checked = value; 586 } 587 else { 588 // TODO: handle error setting form input value 589 control.value = value; 590 } 591 return; 592 } 593 } 594 }; 595 DwtForm.prototype._getControlValue = function(id) { 596 var control = this._items[id].control; 597 if (control) { 598 if (control instanceof DwtCheckbox || control instanceof DwtRadioButton) { 599 return control.isSelected(); 600 } 601 if (control.getSelectedValue) { 602 return control.getSelectedValue(); 603 } 604 if (control.getValue) { 605 return control.getValue(); 606 } 607 if (control.getText && !(control instanceof DwtButton)) { 608 return control.getText(); 609 } 610 if (!(control instanceof DwtControl)) { 611 if (control.type == "checkbox" || control == "radio") return control.checked; 612 return control.value; 613 } 614 } 615 }; 616 617 DwtForm.prototype._deleteItem = function(id) { 618 delete this._items[id]; 619 delete this._dirty[id]; 620 delete this._invalid[id]; 621 delete this._ignore[id]; 622 }; 623 624 // utility 625 626 DwtForm.prototype._call = function(func, args) { 627 if (func) { 628 if (args) return func.apply(this, args); 629 // NOTE: Hack for IE which barfs with null args on apply 630 return func.call(this); 631 } 632 }; 633 634 // html creation 635 636 DwtForm.prototype._createHtml = function(templateId) { 637 this._createHtmlFromTemplate(templateId || this.TEMPLATE, { id: this._htmlElId }); 638 }; 639 640 DwtForm.prototype._createHtmlFromTemplate = function(templateId, data) { 641 DwtComposite.prototype._createHtmlFromTemplate.apply(this, arguments); 642 643 // initialize state 644 var tabIndexes = []; 645 this._items = {}; 646 this._tabGroup.removeAllMembers(); 647 this._onupdate = null; 648 this._onreset = null; 649 this._ondirty = null; 650 651 // create form 652 var form = this.form; 653 if (form && form.items) { 654 // create controls 655 this._registerControls(form.items, null, tabIndexes); 656 // create handlers 657 this._onupdate = DwtForm.__makeFunc(form.onupdate); 658 this._onreset = DwtForm.__makeFunc(form.onreset); 659 this._ondirty = DwtForm.__makeFunc(form.ondirty); 660 } 661 662 // add links to list of tabIndexes 663 var links = this.getHtmlElement().getElementsByTagName("A"); 664 for (var i = 0; i < links.length; i++) { 665 var link = links[i]; 666 if (!link.href || link.getAttribute("notab") == "true") continue; 667 var controlId = link.id && link.id.substr(this.getHTMLElId().length+1); 668 if (this._items[controlId]) continue; 669 tabIndexes.push({ 670 tabindex: link.getAttribute("tabindex") || Number.MAX_VALUE, 671 control: link 672 }); 673 } 674 675 // add controls to tab group 676 tabIndexes.sort(DwtForm.__byTabIndex); 677 for (var i = 0; i < tabIndexes.length; i++) { 678 var control = tabIndexes[i].control; 679 var member = (control.getTabGroupMember && control.getTabGroupMember()) || control; 680 this._tabGroup.addMember(member); 681 } 682 }; 683 684 DwtForm.prototype._registerControls = function(itemDefs, parentDef, 685 tabIndexes, params, 686 parent, defaultType) { 687 for (var i = 0; i < itemDefs.length; i++) { 688 this._registerControl(itemDefs[i], parentDef, tabIndexes, params, parent, defaultType); 689 } 690 }; 691 692 DwtForm.prototype._registerControl = function(itemDef, parentDef, 693 tabIndexes, params, 694 parent, defaultType) { 695 // create item entry 696 var id = itemDef.id || [this._htmlElId, Dwt.getNextId()].join("_"); 697 var item = this._items[id] = { 698 id: id, // for convenience 699 def: itemDef, 700 parentDef: parentDef, 701 equals: DwtForm.__makeFunc(itemDef.equals) || DwtForm.__equals, 702 getter: DwtForm.__makeGetter(itemDef), 703 setter: DwtForm.__makeSetter(itemDef), 704 value: itemDef.value, 705 visible: DwtForm.__makeFunc(itemDef.visible), 706 enabled: DwtForm.__makeFunc(itemDef.enabled), 707 validator: DwtForm.__makeFunc(itemDef.validator), 708 ignore: DwtForm.__makeFunc(itemDef.ignore), 709 control: itemDef.control 710 }; 711 // NOTE: This is used internally for indexing of rows 712 if (itemDef.aka) { 713 this._items[id].aka = itemDef.aka; 714 this._items[itemDef.aka] = item; 715 } 716 717 // is control already created? 718 var control = item.control; 719 if (control) { 720 return control; 721 } 722 723 // create control 724 parent = parent || this; 725 var type = itemDef.type = itemDef.type || defaultType; 726 var isMenu = (parentDef && parentDef.menu == itemDef); 727 var element = document.getElementById([parent._htmlElId,id].join("_")); 728 if (Dwt.instanceOf(type, "DwtRadioButtonGroup")) { 729 // create control 730 control = new window[type]({}); 731 item.control = control; 732 733 // add children 734 var nparams = { 735 name: [parent._htmlElId, id].join("_"), 736 value: itemDef.value 737 }; 738 if (itemDef.items) { 739 for (var i = 0; i < itemDef.items.length; i++) { 740 var radioItemDef = itemDef.items[i]; 741 var checked = radioItemDef.checked || radioItemDef.value == itemDef.value; 742 var radio = this._registerControl(radioItemDef, itemDef, tabIndexes, nparams, parent, "DwtRadioButton"); 743 this._items[radioItemDef.id].value = checked; 744 if (radio) { 745 control.addRadio(radio.getInputElement().id, radio, checked); 746 // handlers 747 var handler = DwtForm.__makeFunc(radioItemDef.onclick || itemDef.onclick); 748 radio.addSelectionListener(new AjxListener(this, this._radio2group2model, [radioItemDef.id, id, handler])); 749 // HACK: Work around fact that the DwtRadioButtonGroup overwrites 750 // the radio button input element's onclick handler. 751 DwtForm.__hack_fixRadioButtonHandler(radio); 752 } 753 } 754 } 755 } 756 else if (type) { 757 if (Dwt.instanceOf(type, "DwtInputField")) { 758 item.value = item.value || ""; 759 } 760 if (Dwt.instanceOf(type, "DwtFormRows")) { 761 item.equals = DwtFormRows.__equals; 762 } 763 if (element || isMenu) { 764 control = item.control = this._createControl(itemDef, parentDef, tabIndexes, params, parent, defaultType); 765 } 766 } 767 else if (element) { 768 this._attachElementHandlers(itemDef, parentDef, tabIndexes, parent, element); 769 control = item.control = element; 770 if (itemDef.items) { 771 this._registerControls(itemDef.items, itemDef, tabIndexes, null, parent, null); 772 } 773 } 774 if (element && control instanceof DwtControl) { 775 control.replaceElement(element); 776 } 777 if (element && control instanceof DwtInputField) { 778 control.getInputElement().id += "_input"; 779 control.setHandler(DwtEvent.ONPASTE, this._onPaste.bind(this, id)); 780 } 781 782 // add to list of tab indexes 783 if (itemDef.notab == null) { 784 itemDef.notab = element && element.getAttribute("notab") == "true"; 785 } 786 if (tabIndexes && control && !itemDef.notab && !(control instanceof DwtRadioButtonGroup)) { 787 tabIndexes.push({ 788 tabindex: (element && element.getAttribute("tabindex")) || Number.MAX_VALUE, 789 control: control 790 }); 791 } 792 793 // clean up 794 if (control instanceof DwtListView) { 795 item.getter = item.getter || AjxCallback.simpleClosure(this.__list_getValue, this, id); 796 item.setter = item.setter || AjxCallback.simpleClosure(this.__list_setValue, this, id); 797 } 798 799 // return control 800 return control; 801 }; 802 803 804 DwtForm.prototype._onPaste = function(id, evt) { 805 // Delay the value processing - the paste text will not be applied to the control till after this event 806 AjxTimedAction.scheduleAction(new AjxTimedAction(this, this._applyPasteInput, [id]), 100); 807 }; 808 809 DwtForm.prototype._applyPasteInput = function(id, value) { 810 this._setModelValue(id, this._getControlValue(id)); 811 } 812 813 814 DwtForm.prototype._attachElementHandlers = function(itemDef, parentDef, tabIndexes, parent, element) { 815 var id = itemDef.id; 816 var name = element.nodeName.toLowerCase(); 817 var type = element.type; 818 if (type == "checkbox" || type == "radio") { 819 var parentId; 820 if (type == "radio") { 821 parentId = element.name; 822 if (!this._items[parentId]) this._items[parentId] = { id: parentId }; 823 if (element.checked) { 824 this._items[parentId].value = element.value; 825 } 826 } 827 // checked 828 var onclick = element.onclick ; 829 var handler = DwtForm.__makeFunc(itemDef.onclick); 830 element.onclick = AjxCallback.simpleClosure(this._htmlInput_checked, this, id, parentId, handler, onclick); 831 } 832 else if (name == "select") { 833 // map selectedIndex to value of option 834 var onchange = element.onchange; 835 var handler = DwtForm.__makeFunc(itemDef.onchange); 836 element.onchange = AjxCallback.simpleClosure(this._htmlSelect_selectedIndex, this, id, handler, onchange); 837 } 838 else if (name == "button" || name == "a" || 839 type == "button" || type == "reset" || type == "submit") { 840 // checked 841 var onclick = element.onclick ; 842 var handler = DwtForm.__makeFunc(itemDef.onclick); 843 element.onclick = AjxCallback.simpleClosure(this._htmlElement, this, id, handler, onclick); 844 } 845 else if (name == "textarea" || name == "input") { // type == "text" || || type == "file" || type == "password") { 846 // value 847 var onchange = element.onchange; 848 var handler = DwtForm.__makeFunc(itemDef.onchange); 849 element.onchange = AjxCallback.simpleClosure(this._htmlInput_value, this, id, handler, onchange); 850 } 851 // TODO: attach other handlers 852 return element; 853 }; 854 855 DwtForm.prototype._createControl = function(itemDef, parentDef, 856 tabIndexes, params, 857 parent, defaultType) { 858 var id = itemDef.id || [this._htmlElId, Dwt.getNextId()].join("_"); 859 var type = itemDef.type = itemDef.type || defaultType; 860 params = params ? AjxUtil.createProxy(params) : {}; 861 params.id = params.id || [this._htmlElId, id].join("_"); 862 params.parent = parent || this; 863 params.template = itemDef.template || params.template; 864 params.className = itemDef.className || params.className; 865 866 // constructor params for radio buttons 867 var isRadioButton = Dwt.instanceOf(type, "DwtRadioButton"); 868 var isCheckBox = Dwt.instanceOf(type, "DwtCheckbox"); 869 if (isRadioButton || isCheckBox) { 870 params.name = itemDef.name || params.name; 871 params.value = itemDef.value || params.value; 872 params.checked = itemDef.checked != null ? itemDef.checked : params.checked; 873 } 874 875 // constructor params for input fields 876 var isTextField = Dwt.instanceOf(type, "DwtInputField"); 877 if (isTextField) { 878 params.type = itemDef.password ? DwtInputField.PASSWORD : null; 879 params.size = itemDef.cols; 880 params.rows = itemDef.rows; 881 } 882 883 var isTabPage = Dwt.instanceOf(type, "DwtTabViewPage"); 884 if (isTabPage) { 885 params.contentTemplate = itemDef.template; 886 delete itemDef.template; 887 } 888 889 var isTree = Dwt.instanceOf(type, "DwtTree"); 890 if (isTree) { 891 params.style = itemDef.style; 892 } 893 894 // add extra params 895 params.formItemDef = itemDef; 896 if (itemDef.params) { 897 for (var p in itemDef.params) { 898 params[p] = itemDef.params[p]; 899 } 900 } 901 902 // create control 903 var control = new window[type](params); 904 905 // init select 906 if (control instanceof DwtSelect) { 907 var options = itemDef.items; 908 if (options) { 909 for (var i = 0; i < options.length; i++) { 910 var option = options[i]; 911 // convert to format that DwtSelect#addOption recognizes 912 option.displayValue = option.label || option.value; 913 control.addOption(option); 914 } 915 } 916 var handler = DwtForm.__makeFunc(itemDef.onchange); 917 control.addChangeListener(new AjxListener(this, this._control2model, [id, handler])); 918 } 919 920 // init button, menu item 921 else if (control instanceof DwtButton || control instanceof DwtMenuItem) { 922 if (itemDef.label) { control.setText(itemDef.label); } 923 if (itemDef.image) { control.setImage(itemDef.image, null, itemDef.imageAltText); } 924 if (itemDef.menu) { 925 var isMenu = Dwt.instanceOf(itemDef.menu.type || "DwtMenu", "DwtMenu"); 926 var menu; 927 if (isMenu) { 928 menu = this._registerControl(itemDef.menu, itemDef, null, null, control, "DwtMenu"); 929 } 930 else { 931 menu = new DwtMenu({parent:control}); 932 var style = Dwt.instanceOf(itemDef.menu.type, "DwtCalendar") ? 933 DwtMenu.CALENDAR_PICKER_STYLE : DwtMenu.GENERIC_WIDGET_STYLE; 934 this._registerControl(itemDef.menu, itemDef, null, { style: style }, menu); 935 } 936 control.setMenu(menu); 937 } 938 var parentId; 939 if (parent instanceof DwtToolBar || parent instanceof DwtMenu) { 940 parentId = parentDef.id; 941 } 942 // handlers 943 var handler = DwtForm.__makeFunc(itemDef.onclick || (parentDef && parentDef.onclick)); 944 control.addSelectionListener(new AjxListener(this, this._item2parent, [id, parentId, handler])); 945 } 946 947 // init checkbox, radio button 948 else if (control instanceof DwtCheckbox && !(control instanceof DwtRadioButton)) { 949 var handler = DwtForm.__makeFunc(itemDef.onclick); 950 control.addSelectionListener(new AjxListener(this, this._control2model, [id, handler])); 951 } 952 953 // init input field 954 else if (control instanceof DwtInputField) { 955 var changehandler = DwtForm.__makeFunc(itemDef.onchange); 956 var onkeyup = AjxCallback.simpleClosure(this._input2model2handler, this, id, changehandler); 957 control.addListener(DwtEvent.ONKEYUP, onkeyup); 958 if (AjxEnv.isFirefox){ 959 var onkeydown = this._onkeydownhandler.bind(this, id, changehandler); 960 control.addListener(DwtEvent.ONKEYDOWN, onkeydown); 961 } 962 var blurhandler = DwtForm.__makeFunc(itemDef.onblur); 963 if (blurhandler) { 964 var onblur = AjxCallback.simpleClosure(this._input2model2handler, this, id, blurhandler); 965 control.addListener(DwtEvent.ONBLUR, onblur); 966 } 967 968 itemDef.tooltip = itemDef.tooltip || itemDef.hint; 969 control.setHint(itemDef.hint); 970 control.setLabel(itemDef.label || itemDef.tooltip); 971 } 972 973 // init list 974 else if (control instanceof DwtListView) { 975 control.addSelectionListener(new AjxListener(this, this._handleListSelection, [id])); 976 } 977 978 // init menu 979 else if (control instanceof DwtMenu) { 980 if (itemDef.items) { 981 var menuItemDefs = itemDef.items; 982 for (var i = 0; i < menuItemDefs.length; i++) { 983 var menuItemDef = menuItemDefs[i]; 984 if (menuItemDef.type == DwtMenuItem.SEPARATOR_STYLE) { 985 new DwtMenuItem({parent:control, style:DwtMenuItem.SEPARATOR_STYLE}); 986 continue; 987 } 988 this._registerControl(menuItemDef, itemDef, null, null, control, "DwtMenuItem"); 989 } 990 } 991 } 992 993 // init tabs 994 else if (control instanceof DwtTabView) { 995 var pageDefs = itemDef.items; 996 if (pageDefs) { 997 this._registerControls(pageDefs, itemDef, null, null, control, "DwtTabViewPage"); 998 } 999 } 1000 1001 // init tab page 1002 else if (control instanceof DwtTabViewPage && parent instanceof DwtTabView) { 1003 var key = parent.addTab(itemDef.label, control); 1004 if (itemDef.image) { 1005 parent.getTabButton(key).setImage(itemDef.image, null, itemDef.imageAltText); 1006 } 1007 if (itemDef.items) { 1008 this._registerControls(itemDef.items, itemDef, tabIndexes, null, control); 1009 } 1010 } 1011 1012 // init toolbar 1013 else if (control instanceof DwtToolBar) { 1014 var toolbarItemDefs = itemDef.items; 1015 if (toolbarItemDefs) { 1016 for (var i = 0; i < toolbarItemDefs.length; i++) { 1017 var toolbarItemDef = toolbarItemDefs[i]; 1018 if (toolbarItemDef.type == DwtToolBar.SPACER) { 1019 control.addSpacer(toolbarItemDef.size); 1020 continue; 1021 } 1022 if (toolbarItemDef.type == DwtToolBar.SEPARATOR) { 1023 control.addSeparator(toolbarItemDef.className); 1024 continue; 1025 } 1026 if (toolbarItemDef.type == DwtToolBar.FILLER) { 1027 control.addFiller(toolbarItemDef.className); 1028 continue; 1029 } 1030 this._registerControl(toolbarItemDef, itemDef, null, null, control, "DwtToolBarButton"); 1031 } 1032 } 1033 } 1034 else if (control instanceof DwtCalendar) { 1035 if (itemDef.onselect instanceof AjxListener) { 1036 control.addSelectionListener(itemDef.onselect); 1037 } 1038 } 1039 1040 // TODO: other controls (e.g. combobox, listview, slider, spinner, tree) 1041 1042 // init anonymous composites 1043 else if (control instanceof DwtComposite) { 1044 if (itemDef.items) { 1045 this._registerControls(itemDef.items, itemDef, tabIndexes, null, control); 1046 } 1047 } 1048 1049 // size control 1050 if (itemDef.width || itemDef.height) { 1051 if (control instanceof DwtInputField) { 1052 Dwt.setSize(control.getInputElement(), itemDef.width, itemDef.height); 1053 } 1054 else { 1055 control.setSize(itemDef.width, itemDef.height); 1056 } 1057 } 1058 1059 if (itemDef.tooltip) { 1060 control.setToolTipContent(itemDef.tooltip); 1061 } 1062 1063 // return control 1064 return control; 1065 }; 1066 1067 DwtForm.prototype._onkeydownhandler = function(id, changehandler){ 1068 setTimeout(this._input2model2handler.bind(this, id, changehandler), 500); 1069 }; 1070 1071 DwtForm.prototype._initControl = function(itemDef, useCurrentValues) { 1072 var id = itemDef.id; 1073 if (itemDef.label) this.setLabel(id, itemDef.label); 1074 var item = this._items[id]; 1075 if (useCurrentValues) { 1076 item.ovalue = item.value; 1077 } 1078 else if (itemDef.value) { 1079 if (Dwt.instanceOf(itemDef.type, "DwtRadioButton")) { 1080 item.ovalue = item.value = item.control && item.control.isSelected(); 1081 } 1082 else { 1083 this.setValue(id, itemDef.value, true); 1084 item.ovalue = item.value; 1085 } 1086 } 1087 else { 1088 item.ovalue = null; 1089 } 1090 if (typeof itemDef.enabled == "boolean") this.setEnabled(id, itemDef.enabled); 1091 if (typeof itemDef.visible == "boolean") this.setVisible(id, itemDef.visible); 1092 }; 1093 1094 // html handlers 1095 1096 DwtForm.prototype._htmlElement = function(id, formHandler, elementHandler, evt) { 1097 if (formHandler) { 1098 this._call(formHandler, [id]); 1099 } 1100 if (elementHandler) { 1101 elementHandler(evt); 1102 } 1103 }; 1104 1105 DwtForm.prototype._htmlInput_checked = function(id, parentId, handler, onclick, evt) { 1106 var control = this.getControl(id); 1107 var checked = control.checked; 1108 this._setModelValue(id, checked); 1109 if (parentId && checked) { 1110 this._setModelValue(parentId, control.value); 1111 } 1112 this.update(); 1113 this._htmlElement(id, handler, onclick, evt); 1114 }; 1115 1116 DwtForm.prototype._htmlInput_value = function(id, handler, onchange, evt) { 1117 this._setModelValue(id, this.getControl(id).value); 1118 this.update(); 1119 this._htmlElement(id, handler, onchange, evt); 1120 }; 1121 1122 DwtForm.prototype._htmlSelect_selectedIndex = function(id, handler, onchange, evt) { 1123 var select = this.getControl(id); 1124 this._setModelValue(id, select.options[select.selectedIndex].value); 1125 this.update(); 1126 this._htmlElement(id, handler, onchange, evt); 1127 }; 1128 1129 // dwt handlers 1130 1131 DwtForm.prototype._control2model = function(id, handler) { 1132 this._setModelValue(id, this._getControlValue(id)); 1133 this.update(); 1134 if (handler) { 1135 this._call(handler, [id]); 1136 } 1137 }; 1138 1139 DwtForm.prototype._radio2group2model = function(radioId, groupId, handler) { 1140 this._setModelValue(groupId, this.getControl(radioId).getValue()); 1141 this._setModelValue(radioId, this._getControlValue(radioId)); 1142 this.update(); 1143 if (handler) { 1144 this._call(handler, [radioId]); 1145 } 1146 }; 1147 1148 DwtForm.prototype._input2model2handler = function(id, handler) { 1149 this._setModelValue(id, this._getControlValue(id)); 1150 this.update(); 1151 if (handler) { 1152 this._call(handler, [id]); 1153 } 1154 }; 1155 1156 DwtForm.prototype._item2parent = function(itemId, parentId, handler) { 1157 var control = this.getControl(itemId); 1158 var itemDef = this._items[itemId].def; 1159 if (control instanceof DwtButtonColorPicker || (itemDef.menu && !itemDef.onclick)) { 1160 control._toggleMenu(); // HACK: button should have public API 1161 } 1162 else if (parentId) { 1163 this._setModelValue(parentId, this._getControlValue(itemId) || itemId); 1164 this.update(); 1165 } 1166 if (handler) { 1167 this._call(handler, [itemId]); 1168 } 1169 }; 1170 1171 DwtForm.prototype._handleListSelection = function(id, evt) { 1172 this.update(); 1173 }; 1174 1175 // setters and getters 1176 1177 DwtForm.prototype.__list_getValue = function(id) { 1178 return this.getControl(id).getSelection(); 1179 }; 1180 DwtForm.prototype.__list_setValue = function(id, value) { 1181 this.getControl(id).setSelection(value); 1182 }; 1183 1184 // 1185 // Private functions 1186 // 1187 1188 // code generation 1189 1190 DwtForm.__makeGetter = function(item) { 1191 var getter = item.getter; 1192 if (getter) return DwtForm.__makeFunc(getter); 1193 1194 var ref = item.ref; 1195 if (!ref) return null; 1196 1197 var parts = ref.split("."); 1198 var body = [ 1199 "var context = this.model;" 1200 ]; 1201 for (var i = 0; i < parts.length; i++) { 1202 var name = parts[i]; 1203 var fname = DwtForm.__makeFuncName(name); 1204 if (i == parts.length - 1) break; 1205 body.push( 1206 "context = context && (context.",fname," ? context.",fname,"() : context.",name,");" 1207 ); 1208 } 1209 body.push( 1210 "var value = context ? (context.",fname," ? context.",fname,"() : context.",name,") : this._items.",name,".value;", 1211 "return value !== undefined ? value : defaultValue;" 1212 ); 1213 return new Function("defaultValue", body.join("")); 1214 }; 1215 1216 DwtForm.__makeSetter = function(item) { 1217 var setter = item.setter; 1218 if (setter) return DwtForm.__makeFunc(setter); 1219 1220 var ref = item.ref; 1221 if (!ref) return null; 1222 1223 var parts = ref.split("."); 1224 var body = [ 1225 "var context = this.model;" 1226 ]; 1227 for (var i = 0; i < parts.length; i++) { 1228 var isLast = i == parts.length - 1; 1229 var name = parts[i]; 1230 var fname = DwtForm.__makeFuncName(name, isLast ? "set" : "get"); 1231 if (isLast) break; 1232 body.push( 1233 "context = context && (context.",fname," ? context.",fname,"() : context.",name,");" 1234 ); 1235 } 1236 body.push( 1237 "if (context) {", 1238 "if (context.",fname,") {", 1239 "context.",fname,"(value);", 1240 "}", 1241 "else {", 1242 "context.",name," = value;", 1243 "}", 1244 "}" 1245 ); 1246 return new Function("value", body.join("\n")); 1247 }; 1248 1249 DwtForm.__makeFuncName = function(name, prefix) { 1250 return [prefix||"get",name.substr(0,1).toUpperCase(),name.substr(1)].join(""); 1251 }; 1252 1253 DwtForm.__makeFunc = function(value) { 1254 if (value == null) return null; 1255 if (typeof value == "function" && !(value instanceof RegExp)) return value; 1256 var body = [ 1257 "with (this._context) {", 1258 "return (",value,");", 1259 "}" 1260 ].join(""); 1261 return new Function(body); 1262 }; 1263 1264 DwtForm.__equals = function(a, b) { 1265 return a == b; 1266 }; 1267 1268 // Array.sort 1269 1270 DwtForm.__byTabIndex = function(a, b) { 1271 return a.tabindex - b.tabindex; 1272 }; 1273 1274 // hacks 1275 1276 DwtForm.__hack_fixRadioButtonHandler = function(radio) { 1277 var handlers = [radio.getInputElement().onclick, DwtCheckbox.__handleClick]; 1278 var handler = function(evt) { 1279 for (var i = 0; i < handlers.length; i++) { 1280 var func = handlers[i]; 1281 if (func) { 1282 func(evt); 1283 } 1284 } 1285 }; 1286 Dwt.setHandler(radio.getInputElement(), DwtEvent.ONCLICK, handler); 1287 }; 1288 1289 // 1290 // Class: DwtFormRows 1291 // 1292 1293 // TODO: tab-group 1294 1295 /** 1296 * 1297 * @extends DwtForm 1298 * 1299 * @private 1300 */ 1301 DwtFormRows = function(params) { 1302 if (arguments.length == 0) return; 1303 this._itemDef = params.formItemDef || {}; 1304 params.className = params.className || "DwtFormRows"; 1305 DwtForm.call(this, { 1306 id:params.id, parent:params.parent, 1307 form:{}, template:this._itemDef.template 1308 }); 1309 1310 // init state 1311 this._rowsTabGroup = new DwtTabGroup(this._htmlElId); 1312 1313 // save state 1314 this._rowDef = this._itemDef.rowitem || {}; 1315 this._equals = DwtForm.__makeFunc(this._rowDef.equals) || DwtForm.__equals; 1316 this._rowCount = 0; 1317 this._minRows = this._itemDef.minrows || 1; 1318 this._maxRows = this._itemDef.maxrows || Number.MAX_VALUE; 1319 if (this._itemDef.rowtemplate) { 1320 this.ROW_TEMPLATE = this._itemDef.rowtemplate; 1321 } 1322 1323 // add default rows 1324 var itemDefs = this._itemDef.items || []; 1325 for (var i = 0; i < itemDefs .length; i++) { 1326 this.addRow(itemDefs[i]); 1327 } 1328 1329 // add empty rows to satisfy minimum row count 1330 for ( ; i < this._minRows; i++) { 1331 this.addRow(); 1332 } 1333 1334 // remember listeners 1335 this._onaddrow = DwtForm.__makeFunc(this._itemDef.onaddrow); 1336 this._onremoverow = DwtForm.__makeFunc(this._itemDef.onremoverow); 1337 }; 1338 DwtFormRows.prototype = new DwtForm; 1339 DwtFormRows.prototype.constructor = DwtFormRows; 1340 1341 DwtFormRows.prototype.toString = function() { 1342 return "DwtFormRows"; 1343 }; 1344 1345 // Data 1346 1347 DwtFormRows.prototype.TEMPLATE = "dwt.Widgets#DwtFormRows"; 1348 DwtFormRows.prototype.ROW_TEMPLATE = "dwt.Widgets#DwtFormRow"; 1349 1350 // Public methods 1351 1352 DwtFormRows.prototype.getTabGroupMember = function() { 1353 return this._rowsTabGroup; 1354 }; 1355 1356 DwtFormRows.prototype.setValue = function(array) { 1357 if (arguments.length > 1) { 1358 DwtForm.prototype.setValue.apply(this, arguments); 1359 return; 1360 } 1361 // adjust row count 1362 var min = Math.max(array.length, this._minRows); 1363 for (var i = this._rowCount; i > min; i--) { 1364 this.removeRow(i-1); 1365 } 1366 var max = Math.min(array.length, this._maxRows); 1367 for (var i = this._rowCount; i < max; i++) { 1368 this.addRow(); 1369 } 1370 // initialize values 1371 for (var i = 0; i < max; i++) { 1372 this.setValue(String(i), array[i], true); 1373 } 1374 for (var i = array.length; i < this._rowCount; i++) { 1375 this.setValue(String(i), null, true); 1376 } 1377 }; 1378 1379 DwtFormRows.prototype.getValue = function() { 1380 if (arguments.length > 0) { 1381 return DwtForm.prototype.getValue.apply(this, arguments); 1382 } 1383 var array = new Array(this._rowCount); 1384 for (var i = 0; i < this._rowCount; i++) { 1385 array[i] = this.getValue(String(i)); 1386 } 1387 return array; 1388 }; 1389 1390 DwtFormRows.prototype.getRowCount = function() { 1391 return this._rowCount; 1392 }; 1393 1394 DwtFormRows.prototype.addRow = function(itemDef, index) { 1395 if (this._rowCount >= this._maxRows) { 1396 return; 1397 } 1398 itemDef = itemDef || (this._rowDef && AjxUtil.createProxy(this._rowDef)); 1399 if (!itemDef) { 1400 return; 1401 } 1402 1403 if (index == null) index = this._rowCount; 1404 1405 // move other rows "up" 1406 for (var i = this._rowCount - 1; i >= index; i--) { 1407 var oindex = i, nindex = i+1; 1408 var item = this._items[oindex]; 1409 item.aka = String(nindex); 1410 delete this._items[oindex]; 1411 this._items[item.aka] = item; 1412 this._setControlIds(item.id, item.aka); 1413 } 1414 1415 // initialize definition 1416 itemDef.id = itemDef.id || Dwt.getNextId(); 1417 itemDef.aka = String(index); 1418 this._rowCount++; 1419 1420 // create row html 1421 var data = { id: [this.getHTMLElId(), itemDef.id].join("_") }; 1422 var rowHtml = AjxTemplate.expand(this.ROW_TEMPLATE, data); 1423 1424 var rowsEl = this._rowsEl; 1425 rowsEl.appendChild(Dwt.toDocumentFragment(rowHtml, data.id+"_row")); 1426 var rowEl = rowsEl.lastChild; 1427 if (index != this._rowCount - 1) { 1428 rowsEl.insertBefore(rowEl, rowsEl.childNodes[index]); 1429 } 1430 1431 // create controls 1432 var tabIndexes = []; 1433 var rowControl = this._registerControl(itemDef, null, tabIndexes); 1434 1435 var addDef = this._itemDef.additem ? AjxUtil.createProxy(this._itemDef.additem) : { image: "Add", tooltip: ZmMsg.addRow }; 1436 addDef.id = addDef.id || itemDef.id+"_add"; 1437 addDef.visible = "this.getRowCount() < this.getMaxRows()"; 1438 addDef.ignore = true; 1439 var addButton = this._registerControl(addDef,null,tabIndexes,null,null,"DwtButton"); 1440 if (!addDef.onclick) { 1441 addButton.addSelectionListener(new AjxListener(this, this._handleAddRow, [itemDef.id])); 1442 } 1443 1444 var removeDef = this._itemDef.removeitem ? AjxUtil.createProxy(this._itemDef.removeitem) : { image: "Remove", tooltip: ZmMsg.removeRow }; 1445 removeDef.id = removeDef.id || itemDef.id+"_remove"; 1446 removeDef.visible = "this.getRowCount() > this.getMinRows()"; 1447 removeDef.ignore = true; 1448 var removeButton = this._registerControl(removeDef,null,tabIndexes,null,null,"DwtButton"); 1449 if (!removeDef.onclick) { 1450 removeButton.addSelectionListener(new AjxListener(this, this._handleRemoveRow, [itemDef.id])); 1451 } 1452 1453 // remember where we put it 1454 var item = this._items[itemDef.id]; 1455 item._rowEl = rowEl; 1456 item._addId= addDef.id; 1457 item._removeId = removeDef.id; 1458 1459 // set control identifiers 1460 this._setControlIds(item.id, index); 1461 1462 // create tab group for row 1463 var tabGroup = new DwtTabGroup(itemDef.id); 1464 tabIndexes.sort(DwtForm.__byTabIndex); 1465 for (var i = 0; i < tabIndexes.length; i++) { 1466 var control = tabIndexes[i].control; 1467 tabGroup.addMember(control.getTabGroupMember() || control); 1468 } 1469 1470 // add to tab group 1471 if (index == this._rowCount - 1) { 1472 this._rowsTabGroup.addMember(tabGroup); 1473 } 1474 else { 1475 var indexItemDef = this._items[String(index+1)]; 1476 this._rowsTabGroup.addMemberBefore(tabGroup, DwtTabGroup.getByName(indexItemDef.id)); 1477 } 1478 1479 // update display and notify handler 1480 this.update(); 1481 if (this._onaddrow) { 1482 this._call(this._onaddrow, [index]); 1483 } 1484 1485 return rowControl; 1486 }; 1487 1488 DwtFormRows.prototype.removeRow = function(indexOrId) { 1489 if (this._rowCount <= this._minRows) { 1490 return; 1491 } 1492 1493 var item = this._items[indexOrId]; 1494 1495 // this only recognizes if a properly accessible widgets (i.e. those that 1496 // receive browser focus) had focus 1497 var hadFocus = Dwt.isAncestor(item._rowEl, document.activeElement); 1498 1499 // delete item at specified index 1500 if (item.control instanceof DwtControl) { 1501 this.removeChild(item.control); 1502 } 1503 delete this._items[item.aka]; 1504 this._deleteItem(item.id); 1505 1506 // delete add item 1507 var addItem = this._items[item._addId]; 1508 if (addItem) { 1509 this.removeChild(addItem.control); 1510 this._deleteItem(addItem.id); 1511 } 1512 1513 // delete remove item 1514 var removeItem = this._items[item._removeId]; 1515 if (removeItem) { 1516 this.removeChild(removeItem.control); 1517 this._deleteItem(removeItem.id); 1518 } 1519 1520 // shift everything down one, removing old last row 1521 var fromIndex = Number(item.aka); 1522 for (var i = fromIndex + 1; i < this._rowCount; i++) { 1523 var oindex = i, nindex = i-1; 1524 this._items[nindex] = this._items[oindex]; 1525 this._items[nindex].aka = String(nindex); 1526 this._setControlIds(this._items[nindex].id, this._items[nindex].aka); 1527 } 1528 this._deleteItem(String(--this._rowCount)); 1529 1530 // remove row element 1531 var rowEl = item._rowEl; 1532 rowEl.parentNode.removeChild(rowEl); 1533 delete item._rowEl; 1534 1535 // remove from tab group 1536 var tabGroup = DwtTabGroup.getByName(item.id); 1537 this._rowsTabGroup.removeMember(tabGroup); 1538 1539 // update display and notify handler 1540 this.update(); 1541 1542 if (hadFocus) { 1543 var otherItem = this._items[item.aka] || this._items[this._rowCount - 1]; 1544 otherItem.control.getTabGroupMember().focus(); 1545 } 1546 1547 if (this._onremoverow) { 1548 this._call(this._onremoverow, [Number(item.aka)]); 1549 } 1550 }; 1551 1552 DwtFormRows.prototype.getMinRows = function() { 1553 return this._minRows; 1554 }; 1555 DwtFormRows.prototype.getMaxRows = function() { 1556 return this._maxRows; 1557 }; 1558 DwtFormRows.prototype.getRowCount = function() { 1559 return this._rowCount; 1560 }; 1561 1562 DwtFormRows.prototype.getIndexForRowId = function(rowId) { 1563 var children = this._rowsEl.childNodes; 1564 for (var i = 0; i < children.length; i++) { 1565 if (children[i].id == [this._htmlElId,rowId,"row"].join("_")) { 1566 return i; 1567 } 1568 } 1569 return -1; 1570 }; 1571 1572 DwtFormRows.__equals = function(a,b) { 1573 if (a === b) return true; 1574 if (!a || !b || a.length != b.length) return false; 1575 for (var i = 0; i < a.length; i++) { 1576 if (!this._call(this._equals, [a[i],b[i]])) { 1577 return false; 1578 } 1579 } 1580 return true; 1581 }; 1582 1583 // Protected methods 1584 1585 /** Override to set child controls' identifiers. */ 1586 DwtFormRows.prototype._setControlIds = function(rowId, index) { 1587 var id = [this.getHTMLElId(), index].join("_"); 1588 var item = this._items[rowId]; 1589 this._setControlId(item && item.control, id); 1590 var addButton = this._items[item._addId]; 1591 this._setControlId(addButton && addButton.control, id+"_add"); 1592 var removeButton = this._items[item._removeId]; 1593 this._setControlId(removeButton && removeButton.control, id+"_remove"); 1594 // TODO: update parentid attribute of children 1595 }; 1596 1597 DwtFormRows.prototype._setControlId = function(control, id) { 1598 if (!control) return; 1599 if (control instanceof DwtControl) { 1600 control.setHtmlElementId(id); 1601 } 1602 else { 1603 control.id = id; 1604 } 1605 }; 1606 1607 DwtFormRows.prototype._handleAddRow = function(rowId) { 1608 if (this.getRowCount() < this.getMaxRows()) { 1609 var index = this.getIndexForRowId(rowId) + 1; 1610 this.addRow(null, index); 1611 } 1612 }; 1613 1614 DwtFormRows.prototype._handleRemoveRow = function(rowId) { 1615 this.removeRow(rowId); 1616 }; 1617 1618 // DwtForm methods 1619 1620 DwtFormRows.prototype._setModelValue = function(id, value) { 1621 if (DwtForm.prototype._setModelValue.apply(this, arguments)) { 1622 this.parent.setDirty(this._itemDef.id, true); 1623 } 1624 }; 1625 1626 // DwtControl methods 1627 1628 DwtFormRows.prototype._createHtmlFromTemplate = function(templateId, data) { 1629 DwtForm.prototype._createHtmlFromTemplate.apply(this, arguments); 1630 this._rowsEl = document.getElementById(this._htmlElId+"_rows"); 1631 }; 1632 1633 1634