1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * XFormItem class: "dynselect" 26 * @constructor DynSelect_XFormItem 27 * @class DynSelect_XFormItem 28 * A select box with asynchronous autocomplete capability 29 * 30 * 31 * @author Greg Solovyev 32 * 33 * @private 34 * 35 */ 36 DynSelect_XFormItem = function() {} 37 XFormItemFactory.createItemType("_DYNSELECT_", "dynselect", DynSelect_XFormItem, OSelect1_XFormItem); 38 39 // By Default auto-complete shall be disabled. 40 DynSelect_XFormItem.prototype.autoCompleteEnabled = false; 41 42 DynSelect_XFormItem.prototype.dataFetcherClass = null; 43 DynSelect_XFormItem.prototype.dataFetcherMethod = null; 44 DynSelect_XFormItem.prototype.dataFetcherObject = null; 45 DynSelect_XFormItem.prototype.dataFetcherTypes = null; 46 DynSelect_XFormItem.prototype.dataFetcherAttrs = null; 47 DynSelect_XFormItem.prototype.dataFetcherDomain = null; 48 DynSelect_XFormItem.prototype.entryKeyMethod = null; 49 DynSelect_XFormItem.prototype.bmolsnr = true; 50 DynSelect_XFormItem.prototype.emptyText = ""; 51 DynSelect_XFormItem.prototype.cssClass = "dynselect"; 52 DynSelect_XFormItem.prototype.edited = false; 53 DynSelect_XFormItem.prototype.focusable = true; 54 55 // Make sure there's enough delay between keystroke pauses 56 // TODO This is just a simple fix for now. A more complete and comprehensive fix is needed 57 // OLD --- DynSelect_XFormItem.LOAD_PAUSE = AjxEnv.isIE ? 500 : 250; // delay between chunks 58 DynSelect_XFormItem.LOAD_PAUSE = 750; // delay between chunks 59 60 DynSelect_XFormItem.prototype.initFormItem = function () { 61 // if we're dealing with an XFormChoices object... 62 63 var choices = this.getChoices(); 64 if (choices instanceof Array) { 65 choices = new XFormChoices(choices,XFormChoices.SIMPLE_LIST); 66 } 67 68 if (!choices) { 69 choices = new XFormChoices([], XFormChoices.OBJECT_LIST, "name", "name"); 70 } 71 72 this.setChoices(choices); 73 74 // ...set up to receive notification when its choices change 75 var listener = new AjxListener(this, this.choicesChangeLsnr); 76 77 this.choices.addListener(DwtEvent.XFORMS_CHOICES_CHANGED, listener); 78 79 this.dataFetcherClass = this.getInheritedProperty("dataFetcherClass"); 80 this.dataFetcherMethod = this.getInheritedProperty("dataFetcherMethod"); 81 this.dataFetcherTypes = this.getInheritedProperty("dataFetcherTypes"); 82 this.dataFetcherAttrs = this.getInheritedProperty("dataFetcherAttrs"); 83 this.dataFetcherDomain = this.getInheritedProperty("dataFetcherDomain"); 84 this.autoCompleteEnabled = this.getInheritedProperty("autoCompleteEnabled"); 85 this.dataFetcherObject = null; 86 if(!this.dataFetcherMethod) { 87 this.dataFetcherMethod = DynSelect_XFormItem.fetchDataDefault; 88 this.dataFetcherObject = this; 89 } 90 91 var currentTabId = XFormItem.getParentTabGroupId(this); 92 this.getForm().indexItem(this, this.getId() + "_display"); 93 94 if(currentTabId) { 95 var tabGroupItem = this.getForm().getItemById(currentTabId); 96 if(tabGroupItem) { 97 tabGroupItem.tabIdOrder.push(this.getId()+"_display"); 98 } 99 } 100 } 101 102 DynSelect_XFormItem.prototype.changeChoicesCallback = function (data, more, total) { 103 var choices = this.choices ? this.choices : this.getChoices(); 104 105 if(!choices) { 106 return; 107 } 108 109 choices.setChoices(data); 110 choices.dirtyChoices(); 111 112 if(AjxUtil.isEmpty(data)) { 113 this.hideMenu(); 114 } else { 115 if(!this.menuUp) { 116 this.showMenu(); 117 } 118 } 119 } 120 121 DynSelect_XFormItem.fetchDataDefault = function (callArgs) { 122 var callback = callArgs["callback"]; 123 callback.run(this.choices.getChoiceObject(), false, null); 124 } 125 126 DynSelect_XFormItem.prototype.onKeyUp = function(value, event) { 127 var lastTypeTime = new Date().getTime(); 128 this._lastTypeTime = lastTypeTime; 129 130 if (window.console && window.console.log) { 131 window.console.log("onKeyUp " + value + " @ "+lastTypeTime); 132 } 133 134 this.edited = true; 135 this.hideNote(); 136 137 if (event.keyCode == XFG.ARROW_UP) { 138 if(!this.menuUp) { 139 this.showMenu(); 140 } 141 this.hilitePreviousChoice(event); 142 this.isSelecting = true; 143 return; 144 } 145 146 if (event.keyCode == XFG.ARROW_DOWN) { 147 if(!this.menuUp) { 148 this.showMenu(); 149 } 150 151 this.hiliteNextChoice(event); 152 this.isSelecting = true; 153 return; 154 } 155 156 if (this.isSelecting && 157 this.menuUp && 158 event.keyCode == DwtKeyEvent.KEY_ENTER && 159 this.__currentHiliteItem != null && 160 this.__currentHiliteItem != undefined) { 161 162 var value = this.getNormalizedValues()[this.__currentHiliteItem]; 163 164 if (value != null && value != undefined) { 165 this.setValue(value, true, event); 166 this.hideMenu(); 167 this.processEntryKey(); 168 return; 169 } 170 } else if (this.menuUp && event.keyCode==DwtKeyEvent.KEY_ENTER) { 171 this.hideMenu(); 172 this.processEntryKey(); 173 return; 174 } 175 176 this.isSelecting = false; 177 178 var method = this.getKeyUpMethod(); 179 if (method) { 180 method.call(this, value, event); 181 } else { 182 var key = DwtKeyEvent.getCharCode(event); 183 184 // don't fire off another if we've already set one up unless this is an ENTER key 185 if (!AjxUtil.isEmpty(this.keyPressDelayHdlr)) { 186 AjxTimedAction.cancelAction(this.keyPressDelayHdlr); 187 this.keyPressDelayHdlr = null; 188 } 189 190 var form = this.getForm(); 191 192 var evt = new DwtKeyEvent(); 193 evt.setFromDhtmlEvent(event); 194 195 if (key == DwtKeyEvent.KEY_TAB) { 196 DwtUiEvent.setBehaviour(event, true, false); 197 return false; 198 } else if(!(event.keyCode==XFG.ARROW_RIGHT || event.keyCode==XFG.ARROW_LEFT || event.keyCode == DwtKeyEvent.KEY_ESCAPE)) { 199 var action = new AjxTimedAction(this, this.handleKeyPressDelay, [evt, value,lastTypeTime]); 200 this.keyPressDelayHdlr = AjxTimedAction.scheduleAction(action, DynSelect_XFormItem.LOAD_PAUSE); 201 } 202 } 203 } 204 205 DynSelect_XFormItem.prototype.onKeyDown = function (value, event) { 206 var key = DwtKeyEvent.getCharCode(event); 207 if (key == DwtKeyEvent.KEY_ENTER) { 208 DwtUiEvent.setBehaviour(event, true, true); // keyup handle will see enter key 209 return false; 210 } else if (key == DwtKeyEvent.KEY_TAB) { 211 DwtUiEvent.setBehaviour(event, true, false); 212 if (this.menuUp) { 213 this.hideMenu(); 214 } 215 216 var currentTabId = XFormItem.getParentTabGroupId(this); 217 if(event.shiftKey) { 218 this.getForm().focusPrevious(this.getId()+"_display" , currentTabId); 219 } else { 220 this.getForm().focusNext(this.getId()+"_display", currentTabId); 221 } 222 223 return true; 224 } 225 226 return true; 227 } 228 229 DynSelect_XFormItem.prototype.resetChoices = function () { 230 var choices = this.getChoices(); 231 choices.setChoices([]); 232 choices.dirtyChoices(); 233 234 if (!this.dataFetcherObject && 235 this.dataFetcherClass != null && 236 this.dataFetcherMethod != null) { 237 238 this.dataFetcherObject = new this.dataFetcherClass(this.getForm().getController()); 239 240 } else if (this.getInheritedProperty("dataFetcherInstance")) { 241 this.dataFetcherObject = this.getInstance(); 242 } 243 } 244 245 DynSelect_XFormItem.prototype.handleKeyPressDelay = function (event, value, lastTypeTime) { 246 247 if (event.keyCode == DwtKeyEvent.KEY_ENTER) { 248 this.processEntryKey(); 249 return; 250 } 251 252 if (this.autoCompleteEnabled) { 253 254 var currTime = new Date().getTime(); 255 256 if (window.console && window.console.log) { 257 window.console.log("handleKeyPressDelay " + value + " @ " + currTime); 258 } 259 260 this.keyPressDelayHdlr = null; 261 262 var val = this.preProcessInput(value); 263 264 if (lastTypeTime == this._lastTypeTime) { 265 this.getForm().itemChanged(this, val, event); 266 } else { 267 268 if (window.console && window.console.log) { 269 window.console.log("typing faster than retreiving data"); 270 } 271 272 return; 273 } 274 275 if (!this.dataFetcherObject && 276 this.dataFetcherClass != null && 277 this.dataFetcherMethod != null) { 278 279 this.dataFetcherObject = new this.dataFetcherClass(this.getForm().getController()); 280 281 } else if (this.getInheritedProperty("dataFetcherInstance")) { 282 this.dataFetcherObject = this.getInstance(); 283 } 284 285 if(!this.dataFetcherObject) { 286 return; 287 } 288 289 var callback = new AjxCallback(this, this.changeChoicesCallback); 290 291 var searchByProcessedValue = this.getInheritedProperty("searchByProcessedValue"); 292 293 var callArgs = { 294 event: event, 295 callback: callback, 296 extraLdapQuery: null, 297 form: this.getForm(), 298 types: (this.dataFetcherTypes ? this.dataFetcherTypes : null), 299 attrs: (this.dataFetcherAttrs ? this.dataFetcherAttrs : null), 300 domain: (this.dataFetcherDomain ? this.dataFetcherDomain : null) 301 }; 302 303 if (searchByProcessedValue && !AjxUtil.isEmpty(val)) { 304 callArgs["value"] = val; 305 this.dataFetcherMethod.call(this.dataFetcherObject, callArgs); 306 } else if (!AjxUtil.isEmpty(value)) { 307 callArgs["value"] = value; 308 this.dataFetcherMethod.call(this.dataFetcherObject, callArgs); 309 } 310 311 } 312 313 } 314 315 DynSelect_XFormItem.prototype.outputHTML = function (HTMLoutput) { 316 var id = this.getId(); 317 var ref = this.getFormGlobalRef() + ".getItemById('"+ id + "')"; 318 319 var inputHtml; 320 321 var inputSize = this.getInheritedProperty("inputSize"); 322 var inputWidth = this.getInheritedProperty("inputWidth"); 323 324 var keyPressEv = " onkeypress"; 325 if(!AjxEnv.isFirefox) { 326 keyPressEv = " onkeydown"; 327 } 328 329 var inputWidthString = inputWidth ? "style='width:" + inputWidth + "'" : (inputSize ? "size=" + inputSize : ""); 330 331 inputHtml = [ 332 "<input type=text id=", 333 id, 334 "_display class=", 335 this.getDisplayCssClass(), 336 " value='VALUE' ", 337 " onchange=\"", 338 ref, 339 ".onValueTyped(this.value, event||window.event)\"", 340 keyPressEv + "=\"", 341 ref, 342 ".onKeyDown(this.value, event||window.event)\"", 343 " onkeyup=\"", 344 ref, 345 ".onKeyUp(this.value, event||window.event)\"", 346 inputWidthString, 347 this.getMouseoutHandlerHTML(), 348 ">" 349 ].join(""); 350 351 HTMLoutput.append ( 352 "<div id=", 353 id, 354 this.getCssString(), 355 " onclick=\"", 356 this.getFormGlobalRef(), 357 ".getItemById('", 358 this.getId(), 359 "').onClick(event)\"", 360 " onselectstart=\"return false\"", 361 ">", 362 "<table ", 363 this.getTableCssString(), 364 " cellspacing='0'>", 365 "<tr><td width=100%>", 366 inputHtml, 367 "</td>", 368 "</tr>", 369 "</table>", 370 "</div>" 371 ); 372 373 this.edited = false; 374 } 375 376 DynSelect_XFormItem.prototype.getMouseoutHandlerHTML = function () { 377 var formId = this.getFormGlobalRef(), 378 itemId = this.getId(); 379 380 var onMouseoutAction = ""; 381 382 var onMouseoutFunc = this.getInheritedProperty("onMouseout"); 383 384 onMouseoutAction = AjxBuffer.concat( 385 " onmouseout=\"", 386 onMouseoutFunc || "XFormItem.prototype.hideInputTooltip", 387 ".call(", 388 this.getGlobalRef(), 389 ", event );\" " 390 ); 391 392 return AjxBuffer.concat( onMouseoutAction ); 393 } 394 395 DynSelect_XFormItem.prototype.onClick = function(event) { 396 var choices = this.getNormalizedChoices(); 397 398 if (!this.edited && this.getInheritedProperty("editable")) { 399 this.showInputTooltip(event); 400 } else { 401 if(choices && choices.values && choices.values.length && !(choices.values[0] instanceof XFormChoices)) { 402 this.showMenu(); 403 } 404 } 405 406 if(AjxUtil.isEmpty(this.getInstanceValue()) && this._enabled) { 407 var el = this.getDisplayElement(); 408 el.value = ""; 409 el.className = this.getDisplayCssClass(); 410 } 411 } 412 413 DynSelect_XFormItem.prototype.getArrowElement = function () { 414 return null; 415 } 416 417 DynSelect_XFormItem.prototype.preProcessInput = function (value) { 418 var preProcessMethod = this.getPreProcessMethod(); 419 var val = value; 420 421 if (preProcessMethod) { 422 val = preProcessMethod.call(this, value, this.getForm()); 423 } 424 425 return val; 426 } 427 428 DynSelect_XFormItem.prototype.getPreProcessMethod = function () { 429 return this.cacheInheritedMethod("inputPreProcessor","inputPreProcessor","value, form"); 430 } 431 432 DynSelect_XFormItem.prototype.updateElement = function (newValue) { 433 if (this.getMultiple() && newValue != null && newValue.indexOf(",") > -1) { 434 newValue = newValue.split(","); 435 436 for (var i = 0; i < newValue.length; i++) { 437 newValue[i] = this.getChoiceLabel(newValue[i]); 438 } 439 } else { 440 newValue = this.getChoiceLabel(newValue); 441 } 442 443 var el = this.getDisplayElement(); 444 445 if (el) { 446 if(AjxUtil.isEmpty(newValue) && this._enabled && !this.edited) { 447 var emptyText = this.getInheritedProperty("emptyText"); 448 if(!AjxUtil.isEmpty(emptyText)) { 449 newValue = emptyText; 450 el.className = this.getDisplayCssClass() + "_empty"; 451 } 452 } else if(this._enabled) { 453 el.className = this.getDisplayCssClass(); 454 } 455 456 if(window.console && window.console.log) { 457 console.log("updating element with value: " + newValue + " over " + el.value); 458 459 } 460 461 el.value = newValue; 462 } 463 464 if (this.autoCompleteEnabled) { 465 466 if (AjxUtil.isEmpty(newValue)) { 467 if(!this.dataFetcherObject && 468 this.dataFetcherClass != null && 469 this.dataFetcherMethod != null) { 470 471 this.dataFetcherObject = new this.dataFetcherClass(this.getForm().getController()); 472 } 473 474 if (!this.dataFetcherObject) { 475 return; 476 } 477 } 478 479 } 480 481 } 482 483 DynSelect_XFormItem.prototype.setElementEnabled = function(enabled) { 484 this._enabled = enabled; 485 486 var el = this.getForm().getElement(this.getId()); 487 if (!el || !el.getElementsByTagName || !el.getElementsByTagName("table")[0]) { 488 return; 489 } 490 491 var table = el.getElementsByTagName("table")[0]; 492 493 if (enabled) { 494 if(AjxUtil.isEmpty(this.getInstanceValue()) && !AjxUtil.isEmpty(this.getInheritedProperty("emptyText"))) { 495 this.getDisplayElement().className = this.getDisplayCssClass() + "_empty"; 496 this.getDisplayElement().value = this.getInheritedProperty("emptyText"); 497 } else { 498 this.getDisplayElement().className = this.getDisplayCssClass(); 499 } 500 501 this.getForm().getElement(this.getId()).className = this.cssClass; 502 table.className = this.getTableCssClass(); 503 this.getDisplayElement().disabled=false; 504 505 } else { 506 this.getDisplayElement().className = this.getDisplayCssClass() + "_disabled"; 507 508 var el = this.getArrowElement(); 509 if(el) { 510 AjxImg.setImage(el, "SelectPullDownArrowDis"); 511 } 512 513 this.getForm().getElement(this.getId()).className = this.cssClass + "_disabled"; 514 table.className = this.getTableCssClass()+"_disabled"; 515 this.getDisplayElement().disabled = true; 516 } 517 } 518 519 DynSelect_XFormItem.prototype.processEntryKey = function () { 520 var value = this.getInstanceValue(); 521 522 var processEntryKey = this.getInheritedProperty("entryKeyMethod"); 523 if (processEntryKey instanceof AjxCallback) { 524 processEntryKey.run(this, value); 525 } 526 } 527