1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2006, 2007, 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) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Creates a control that allows the user to select items from a list, and 26 * places the selected items in another list. 27 * @constructor 28 * @class 29 * This class creates and manages a control that lets the user 30 * select items from a list. Two lists are maintained, one with items to select 31 * from, and one that contains the selected items. Between them are buttons 32 * to shuffle items back and forth between the two lists. 33 * <p> 34 * There are two types of buttons: one or more transfer buttons move items from 35 * the source list to the target list, and the remove button moves items from the 36 * target list to the source list. The client can specify its transfer buttons. 37 * If no specification is given, there will be a single transfer button called 38 * "Add".</p> 39 * <p> 40 * The parent must implement search(columnItem, ascending) if column sorting 41 * is supported. It should also create a subclass of {@link DwtChooser} which returns 42 * the appropriate source and target list views, themselves subclasses of 43 * {@link DwtChooserListView}. Those subclasses must implement _getHeaderList() and 44 * _createItemHtml(item).</p> 45 * <p> 46 * There are two different layout styles, horizontal (with the list views at the 47 * left and right) and vertical (with the list views at the top and bottom). There 48 * are two different selection styles, single and multiple, which control how many 49 * items may appear in the target list view. There are two different transfer modes: 50 * one where items are copied between lists, and one where they're moved.</p> 51 * 52 * @author Conrad Damon 53 * 54 * @param {hash} params a hash of parameters 55 * @param {DwtComposite} params.parent the containing widget 56 * @param {string} params.className the CSS class 57 * @param {string} params.slvClassName the CSS class for source list view 58 * @param {string} params.tlvClassName the CSS class for target list view 59 * @param {array} params.buttonInfo the id/label pairs for transfer buttons 60 * @param {DwtChooser.HORIZ_STYLE|DwtChooser.VERT_STYLE} params.layoutStyle the layout style (vertical or horizontal) 61 * @param {DwtChooser.SINGLE_SELECT|DwtChooser.MULTI_SELECT} params.selectStyle the multi-select (default) or single-select 62 * @param {constant} params.mode the items are moved or copied 63 * @param {boolean} params.noDuplicates if <code>true</code>, prevent duplicates in target list 64 * @param {number} params.singleHeight the height of list view for single select style 65 * @param {number} params.listSize the list width (if {@link DwtChooser.HORIZ_STYLE}) or height (if {@link DwtChooser.VERT_STYLE}) 66 * @param {boolean} params.sourceEmptyOk if <code>true</code>, do not show "No Results" in source list view 67 * @param {boolean} params.allButtons if <code>true</code>, offer "Add All" and "Remove All" buttons 68 * @param {boolean} params.hasTextField if <code>true</code>, create a text field for user input 69 * 70 * @extends DwtComposite 71 */ 72 DwtChooser = function(params) { 73 74 if (arguments.length == 0) return; 75 DwtComposite.call(this, params.parent, params.className); 76 77 this._slvClassName = params.slvClassName; 78 this._tlvClassName = params.tlvClassName; 79 this._layoutStyle = params.layoutStyle ? params.layoutStyle : DwtChooser.HORIZ_STYLE; 80 this._selectStyle = params.selectStyle ? params.selectStyle : DwtChooser.MULTI_SELECT; 81 this._mode = params.listStyle ? params.listStyle : DwtChooser.MODE_MOVE; 82 this._noDuplicates = (params.noDuplicates !== false); 83 this._singleHeight = params.singleHeight ? params.singleHeight : 45; // 45 = header row + row with icon 84 this._listSize = params.listSize; 85 this._sourceEmptyOk = params.sourceEmptyOk; 86 this._allButtons = params.allButtons; 87 this._hasTextField = params.hasTextField; 88 89 this._handleButtonInfo(params.buttonInfo); 90 this._mode = params.mode ? params.mode : 91 this._hasMultiButtons ? DwtChooser.MODE_COPY : DwtChooser.MODE_MOVE; 92 93 this._createHtml(); 94 this._initialize(); 95 96 var parentSz = params.parent.getSize(); 97 var listWidth = params.listWidth || parentSz.x; 98 var listHeight = params.listHeight || parentSz.y; 99 if (listWidth && listHeight) { 100 this.resize(listWidth, listHeight); 101 } 102 103 }; 104 105 DwtChooser.prototype = new DwtComposite; 106 DwtChooser.prototype.constructor = DwtChooser; 107 DwtChooser.prototype.isFocusable = true; 108 DwtChooser.prototype.role = 'listbox'; 109 110 // Consts 111 112 // layout style 113 /** 114 * Defines a "horizontal" layout style. 115 */ 116 DwtChooser.HORIZ_STYLE = 1; 117 /** 118 * Defines a "vertical" layout style. 119 */ 120 DwtChooser.VERT_STYLE = 2; 121 122 // number of items target list can hold 123 /** 124 * Defines a "single" select. 125 */ 126 DwtChooser.SINGLE_SELECT = 1; 127 /** 128 * Defines a "multi" select. 129 */ 130 DwtChooser.MULTI_SELECT = 2; 131 132 // what happens to source items during transfer 133 DwtChooser.MODE_COPY = 1; 134 DwtChooser.MODE_MOVE = 2; 135 136 DwtChooser.REMOVE_BTN_ID = "__remove__"; 137 DwtChooser.ADD_ALL_BTN_ID = "__addAll__"; 138 DwtChooser.REMOVE_ALL_BTN_ID = "__removeAll__"; 139 140 DwtChooser.prototype.toString = 141 function() { 142 return "DwtChooser"; 143 }; 144 145 /** 146 * Sets the given list view with the given list. Defaults to source view. 147 * 148 * @param {AjxVector|Array|Object|Hash} items a list of items or hash of lists 149 * @param {DwtChooserListView.SOURCE|DwtChooserListView.TARGET} view the view to set 150 * @param {boolean} clearOtherView if <code>true</code>, clear out other view 151 */ 152 DwtChooser.prototype.setItems = 153 function(items, view, clearOtherView) { 154 view = view ? view : DwtChooserListView.SOURCE; 155 this._reset(view); 156 this.addItems(items, view, true); 157 this._selectFirst(view); 158 if (clearOtherView) { 159 this._reset((view == DwtChooserListView.SOURCE) ? DwtChooserListView.TARGET : DwtChooserListView.SOURCE); 160 } 161 }; 162 163 /** 164 * Gets a copy of the items in the given list. If that's the target list and 165 * there are multiple transfer buttons, then a hash with a vector for each one 166 * is returned. Otherwise, a single vector is returned. Defaults to target view. 167 * 168 * @param {DwtChooserListView.SOURCE|DwtChooserListView.TARGET} view the view to set 169 * @return {AjxVector|array|Object|hash} the item(s) 170 */ 171 DwtChooser.prototype.getItems = 172 function(view) { 173 view = view ? view : DwtChooserListView.TARGET; 174 if (view == DwtChooserListView.SOURCE) { 175 return this.sourceListView.getList().clone(); 176 } else { 177 if (this._hasMultiButtons) { 178 var data = {}; 179 for (var i in this._data) { 180 data[i] = this._data[i].clone(); 181 } 182 return data; 183 } else { 184 return this._data[this._buttonInfo[0].id].clone(); 185 } 186 } 187 }; 188 189 /** 190 * Adds items to the given list view. 191 * 192 * @param {AjxVector|array|Object|hash} items a list of items or hash of lists 193 * @param {DwtChooserListView.SOURCE|DwtChooserListView.TARGET} view the view to set 194 * @param {boolean} skipNotify if <code>true</code>, do not notify listeners 195 * @param {string} id the button ID 196 */ 197 DwtChooser.prototype.addItems = 198 function(items, view, skipNotify, id) { 199 view = view ? view : DwtChooserListView.SOURCE; 200 var list = (items instanceof AjxVector) ? items.getArray() : (items instanceof Array) ? items : [items]; 201 if (view == DwtChooserListView.SOURCE) { 202 for (var i = 0; i < list.length; i++) { 203 this._addToSource(list[i], null, skipNotify); 204 } 205 } else { 206 var data; 207 if (this._selectStyle == DwtChooser.SINGLE_SELECT) { 208 this.targetListView._resetList(); 209 list = (list.length > 0) ? [list[0]] : list; 210 } 211 for (var i = 0; i < list.length; i++) { 212 this._addToTarget(list[i], id, skipNotify); 213 // if (this._selectStyle == DwtChooser.SINGLE_SELECT) { 214 // return; 215 // } 216 } 217 } 218 if (view == DwtChooserListView.SOURCE) { 219 var list = this.sourceListView.getList(); 220 this._sourceSize = list ? list.size() : 0; 221 } 222 }; 223 224 /** 225 * Removes items from the given list view. 226 * 227 * @param {AjxVector|array|Object|hash} list a list of items or hash of lists 228 * @param {DwtChooserListView.SOURCE|DwtChooserListView.TARGET} view the view to set 229 * @param {boolean} skipNotify if <code>true</code>, do not notify listeners 230 */ 231 DwtChooser.prototype.removeItems = 232 function(list, view, skipNotify) { 233 list = (list instanceof AjxVector) ? list.getArray() : (list instanceof Array) ? list : [list]; 234 for (var i = 0; i < list.length; i++) { 235 (view == DwtChooserListView.SOURCE) ? this._removeFromSource(list[i], skipNotify) : this._removeFromTarget(list[i], skipNotify); 236 } 237 }; 238 239 /** 240 * Moves or copies items from the source list to the target list, paying attention 241 * to current mode. 242 * 243 * @param {AjxVector|array|Object|hash} list a list of items or hash of lists 244 * @param {string} id the ID of the transfer button that was used 245 * @param {boolean} skipNotify if <code>true</code>, do not notify listeners 246 */ 247 DwtChooser.prototype.transfer = 248 function(list, id, skipNotify) { 249 id = id ? id : this._activeButtonId; 250 this._setActiveButton(id); 251 if (this._mode == DwtChooser.MODE_MOVE) { 252 if (this._selectStyle == DwtChooser.SINGLE_SELECT) { 253 var tlist = this.targetListView.getList(); 254 if (tlist && tlist.size()) { 255 this.remove(tlist, true); 256 } 257 } 258 this.removeItems(list, DwtChooserListView.SOURCE, true); 259 } 260 this.addItems(list, DwtChooserListView.TARGET, skipNotify); 261 this.sourceListView.deselectAll(); 262 }; 263 264 /** 265 * Removes items from target list, paying attention to current mode. Also handles button state. 266 * 267 * @param {AjxVector|array|Object|hash} list a list of items or hash of lists 268 * @param {boolean} skipNotify if <code>true</code>, do not notify listeners 269 */ 270 DwtChooser.prototype.remove = 271 function(list, skipNotify) { 272 list = (list instanceof AjxVector) ? list.getArray() : (list instanceof Array) ? list : [list]; 273 if (this._mode == DwtChooser.MODE_MOVE) { 274 for (var i = 0; i < list.length; i++) { 275 var index = this._getInsertionIndex(this.sourceListView, list[i]); 276 this.sourceListView.addItem(list[i], index, true); 277 } 278 this._sourceSize = list ? list.length : 0; 279 } 280 this.removeItems(list, DwtChooserListView.TARGET); 281 }; 282 283 /** 284 * Sets the select style to the given style. Performs a resize 285 * in order to adjust the layout, and changes the label on the transfer button if it's 286 * the default one. 287 * 288 * @param {DwtChooser.SINGLE_SELECT|DwtChooser.MULTI_SELECT} style the style single or multiple select 289 * @param {boolean} noResize if <code>true</code>, do not perform resize 290 */ 291 DwtChooser.prototype.setSelectStyle = 292 function(style, noResize) { 293 if (style == this._selectStyle) return; 294 295 this._selectStyle = style; 296 if (this._defLabel) { 297 var button = this._button[this._buttonInfo[0].id]; 298 button.setText((style == DwtChooser.SINGLE_SELECT) ? AjxMsg.select : AjxMsg.add); 299 } 300 if (!noResize) { 301 var curSz = this.getSize(); 302 this.resize(curSz.x, curSz.y); 303 } 304 305 // "Add All" and "Remove All" buttons only shown if MULTI_SELECT 306 if (this._allButtons) { 307 this._addAllButton.setVisible(style == DwtChooser.MULTI_SELECT); 308 this._removeAllButton.setVisible(style == DwtChooser.MULTI_SELECT); 309 this._enableButtons(); 310 } 311 312 // if we're going from multi to single, preserve only the first target item 313 if (style == DwtChooser.SINGLE_SELECT) { 314 var list = this.targetListView.getList(); 315 var a = list ? list.clone().getArray() : null; 316 if (a && a.length) { 317 this._reset(DwtChooserListView.TARGET); 318 this.addItems(a[0], DwtChooserListView.TARGET, true); 319 this.targetListView.deselectAll(); 320 if (a.length > 1 && this._mode == DwtChooser.MODE_MOVE) { 321 this.addItems(a.slice(1), DwtChooserListView.SOURCE, true); 322 } 323 this._enableButtons(); 324 } 325 } 326 327 this.sourceListView.setMultiSelect(style == DwtChooser.MULTI_SELECT); 328 this.targetListView.setMultiSelect(style == DwtChooser.MULTI_SELECT); 329 }; 330 331 /** 332 * Resets one or both list views, and the buttons. Defaults to resetting both list views. 333 * 334 * @param {DwtChooser.SINGLE_SELECT|DwtChooser.MULTI_SELECT} style the style single or multiple select 335 */ 336 DwtChooser.prototype.reset = 337 function(view) { 338 this._reset(view); 339 this._setActiveButton(this._buttonInfo[0].id); // make first button active by default 340 this._enableButtons(); 341 if (this._hasTextField) { 342 this._textField.setValue(""); 343 } 344 }; 345 346 /** 347 * Resets one or both list views. Defaults to resetting both list views. 348 * 349 * @param {DwtChooser.SINGLE_SELECT|DwtChooser.MULTI_SELECT} style the style single or multiple select 350 * 351 * @private 352 */ 353 DwtChooser.prototype._reset = 354 function(view) { 355 // clear out source list view and related data 356 if (!view || view == DwtChooserListView.SOURCE) { 357 this.sourceListView._resetList(); 358 } 359 360 // clear out target list view and related data 361 if (!view || view == DwtChooserListView.TARGET) { 362 this.targetListView._resetList(); 363 for (var i in this._data) { 364 this._data[i].removeAll(); 365 } 366 } 367 }; 368 369 /** 370 * Adds a state change listener. 371 * 372 * @param {AjxListener} listener a listener 373 */ 374 DwtChooser.prototype.addStateChangeListener = 375 function(listener) { 376 this.targetListView.addStateChangeListener(listener); 377 }; 378 379 /** 380 * Removes a state change listener. 381 * 382 * @param {AjxListener} listener a listener 383 */ 384 DwtChooser.prototype.removeStateChangeListener = 385 function(listener) { 386 this.targetListView.removeStateChangeListener(listener); 387 }; 388 389 /** 390 * Gets the source <code><divgt;</code> that contains the source list view. 391 * 392 * @return {Element} the element 393 */ 394 DwtChooser.prototype.getSourceListView = 395 function() { 396 return document.getElementById(this._sourceListViewDivId); 397 }; 398 399 /** 400 * Gets the source <code><div></code> that contains the buttons 401 * 402 * @return {Element} the element 403 */ 404 DwtChooser.prototype.getButtons = 405 function() { 406 return document.getElementById(this._buttonsDivId); 407 }; 408 409 /** 410 * Gets the source <code><div></code> that contains the target list view. 411 * 412 * @return {Element} the element 413 */ 414 DwtChooser.prototype.getTargetListView = 415 function() { 416 return document.getElementById(this._targetListViewDivId); 417 }; 418 419 /** 420 * Gets the text input field. 421 * 422 * @return {DwtInputField} the text input field 423 */ 424 DwtChooser.prototype.getTextField = 425 function() { 426 return this._textField; 427 }; 428 429 /** 430 * Creates the HTML framework, with placeholders for elements which are created later. 431 * 432 * @private 433 */ 434 DwtChooser.prototype._createHtml = 435 function() { 436 437 this._sourceListViewDivId = Dwt.getNextId(); 438 this._targetListViewDivId = Dwt.getNextId(); 439 this._buttonsDivId = Dwt.getNextId(); 440 this._removeButtonDivId = Dwt.getNextId(); 441 if (this._allButtons) { 442 this._addAllButtonDivId = Dwt.getNextId(); 443 this._removeAllButtonDivId = Dwt.getNextId(); 444 } 445 if (this._hasTextField) { 446 this._textFieldTdId = Dwt.getNextId(); 447 } 448 449 var html = []; 450 var idx = 0; 451 452 if (this._layoutStyle == DwtChooser.HORIZ_STYLE) { 453 // start new table for list views 454 html[idx++] = "<table>"; 455 html[idx++] = "<tr>"; 456 457 // source list 458 html[idx++] = "<td id='"; 459 html[idx++] = this._sourceListViewDivId; 460 html[idx++] = "'></td>"; 461 462 // transfer buttons 463 html[idx++] = "<td valign='middle' id='"; 464 html[idx++] = this._buttonsDivId; 465 html[idx++] = "'>"; 466 if (this._allButtons) { 467 html[idx++] = "<div id='"; 468 html[idx++] = this._addAllButtonDivId; 469 html[idx++] = "'></div><br>"; 470 } 471 for (var i = 0; i < this._buttonInfo.length; i++) { 472 var id = this._buttonInfo[i].id; 473 html[idx++] = "<div id='"; 474 html[idx++] = this._buttonDivId[id]; 475 html[idx++] = "'></div><br>"; 476 } 477 // remove button 478 html[idx++] = "<br><div id='"; 479 html[idx++] = this._removeButtonDivId; 480 html[idx++] = "'></div>"; 481 if (this._allButtons) { 482 html[idx++] = "<br><div id='"; 483 html[idx++] = this._removeAllButtonDivId; 484 html[idx++] = "'></div><br>"; 485 } 486 html[idx++] = "</td>"; 487 488 // target list 489 html[idx++] = "<td id='"; 490 html[idx++] = this._targetListViewDivId; 491 html[idx++] = "'></td>"; 492 493 html[idx++] = "</tr>"; 494 495 if (this._hasTextField) { 496 html[idx++] = "<tr><td>"; 497 html[idx++] = "<table width=100%><tr><td style='white-space:nowrap; width:1%'>"; 498 html[idx++] = AjxMsg.add; 499 html[idx++] = ":</td><td id='"; 500 html[idx++] = this._textFieldTdId; 501 html[idx++] = "'></td></tr></table>"; 502 html[idx++] = "</td><td> </td><td> </td></tr>"; 503 } 504 505 html[idx++] = "</table>"; 506 } else { 507 // source list 508 html[idx++] = "<div id='"; 509 html[idx++] = this._sourceListViewDivId; 510 html[idx++] = "'></div>"; 511 512 // transfer buttons 513 html[idx++] = "<div align='center' id='"; 514 html[idx++] = this._buttonsDivId; 515 html[idx++] = "'>"; 516 html[idx++] = "<table class='ZPropertySheet' cellspacing='6'><tr>"; 517 if (this._allButtons) { 518 html[idx++] = "<td id='"; 519 html[idx++] = this._addAllButtonDivId; 520 html[idx++] = "'></td>"; 521 } 522 for (var i = 0; i < this._buttonInfo.length; i++) { 523 var id = this._buttonInfo[i].id; 524 html[idx++] = "<td id='"; 525 html[idx++] = this._buttonDivId[id]; 526 html[idx++] = "'></td>"; 527 } 528 // remove button 529 html[idx++] = "<td id='"; 530 html[idx++] = this._removeButtonDivId; 531 html[idx++] = "'></td>"; 532 if (this._allButtons) { 533 html[idx++] = "<td id='"; 534 html[idx++] = this._removeAllButtonDivId; 535 html[idx++] = "'></td>"; 536 } 537 html[idx++] = "</tr></table></div>"; 538 539 // target list 540 html[idx++] = "<div id='"; 541 html[idx++] = this._targetListViewDivId; 542 html[idx++] = "'></div>"; 543 } 544 545 this.getHtmlElement().innerHTML = html.join(""); 546 }; 547 548 /* 549 * Takes button info and sets up various bits of internal data for later use. 550 */ 551 DwtChooser.prototype._handleButtonInfo = 552 function(buttonInfo) { 553 554 if (!buttonInfo) { 555 this._defLabel = (this._selectStyle == DwtChooser.SINGLE_SELECT) ? AjxMsg.select : AjxMsg.add; 556 buttonInfo = [ { label: this._defLabel } ]; 557 } 558 this._buttonInfo = buttonInfo; 559 560 // create IDs for button elements and their containers 561 this._buttonDivId = {}; 562 this._buttonId = {}; 563 if (this._buttonInfo.length == 1) { 564 if (!this._buttonInfo[0].id) { 565 this._buttonInfo[0].id = Dwt.getNextId("DwtChooserButtonInfo_"); 566 } 567 this._activeButtonId = this._buttonInfo[0].id; 568 } 569 for (var i = 0; i < this._buttonInfo.length; i++) { 570 var id = this._buttonInfo[i].id; 571 this._buttonDivId[id] = Dwt.getNextId("DwtChooserButtonDiv_"); 572 this._buttonId[id] = Dwt.getNextId("DwtChooserButton_"); 573 } 574 this._hasMultiButtons = (this._buttonInfo.length > 1); 575 }; 576 577 /** 578 * Creates and places elements into the DOM. 579 * 580 * @private 581 */ 582 DwtChooser.prototype._initialize = 583 function() { 584 585 // create and add transfer buttons 586 var buttonListener = new AjxListener(this, this._transferButtonListener); 587 this._button = {}; 588 this._buttonIndex = {}; 589 this._data = {}; 590 for (var i = 0; i < this._buttonInfo.length; i++) { 591 var id = this._buttonInfo[i].id; 592 this._button[id] = this._setupButton(id, this._buttonId[id], this._buttonDivId[id], this._buttonInfo[i].label); 593 this._button[id].addSelectionListener(buttonListener); 594 this._buttonIndex[id] = i; 595 this._data[id] = new AjxVector(); 596 } 597 598 // create and add source list view 599 this.sourceListView = this._createSourceListView(); 600 this._addListView(this.sourceListView, this._sourceListViewDivId); 601 this.sourceListView.addSelectionListener(new AjxListener(this, this._sourceListener)); 602 603 // create and add target list view 604 this.targetListView = this._createTargetListView(); 605 this._addListView(this.targetListView, this._targetListViewDivId); 606 this.targetListView.addSelectionListener(new AjxListener(this, this._targetListener)); 607 608 // create and add the remove button 609 this._removeButtonId = Dwt.getNextId("DwtChooserRemoveButton_"); 610 this._removeButton = this._setupButton(DwtChooser.REMOVE_BTN_ID, this._removeButtonId, this._removeButtonDivId, AjxMsg.remove); 611 this._removeButton.addSelectionListener(new AjxListener(this, this._removeButtonListener)); 612 613 if (this._allButtons) { 614 // create and add "Add All" and "Remove All" buttons 615 this._addAllButtonId = Dwt.getNextId(); 616 this._addAllButton = this._setupButton(DwtChooser.ADD_ALL_BTN_ID, this._addAllButtonId, this._addAllButtonDivId, AjxMsg.addAll); 617 this._addAllButton.addSelectionListener(new AjxListener(this, this._addAllButtonListener)); 618 this._removeAllButtonId = Dwt.getNextId(); 619 this._removeAllButton = this._setupButton(DwtChooser.REMOVE_ALL_BTN_ID, this._removeAllButtonId, this._removeAllButtonDivId, AjxMsg.removeAll); 620 this._removeAllButton.addSelectionListener(new AjxListener(this, this._removeAllButtonListener)); 621 if (this._selectStyle == DwtChooser.SINGLE_SELECT) { 622 this._addAllButton.setVisible(false); 623 this._removeAllButton.setVisible(false); 624 } 625 } 626 627 if (this._hasTextField) { 628 var params = {parent: this, type: DwtInputField.STRING}; 629 this._textField = new DwtInputField(params); 630 this._textField.reparentHtmlElement(this._textFieldTdId); 631 this._textField._chooser = this; 632 this._textField.setHandler(DwtEvent.ONKEYUP, DwtChooser._onKeyUp); 633 Dwt.setSize(this._textField.getInputElement(), "100%", Dwt.DEFAULT); 634 } 635 636 if (this._selectStyle == DwtChooser.SINGLE_SELECT) { 637 this.sourceListView.setMultiSelect(false); 638 this.targetListView.setMultiSelect(false); 639 } 640 }; 641 642 DwtChooser.prototype.getTabGroupMember = 643 function() { 644 var tg = new DwtTabGroup(this.toString()); 645 tg.addMember(this.sourceListView); 646 for (var i = 0; i < this._buttonInfo.length; i++) { 647 tg.addMember(this._button[this._buttonInfo[i].id]); 648 } 649 tg.addMember(this._removeButton); 650 if (this._addAllButton) { 651 tg.addMember(this._addAllButton); 652 tg.addMember(this._removeAllButton); 653 } 654 if (this._hasTextField) { 655 tg.addMember(this._textField); 656 } 657 tg.addMember(this.targetListView); 658 return tg; 659 }; 660 661 /** 662 * Returns a source list view object. 663 * 664 * @private 665 */ 666 DwtChooser.prototype._createSourceListView = 667 function() { 668 return new DwtChooserListView(this, DwtChooserListView.SOURCE, this._slvClassName); 669 }; 670 671 /** 672 * Returns a target list view object. 673 * 674 * @private 675 */ 676 DwtChooser.prototype._createTargetListView = 677 function() { 678 return new DwtChooserListView(this, DwtChooserListView.TARGET, this._tlvClassName); 679 }; 680 681 /** 682 * Adds a list view into the DOM and sets its size to fit in its container. 683 * 684 * @param listView [DwtChooserListView] the list view 685 * @param listViewDivId [string] ID of container DIV 686 * 687 * @private 688 */ 689 DwtChooser.prototype._addListView = 690 function(listView, listViewDivId) { 691 var listDiv = document.getElementById(listViewDivId); 692 listDiv.appendChild(listView.getHtmlElement()); 693 listView.setUI(null, true); // renders headers and empty list 694 listView._initialized = true; 695 }; 696 697 /** 698 * Sizes the list views based on the given available width and height. 699 * 700 * @param {number} width the width (in pixels) 701 * @param {number} height the height (in pixels) 702 */ 703 DwtChooser.prototype.resize = 704 function(width, height) { 705 if (!width || !height) return; 706 if (width == Dwt.DEFAULT && height == Dwt.DEFAULT) return; 707 708 var buttonsDiv = document.getElementById(this._buttonsDivId); 709 var btnSz = Dwt.getSize(buttonsDiv); 710 var w, sh, th; 711 if (this._layoutStyle == DwtChooser.HORIZ_STYLE) { 712 w = this._listSize ? this._listSize : (width == Dwt.DEFAULT) ? width : Math.floor(((width - btnSz.x) / 2) - 12); 713 sh = th = height; 714 } else { 715 w = width; 716 if (this._selectStyle == DwtChooser.SINGLE_SELECT) { 717 sh = this._listSize ? this._listSize : (height == Dwt.DEFAULT) ? height : height - btnSz.y - this._singleHeight - 30; 718 th = (height == Dwt.DEFAULT) ? height : height - btnSz.y - sh - 30; 719 } else { 720 sh = th = this._listSize ? this._listSize : (height == Dwt.DEFAULT) ? height : Math.floor(((height - btnSz.y) / 2) - 12); 721 } 722 } 723 this.sourceListView.setSize((w == Dwt.DEFAULT) ? w : w+2, sh); 724 this.targetListView.setSize((w == Dwt.DEFAULT) ? w : w+2, th); 725 }; 726 727 /** 728 * Creates a transfer or remove button. 729 * 730 * @param {string} id the button ID 731 * @param {string} buttonId the ID of button element 732 * @param {string} buttonDivId the ID of DIV that contains button 733 * @param {string} label the button text 734 * 735 * @private 736 */ 737 DwtChooser.prototype._setupButton = 738 function(id, buttonId, buttonDivId, label) { 739 var button = new DwtButton({parent:this, id:buttonId}); 740 button.setText(label); 741 button.id = buttonId; 742 button._buttonId = id; 743 744 var buttonDiv = document.getElementById(buttonDivId); 745 buttonDiv.appendChild(button.getHtmlElement()); 746 747 return button; 748 }; 749 750 // Listeners 751 752 /** 753 * Single-click selects an item, double-click adds selected items to target list. 754 * 755 * @param {DwtEvent} ev the click event 756 * 757 * @private 758 */ 759 DwtChooser.prototype._sourceListener = 760 function(ev) { 761 if (ev.detail == DwtListView.ITEM_DBL_CLICKED) { 762 // double-click performs transfer 763 this.transfer(this.sourceListView.getSelection(), this._activeButtonId); 764 this.sourceListView.deselectAll(); 765 } else if (this._activeButtonId == DwtChooser.REMOVE_BTN_ID) { 766 // single-click activates appropriate transfer button if needed 767 var id = this._lastActiveTransferButtonId ? this._lastActiveTransferButtonId : this._buttonInfo[0].id; 768 this._setActiveButton(id); 769 } 770 this.targetListView.deselectAll(); 771 this._enableButtons(); 772 }; 773 774 /** 775 * Single-click selects an item, double-click removes it from the target list. 776 * 777 * @param {DwtEvent} ev the click event 778 * 779 * @private 780 */ 781 DwtChooser.prototype._targetListener = 782 function(ev) { 783 if (ev.detail == DwtListView.ITEM_DBL_CLICKED) { 784 this.remove(this.targetListView.getSelection()); 785 } else { 786 this._setActiveButton(DwtChooser.REMOVE_BTN_ID); 787 this.sourceListView.deselectAll(); 788 this._enableButtons(); 789 } 790 }; 791 792 /** 793 * Clicking a transfer button moves selected items to the target list. 794 * 795 * @param {DwtEvent} ev the click event 796 * 797 * @private 798 */ 799 DwtChooser.prototype._transferButtonListener = 800 function(ev) { 801 var button = DwtControl.getTargetControl(ev); 802 var id = button._buttonId; 803 var sel = this.sourceListView.getSelection(); 804 if (sel && sel.length) { 805 this.transfer(sel, id); 806 this._enableButtons(); 807 } else { 808 var email = this._getEmailFromText(); 809 if (email) { 810 this.transfer([email], id); 811 } else { 812 this._setActiveButton(id); 813 } 814 } 815 }; 816 817 /** 818 * Clicking the remove button removes selected items from the target list. 819 * 820 * @param {DwtEvent} ev the click event 821 * 822 * @private 823 */ 824 DwtChooser.prototype._removeButtonListener = 825 function(ev) { 826 this.remove(this.targetListView.getSelection()); 827 var list = this.targetListView.getList(); 828 if (list && list.size()) { 829 this._selectFirst(DwtChooserListView.TARGET); 830 } else { 831 this._enableButtons(); 832 } 833 }; 834 835 /** 836 * Populates the target list with all items. 837 * 838 * @param {DwtEvent} ev the click event 839 * 840 * @private 841 */ 842 DwtChooser.prototype._addAllButtonListener = 843 function(ev) { 844 this.transfer(this.sourceListView.getList().clone()); 845 this._selectFirst(DwtChooserListView.TARGET); 846 }; 847 848 /** 849 * Clears the target list. 850 * 851 * @param {DwtEvent} ev the click event 852 * 853 * @private 854 */ 855 DwtChooser.prototype._removeAllButtonListener = 856 function(ev) { 857 this.remove(this.targetListView.getList().clone()); 858 this._selectFirst(DwtChooserListView.SOURCE); 859 }; 860 861 862 863 // Miscellaneous methods 864 865 /** 866 * Enable/disable buttons as appropriate. 867 * 868 * @private 869 */ 870 DwtChooser.prototype._enableButtons = 871 function(sForce, tForce) { 872 var sourceList = this.sourceListView.getList(); 873 var targetList = this.targetListView.getList(); 874 var sourceEnabled = (sForce || (this.sourceListView.getSelectionCount() > 0)); 875 for (var i = 0; i < this._buttonInfo.length; i++) { 876 var id = this._buttonInfo[i].id; 877 this._button[id].setEnabled(sourceEnabled); 878 } 879 var targetEnabled = (tForce || (this.targetListView.getSelectionCount() > 0)); 880 this._removeButton.setEnabled(targetEnabled); 881 882 if (this._allButtons && (this._selectStyle == DwtChooser.MULTI_SELECT)) { 883 var sourceSize = sourceList ? sourceList.size() : 0; 884 var targetSize = targetList ? targetList.size() : 0; 885 this._addAllButton.setEnabled(sourceSize > 0); 886 this._removeAllButton.setEnabled(targetSize > 0); 887 } 888 }; 889 890 /** 891 * Selects the first item in the given list view. 892 * 893 * @param {constant} view the source or target 894 * 895 * @private 896 */ 897 DwtChooser.prototype._selectFirst = 898 function(view, index) { 899 var listView = (view == DwtChooserListView.SOURCE) ? this.sourceListView : this.targetListView; 900 var list = listView.getList(); 901 if (list && list.size() > 0) { 902 listView.setSelection(list.get(0)); 903 } 904 }; 905 906 /** 907 * Makes a button "active" (the default for double-clicks). Done by 908 * manipulating the style class. The active/non-active class is set as the 909 * "_origClassName" so that activation/triggering still work. This only 910 * applies if there are multiple transfer buttons. 911 * 912 * @param {string} id the ID of button to make active 913 * 914 * @private 915 */ 916 DwtChooser.prototype._setActiveButton = 917 function(id) { 918 if (!this._hasMultiButtons) { 919 return; 920 } 921 if (id != this._activeButtonId) { 922 var buttonId = (this._activeButtonId == DwtChooser.REMOVE_BTN_ID) ? this._removeButtonId : this._buttonId[this._activeButtonId]; 923 if (buttonId) { 924 var oldButton = DwtControl.findControl(document.getElementById(buttonId)); 925 if (oldButton) { 926 oldButton.setDisplayState(DwtControl.NORMAL); 927 } 928 } 929 buttonId = (id == DwtChooser.REMOVE_BTN_ID) ? this._removeButtonId : this._buttonId[id]; 930 var button = DwtControl.findControl(document.getElementById(buttonId)); 931 if (button) { 932 button.setDisplayState(DwtControl.DEFAULT); 933 } 934 this._activeButtonId = id; 935 if (id != DwtChooser.REMOVE_BTN_ID) { 936 this._lastActiveTransferButtonId = id; 937 } 938 } 939 }; 940 941 /** 942 * Returns true if the list contains the item. Default implementation is identity. 943 * 944 * @param {Object} item the item 945 * @param {AjxVector} list the list to check against 946 * 947 * @private 948 */ 949 DwtChooser.prototype._isDuplicate = 950 function(item, list) { 951 return list.contains(item); 952 }; 953 954 /** 955 * Adds an item to the end of the source list. 956 * 957 * @param {Object} item the item to add 958 * @param {boolean} skipNotify if <code>true</code>, don't notify listeners 959 * 960 * @private 961 */ 962 DwtChooser.prototype._addToSource = 963 function(item, index, skipNotify) { 964 if (!item) return; 965 if (!item._chooserIndex) { 966 var list = this.sourceListView.getList(); 967 item._chooserIndex = list ? list.size() + 1 : 1; 968 } 969 this.sourceListView.addItem(item, index, skipNotify); 970 }; 971 972 /** 973 * Adds an item to the target list. If there are multiple transfer buttons, it keeps 974 * the items grouped depending on which button was used to move them. 975 * 976 * @param {Object} item the item to add 977 * @param {string} id the ID of the transfer button that was used 978 * @param {boolean} skipNotify if <code>true</code>, don't notify listeners 979 * 980 * @private 981 */ 982 DwtChooser.prototype._addToTarget = 983 function(item, id, skipNotify) { 984 if (!item) return; 985 id = id ? id : this._activeButtonId; 986 if (this._noDuplicates && this._data[id] && this._isDuplicate(item, this._data[id])) { 987 return; 988 } 989 990 // item is being added to target list with multiple transfer buttons, 991 // so we need to clone it on second and subsequent transfers 992 var list = this.targetListView.getList(); 993 if (list && list.contains(item) && item.clone) { 994 var newItem = item.clone(); 995 newItem.id = Dwt.getNextId(); 996 item = newItem; 997 } 998 999 var idx = null; 1000 if (this._hasMultiButtons) { 1001 // get a list of all the items in order 1002 list = []; 1003 for (var i = 0; i < this._buttonInfo.length; i++) { 1004 list = list.concat(this._data[this._buttonInfo[i].id].getArray()); 1005 } 1006 // find the first item with a higher button index 1007 var buttonIdx = this._buttonIndex[id]; 1008 for (idx = 0; idx < list.length; idx++) { 1009 var testButtonIdx = this._buttonIndex[list[idx]._buttonId]; 1010 if (testButtonIdx > buttonIdx) { 1011 break; 1012 } 1013 } 1014 } 1015 1016 item._buttonId = id; 1017 item.id = item.id || Dwt.getNextId(); 1018 this._data[id].add(item); 1019 this.targetListView.addItem(item, idx, skipNotify); 1020 }; 1021 1022 /** 1023 * Removes an item from the source list. 1024 * 1025 * @param {Object} item the item to remove 1026 * @param {boolean} skipNotify if <code>true</code>, don't notify listeners 1027 * 1028 * @private 1029 */ 1030 DwtChooser.prototype._removeFromSource = 1031 function(item, skipNotify) { 1032 if (!item) return; 1033 var list = this.sourceListView.getList(); 1034 if (!list) return; 1035 if (!list.contains(item)) return; 1036 1037 this.sourceListView.removeItem(item, skipNotify); 1038 }; 1039 1040 /** 1041 * Removes an item from the target list. 1042 * 1043 * @param {Object} item the item to remove 1044 * @param {boolean} skipNotify if <code>true</code>, don't notify listeners 1045 * 1046 * @private 1047 */ 1048 DwtChooser.prototype._removeFromTarget = 1049 function(item, skipNotify) { 1050 if (!item) return; 1051 var list = this.targetListView.getList(); 1052 if (!list) return; 1053 if (!list.contains(item)) return; 1054 1055 this._data[item._buttonId].remove(item); 1056 this.targetListView.removeItem(item, skipNotify); 1057 }; 1058 1059 DwtChooser.prototype._getInsertionIndex = 1060 function(view, item) { 1061 var list = view.getList(); 1062 if (!list) return null; 1063 var a = list.getArray(); 1064 for (var i = 0; i < a.length; i++) { 1065 if (item._chooserIndex && a[i]._chooserIndex && (a[i]._chooserIndex >= item._chooserIndex)) { 1066 return i; 1067 } 1068 } 1069 return null; 1070 }; 1071 1072 DwtChooser.prototype._getEmailFromText = 1073 function() { 1074 if (this._hasTextField) { 1075 var text = this._textField.getValue(); 1076 var email = AjxEmailAddress.parse(text); 1077 if (email) { 1078 email.id = Dwt.getNextId(); 1079 return email; 1080 } 1081 } 1082 }; 1083 1084 DwtChooser._onKeyUp = 1085 function(ev) { 1086 var el = DwtUiEvent.getTarget(ev); 1087 var obj = DwtControl.findControl(el); // DwtInputField 1088 var chooser = obj._chooser; 1089 var key = DwtKeyEvent.getCharCode(ev); 1090 if (DwtKeyEvent.IS_RETURN[key]) { 1091 var email = chooser._getEmailFromText(); 1092 if (email) { 1093 chooser.transfer([email], chooser._activeButtonId); 1094 el.value = ""; 1095 } 1096 } 1097 chooser._enableButtons(el.value.length); 1098 }; 1099 1100 /** 1101 * Creates a chooser list view. 1102 * @constructor 1103 * @class 1104 * This base class represents a list view which contains items that can be transferred from it 1105 * (source) or to it (target). Subclasses should implement _getHeaderList(), 1106 * _sortColumn(), and _createItemHtml(). 1107 * 1108 * @param {hash} params a hash of parameters 1109 * @param {DwtComposite} params.parent the containing widget 1110 * @param {constant} params.type the source or target 1111 * @param {string} params.className the CSS class 1112 * @param {constant} params.view the context for use in creating IDs 1113 * 1114 * @extends DwtListView 1115 */ 1116 DwtChooserListView = function(params) { 1117 1118 if (arguments.length == 0) return; 1119 params = Dwt.getParams(arguments, DwtChooserListView.PARAMS); 1120 params.className = params.className || "DwtChooserListView"; 1121 params.headerList = this._getHeaderList(parent); 1122 DwtListView.call(this, params); 1123 1124 this.type = params.type; 1125 this._chooserParent = params.parent.parent; 1126 1127 // create a drag source so that dragging a column header will trigger mouse capture 1128 this._dragSrc = new DwtDragSource(Dwt.DND_DROP_MOVE); 1129 this.setDragSource(this._dragSrc); 1130 }; 1131 1132 DwtChooserListView.PARAMS = ["parent", "type", "className", "view"]; 1133 1134 /** 1135 * Defines the "source" list view type. 1136 */ 1137 DwtChooserListView.SOURCE = 1; 1138 /** 1139 * Defines the "target" list view type. 1140 */ 1141 DwtChooserListView.TARGET = 2; 1142 1143 DwtChooserListView.prototype = new DwtListView; 1144 DwtChooserListView.prototype.constructor = DwtChooserListView; 1145 1146 DwtChooserListView.prototype._getHeaderList = function() {}; 1147 1148 DwtChooserListView.prototype.toString = 1149 function() { 1150 return "DwtChooserListView"; 1151 }; 1152 1153 /* 1154 * Override to handle empty results set. Always omit the "No Results" message if 1155 * this is a target list view, or if we've been told to ignore it in the source view. 1156 */ 1157 DwtChooserListView.prototype.setUI = 1158 function(defaultColumnSort, noResultsOk) { 1159 noResultsOk = noResultsOk ? noResultsOk : ((this.type == DwtChooserListView.TARGET) || 1160 this.parent._sourceEmptyOk); 1161 DwtListView.prototype.setUI.call(this, defaultColumnSort, noResultsOk); 1162 }; 1163 1164 /** 1165 * DwtListView override to ignore right-clicks in list view. 1166 * 1167 * @param {Element} clickedEl the element that was clicked 1168 * @param {DwtEvent} ev the click event 1169 * 1170 * @private 1171 */ 1172 DwtChooserListView.prototype._itemClicked = 1173 function(clickedEl, ev) { 1174 // Ignore right-clicks, we don't support action menus 1175 if (!ev.shiftKey && !ev.ctrlKey && ev.button == DwtMouseEvent.RIGHT) { 1176 return; 1177 } else { 1178 DwtListView.prototype._itemClicked.call(this, clickedEl, ev); 1179 } 1180 }; 1181 1182 /** 1183 * Called when a column header has been clicked. 1184 * 1185 * @param {string} columnItem the ID for column that was clicked 1186 * @param {boolean} ascending if <code>true</code>, sort in ascending order 1187 * 1188 * @private 1189 */ 1190 DwtChooserListView.prototype._sortColumn = 1191 function(columnItem, ascending) { 1192 this._chooserParent.search(columnItem, ascending); 1193 }; 1194 1195 DwtChooserListView.prototype._getHeaderSashLocation = 1196 function() { 1197 1198 var el = this.getHtmlElement(); 1199 if (Dwt.getPosition(el) == Dwt.ABSOLUTE_STYLE) { 1200 return DwtListView.prototype._getHeaderSashLocation.call(this); 1201 } 1202 1203 var thisLoc = Dwt.toWindow(el, 0, 0); 1204 var contLoc = Dwt.toWindow(this._chooserParent.getHtmlElement(), 0, 0); 1205 if (!this._tmpPoint) { 1206 this._tmpPoint = new DwtPoint(); 1207 } 1208 this._tmpPoint.x = thisLoc.x - contLoc.x; 1209 this._tmpPoint.y = thisLoc.y - contLoc.y; 1210 1211 return this._tmpPoint; 1212 }; 1213