1 /* 2 * All content on this website (including text, images, source 3 * code and any other original works), unless otherwise noted, 4 * is licensed under a Creative Commons License. 5 * 6 * http://creativecommons.org/licenses/by-nc-sa/2.5/ 7 * 8 * Copyright (C) Open-Xchange Inc., 2006-2011 9 * Mail: info@open-xchange.com 10 */ 11 12 /** 13 * @fileOverview Open-Xchange GUI Plugin API 14 * @author Viktor Pracht <Viktor.Pracht@open-xchange.org> 15 */ 16 17 /** 18 * @namespace 19 */ 20 ox.UI = {}; 21 22 /** 23 * @namespace 24 */ 25 ox.Configuration = { 26 27 /** 28 * Displays an error message to the user. 29 * @param {I18nString} text The error message to display. 30 */ 31 error: function(text) { 32 setTimeout(function() { 33 triggerEvent("OX_New_Error", 4, expectI18n(text)); 34 }, 0); 35 }, 36 37 /** 38 * Displays an information message to the user. 39 * @param {I18nString} text The information message to display. 40 */ 41 info: function(text) { 42 triggerEvent("OX_New_Info", 4, expectI18n(text)); 43 }, 44 45 /** 46 * Adds a new node to the configuration tree. 47 * The parent node must already exist. 48 * @class Leaf node in the configuration tree 49 * @param {String} path The path in the configuration tree. 50 * Must start with "configuration" and contain at least one more 51 * element. Path elements are separated by a slash ("/"). There must be 52 * no slash at the end. If the parent of the node does not already 53 * exist, an Error exception is thrown. 54 * @param {I18nString} name The name of the page. 55 */ 56 LeafNode: function(path, name) { 57 var Self = new ox.Configuration.Node(path, name, false, function() { 58 if (Self.click) return Self.click(); 59 }); 60 return Self; 61 }, 62 63 /** 64 * This callback is called when the user clicks on the node. 65 * @name ox.Configuration.LeafNode.prototype.click 66 * @type Function 67 */ 68 69 /** 70 * Adds a new inner node to the configuration tree. 71 * The parent node must already exist. 72 * @class Inner node in the configuration tree. 73 * @param {String} path The path in the configuration tree. 74 * Must start with "configuration" and contain at least one more 75 * element. Path elements are separated by a slash ("/"). There must be 76 * no slash at the end. If the parent of the node does not already 77 * exist, an Error exception is thrown. 78 * @param {I18nString} name The name of the page. 79 */ 80 InnerNode: function(path, name) { 81 return new ox.Configuration.Node(path, name, true); 82 }, 83 84 /** 85 * @class 86 * @private 87 */ 88 Node: function(path, name, folder, click) { 89 if (path in ox.Configuration.nodes) 90 return ox.Configuration.nodes[path]; 91 var match = /^(.*)\/[^\/]+$/.exec(path); 92 if (!match) throw Error("Invalid path: '" + path + "'"); 93 this.id = folder ? path 94 : "configuration/modules/" + ox.Configuration.id++; 95 var txtNode = (name instanceof I18nString) || typeof(name) == "string" 96 ? addTranslated(name) : name; 97 var node = ox.widgets.configTree.add({ parent: match[1], id: this.id, 98 domNode: txtNode, innerNode: folder, 99 onClick: click }); 100 ox.Configuration.nodes[path] = this; 101 }, 102 103 /** 104 * A map from paths to configuration tree nodes. 105 * @private 106 */ 107 nodes: {}, 108 109 /** 110 * A counter for unique node IDs. 111 * @private 112 */ 113 id: 0, 114 115 /** 116 * An array with all created configuration pages. 117 * @private 118 */ 119 pages: [] 120 121 }; 122 123 /** 124 * @namespace 125 */ 126 ox.JSON = {}; 127 128 /** 129 * Asynchronously requests a JSON object from the server. 130 * This method retrieves a JSON object from the server by issuing an HTTP 131 * GET request to the specified URI and calling the specified callback when 132 * the retrieval is complete. 133 * @param {String} uri The URI for the HTTP GET request. 134 * @param {Function} ok A callback function which is called with the 135 * received JSON object or raw data as parameter. If there was any error 136 * then this function is not called. 137 * @param {Function} error An optional callback function wihch is called when 138 * the server returns an error. The function takes two parameters: result and 139 * status. If the HTTP status code was 200, then result is the JSON object and 140 * status is not set. If the HTTP status was not 200, then result is the status 141 * string and status is the HTTP status code. The function should return true 142 * when it handles the error. Otherwise, the default error handler specified by 143 * JSON.errorHandler will be called after this function returns. If this 144 * parameter is not specified, the default error handler is called directly. 145 * @param {Boolean} raw Specifies whether the response data should be 146 * passed to the callback as-is or first parsed as a JSON object. Defaults 147 * to the latter. 148 */ 149 ox.JSON.get = function(uri, ok, error, raw) { 150 (new JSONX()).get(uri, null, ok, error, raw); 151 }; 152 153 /** 154 * Asynchronously posts url-encoded data and retrieves a JSON object from 155 * the server. 156 * This method posts an object to the server by issuing an HTTP 157 * POST request to the specified URI and calling the specified callback when 158 * the reply arrives. 159 * @param {String} uri The URI for the HTTP POST request. 160 * @param {Object} data An object which is serialized using the 161 * application/x-www-form-urlencoded encoding and sent as the body of the 162 * request. 163 * @param {Function} ok A callback function which is called with the received 164 * JSON object or raw data as parameter. If there was any error then this 165 * function is not called. 166 * @param {Function} error An optional callback function wihch is called when 167 * the server returns an error. The function takes two parameters: result and 168 * status. If the HTTP status code was 200, then result is the JSON object and 169 * status is not set. If the HTTP status was not 200, then result is the status 170 * string and status is the HTTP status code. The function should return true 171 * when it handles the error. Otherwise, the default error handler specified by 172 * JSON.errorHandler will be called after this function returns. If this 173 * parameter is not specified, the default error handler is called directly. 174 * @param {Boolean} raw Specifies whether the response data should be 175 * passed to the callback as-is or first parsed as a JSON object. Defaults 176 * to the latter. 177 */ 178 ox.JSON.post = function(uri, data, ok, error, raw) { 179 (new JSONX()).post(uri, data, null, ok, error, raw); 180 }; 181 182 /** 183 * Asynchronously sends a JSON object and retrieves a JSON object from 184 * the server. 185 * This method sends a JSON object to the server by issuing an HTTP 186 * PUT request to the specified URI and calling the specified callback when 187 * the reply arrives. 188 * @param {String} uri The URI for the HTTP POST request. 189 * @param {Object} data An object which is serialized using JSON syntax and 190 * sent as the body of the request. 191 * @param {Function} ok A callback function which is called with the received 192 * JSON object or raw data as parameter. If there was any error then this 193 * function is not called. 194 * @param {Function} error An optional callback function wihch is called when 195 * the server returns an error. The function takes two parameters: result and 196 * status. If the HTTP status code was 200, then result is the JSON object and 197 * status is not set. If the HTTP status was not 200, then result is the status 198 * string and status is the HTTP status code. The function should return true 199 * when it handles the error. Otherwise, the default error handler specified by 200 * JSON.errorHandler will be called after this function returns. If this 201 * parameter is not specified, the default error handler is called directly. 202 * @param {Boolean} raw Specifies whether the response data should be 203 * passed to the callback as-is or first parsed as a JSON object. Defaults 204 * to the latter. 205 */ 206 ox.JSON.put = function(uri, data, ok, error, raw) { 207 (new JSONX()).put(uri, data, null, ok, error, raw); 208 }; 209 210 /** 211 * @class Abstract base class of all widgets. 212 */ 213 ox.UI.Widget = function() { 214 /** 215 * Specifies whether the widget was already initialized. 216 * @type Boolean 217 * @default false 218 */ 219 this.initialized = false; 220 }; 221 222 /** 223 * The default value of this widget, which is used when the model does 224 * not contain the field for this widget. 225 * @name ox.UI.Widget.prototype.default_value 226 */ 227 228 /** 229 * The topmost DOM node of the widget. It is used by the default implementations 230 * of {@link #show}, {@link #hide} and {@link #remove}. 231 * @name ox.UI.Widget.prototype.node 232 * @type DOM Node 233 */ 234 235 /** 236 * A DOM node which is used by the default implementation of {@link #enable} and 237 * {@link #disable} to control the disabled state of the widget. The DOM node 238 * should have a property named "disabled". If the value equals false, disabling 239 * will have no effect besides updating the {@link #enabled} field and applying 240 * the disabled CSS. 241 * @name ox.UI.Widget.prototype.formnode 242 * @type DOM Node 243 */ 244 245 /** 246 * The width of the widget as a CSS length specification. 247 * If not specified, defaults to the {@link ox.UI.Container#childWidth} of 248 * the parent. 249 * @name ox.UI.Widget.prototype.width 250 * @type String 251 */ 252 253 ox.UI.Widget.setDisabledClass = classNameSetter("font-color-disabled"); 254 255 ox.UI.Widget.prototype = { 256 257 /** 258 * Adds the DOM nodes of this widget to its parent container. 259 * @param {String} node_id The ID of the current page. This ID is required 260 * for adding menu entries. 261 */ 262 addContent: function(node_id) { 263 this.initialized = true; 264 if (!this.isEnabled) this.applyEnabled(); 265 if (!this.isVisible) this.applyVisible(); 266 }, 267 268 /** 269 * Returns the current value of the widget. The returned type depends on 270 * the actual widget class. 271 */ 272 get: function() {}, 273 274 /** 275 * Sets the value which is displayed by the widget. 276 * @param value The new value of the widget. The type depends on the actual 277 * widget class. 278 */ 279 set: function(value) {}, 280 281 /** 282 * Resizes the widget. 283 */ 284 resize: function() {}, 285 286 /** 287 * Removes the widget's DOM nodes from the form. 288 */ 289 remove: function() { 290 if (this.node) this.node.parentNode.removeChild(this.node); 291 }, 292 293 /** 294 * Displays the widget if it was previously hidden. 295 */ 296 show: function() { this.setVisible(true); }, 297 298 /** 299 * Hides the widget. 300 */ 301 hide: function() { this.setVisible(false); }, 302 303 /** 304 * Sets the visibility of the widget. 305 * @param {Boolean} visible The new visibility status. 306 */ 307 setVisible: function(visible) { 308 this.visible = visible; 309 visible = visible && (!this.parent || this.parent.isVisible); 310 if (this.isVisible != visible) { 311 this.isVisible = visible; 312 if (this.initialized) this.applyVisible(); 313 } 314 }, 315 316 /** 317 * Applies the visibility status to the actual widget. 318 * Only called when the widget is initialized. 319 * @protected 320 */ 321 applyVisible: function() { 322 if (this.node) this.node.style.display = this.isVisible ? "" : "none"; 323 }, 324 325 /** 326 * Specifies whether the widget is visible. 327 * @type Boolean 328 * @default true 329 */ 330 visible: true, 331 332 /** 333 * The actual visibility of the widget, which can be false even if visible 334 * is set to true, because the widget's container is invisible. 335 */ 336 isVisible: true, 337 338 /** 339 * Enables the widget for user interaction. 340 */ 341 enable: function() { this.setEnabled(true); }, 342 343 /** 344 * Disables the widget for user interaction. 345 */ 346 disable: function() { this.setEnabled(false); }, 347 348 /** 349 * Enables or disables the widget. 350 * @param {Boolean} enabled The new enabled status. 351 */ 352 setEnabled: function(enabled) { 353 this.enabled = enabled; 354 enabled = enabled && (!this.parent || this.parent.isEnabled); 355 if (this.isEnabled != enabled) { 356 this.isEnabled = enabled; 357 if (this.initialized) this.applyEnabled(); 358 } 359 }, 360 361 /** 362 * Applies the value of this.isEnabled to the actual widget. 363 * When this method is called, the widget is already initialized. 364 * @protected 365 */ 366 applyEnabled: function() { 367 if (this.formnode) this.formnode.disabled = !this.isEnabled; 368 if (this.node) 369 ox.UI.Widget.setDisabledClass(this.node, !this.isEnabled); 370 }, 371 372 /** 373 * Specifies whether the widget is enabled. 374 * @type Boolean 375 * @default true 376 */ 377 enabled: true, 378 379 /** 380 * The actual enabled state of the widget, which can be false even if 381 * enabled is set to true, because the widget's container is disabled. 382 */ 383 isEnabled: true, 384 385 /** 386 * Sets the widget's container. 387 * @param {ox.UI.Container} parent The new parent container. 388 */ 389 setParent: function(parent) { 390 this.parent = parent; 391 this.setEnabled(this.enabled); 392 this.setVisible(this.visible); 393 }, 394 395 /** 396 * The widget which contains this widget. null if this widget is not a child 397 * of another widget. 398 * @default null 399 */ 400 parent: null 401 402 }; 403 404 /** 405 * @class Static explanation text. 406 * Do not specify a field name when adding instances of this class to 407 * containers. 408 * @augments ox.UI.Widget 409 * @param {I18nString} text The displayed text. 410 */ 411 ox.UI.Text = function(text) { 412 ox.UI.Widget.apply(this); 413 this.text = text; 414 }; 415 416 ox.UI.Text.prototype = extend(ox.UI.Widget, 417 /** @lends ox.UI.Text.prototype */ 418 { 419 420 addContent: function(node_id) { 421 this.node = this.parent.addRow(addTranslated(this.text), false); 422 ox.UI.Widget.prototype.addContent.apply(this, arguments); 423 } 424 425 }); 426 427 /** 428 * @class A text input field. 429 * @param {I18nString} label The label for the input field. 430 */ 431 ox.UI.Input = function(label) { 432 ox.UI.Widget.apply(this); 433 this.label = label; 434 }; 435 436 ox.UI.Input.prototype = extend(ox.UI.Widget, 437 /** @lends ox.UI.Input.prototype */ 438 { 439 440 /** 441 * The default value of the input field, which is used when the model 442 * does not contain the field for this input field. 443 * @default an empty string 444 */ 445 default_value: "", 446 447 addContent: function (node_id) { 448 this.formnode = 449 newnode("input", 450 { width: this.width || this.parent.childWidth }, 451 { type: this.inputType }); 452 this.node = this.parent.addCells(this.label, this.formnode); 453 ox.UI.Widget.prototype.addContent.apply(this, arguments); 454 // bind event 455 var self = this; 456 jQuery(this.formnode).bind("keydown paste cut", function (e) { 457 var node = this; 458 setTimeout(function () { 459 self.onChange.call(node, e); 460 }, 10); 461 }); 462 }, 463 464 /** 465 * Change event 466 */ 467 onChange: function (e) { 468 }, 469 470 /** 471 * Returns the current value of the input field. 472 * @type String 473 */ 474 get: function() { return this.formnode.value; }, 475 476 /** 477 * Sets the value of the input field. 478 * @param {String} value 479 */ 480 set: function(value) { this.formnode.value = value; }, 481 482 /** 483 * The type attribute of the HTML input element. 484 * @type String 485 * @default "text" 486 */ 487 inputType: "text" 488 489 }); 490 491 /** 492 * @class A password input field. 493 * @augments ox.UI.Input 494 * @param {I18nString} label The label for the input field. 495 */ 496 ox.UI.Password = function(label) { 497 ox.UI.Input.apply(this, arguments); 498 } 499 500 ox.UI.Password.prototype = extend(ox.UI.Input, 501 /** @lends ox.UI.Password.prototype */ 502 { inputType: "password" }); 503 504 /** 505 * @class a multi-line text input area. 506 * @param {I18nString} label The label for the input field. 507 */ 508 ox.UI.TextArea = function(label) { 509 ox.UI.Widget.apply(this); 510 this.label = label; 511 } 512 513 ox.UI.TextArea.prototype = extend(ox.UI.Widget, 514 /** @lends ox.UI.TextArea.prototype */ 515 { 516 517 default_value: "", 518 519 addContent: function(node_id) { 520 this.formnode = newnode("textarea", 521 { width: this.width || this.parent.childWidth }, 0); 522 if (this.height) this.formnode.style.height = this.height; 523 this.node = this.parent.addCells(this.label, this.formnode); 524 ox.UI.Widget.prototype.addContent.apply(this, arguments); 525 }, 526 527 /** 528 * Returns the current value of the text area. 529 * @type String 530 */ 531 get: function() { return this.formnode.value; }, 532 533 /** 534 * Sets the value of the text area. 535 * @param {String} value 536 */ 537 set: function(value) { this.formnode.value = value; } 538 539 }); 540 541 /** 542 * @class A check box for a single boolean field. 543 * @augments ox.UI.Widget 544 * @param {I18nString} label The label for the checkbox. 545 */ 546 ox.UI.CheckBox = function(label) { 547 ox.UI.Widget.apply(this); 548 this.label = label; 549 }; 550 551 /** 552 * @private 553 */ 554 ox.UI.CheckBox.id = 0; 555 556 /** 557 * This callback is called when the user changes the state of the CheckBox. 558 * @name ox.UI.CheckBox.prototype.changed 559 * @type Function 560 */ 561 562 ox.UI.CheckBox.prototype = extend(ox.UI.Widget, 563 /** @lends ox.UI.CheckBox.prototype */ 564 { 565 566 addContent: function(node_id) { 567 var Self = this; 568 var id = "ox.UI.CheckBox." + ox.UI.CheckBox.id++; 569 this.formnode = newnode("input", 0, { type: "checkbox", id: id }); 570 this.node = this.parent.addRow(newnode("label", 0, { htmlFor: id }, [ 571 this.formnode, 572 document.createTextNode(" "), 573 addTranslated(this.label) 574 ]), true); 575 addDOMEvent(this.formnode, "click", function() { 576 if (Self.changed) Self.changed(); 577 }); 578 ox.UI.Widget.prototype.addContent.apply(this, arguments); 579 }, 580 581 /** 582 * Returns the current value of the CheckBox. 583 * @type Boolean 584 */ 585 get: function() { return this.formnode.checked; }, 586 587 /** 588 * Sets the value of the CheckBox 589 * @param {Boolean} value 590 */ 591 set: function(value) { this.formnode.checked = value; } 592 593 }); 594 595 /** 596 * @class Abstract base class of selection widgets. 597 * This class handles the association between displayed objects and value 598 * objects. 599 * @augments ox.UI.Widget 600 */ 601 ox.UI.Selection = function() { 602 ox.UI.Widget.apply(this); 603 this.values = []; 604 this.display_values = []; 605 }; 606 607 /** 608 * This callback is called when the user selects a different value. 609 * @name ox.UI.Selection.prototype.changed 610 * @type Function 611 */ 612 613 ox.UI.Selection.prototype = extend(ox.UI.Widget, 614 /** @lends ox.UI.Selection.prototype */ 615 { 616 617 /** 618 * Sets the list of possible values and the corresponding displayed 619 * values. 620 * The parameters are used directly, without copying. Therefore, 621 * the arrays should not be modified after being passed to this method. 622 * An exception is modifying the arrays and immediately calling this 623 * method again to update the displayed widget. 624 * @param {Array} values An array of possible values. 625 * @param {Array} display_values An array of displayed values. 626 * Each element represents the value in the values array with the same 627 * index. The type of elements depends on the actual selection class. 628 */ 629 setEntries: function(values, display_values) { 630 this.values = values; 631 this.display_values = display_values; 632 } 633 634 }); 635 636 /** 637 * @class A combination of a text input field and a drop-down list. 638 * @augments ox.UI.Selection 639 * @param {I18nString} label The label of the ComboBox. 640 * @param {Boolean} editable Specifies whether the text input field can contain 641 * values which are not in the drop-down list. Defaults to false. 642 * Not implemented yet. 643 * 644 */ 645 ox.UI.ComboBox = function(label, editable) { 646 ox.UI.Selection.apply(this); 647 this.label = label; 648 this.editable = editable; 649 } 650 651 ox.UI.ComboBox.prototype = extend(ox.UI.Selection, 652 /** @lends ox.UI.ComboBox.prototype */ 653 { 654 655 /** 656 * @private 657 */ 658 recreate: function() { 659 var nokey = {}; 660 var key = this.combobox ? this.combobox.getKey() : nokey; 661 removeChildNodes(this.div); 662 var Self = this; 663 var numValues = this.values && this.values.length ? this.values.length : 0; 664 this.combobox = new ComboBox3.impl(window, this.div, 665 this.width || this.parent.childWidth, 0, true, 666 Math.min(8, numValues), 667 function(value) { 668 if (Self.changed) Self.changed(value); 669 }); 670 if (numValues) { 671 for (var i = 0; i < numValues; i++) { 672 this.combobox.addElement(this.display_values[i], 673 this.values[i]); 674 } 675 } else { 676 this.combobox.addElement(noI18n("\xa0"), null); 677 } 678 this.combobox.getDomNode(); 679 if (key != nokey) this.combobox.setKey(key); 680 if (!this.enabled) this.combobox.disable(); 681 }, 682 683 addContent: function(node_id) { 684 this.div = newnode("div"); 685 this.node = this.parent.addCells(this.label, this.div); 686 this.recreate(); 687 ox.UI.Selection.prototype.addContent.apply(this, arguments); 688 }, 689 690 resize: function() { this.recreate(); }, 691 692 /** 693 * Sets the list of possible values and the corresponding displayed 694 * textual descriptions. 695 * The parameters are used directly, without copying. Therefore, 696 * the arrays should not be modified after being passed to this method. 697 * An exception is modifying the arrays and immediately calling this 698 * method again to update the displayed widget. 699 * @param {Array} values An array of possible values. 700 * @param {I18nString[]} display_values An array of displayed texts. 701 * Each element represents the value in the values array with the same 702 * index. 703 */ 704 setEntries: function(values, display_values) { 705 ox.UI.Selection.prototype.setEntries.apply(this, arguments); 706 if (this.initialized) this.recreate(); 707 }, 708 709 /** 710 * Returns the current value of the ComboBox. 711 */ 712 get: function() { 713 return this.combobox.getKey(); 714 }, 715 716 /** 717 * Sets the value of the ComboBox. 718 */ 719 set: function(value) { 720 this.combobox.setKey(value); 721 }, 722 723 applyEnabled: function() { 724 if (this.isEnabled) 725 this.combobox.enable(); 726 else 727 this.combobox.disable(); 728 ox.UI.Widget.setDisabledClass(this.node, !this.isEnabled); 729 } 730 731 }); 732 733 /** 734 * @class A group of radio buttons. Only one option can be selected at a time. 735 * @augments ox.UI.Selection 736 */ 737 ox.UI.RadioButtons = function() { 738 ox.UI.Selection.apply(this); 739 this.name = ++ox.UI.RadioButtons.lastName; 740 } 741 742 ox.UI.RadioButtons.lastName = 0; 743 744 ox.UI.RadioButtons.prototype = extend(ox.UI.Selection, 745 /** @lends ox.UI.RadioButtons.prototype */ 746 { 747 748 /** 749 * @private 750 */ 751 recreate: function() { 752 removeChildNodes(this.div); 753 this.nodes = new Array(this.values.length); 754 var Self = this; 755 for (var i = 0; i < this.values.length; i++) { 756 this.nodes[i] = newradio(0, 757 { type: "radio", name: this.name, value: this.values[i], 758 className: "noborder nobackground" }); 759 addDOMEvent(this.nodes[i], "click", function() { 760 if (Self.changed) Self.changed(); 761 }); 762 this.div.appendChild(newnode("label", 0, 763 { htmlFor: "ox.UI.RadioButtons." + this.name + "." + i }, 764 [ 765 this.nodes[i], 766 document.createTextNode(" "), 767 addTranslated(this.display_values[i]) 768 ])); 769 this.div.appendChild(newnode("br")); 770 } 771 }, 772 773 addContent: function(node_id) { 774 this.div = newnode("div"); 775 this.node = this.parent.addRow(this.div, true); 776 this.recreate(); 777 ox.UI.Selection.prototype.addContent.apply(this, arguments); 778 }, 779 780 /** 781 * Sets the list of possible values and the corresponding displayed 782 * labels. 783 * The parameters are used directly, without copying. Therefore, 784 * the arrays should not be modified after being passed to this method. 785 * An exception is modifying the arrays and immediately calling this 786 * method again to update the displayed widget. 787 * @param {Array} values An array of possible values. 788 * @param {String[]} display_values An array of displayed labels. 789 * Each element represents the value in the values array with the same 790 * index. 791 */ 792 setEntries: function() { 793 ox.UI.Selection.prototype.setEntries.apply(this, arguments); 794 if (this.initialized) this.recreate(); 795 }, 796 797 /** 798 * Returns the current value of the RadioButtons. 799 */ 800 get: function() { 801 for (var i = 0; i < this.values.length; i++) 802 if (this.nodes[i].checked) return this.values[i]; 803 }, 804 805 /** 806 * Sets the value of the RadioButtons. 807 */ 808 set: function(value) { 809 for (var i = 0; i < this.values.length; i++) 810 this.nodes[i].checked = this.values[i] == value; 811 }, 812 813 applyEnabled: function() { 814 for (var i = 0; i < this.nodes.length; i++) 815 this.nodes[i].disabled = !this.isEnabled; 816 ox.UI.Widget.setDisabledClass(this.node, !this.isEnabled); 817 } 818 819 }); 820 821 /** 822 * @class A button. 823 * @augments ox.UI.Widget 824 * @para {I18nStirng} text The text of the button. 825 */ 826 ox.UI.Button = function(text) { 827 ox.UI.Widget.apply(this); 828 this.text = text; 829 }; 830 831 /** 832 * A callback which is called when the button is clicked. 833 * @name ox.UI.Button.prototype.click 834 * @type Function 835 */ 836 837 ox.UI.Button.prototype = extend(ox.UI.Widget, 838 /** @lends ox.UI.Button.prototype */ 839 { 840 841 addContent: function(node_id) { 842 this.formnode = newnode("button", 0, 0, [addTranslated(this.text)]); 843 var Self = this; 844 addDOMEvent(this.formnode, "click", function() { 845 if (Self.click) Self.click(); 846 }); 847 this.node = this.parent.addRow(this.formnode, false); 848 ox.UI.Widget.prototype.addContent.apply(this, arguments); 849 } 850 851 }); 852 853 /** 854 * @class Abstract base class of all controllers. 855 * Controllers are responsible for connecting widgets to the data model. 856 */ 857 ox.UI.Controller = {}; 858 859 /** 860 * @class An exception object which can be thrown by ox.UI.Controller.set to 861 * indicate an attempt to set an invalid value. 862 * @param {String} message An optional message which, if specified, is assigned 863 * to the property message of the created instance. 864 */ 865 ox.UI.Controller.InvalidData = function(message) { 866 if (message != undefined) this.message = message; 867 }, 868 869 ox.UI.Controller.InvalidData.prototype = new Error("Invalid data"); 870 ox.UI.Controller.InvalidData.prototype.constructor = 871 ox.UI.Controller.InvalidData; 872 ox.UI.Controller.InvalidData.prototype.name = "ox.UI.Controller.InvalidData"; 873 874 /** 875 * Adds a child value to a container value. Optionally, a replacement value for 876 * the entire container value may be returned. This is useful for composite 877 * widgets which represent scalars (i. e. not objects) as values. 878 * @name ox.UI.Controller.prototype.get 879 * @function 880 * @param data The value of the container. 881 * @param value The value of the child widget. 882 * @type Any 883 * @return undefined if the data was modified in-place, or a new value of 884 * the container. 885 */ 886 887 /** 888 * Extracts a child value from a container value. 889 * Throws ox.UI.Controller.InvalidData if the extracted data is invalid. 890 * @name ox.UI.Controller.prototype.set 891 * @function 892 * @param data The value of the container. 893 * @return The value of the child widget, or undefined if the default value of 894 * the widget should be used. 895 */ 896 897 /** 898 * @class Abstract base class of all containers. 899 * @augments ox.UI.Widget 900 */ 901 ox.UI.Container = function() { 902 ox.UI.Widget.apply(this); 903 this.children = []; 904 this.names = {}; 905 }; 906 907 /** 908 * Adds a content row to the container. 909 * @name ox.UI.Container.prototype.addRow 910 * @function 911 * @param {DOM Node} child A DOM node which is or contains the chlid widget. 912 * @param {Boolean} table Specifies whether the contents of this row should 913 * align themselves with the content of other rows (if true), or not (if false). 914 * @type DOM node 915 * @return The DOM TR or DIV element of the added row. 916 */ 917 918 /** 919 * Adds a row consisting of two cells: a label and a form element. 920 * The form element will align itself with other form elements 921 * @name ox.UI.Container.prototype.addCells 922 * @function 923 * @param {I18nString} label An optional label text for the form element. 924 * @param {DOM node} input The DOM node which contains the form element. 925 * @type DOM node 926 * @return The DOM TR element of the added row. 927 */ 928 929 ox.UI.Container.prototype = extend(ox.UI.Widget, 930 /** @lends ox.UI.Container.prototype */ 931 { 932 addContent: function(node_id) { 933 this.node_id = node_id; 934 for (var i = 0; i < this.children.length; i++) 935 this.children[i].addContent(node_id); 936 ox.UI.Widget.prototype.addContent.apply(this, arguments); 937 }, 938 939 default_value: {}, 940 941 /** 942 * Adds a widget to this container as a new child. 943 * The new widget appears behind all previously added widgets. 944 * The value of the container is an object which contains the values of 945 * all children. Each child value appears in the value of the container 946 * as a field with the name specified by the parameter name. 947 * @param {Widget} widget The widget to add. 948 * @param {String or ox.UI.Controller} name If a string, the field name 949 * under which the child value will appear in the container's value. 950 * If an {@link ox.UI.Controller}, the controller which is responsible 951 * for building and parsing the container value. If not specified, 952 * the child value will not be connected to the container value. 953 * @see ox.Configuration.Group.NoField 954 */ 955 addWidget: function(widget, name) { 956 if (name !== undefined) this.names[this.children.length] = name; 957 this.children.push(widget); 958 widget.setParent(this); 959 if (this.initialized) widget.addContent(this.node_id); 960 }, 961 962 /** 963 * Removes a previously added widget. 964 * @param {Widget} widget The widget to remove. 965 */ 966 deleteWidget: function(widget) { 967 for (var i = 0; i < this.children.length; i++) { 968 if (this.children[i] == widget) { 969 this.children[i].remove(this); 970 this.children.splice(i, 1); 971 widget.setParent(null); 972 break; 973 } 974 } 975 }, 976 977 /** 978 * Returns an object with all child values. 979 * Only children with specified field names are queried. 980 * @type Object 981 */ 982 get: function() { 983 var data = {}; 984 for (var i = 0; i < this.children.length; i++) { 985 if (i in this.names) { 986 var child = this.children[i]; 987 if (child.visible && child.isEnabled) { 988 var value = child.get(); 989 if (typeof this.names[i] == "string") { 990 data[this.names[i]] = value; 991 } else { 992 var newData = this.names[i].get(data, value); 993 if (newData !== undefined) data = newData; 994 } 995 } 996 } 997 } 998 return data; 999 }, 1000 1001 /** 1002 * Sets the values of all children. 1003 * @param {Object} data An object with a field for every child. 1004 * If a child was added with a field name, but the object does not 1005 * contain that field, the child will be set to its default value. 1006 */ 1007 set: function(value) { 1008 value = value || {}; 1009 for (var i = 0; i < this.children.length; i++) { 1010 if (i in this.names) { 1011 if (typeof this.names[i] == "string") { 1012 var val = value[this.names[i]]; 1013 } else { 1014 var val = this.names[i].set(value); 1015 } 1016 if (val === undefined || val === null) 1017 val = this.children[i].default_value; 1018 this.children[i].set(val); 1019 } 1020 } 1021 }, 1022 1023 resize: function() { 1024 for (var i = 0; i < this.children.length; i++) 1025 this.children[i].resize(); 1026 }, 1027 1028 applyVisible: function() { 1029 ox.UI.Widget.prototype.applyVisible.call(this); 1030 for (var i = 0; i < this.children.length; i++) { 1031 var child = this.children[i]; 1032 child.setVisible(child.visible); 1033 } 1034 }, 1035 1036 applyEnabled: function() { 1037 ox.UI.Widget.prototype.applyEnabled.call(this); 1038 for (var i = 0; i < this.children.length; i++) { 1039 var child = this.children[i]; 1040 child.setEnabled(child.enabled); 1041 } 1042 } 1043 1044 }); 1045 1046 registerView("configuration/modules", 1047 function() { showNode("modules"); }, 1048 null, null, 1049 function() { hideNode("modules"); }); 1050 1051 register("Loaded", function() { 1052 ox.Configuration.View.i18n = new I18nNode(noI18n("")); 1053 ox.Configuration.View.i18n.disable(); 1054 $("modules.header").appendChild(ox.Configuration.View.i18n.node); 1055 }); 1056 1057 resizeEvents.register("Resized", function() { 1058 var current = ox.Configuration.View.current; 1059 if (current) setTimeout(function() { current.resize(); }, 0); 1060 }); 1061 1062 /** 1063 * Attaches a content page to a leaf node in the configuration tree. 1064 * The order of events for a view is <ol> 1065 * <li>{@link #init} (first time only),</li> 1066 * <li>{@link #addContent} (first time only),</li> 1067 * <li>{@link #enter},</li> 1068 * <li>user edits the view,</li> 1069 * <li>{@link #viewModified}</li> 1070 * <li>optionally, either {@link #saveView} or {@link #cancelView},</li> 1071 * <li>{@link #leave}.</li></ol> 1072 * @class Abstract base class of configuration pages. 1073 * @augments ox.UI.Container 1074 * @param {ox.Configuration.LeafNode} node A {@link ox.Configuration.LeafNode} 1075 * which will be configured to open this page. 1076 * @param {I18nString} title The page title. 1077 */ 1078 ox.Configuration.View = function(node, title) { 1079 ox.UI.Container.apply(this); 1080 var Self = this; 1081 var view = node.id; 1082 var old_configuration_changed; 1083 registerView(view, function() { 1084 if (!Self.initialized) { 1085 if (Self.init) Self.init(); 1086 if (!Self.toolbar) { 1087 Self.toolbar = temporary.configuration.newToolbar(title, []); 1088 } 1089 ox.widgets.toolBar.views[view] = Self.toolbar; 1090 changeDisplay.update(view); 1091 Self.addContent(view); 1092 } 1093 var i18n = ox.Configuration.View.i18n; 1094 if (title instanceof I18nString) { 1095 i18n.callback = function() { return String(title); }; 1096 } else if (debug) { 1097 (console.warn || console["log"] || alert)(format( 1098 "The string \"%s\" is not internationalized!", 1099 title)); 1100 if (typeof title != "function") { 1101 i18n.callback = function() { return String(_(title)); }; 1102 } else { 1103 i18n.callback = title; 1104 } 1105 } 1106 i18n.update(); 1107 i18n.enable(); 1108 $("modules.content").appendChild(Self.content); 1109 temporary.configuration.showToolbar(Self.toolbar); 1110 }, function() { 1111 ox.Configuration.View.current = Self; 1112 old_configuration_changed = configuration_changed; 1113 configuration_changed = modified; 1114 register("OX_SAVE_OBJECT", save); 1115 register("OX_Cancel_Object", cancel); 1116 Self.enter(); 1117 }, function() { 1118 Self.leave(); 1119 unregister("OX_SAVE_OBJECT", save); 1120 unregister("OX_Cancel_Object", cancel); 1121 configuration_changed = old_configuration_changed; 1122 ox.Configuration.View.current = null; 1123 }, function() { 1124 $("modules.content").removeChild(Self.content); 1125 ox.Configuration.View.i18n.disable(); 1126 }); 1127 ox.Configuration.pages.push(this); 1128 node.click = function() { 1129 configuration_askforSave(function() { 1130 triggerEvent("OX_Switch_View", node.id); 1131 }); 1132 }; 1133 1134 function modified() { return Self.viewModified(); } 1135 1136 function save() { if (Self.viewModified()) Self.saveView(); } 1137 1138 function cancel() { Self.cancelView(); } 1139 }; 1140 1141 /** 1142 * A callback which is called before the view is opened for the first time. 1143 * Usually, child elements are created and added here. 1144 * @name ox.Configuration.View.prototype.init 1145 * @type Function 1146 */ 1147 1148 /** 1149 * DOM node with the content of the view. 1150 * @name ox.Configuration.View.prototype.content 1151 * @type DOM node 1152 */ 1153 1154 /** 1155 * Returns whether the view contents were modified and may need to be saved. 1156 * @name ox.Configuration.View.prototype.viewModified 1157 * @function 1158 * @type Boolean 1159 * @return true if the view contents were modified. 1160 */ 1161 1162 ox.Configuration.View.prototype = extend(ox.UI.Container, 1163 /** @lends ox.Configuration.View.prototype */ 1164 { 1165 1166 /** 1167 * Performs initialization when the user enters the view. 1168 */ 1169 enter: function() {}, 1170 1171 /** 1172 * Performs cleanup when the user leaves the view. 1173 */ 1174 leave: function() {}, 1175 1176 /** 1177 * Saves view contents. 1178 * @param {Function} callback An optional callback functi which is 1179 * called after the view is saved. 1180 */ 1181 saveView: function(callback) {}, 1182 1183 /** 1184 * Reverts modifications made by the user. 1185 */ 1186 cancelView: function() {}, 1187 1188 /** 1189 * Width of child widgets, as a CSS length value. 1190 * @type String 1191 * @default "20em" 1192 */ 1193 childWidth: "20em" 1194 1195 }); 1196 1197 /** 1198 * @class Resizable vertical split view. 1199 * All sizes are specified as a fraction of the total available space. 1200 * @augments ox.Configuration.View 1201 * @param {ox.Configuration.LeafNode} node An {@link ox.Configuration.LeafNode} 1202 * which will be configured to open this page. 1203 * @param {I18nString} title The page title. 1204 * @param {Number} size The width of the left panel. 1205 * @param {Boolean} new_button Specifies whether the big button in the menu 1206 * should be a "New" button instead of a "Save" button. 1207 */ 1208 ox.Configuration.VSplit = function(node, title, size, new_button) { 1209 ox.Configuration.View.call(this, node, title); 1210 1211 /** 1212 * Current width of the left panel. 1213 * @type Number 1214 * @private 1215 */ 1216 this.size = size; 1217 1218 /** 1219 * Specifies whether the currently edited data is a new list entry. 1220 * @type Boolean 1221 * @private 1222 */ 1223 this.isNew = false; 1224 1225 var Self = this; 1226 this.toolbar = temporary.configuration.newToolbar(title, 1227 new_button ? [{ 1228 title: _("New"), 1229 id: "new", 1230 buttons: [{ 1231 title: _("New"), 1232 id: "new", 1233 icons: ["img/new.png"], 1234 big: true, 1235 action: function() { if (Self.onNew) Self.onNew(); } 1236 }] 1237 }] : [temporary.configuration.saveButton]); 1238 }; 1239 1240 /** 1241 * A callback which is called when the big "New" button in the menu is clicked. 1242 * @name ox.Configuration.VSplit.prototype.onNew 1243 * @type Function 1244 */ 1245 1246 /** 1247 * The LiveGrid which is displayed in the left panel. It must be set before 1248 * the view is entered for the first time. 1249 * @name ox.Configuration.VSplit.prototype.list 1250 * @type LiveGrid 1251 * @deprecated 1252 */ 1253 1254 /** 1255 * Specifies whether the list view was modified and needs to be saved. 1256 * @name ox.Configuration.VSplit.prototype.listModified 1257 * @type Boolean 1258 * @default false 1259 */ 1260 1261 /** 1262 * Enables the list view when the view is entered. Disabling is done 1263 * automatically by calling the disable() method of the list. 1264 * @name ox.Configuration.VSplit.prototype.enableList 1265 * @function 1266 */ 1267 1268 /** 1269 * A callback which is called to get the data for the detail view. 1270 * It has a continuation function as parameter, which should be called with 1271 * the data as parameter when the data becomes available. If the continuation 1272 * function is called without a parameter, the detail view is set to the default 1273 * value and disabled. 1274 * @name ox.Configuration.VSplit.prototype.load 1275 * @type Function 1276 */ 1277 1278 /** 1279 * A callback which is called to save the data of the detail view. 1280 * It has two parameters: data and cont. data is the data object to save. 1281 * cont is a continuation functions which should be called after the data was 1282 * saved successfully. If the continuation function is called with a parameter, 1283 * the value of the parameter is used as the new value. 1284 * @name ox.Configuration.VSplit.prototype.save 1285 * @type Function 1286 */ 1287 1288 /** 1289 * An unmodified copy of the data object which is edited in the detail view. 1290 * @name ox.Configuration.VSplit.prototype.original 1291 */ 1292 1293 ox.Configuration.VSplit.prototype = extend(ox.Configuration.View, 1294 /** @lends ox.Configuration.VSplit.prototype */ 1295 { 1296 1297 addContent: function(node_id) { 1298 var selection = this.list.selection; 1299 selection.events.register("Selected", function(count) { 1300 menuselectedfolders = []; 1301 triggerEvent("OX_SELECTED_ITEMS_CHANGED", selection.length); 1302 triggerEvent("OX_SELECTION_CHANGED", selection.length); 1303 }); 1304 selection.events.trigger("Selected", 0); 1305 var list_parent = newnode("div", { 1306 position: "absolute", top: "1.6em", bottom: 0, overflow: "auto", 1307 width: "100%" 1308 }); 1309 this.left = newnode("div", { 1310 position: "absolute", left: 0, top: 0, width: 0, height: "100%", 1311 overflow: "hidden" 1312 }, 0, [this.list.getHeader(), list_parent]); 1313 this.list.getTable(list_parent); 1314 this.split = newnode("div", { 1315 position: "absolute", left: 0, top: 0, height: "100%", 1316 width: this.split_size + "px", cursor: "e-resize" 1317 }, { className: "sizesplit-vline" }, [ 1318 newnode("img", { top: "50%", marginTop: "-10px" }, 1319 { src: getFullImgSrc("img/split_grip_v.gif") }) 1320 ]); 1321 addDOMEvent(this.split, "mousedown", d); 1322 this.right = this.table = newnode("div", { 1323 position: "absolute", right: 0, top: 0, height: "100%", 1324 left: this.split_size + "px", overflow: "auto" 1325 }); 1326 this.content = newnode("div", { 1327 position: "absolute", left: 0, top: 0, width: "100%", 1328 height: "100%" 1329 }, 0, [this.left, this.split, this.right]); 1330 ox.Configuration.View.prototype.addContent.apply(this, arguments); 1331 1332 var Self = this; 1333 1334 function d(e) { 1335 hideIFrames(); 1336 Self.content.style.cursor = "e-resize"; 1337 addDOMEvent(body, "mousemove", m); 1338 addDOMEvent(body, "mouseup", u); 1339 var pxsize = Self.getPixel(Self.size); 1340 var offset = pxsize - e.clientX; 1341 if (!Self.animated) { 1342 var movingSplit = Self.split.cloneNode(true); 1343 movingSplit.style.left = pxsize + "px"; 1344 movingSplit.className = movingSplit.className + " moving"; 1345 Self.content.appendChild(movingSplit); 1346 } 1347 cancelDefault(e); 1348 1349 function getFraction(x) { 1350 return (x + offset) / 1351 (Self.content.clientWidth - Self.split_size); 1352 } 1353 1354 function m(e) { 1355 stopEvent(e); 1356 Self.size = getFraction(e.clientX); 1357 if (Self.animated) { 1358 Self.setSize(Self.size); 1359 } else { 1360 movingSplit.style.left = (e.clientX + offset) + "px"; 1361 } 1362 } 1363 1364 function u(e) { 1365 showIFrames(); 1366 removeDOMEvent(body, "mousemove", m); 1367 removeDOMEvent(body, "mouseup", u); 1368 Self.content.style.cursor = ""; 1369 if (!Self.animated) { 1370 Self.content.removeChild(movingSplit); 1371 movingSplit = null; 1372 Self.setSize(Self.size); 1373 } 1374 } 1375 1376 } 1377 1378 }, 1379 1380 /** 1381 * Computes the width of the left panel in pixels. 1382 * @param {Number} size The width of the left panel as a fraction of 1383 * the total available space. 1384 * @type Number 1385 * @return The width of the left panel in pixels. 1386 * @private 1387 */ 1388 getPixel: function(size) { 1389 size = size < this.min ? this.min 1390 : size > this.max ? this.max 1391 : size; 1392 return size * (this.content.clientWidth - this.split_size); 1393 }, 1394 1395 addRow: function(child, table) { 1396 if (table) { 1397 var row = newnode("tr", 0, 0, [ 1398 newnode("td", { paddingLeft: "20px" }, { colSpan: 2 }, 1399 child ? [child] : 0) 1400 ]); 1401 if (this.lastRow) { 1402 this.lastRow.appendChild(row); 1403 } else { 1404 this.lastRow = newnode("tbody", { vAlign: "top" }, 0, 1405 [row]); 1406 this.table.appendChild(newnode("table", 0, 0, 1407 [this.lastRow])); 1408 } 1409 return row; 1410 } else { 1411 var row = newnode("div", { paddingLeft: "20px" }, 0, 1412 child ? [child] : 0); 1413 this.table.appendChild(row); 1414 this.lastRow = null; 1415 return row; 1416 } 1417 }, 1418 1419 addCells: function(label, input) { 1420 var tr = this.addRow(label ? addTranslated(label) : null, true); 1421 tr.firstChild.colSpan = 1; 1422 tr.appendChild(newnode("td", { paddingLeft: "10px" }, 0, 1423 input ? [input] : 0)); 1424 return tr; 1425 }, 1426 1427 viewModified: function() { 1428 return this.listModified || !equals(this.get(), this.original); 1429 }, 1430 1431 enter: function() { 1432 if (this.enableList) { 1433 var Self = this; 1434 this.selected_cb = selected; 1435 this.list.selection.events.register("Selected", selected); 1436 this.enableList(); 1437 if (this.list.selection.count == 1) { 1438 this.enable(); 1439 } else { 1440 this.set(this.default_value); 1441 this.id = undefined; 1442 this.disable(); 1443 } 1444 } 1445 this.original = clone(this.get()); 1446 1447 function selected(count) { 1448 if (!count && Self.isNew) return; 1449 Self.isNew = false; 1450 var data = Self.get(); 1451 if (Self.enabled && !equals(data, Self.original) && Self.save) { 1452 Self.afterSave = saved; 1453 configuration_askforSave(); 1454 } else { 1455 saved(); 1456 } 1457 1458 function saved() { 1459 Self.afterSave = null; 1460 if (count == 1) { 1461 if (Self.load) { 1462 try { 1463 Self.load(function(data) { 1464 if (data == undefined) { 1465 Self.set(Self.default_value); 1466 Self.id = undefined; 1467 Self.disable(); 1468 } else { 1469 Self.set(data); 1470 Self.enable(); 1471 Self.id = data.id; 1472 } 1473 Self.original = clone(Self.get()); 1474 }); 1475 } catch (e) { 1476 if (e instanceof ox.UI.Controller.InvalidData) { 1477 Self.set(Self.default_value); 1478 Self.id = undefined; 1479 Self.disable(); 1480 } else throw e; 1481 } 1482 } else { 1483 Self.enable(); 1484 Self.original = clone(Self.get()); 1485 } 1486 } else { 1487 Self.set(Self.default_value); 1488 Self.id = undefined; 1489 Self.disable(); 1490 Self.original = clone(Self.get()); 1491 } 1492 } 1493 } 1494 1495 }, 1496 1497 leave: function() { 1498 this.list.selection.events.unregister("Selected", this.selected_cb); 1499 this.list.disable(); 1500 }, 1501 1502 saveView: function(callback) { 1503 this.isNew = false; 1504 if (this.save) { 1505 var data = this.get(); 1506 var data2 = clone(data); 1507 if (this.id != undefined) data2.id = this.id; 1508 var Self = this; 1509 this.save(data2, function(data3) { 1510 if (Self.afterSave) { 1511 Self.afterSave(); 1512 } else if (data3) { 1513 Self.set(data3); 1514 Self.id = data3.id; 1515 Self.original = clone(Self.get()); 1516 } else { 1517 Self.original = clone(data); 1518 } 1519 if (callback) callback(); 1520 }); 1521 } else if (callback) { 1522 callback(); 1523 } 1524 }, 1525 1526 cancelView: function() { 1527 this.isNew = false; 1528 if (this.afterSave) { 1529 this.afterSave(); 1530 } else { 1531 this.set(this.original); 1532 } 1533 }, 1534 1535 /** 1536 * Changes the width of the left panel. 1537 * @param {Number} size The new width of the left panel. 1538 */ 1539 setSize: function(size) { 1540 this.size = size; 1541 size = this.getPixel(size); 1542 this.left.style.width = size + "px"; 1543 this.split.style.left = size + "px"; 1544 this.right.style.left = (size + this.split_size) + "px"; 1545 if (IE6) { 1546 var h = this.content.parentNode.clientHeight + "px"; 1547 this.content.style.height = h; 1548 this.left.style.height = h; 1549 this.split.style.height = h; 1550 this.right.style.height = h; 1551 this.right.style.width = 1552 (this.content.parentNode.clientWidth 1553 - this.getPixel(this.size) - this.split_size) + "px"; 1554 } 1555 }, 1556 1557 resize: function() { 1558 this.setSize(this.size); 1559 }, 1560 1561 /** 1562 * Width of the split handle in pixels. 1563 */ 1564 split_size: 7, 1565 1566 /** 1567 * Specifies whether resizing takes effect immediately during dragging 1568 * (true) or only at the end (false). 1569 * @type Boolean 1570 * @default true 1571 */ 1572 animated: true, 1573 1574 /** 1575 * Minimum width of the left panel. 1576 * @type Number 1577 * @default 0 1578 */ 1579 min: 0, 1580 1581 /** 1582 * Maximum width of the left panel. 1583 * @type Number 1584 * @default 1 1585 */ 1586 max: 1, 1587 1588 original: {}, 1589 1590 addNew: function(data) { 1591 this.isNew = true; 1592 this.list.selection.reset(); 1593 this.set(data); 1594 this.enable(); 1595 this.original = clone(this.get()); 1596 this.id = data.id; 1597 } 1598 1599 }); 1600 1601 /** 1602 * @class A configuration page with a single form. 1603 * @augments ox.Configuration.View 1604 * @param {ox.Configuration.LeafNode} node A {@link ox.Configuration.LeafNode} 1605 * which will be configured to open this page. 1606 * @param {I18nString} title The page title. 1607 * @param {Boolean} save_button Whether the default save button should be 1608 * displayed in the menu. Defaults to true. Without the save button, 1609 * viewModified always returns false. 1610 */ 1611 ox.Configuration.Page = function(node, title, save_button) { 1612 ox.Configuration.View.apply(this, arguments); 1613 if (save_button == undefined) save_button = true; 1614 if (save_button) { 1615 this.toolbar = temporary.configuration.newToolbar(title, 1616 [temporary.configuration.saveButton]); 1617 } else { 1618 this.viewModified = ox.Configuration.IFrame.prototype.viewModified; 1619 } 1620 }; 1621 1622 /** 1623 * A callback which is called to get the data for the view. 1624 * It has a continuation function as parameter, which should be called with 1625 * the data as parameter when the data becomes available. If the continuation is 1626 * called without a parameter, the view is set to the default value and 1627 * disabled. 1628 * @name ox.Configuration.Page.prototype.load 1629 * @type Function 1630 */ 1631 1632 /** 1633 * A callback which is called to save the page's data. 1634 * It has two parameters: data and cont. data is the data object to save. 1635 * cont is a continuation functions which should be called after the data was 1636 * saved successfully. If the continuation function is called with a parameter, 1637 * the value of the parameter is used as the new value. 1638 * @name ox.Configuration.Page.prototype.save 1639 * @type Function 1640 */ 1641 1642 /** 1643 * An unmodified copy of the currently edited data object. 1644 * @name ox.Configuration.Page.prototype.original 1645 */ 1646 1647 ox.Configuration.Page.prototype = extend(ox.Configuration.View, 1648 /** @lends ox.Configuration.Page.prototype */ 1649 { 1650 1651 addContent: function(node_id) { 1652 // TODO: Framework above Page 1653 this.content = this.table = newnode("div"); 1654 ox.Configuration.View.prototype.addContent.apply(this, arguments); 1655 }, 1656 1657 resize: function() {}, 1658 1659 addRow: ox.Configuration.VSplit.prototype.addRow, 1660 1661 addCells: ox.Configuration.VSplit.prototype.addCells, 1662 1663 viewModified: function() { 1664 return !equals(this.get(), this.original); 1665 }, 1666 1667 enter: function() { 1668 if (this.load) { 1669 var Self = this; 1670 this.load(function(data) { 1671 if (data == undefined) { 1672 Self.set(Self.default_value); 1673 Self.disable(); 1674 } else { 1675 Self.set(data); 1676 Self.enable(); 1677 } 1678 Self.original = clone(Self.get()); 1679 }); 1680 } else { 1681 this.original = clone(this.get()); 1682 } 1683 }, 1684 1685 saveView: function(callback) { 1686 if (this.save) { 1687 var data = this.get(); 1688 var Self = this; 1689 this.save(data, function(updated) { 1690 if (updated) { 1691 Self.set(updated); 1692 Self.original = clone(Self.get()); 1693 } else { 1694 Self.original = clone(data); 1695 } 1696 if (callback) callback(); 1697 }); 1698 } 1699 }, 1700 1701 cancelView: function() { 1702 this.set(this.original); 1703 } 1704 1705 }); 1706 1707 /** 1708 * Creates a new widget group for a configuration page. 1709 * @class A widget group in a configuration page. 1710 * @augments ox.UI.Container 1711 * @param {I18nString} title The title of the group. 1712 */ 1713 ox.Configuration.Group = function(title) { 1714 ox.UI.Container.apply(this); 1715 this.title = title; 1716 }; 1717 1718 ox.Configuration.Group.prototype = extend(ox.UI.Container, 1719 /** @lends ox.Configuration.Group.prototype */ 1720 { 1721 1722 addContent: function(node_id) { 1723 if (this.title) { 1724 this.node = this.parent.addRow(addTranslated(this.title), true); 1725 this.node.style.paddingTop = "1.6em"; 1726 this.node.className = "height16 font-color-default " + 1727 "font-weight-high font-style-low"; 1728 } 1729 this.childWidth = this.parent.childWidth; 1730 ox.UI.Container.prototype.addContent.apply(this, arguments); 1731 }, 1732 1733 addRow: function(child, table) { 1734 return this.parent.addRow(child, table); 1735 }, 1736 1737 addCells: function(label, input) { 1738 return this.parent.addCells(label, input); 1739 } 1740 1741 }); 1742 1743 /** 1744 * A pre-defined controller object for use as the second parameter to 1745 * ox.UI.Container#addWidget. It inserts child values directly into the parent's 1746 * value object, without a nested object. 1747 */ 1748 ox.Configuration.Group.NoField = { 1749 get: function(data, value) { 1750 if (typeof value == "object" && value !== null) { 1751 for (var i in value) data[i] = value[i]; 1752 } else { 1753 return value; 1754 } 1755 }, 1756 set: function(data) { return data; } 1757 }; 1758 1759 /** 1760 * @class A group which contains an array as value. Children are usually added 1761 * and remoevd at runtime by the user. 1762 * @augments ox.UI.Container 1763 * @param {I18nString} title An optional title of the group. 1764 */ 1765 ox.Configuration.ArrayGroup = function(title) { 1766 ox.UI.Container.apply(this); 1767 this.title = title; 1768 }; 1769 1770 /** 1771 * A callback which is caled to add the widget for a new array element. 1772 * @name ox.Configuration.ArrayGroup.prototype.addElement 1773 * @type Function 1774 */ 1775 1776 ox.Configuration.ArrayGroup.prototype = extend(ox.UI.Container, 1777 /** @lends ox.Configuration.ArrayGroup */ 1778 { 1779 1780 default_value: [], 1781 1782 addContent: function(node_id) { 1783 if (this.title) { 1784 this.tnode = this.parent.addRow( 1785 this.title ? addTranslated(this.title) : null, true); 1786 this.tnode.style.paddingTop = "1.6em"; 1787 this.tnode.className = "height16 font-color-default " + 1788 "font-weight-high font-style-low"; 1789 } 1790 this.childWidth = this.parent.childWidth; 1791 this.tbody = newnode("tbody"); 1792 this.node = this.parent.addRow(newnode("table", 0, 0, [this.tbody]), 1793 false); 1794 ox.UI.Container.prototype.addContent.apply(this, arguments); 1795 }, 1796 1797 remove: function() { 1798 if (this.tnode) this.tnode.parentNode.removeChild(this.tnode); 1799 this.node.parentNode.removeChild(this.node); 1800 }, 1801 1802 applyVisible: function() { 1803 if (this.tnode) 1804 this.tnode.style.display = this.isVisible ? "" : "none"; 1805 this.node.style.display = this.isVisible ? "" : "none"; 1806 }, 1807 1808 addRow: function(child, table) { 1809 var row = newnode("tr", 0, 0, [ 1810 newnode("td", { paddingLeft: "20px" }, { colSpan: 2 }, 1811 child ? [child] : 0) 1812 ]); 1813 this.tbody.appendChild(row); 1814 return row; 1815 }, 1816 1817 addCells: ox.Configuration.VSplit.prototype.addCells, 1818 1819 get: function() { 1820 var value = new Array(this.children.length); 1821 for (var i = 0; i < this.children.length; i++) 1822 value[i] = this.children[i].get(); 1823 return value; 1824 }, 1825 1826 set: function(value) { 1827 for (var i = this.children.length - 1; i >= value.length; i--) 1828 this.deleteWidget(this.children[i]); 1829 for (var i = this.children.length; i < value.length; i++) 1830 this.addElement(); 1831 for (var i = 0; i < this.children.length; i++) 1832 this.children[i].set(value[i]); 1833 } 1834 1835 }); 1836 1837 /** 1838 * @class A container which arranges its children horizontally. 1839 */ 1840 ox.Configuration.HLayout = function() { 1841 ox.UI.Container.apply(this); 1842 } 1843 1844 /** 1845 * @private 1846 */ 1847 ox.Configuration.HLayout.id = 0; 1848 1849 ox.Configuration.HLayout.prototype = extend(ox.UI.Container, 1850 /** @lends ox.Configuration.HLayout.prototype */ 1851 { 1852 1853 addContent: function(node_id) { 1854 this.tr = newnode("tr"); 1855 this.node = this.parent.addRow( 1856 newnode("table", 0, { cellpadding: "5px" }, 1857 [newnode("tbody", 0, 0, [this.tr])]), 1858 true); 1859 ox.UI.Container.prototype.addContent.apply(this, arguments); 1860 }, 1861 1862 applyVisible: ox.UI.Widget.prototype.applyVisible, 1863 1864 addRow: function(child, table) { 1865 var td = newnode("td", 0, 0, [child]); 1866 this.tr.appendChild(td); 1867 return td; 1868 }, 1869 1870 addCells: function(label, input) { 1871 var td = newnode("td", 0, 0, label 1872 ? [addTranslated(label), input] 1873 : [input]); 1874 this.tr.appendChild(td); 1875 return td; 1876 }, 1877 1878 childWidth: "10em" 1879 1880 }); 1881 1882 /** 1883 * @class An editable list with "Add" and "Remove" buttons in the menu. 1884 * @augments ox.UI.Widget 1885 * @param {I18nString} section Name of the menu section. 1886 * @param {String} height The height of the list as a CSS length. 1887 * @param {I18nString} label An optional label for the list. 1888 */ 1889 ox.Configuration.EditableList = function(section, height, label) { 1890 ox.UI.Widget.apply(this); 1891 this.section = section; 1892 this.height = height; 1893 this.label = label; 1894 this.storage = new Storage(0, []); 1895 }; 1896 1897 /** 1898 * @private 1899 */ 1900 ox.Configuration.EditableList.id = 31; 1901 1902 /** 1903 * This method is called when the "Add" button is clicked. 1904 * @name ox.Configuration.EditableList.prototype.add 1905 * @function 1906 * @param {Function} cont A callback function which should be called with 1907 * an array of new elements as parameter. 1908 */ 1909 1910 /** 1911 * This method is called when the "Remove" button is clicked. 1912 * @name ox.Configuration.EditableList.prototype.onDelete 1913 * @function 1914 * @param {Array} removed An array with currently selected values. 1915 * @param {Function} cont A callback function which should be called after all 1916 * side effects have been performed. This function performs the actual removal. 1917 * If it is not called, no entries are removed from the list. 1918 * @param {Function} dontDelete A function which can be used to cancel 1919 * the removal of individual values. For each value which should be kept, this 1920 * function should be called with the index of the value in the array 1921 * <code>removed</code> as parameter. 1922 */ 1923 1924 ox.Configuration.EditableList.prototype = extend(ox.UI.Widget, 1925 /** @lends ox.Configuration.EditableList.prototype */ 1926 { 1927 1928 addContent: function(node_id) { 1929 var selection = new Selection(); 1930 this.grid = new LiveGrid([{ 1931 text: addTranslated(this.label), 1932 index: 1, 1933 clear: LiveGrid.makeClear(""), 1934 set: LiveGrid.defaultSet 1935 }], selection); 1936 this.grid.emptylivegridtext = this.emptyText; 1937 this.head = newnode("div"); 1938 this.body = newnode("div", 1939 { height: this.height, position: "relative" }); 1940 this.head.appendChild(this.grid.getHeader()); 1941 this.grid.getTable(this.body); 1942 this.applyEnabled(); 1943 1944 this.node = this.parent.addRow(newnode("div", 0, 0, 1945 [this.head, this.body])); 1946 var id = "ox.Configuration.EditableList." 1947 + ox.Configuration.EditableList.id++; 1948 var menu = MenuNodes.createSmallButtonContext(id, this.section); 1949 var Self = this; 1950 MenuNodes.createSmallButton(menu, id + ".add", _("Add"), 1951 getFullImgSrc("img/dummy.gif"), getFullImgSrc("img/dummy.gif"), 1952 function() { 1953 if (Self.add) Self.add(function(values) { 1954 if (!values.length) return; 1955 var oldlen = Self.values.length; 1956 Self.values = Self.values.concat(values); 1957 var data = new Array(values.length); 1958 for (var i = 0; i < values.length; i++) 1959 data[i] = [i + oldlen, Self.getText(values[i])]; 1960 Self.storage.append(data); 1961 Self.grid.focus = oldlen; 1962 Self.grid.showFocus(); 1963 }); 1964 }); 1965 MenuNodes.createSmallButton(menu, id + ".remove", _("Remove"), 1966 getFullImgSrc("img/dummy.gif"), getFullImgSrc("img/dummy.gif"), 1967 del); 1968 this.grid.events.register("Deleted", del); 1969 function del() { 1970 var indices = Self.grid.selection.getSelected(); 1971 var deleted = {}; 1972 var values = new Array(indices.length); 1973 for (var i = 0; i < indices.length; i++) { 1974 deleted[indices[i]] = true; 1975 values[i] = Self.values[indices[i]]; 1976 } 1977 if (Self.onDelete) { 1978 Self.onDelete(values, cont, dontDelete); 1979 } else { 1980 cont(); 1981 } 1982 function cont() { 1983 for (var d = 0; d < Self.values.length && !deleted[d]; d++); 1984 for (var s = d + 1; s < Self.values.length; s++) { 1985 if (!deleted[s]) Self.values[d++] = Self.values[s]; 1986 } 1987 Self.values.length = d; 1988 Self.set(Self.values); 1989 } 1990 function dontDelete(index) { delete deleted[indices[index]]; } 1991 } 1992 addMenuNode(menu.node, MenuNodes.FIXED, 1993 ox.Configuration.EditableList.id); 1994 changeDisplay(node_id, id); 1995 selection.events.register("Selected", function(count) { 1996 menuselectedfolders = []; 1997 triggerEvent("OX_SELECTED_ITEMS_CHANGED", count); 1998 triggerEvent("OX_SELECTION_CHANGED", count); 1999 }); 2000 //menuarrows[node_id] = {}; 2001 register("OX_SELECTED_ITEMS_CHANGED", function() { 2002 menuglobalzaehler = 0; 2003 //menuarrows[node_id][id] = []; 2004 menu_display_contents(node_id, id, true, id + ".add"); 2005 menu_display_contents(node_id, id, selection.count > 0, 2006 id + ".remove"); 2007 }); 2008 ox.UI.Widget.prototype.addContent.apply(this, arguments); 2009 }, 2010 2011 /** 2012 * The text which is displayed in the list when it contains no entries. 2013 * @type I18nString 2014 */ 2015 emptyText: noI18n(""), 2016 2017 default_value: [], 2018 2019 get: function() { return this.values; }, 2020 2021 set: function(value) { 2022 this.values = new Array(value.length); 2023 var data = new Array(value.length); 2024 for (var i = 0; i < value.length; i++) { 2025 this.values[i] = value[i]; 2026 data[i] = [i, this.getText(value[i])]; 2027 } 2028 this.storage.remove(0, this.storage.ids.length); 2029 this.storage.append(data); 2030 }, 2031 2032 /** 2033 * A callback function which is used to extract a human-readable 2034 * description from an array element of the value. This description is 2035 * displayed in the list. The default implementation handles I18nString 2036 * objects and plain strings. 2037 * @param elem The array element from which to extract the description. 2038 * @type String 2039 * @return A textual description of the specified element. 2040 */ 2041 getText: function(elem) { 2042 return typeof elem == "function" ? elem() : elem; 2043 }, 2044 2045 applyEnabled: function() { 2046 if (this.isEnabled) { 2047 this.grid.enable(this.storage); 2048 } else { 2049 this.grid.disable(); 2050 } 2051 } 2052 2053 }); 2054 2055 /** 2056 * @class A group of widgets with a common caption (legend). 2057 * The group is represented by a <fieldset> element. 2058 * @param {I18nString} legend An optional legend text. If not specified, 2059 * #getLegend must be overwritten to return the legend as an array of DOM nodes. 2060 */ 2061 ox.UI.FieldSet = function(legend) { 2062 ox.UI.Container.apply(this); 2063 if (legend) this.legend = legend; 2064 }; 2065 2066 ox.UI.FieldSet.prototype = extend(ox.UI.Container, 2067 /** @lends ox.UI.FieldSet.prototype */ 2068 { 2069 2070 addContent: function(node_id) { 2071 this.table = this.fieldset = newnode("fieldset", 0, 0, 2072 [newnode("legend", 0, 0, this.getLegend())]); 2073 this.node = this.parent.addRow(this.fieldset, false); 2074 this.childWidth = this.parent.childWidth; 2075 ox.UI.Container.prototype.addContent.apply(this, arguments); 2076 }, 2077 2078 /** 2079 * Builds the legend of the FieldSet. Descendants can overwrite this 2080 * method to create different legends. 2081 * @type Array 2082 * @return An array of DOM nodes which constitute the children of the 2083 * <legend> node. 2084 * @protected 2085 */ 2086 getLegend: function() { return [addTranslated(this.legend)]; }, 2087 2088 applyVisible: ox.UI.Widget.prototype.applyVisible, 2089 2090 addRow: ox.Configuration.VSplit.prototype.addRow, 2091 2092 addCells: ox.Configuration.VSplit.prototype.addCells 2093 /* 2094 addRow: function(child, table) { 2095 var td = newnode("td", 0, 0, [child]); 2096 this.tr.appendChild(td); 2097 return td; 2098 }, 2099 2100 addCells: function(label, input) { 2101 var td = newnode("td", 0, 0, label 2102 ? [addTranslated(label), input] 2103 : [input]); 2104 this.tr.appendChild(td); 2105 return td; 2106 } 2107 */ 2108 }); 2109 2110 /** 2111 * @class A FieldSet which can be enabled and disabled by a checkbox in 2112 * the legend. 2113 * @param {I18nString} legend The text of CheckBox in the legend. 2114 */ 2115 ox.UI.CheckedFieldSet = function(legend) { 2116 ox.UI.FieldSet.apply(this, arguments); 2117 }; 2118 2119 ox.UI.CheckedFieldSet.id = 0; 2120 2121 ox.UI.CheckedFieldSet.prototype = extend(ox.UI.FieldSet, 2122 /** @lends ox.UI.CheckedFieldSet.prototype */ 2123 { 2124 2125 getLegend : function() { 2126 var checkbox = newnode("input", 0, { 2127 type: "checkbox", 2128 id: "ox.UI.CheckedFieldSet." + ox.UI.CheckedFieldSet.id++ 2129 }); 2130 var Self = this; 2131 addDOMEvent(checkbox, "click", function() { 2132 Self.setEnabled(checkbox.checked); 2133 }); 2134 return [checkbox, newnode("label", 0, { htmlFor: checkbox.id }, 2135 [addTranslated(this.legend)])]; 2136 } 2137 2138 }); 2139 2140 /** 2141 * Attaches an iframe page to a leaf node in the configuration tree. 2142 * @class A configuration page with external content in an iframe. 2143 * @augments ox.Configuration.View 2144 * @param {ox.Configuration.LeafNode} node A {@link ox.Configuration.LeafNode} 2145 * which will be configured to open this page. 2146 * @param {I18nString} title The page title. 2147 * @param {String} src The URI of the iframe content. 2148 * @param {Boolean} save_button Whether the default save button should be 2149 * displayed in the menu. 2150 */ 2151 ox.Configuration.IFrame = function(node, title, src, save_button) { 2152 ox.Configuration.View.apply(this, [node, title]); 2153 this.src = src; 2154 if (save_button) { 2155 this.toolbar = temporary.configuration.newToolbar(title, 2156 [temporary.configuration.saveButton]); 2157 } 2158 }; 2159 2160 ox.Configuration.IFrame.prototype = extend(ox.Configuration.View, 2161 /** @lends ox.Configuration.IFrame.prototype */ 2162 { 2163 2164 addContent: function(node_id) { 2165 this.content = newnode("iframe", 2166 { position: "absolute", // stupid IE7 2167 width: "100%", height: "100%", border: 0 }, 2168 { src: this.src }); 2169 initialized = true; 2170 }, 2171 2172 resize: function() {}, 2173 2174 viewModified: function() { return false; } 2175 2176 }); 2177 2178 /** 2179 * @name ox.Configuration.IFrame.prototype.addWidget 2180 * @private 2181 */ 2182 delete ox.Configuration.IFrame.prototype.addWidget; 2183 2184 2185 /** 2186 * A form with dynamic contents created from a formDescription 2187 * @param {I18nString} title A title for the type selection. 2188 * @param {ox.UI.Controller or String} typeController a controller for 2189 * the type selection widget, as used for the second parameter to addWidget(). 2190 * @param {ox.UI.Controller or String} dataController a controller for 2191 * the form contents, as use for the second parameter to addWidget(). 2192 * @ignore 2193 */ 2194 ox.Configuration.DynamicForm = function(title, typeController, dataController) { 2195 this.meta = {}; 2196 function makeDefault(controller) { 2197 if (typeof controller == "string") { 2198 return { 2199 get: function(data, value) { data[controller] = value; }, 2200 set: function(data) { return data[controller]; } 2201 }; 2202 } else { 2203 return controller; 2204 } 2205 } 2206 this.typeController = makeDefault(typeController); 2207 this.dataController = makeDefault(dataController); 2208 ox.UI.Container.call(this); 2209 this.select = new ox.UI.ComboBox(title); 2210 var Self = this; 2211 this.select.changed = function() { 2212 var type = Self.select.get(); 2213 for (var i = Self.children.length - 1; i > 0; i--) { 2214 Self.deleteWidget(Self.children[i]); 2215 } 2216 if (type && (type in Self.meta)) { 2217 var defs = Self.meta[type].formDescription; 2218 for (var i = 0; i < defs.length; i++) { 2219 var def = defs[i]; 2220 var widget = Self.widgets[def.widget](def); 2221 var controller = Self.controllers[def.widget]; 2222 Self.addWidget(widget, controller ? controller(def.name) 2223 : def.name); 2224 if ("defaultValue" in def) { 2225 widget.default_value = def.defaultValue; 2226 widget.set(def.defaultValue); 2227 } 2228 } 2229 } 2230 }; 2231 this.addWidget(this.select); 2232 } 2233 2234 ox.Configuration.DynamicForm.prototype = extend(ox.UI.Container, { 2235 addContent: function(node_id) { 2236 this.childWidth = this.parent.childWidth; 2237 this.table = newnode("div"); 2238 this.node = this.parent.addRow(this.table); 2239 ox.UI.Container.prototype.addContent.apply(this, arguments); 2240 }, 2241 addRow: ox.Configuration.VSplit.prototype.addRow, 2242 addCells: ox.Configuration.VSplit.prototype.addCells, 2243 /** 2244 * Sets the available form descriptions. 2245 * @param {Object} meta An object mapping types to formDescription objects. 2246 */ 2247 setMetadata: function(meta) { 2248 this.meta = {}; 2249 MetaLoop: for (var i in meta) { 2250 var defs = meta[i].formDescription; 2251 for (var j = 0; j < defs.length; j++) { 2252 if (!(defs[j].widget in this.widgets)) continue MetaLoop; 2253 } 2254 this.meta[i] = meta[i]; 2255 } 2256 var ids = [], names = []; 2257 for (var i in this.meta) { 2258 ids.push(i); 2259 names.push(this.meta[i].displayName); 2260 } 2261 if (ids.length) this.select.default_value = ids[0]; 2262 this.select.setEntries(ids, names); 2263 }, 2264 set: function(value) { 2265 this.id = value.id; 2266 var type = this.typeController.set(value); 2267 if (type === undefined || type === null) { 2268 type = this.select.default_value; 2269 } 2270 this.select.set(type); 2271 this.select.setEnabled(!this.immutableType || !this.id); 2272 ox.UI.Container.prototype.set.call(this, 2273 this.dataController.set(value)); 2274 }, 2275 get: function() { 2276 var type = this.select.get(); 2277 if (!type) return; 2278 var value = {}; 2279 if (this.id) value.id = this.id; 2280 this.typeController.get(value, type); 2281 this.dataController.get(value, 2282 ox.UI.Container.prototype.get.call(this)); 2283 return value; 2284 }, 2285 /** 2286 * A map of widget types to widget constructors. 2287 * Each constructor takes a widget definition from a formDescription object 2288 * as it single parameter and returns the created widget. 2289 */ 2290 widgets: { 2291 input: function(def) { 2292 return new ox.UI.Input(noI18n(def.displayName)); 2293 }, 2294 password: function(def) { 2295 return new ox.UI.Password(noI18n(def.displayName)); 2296 }, 2297 checkbox: function(def) { 2298 return new ox.UI.CheckBox(noI18n(def.displayName)); 2299 }, 2300 text: function(def) { 2301 return new ox.UI.Text(noI18n(def.displayName)); 2302 }, 2303 link: function(def) { 2304 var link = new ox.UI.Widget(); 2305 link.default_value = ""; 2306 link.addContent = function() { 2307 this.anchor = newnode("a", 0, 2308 { href: this.default_value, target: "_blank" }, 2309 [document.createTextNode(this.default_value)]); 2310 this.node = this.parent.addCells(noI18n(def.displayName), 2311 this.anchor); 2312 ox.UI.Widget.prototype.addContent.apply(this, arguments); 2313 }; 2314 link.set = function(value) { 2315 this.anchor.href = value; 2316 this.anchor.firstChild.data = value; 2317 }; 2318 link.get = function() { return this.anchor.firstChild.data; }; 2319 return link; 2320 }, 2321 oauthAccount: function(def) { 2322 var account = new ox.Configuration.HLayout(); 2323 account.addContent = function(node_id) { 2324 this.tr = newnode("tr"); 2325 this.node = this.parent.addCells(noI18n(def.displayName), 2326 newnode("table", 0, { cellpadding: "5px" }, 2327 [newnode("tbody", 0, 0, [this.tr])])); 2328 ox.UI.Container.prototype.addContent.apply(this, arguments); 2329 }; 2330 var combobox = new ox.UI.ComboBox(); 2331 updateAccounts(); 2332 function updateAccounts(cb) { 2333 OAuthAccounts.then(function(accounts) { 2334 var ids = jQuery.map(accounts, function(a) { 2335 return a.serviceId == def.options.type ? a.id : null; 2336 }); 2337 var names = jQuery.map(accounts, function(a) { 2338 return a.serviceId == def.options.type 2339 ? noI18n(a.displayName) : null; 2340 }); 2341 combobox.setEntries(ids, names); 2342 if (cb) cb(); 2343 }); 2344 } 2345 account.addWidget(combobox, ox.Configuration.Group.NoField); 2346 var button = new ox.UI.Button(_("New account...")); 2347 button.click = function() { 2348 function s(text) { return newtext(String(text)); } 2349 var input = newnode("input", { marginLeft: "0.5em" }, 2350 { type: "text" }); 2351 OAuthAccounts.then(function(accounts) { 2352 for (var i = 0; i < accounts.length; i++) { 2353 if (accounts[i].id == def.options.type) { 2354 input.value = accounts[i].displayName; 2355 } 2356 } 2357 }); 2358 var div = newnode("div", 0, 0, [ 2359 s(_("Account name")), input, 2360 newnode("div", { marginTop: "0.5em" }, 0, 2361 //#. %s is the product name (by default 'Open-Xchange Server') 2362 [s(format(_("After pressing OK, a new window will open where you can authorize %s to access your account data."), 2363 oxProductInfo.product_name))]) 2364 ]); 2365 newConfirm(String(_("Add a new OAuth account")), "", 2366 AlertPopup.OK + AlertPopup.CANCEL, ok, 0, 0, 0, 0, 0, 0, 0, 2367 0, div); 2368 setTimeout(function() { input.focus(); }, 0); 2369 function ok() { 2370 createOAuthAccount( 2371 { 2372 serviceId: def.options.type, 2373 displayName: input.value 2374 }, 2375 function(data) { 2376 updateAccounts(function() { 2377 combobox.set(data.id); 2378 }); 2379 }); 2380 } 2381 }; 2382 account.addWidget(button); 2383 return account; 2384 } 2385 }, 2386 /** 2387 * Overrides for controllers for individual widget types. 2388 * Each override is a function which takes the field name as parameter 2389 * and returns either an ox.UI.Controller or a string for the second 2390 * parameter to addWidget(). 2391 */ 2392 controllers: { 2393 password: function(name) { 2394 return { 2395 get: function(data, value) { if (value) data[name] = value; }, 2396 set: function(data) { return data[name]; } 2397 }; 2398 } 2399 } 2400 }); 2401 2402 /** 2403 * A tabbed container. Only one child container is visible. 2404 * Should be used as the only top-level child of an ox.Configuration.View. 2405 * All direct children must be instances of ox.Configuration.Tab. 2406 * @class A tabbed container 2407 * @augments ox.UI.Container 2408 */ 2409 ox.Configuration.Tabs = function() { ox.UI.Container.apply(this); }; 2410 2411 ox.Configuration.Tabs.prototype = extend(ox.UI.Container, 2412 /** @lends ox.Configuration.Tabs.prototype */ 2413 { 2414 addContent: function(node_id) { 2415 this.childWidth = this.parent.childWidth; 2416 this.headers = newnode("div", { 2417 position: "absolute", top: 0, left: 0, right: 0, height: "2.2em" 2418 }, { 2419 className: "cpbody-color tabPanelBackground " + 2420 "background-color-additional-content border-color-design" 2421 }); 2422 this.parent.addRow(this.headers, false); 2423 this.body = newnode("div", { 2424 position: "absolute", marginTop: "1px", top: "2.2em", left: 0, 2425 right: 0, bottom: 0 2426 }); 2427 this.parent.addRow(this.body, false); 2428 ox.UI.Container.prototype.addContent.apply(this, arguments); 2429 }, 2430 2431 remove: function() { 2432 this.body.parentNode.removeChild(this.body); 2433 this.headers.parentNode.removeChild(this.headers); 2434 }, 2435 2436 applyVisible: function() { 2437 this.headers.style.display = this.body.style.display = 2438 this.isVisible ? "" : "none"; 2439 for (var i = 0; i < this.children.length; i++) { 2440 var child = this.children[i]; 2441 child.setVisible(child.visible); 2442 } 2443 }, 2444 2445 addWidget: function(widget, name) { 2446 widget.setVisible(!this.children.length); 2447 if (!this.current) this.current = widget; 2448 ox.UI.Container.prototype.addWidget.apply(this, arguments); 2449 }, 2450 2451 /** @private */ 2452 addTab: function(header, body) { 2453 this.headers.appendChild(header); 2454 this.body.appendChild(body); 2455 } 2456 }); 2457 2458 /** 2459 * The only valid child for ox.Configuration.Tabs. 2460 * @class A container representing a tab. 2461 * @augments ox.UI.Container 2462 * @param {I18nString} title The title of the tab. 2463 */ 2464 ox.Configuration.Tab = function(title) { 2465 this.title = title; 2466 ox.UI.Container.call(this); 2467 }; 2468 2469 ox.Configuration.Tab.prototype = extend(ox.UI.Container, 2470 /** @lends ox.Configuration.Tab.prototype */ 2471 { 2472 addContent: function(node_id) { 2473 var Self = this; 2474 this.childWidth = this.parent.childWidth; 2475 this.header = newnode("div", 0, { 2476 className: "tabPanel" + (this.index ? "First" : "") + 2477 "Hi tabPanelHiColor background-color-content " + 2478 "font-style-lable border-color-content-default" 2479 }, this.title instanceof I18nString 2480 ? [newnode("span", 0, 0, [addTranslated(this.title)])] 2481 : this.title); 2482 jQuery(this.header).click(function() { Self.show(); }); 2483 this.node = this.table = newnode("div", 2484 { position: "absolute", left: 0, top: 0, right: 0, bottom: 0 }); 2485 this.parent.addTab(this.header, this.table); 2486 ox.UI.Container.prototype.addContent.apply(this, arguments); 2487 }, 2488 2489 remove: function() { 2490 this.header.parentNode.removeChild(this.header); 2491 this.table.parentNode.removeChild(this.table); 2492 }, 2493 2494 setVisible: function(visible) { 2495 if (visible && this.parent) { 2496 var old = this.parent.current; 2497 if (old && old != this) old.hide(); 2498 this.parent.current = this; 2499 } 2500 ox.UI.Container.prototype.setVisible.apply(this, arguments); 2501 this.visible = true; 2502 }, 2503 2504 applyVisible: function() { 2505 this.header.className = "tabPanel" + (this.index ? "First" : "") + 2506 (this.isVisible 2507 ? "Hi tabPanelHiColor background-color-content " + 2508 "font-style-lable border-color-content-default" 2509 : " tabPanelColors font-color-disabled " + 2510 "background-color-additional-content " + 2511 "border-color-design font-weight-default"); 2512 ox.UI.Container.prototype.applyVisible.call(this); 2513 }, 2514 2515 addRow: ox.Configuration.VSplit.prototype.addRow, 2516 2517 addCells: ox.Configuration.VSplit.prototype.addCells 2518 }); 2519