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