1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 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) 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 combo box. 26 * @constructor 27 * @class 28 * This class represents a combo box. 29 * 30 * @author Dave Comfort 31 * 32 * @param {hash} params a hash of parameters 33 * @param {DwtComposite} parent the parent widget 34 * @param {boolean} useLabel Set to true if the value should be shown in a DwtLabel. Defaults to false, showing it in a DwtInputField. 35 * @param {hash} inputParams params for the input (see {@link DwtInputField} or {@link DwtLabel}) 36 * @param {string} className the CSS class 37 * @param {constant} posStyle the positioning style (see {@link DwtControl}) 38 * @param {int} maxRows The number of maxRows needed in drop down(see {@link DwtMenu}) 39 * @param {constant} layout The layout of the drop down(see {@link DwtMenu}) 40 * @param {boolean} autoScroll Set to true if auto scroll to the input text is needed. Defaults to false. 41 * 42 * @extends DwtComposite 43 */ 44 DwtComboBox = function(params) { 45 if (arguments.length == 0) { return; } 46 params = Dwt.getParams(arguments, DwtComboBox.PARAMS); 47 params.className = params.className || "DwtComboBox"; 48 DwtComposite.call(this, params); 49 50 this.input = null; 51 this._menu = null; 52 this._button = null; 53 54 this._textToValue = {}; // Map of text strings to their values. 55 this._valueToText = {}; 56 this._valueToItem = {}; 57 this._size = 0; 58 59 this._hasMenuCallback = true; 60 this._menuItemListenerObj = new AjxListener(this, this._menuItemListener); 61 62 this._inputParams = params.inputParams; 63 this._useLabel = Boolean(params.useLabel); 64 this._maxRows = params.maxRows; 65 this._layout = params.layout; 66 this._autoScroll = params.autoScroll || false; 67 this._createHtml(); 68 }; 69 70 DwtComboBox.PARAMS = ["parent", "inputParams", "className", "posStyle", "dialog"]; 71 72 DwtComboBox.prototype = new DwtComposite; 73 DwtComboBox.prototype.constructor = DwtComboBox; 74 75 DwtComboBox.prototype.isDwtComboBox = true; 76 DwtComboBox.prototype.toString = function() { return "DwtComboBox"; }; 77 78 // 79 // Data 80 // 81 82 DwtComboBox.prototype.TEMPLATE = "dwt.Widgets#DwtComboBox"; 83 84 // 85 // Public methods 86 // 87 88 DwtComboBox.prototype.getTabGroupMember = function() { 89 return this._tabGroup; 90 }; 91 92 /** 93 * Adds the change listener. 94 * 95 * @param {AjxListener} listener the listener 96 */ 97 DwtComboBox.prototype.addChangeListener = function(listener) { 98 this.addListener(DwtEvent.ONCHANGE, listener); 99 }; 100 101 /** 102 * Removes the change listener. 103 * 104 * @param {AjxListener} listener the listener 105 */ 106 DwtComboBox.prototype.removeChangeListener = function(listener) { 107 this.removeListener(DwtEvent.ONCHANGE, listener); 108 }; 109 110 /** 111 * Adds an entry to the combo box list. 112 * 113 * @param {string} text the user-visible text for the entry 114 * @param {string} value the value for the entry 115 * @param {boolean} selected if <code>true</code>, the entry is selected 116 */ 117 DwtComboBox.prototype.add = 118 function(text, value, selected) { 119 this._textToValue[text] = value; 120 this._valueToText[value] = text; 121 if (!this._hasMenuCallback) { 122 var menu = this._button.getMenu(); 123 this._createMenuItem(menu, text); 124 } 125 if (selected) { 126 this.setText(text); 127 } 128 this._size++; 129 this._updateButton(); 130 }; 131 132 /** 133 * Removes the specified value from the list. 134 * 135 * @param {string} value the value 136 */ 137 DwtComboBox.prototype.remove = function(value) { 138 var item = this._valueToItem[value]; 139 if (item) { 140 this._button.getMenu().removeChild(item); 141 var text = this._valueToText[value]; 142 delete this._textToValue[text]; 143 delete this._valueToText[value]; 144 delete this._valueToItem[value]; 145 if (this.getText() == text) { 146 this.setText(""); 147 } 148 this._size--; 149 this._updateButton(); 150 } 151 }; 152 153 /** 154 * Removes all the items in the list. 155 * 156 */ 157 DwtComboBox.prototype.removeAll = function() { 158 this._button.setMenu(new AjxCallback(this, this._createMenu), true); 159 this._hasMenuCallback = true; 160 161 this._textToValue = {}; 162 this._valueToText = {}; 163 this._valueToItem = {}; 164 this._size = 0; 165 this._updateButton(); 166 }; 167 168 /** 169 * Gets the value of the currently selected entry. If the entry 170 * is one that was not added via the add method (that is, if it was 171 * typed in by the user) then <code>null</code> is returned. 172 * 173 * @return {string} the value 174 */ 175 DwtComboBox.prototype.getValue = 176 function() { 177 var text = this.getText(); 178 return this._textToValue[text]; 179 }; 180 181 /** 182 * Sets the value. 183 * 184 * @param {string} value the value 185 */ 186 DwtComboBox.prototype.setValue = function(value) { 187 var text = this._valueToText[value]; 188 this.setText(text || value); 189 }; 190 191 /** 192 * Gets the text of the currently selected entry. 193 * 194 * @return {string} the text 195 */ 196 DwtComboBox.prototype.getText = 197 function() { 198 return this._useLabel ? this.input.getText() : this.input.getValue(); 199 }; 200 201 /** 202 * Sets the selected text. 203 * 204 * @param {string} text the text 205 */ 206 DwtComboBox.prototype.setText = 207 function(text) { 208 if (this._useLabel) 209 this.input.setText(text); 210 else 211 this.input.setValue(text); 212 }; 213 214 DwtComboBox.prototype.setEnabled = 215 function(enabled) { 216 if (enabled != this._enabled) { 217 DwtComposite.prototype.setEnabled.call(this, enabled); 218 this.input.setEnabled(enabled); 219 this._button.setEnabled(enabled); 220 } 221 }; 222 223 DwtComboBox.prototype.focus = function() { 224 return this.input.focus(); 225 }; 226 227 DwtComboBox.prototype.popdown = function() { 228 if (this._menu) 229 this._menu.popdown(); 230 }; 231 232 // 233 // Protected methods 234 // 235 236 DwtComboBox.prototype._createMenu = 237 function() { 238 var params = {parent:this}; 239 if (this._maxRows) { 240 params.maxRows = this._maxRows; 241 } 242 if (this._layout) { 243 params.layout = this._layout; 244 } 245 var menu = this._menu = new DwtMenu(params); 246 for (var i in this._textToValue) { 247 var item = this._createMenuItem(menu, i); 248 var value = this._textToValue[i]; 249 this._valueToItem[value] = item; 250 } 251 this._hasMenuCallback = false; 252 return menu; 253 }; 254 255 DwtComboBox.prototype._createMenuItem = 256 function(menu, text) { 257 var item = new DwtMenuItem({parent:menu}); 258 item.setText(text); 259 item.addSelectionListener(this._menuItemListenerObj); 260 if (!this._menuWidth) { 261 this._menuWidth = this.getW() - 10; // 10 is some fudge factor that lines up the menu right. 262 } 263 item.getHtmlElement().style.minWidth = this._menuWidth; 264 return item; 265 }; 266 267 DwtComboBox.prototype._menuItemListener = 268 function(ev) { 269 var menuItem = ev.dwtObj; 270 var ovalue = this.getText(); 271 var nvalue = menuItem.getText(); 272 this.setText(nvalue); 273 this._menu.popdown(); 274 275 // notify our listeners 276 var event = DwtUiEvent.getEvent(ev); 277 event._args = { selectObj: this, newValue: nvalue, oldValue: ovalue }; 278 this.notifyListeners(DwtEvent.ONCHANGE, event); 279 280 if (!this._useLabel) { 281 var input = this.input.getInputElement(); 282 input.focus(); 283 input.select(); 284 } 285 }; 286 287 DwtComboBox.prototype._handleKeyDown = function(ev) { 288 var keycode = DwtKeyEvent.getCharCode(ev); 289 290 this.__ovalue = this.getText(); 291 292 return true; 293 }; 294 295 DwtComboBox.prototype._handleKeyUp = function(ev) { 296 // propagate event to DwtInputField 297 DwtInputField._keyUpHdlr(ev); 298 // notify our listeners 299 var event = DwtUiEvent.getEvent(ev); 300 var newValue = this.getText(); 301 event._args = { selectObj: this, newValue: newValue, oldValue: this.__ovalue }; 302 this.notifyListeners(DwtEvent.ONCHANGE, event); 303 if (this._menu && this._autoScroll && newValue != this.__ovalue) { 304 //if auto scroll is on then scroll to the index which starts with 305 //the value in input field 306 var index = 0; 307 for (var text in this._textToValue) { 308 if (text.indexOf(newValue) == 0) { 309 this._menu.scrollToIndex(index); 310 break; 311 } 312 index++; 313 } 314 } 315 return true; 316 }; 317 318 DwtComboBox.prototype._updateButton = 319 function() { 320 this._button.setVisible(this._size > 0); 321 }; 322 323 DwtComboBox.prototype._createHtml = function(templateId) { 324 var data = { id: this._htmlElId }; 325 this._createHtmlFromTemplate(templateId || this.TEMPLATE, data); 326 }; 327 328 DwtComboBox.prototype._createHtmlFromTemplate = function(templateId, data) { 329 DwtComposite.prototype._createHtmlFromTemplate.call(this, templateId, data); 330 331 var inputParams = this._inputParams || {}; 332 inputParams.parent = this; 333 inputParams.size = inputParams.size || 40; 334 delete this._inputParams; 335 336 this.input = (this._useLabel ? 337 new DwtLabel(inputParams) : new DwtInputField(inputParams)); 338 this.input.replaceElement(data.id + "_input"); 339 this.input.setHandler(DwtEvent.ONKEYDOWN, AjxCallback.simpleClosure(this._handleKeyDown, this)); 340 this.input.setHandler(DwtEvent.ONKEYUP, AjxCallback.simpleClosure(this._handleKeyUp, this)); 341 342 this._button = new DwtComboBoxButton({parent:this}); 343 this._button.setMenu(new AjxListener(this, this._createMenu), true); 344 this._button.replaceElement(data.id + "_button"); 345 this._updateButton(); 346 347 this._tabGroup = new DwtTabGroup(this._htmlElId); 348 this._tabGroup.addMember(this.input); 349 this._tabGroup.addMember(this._button); 350 }; 351 352 /** 353 * The input field inherits the id for accessibility purposes. 354 * 355 * @private 356 */ 357 DwtComboBox.prototype._replaceElementHook = 358 function(oel, nel, inheritClass, inheritStyle) { 359 DwtComposite.prototype._replaceElementHook.apply(this, arguments); 360 // set input settings 361 if (oel.size) { 362 var el = (this._useLabel ? 363 this.input.getHtmlElement() : 364 this.input.getInputElement()); 365 el.size = oel.size; 366 } 367 if (oel.title) { 368 this.input.setHint(oel.title); 369 } 370 }; 371 372 // 373 // Classes 374 // 375 376 /** 377 * DwtComboBoxButton: Stylizable button just for use in combo boxes. 378 * 379 * @param {hash} params a hash of parameters 380 * @param {DwtComposite} params.parent the parent widget 381 * @param {string} params.className the CSS class 382 * 383 * @extends DwtButton 384 * @private 385 */ 386 DwtComboBoxButton = function(params) { 387 params = Dwt.getParams(arguments, DwtComboBoxButton.PARAMS); 388 params.posStyle = Dwt.RELATIVE_STYLE; 389 DwtButton.call(this, params); 390 } 391 392 DwtComboBoxButton.prototype = new DwtButton; 393 DwtComboBoxButton.prototype.constructor = DwtComboBoxButton; 394 395 DwtComboBoxButton.prototype.toString = 396 function() { 397 return "DwtComboBoxButton"; 398 }; 399 400 DwtComboBoxButton.PARAMS = ["parent", "className"]; 401 402 // Data 403 404 DwtComboBoxButton.prototype.TEMPLATE = "dwt.Widgets#DwtComboBoxButton" 405 406