1 (function ($, rf) {
  2 
  3     rf.ui = rf.ui || {};
  4 
  5     rf.ui.Select = function(id, options) {
  6         this.id = id;
  7         this.element = this.attachToDom();
  8         var mergedOptions = $.extend({}, defaultOptions, options);
  9         mergedOptions['attachTo'] = id;
 10         mergedOptions['scrollContainer'] = $(document.getElementById(id + "Items")).parent()[0];
 11         mergedOptions['focusKeeperEnabled'] = false;
 12         $super.constructor.call(this, id, mergedOptions);
 13 
 14         this.options = mergedOptions;
 15         this.defaultLabel = mergedOptions.defaultLabel;
 16         var inputLabel = this.__getValue();
 17         this.initialValue = (inputLabel != this.defaultLabel) ? inputLabel : "";
 18         this.selValueInput = $(document.getElementById(id + "selValue"));
 19         this.container = this.selValueInput.parent();
 20         this.clientSelectItems = mergedOptions.clientSelectItems;
 21         this.filterFunction = mergedOptions.filterFunction;
 22 
 23 
 24         if (mergedOptions.showControl && !mergedOptions.disabled) {
 25             this.container.bind("mousedown", $.proxy(this.__onBtnMouseDown, this))
 26                 .bind("mouseup", $.proxy(this.__onMouseUp, this));
 27         }
 28 
 29         this.isFirstAjax = true;
 30         this.previousValue = this.__getValue();
 31         this.selectFirst = mergedOptions.selectFirst;
 32         this.popupList = new rf.ui.PopupList((id + "List"), this, mergedOptions);
 33         this.list = this.popupList.__getList();
 34         this.listElem = $(document.getElementById(id + "List"));
 35 
 36         this.listElem.bind("mousedown", $.proxy(this.__onListMouseDown, this));
 37         this.listElem.bind("mouseup", $.proxy(this.__onMouseUp, this));
 38 
 39         var listEventHandlers = {};
 40         listEventHandlers["listshow" + this.namespace] = $.proxy(this.__listshowHandler, this);
 41         listEventHandlers["listhide" + this.namespace] = $.proxy(this.__listhideHandler, this);
 42         listEventHandlers["change" + this.namespace] = $.proxy(this.__onInputChangeHandler, this);
 43         rf.Event.bind(this.element, listEventHandlers, this);
 44 
 45         this.originalItems = this.list.__getItems();  // initialize here for non-autocomplete use cases
 46         this.enableManualInput = mergedOptions.enableManualInput || mergedOptions.isAutocomplete;
 47 
 48         if (this.enableManualInput) {
 49             updateItemsList.call(this, "", this.clientSelectItems);
 50         }
 51         this.changeDelay = mergedOptions.changeDelay;
 52     };
 53 
 54     rf.ui.InputBase.extend(rf.ui.Select);
 55     var $super = rf.ui.Select.$super;
 56 
 57     var defaultOptions = {
 58         defaultLabel: "",
 59         selectFirst: true,
 60         showControl: true,
 61         enableManualInput: false,
 62         itemCss: "rf-sel-opt",
 63         selectItemCss: "rf-sel-sel",
 64         listCss: "rf-sel-lst-cord",
 65         changeDelay: 8,
 66         disabled: false,
 67         filterFunction : undefined,
 68         isAutocomplete: false,
 69         ajaxMode:true,
 70         lazyClientMode:false,
 71         isCachedAjax:true
 72     };
 73 
 74     var REGEXP_TRIM = /^[\n\s]*(.*)[\n\s]*$/;
 75 
 76     var updateItemsList = function (value, clientSelectItems) {
 77         if (!clientSelectItems) {
 78             clientSelectItems = [];
 79         }
 80         if (clientSelectItems.length || (!this.options.isAutocomplete && !this.options.isCachedAjax)) {
 81             // do no empty if autocomplete/caching is on
 82             this.clientSelectItems = clientSelectItems;
 83         }
 84 
 85         this.originalItems = this.list.__updateItemsList();
 86         this.list.__storeClientSelectItems(clientSelectItems);
 87         if (this.originalItems.length > 0) {
 88             this.cache = new rf.utils.Cache((this.options.ajaxMode ? value : ""), this.originalItems, getData, !this.options.ajaxMode);
 89         }
 90         
 91     };
 92 
 93     var getData = function (nodeList) {
 94         var data = [];
 95         nodeList.each(function () {
 96             data.push($(this).text().replace(REGEXP_TRIM, "$1"));
 97         });
 98         return data;
 99     }
100 
101     $.extend(rf.ui.Select.prototype, ( function () {
102         return{
103             name : "select",
104             defaultLabelClass : "rf-sel-dflt-lbl",
105 
106             __listshowHandler: function(e) {
107                 if (this.originalItems.length == 0 && this.isFirstAjax) {
108                     this.callAjax(e);
109                 }
110             },
111 
112             __listhideHandler: function(e) {
113             },
114             
115             __onInputChangeHandler: function(e) {
116                 this.__setValue(this.input.val());
117             },
118 
119             __onBtnMouseDown: function(e) {
120                 if (!this.popupList.isVisible() && !this.options.isAutocomplete) {
121                     this.__updateItems();
122                     this.__showPopup();
123                 } else {
124                     this.__hidePopup();
125                 }
126                 this.isMouseDown = true;
127             },
128 
129             __focusHandler: function(e) {
130                 if (!this.focused) {
131                     if (this.__getValue() == this.defaultLabel) {
132                         this.__setValue("");
133                     }
134                     this.focusValue = this.selValueInput.val();
135                     this.focused = true;
136                     this.invokeEvent.call(this, "focus", document.getElementById(this.id), e);
137                 }
138             },
139 
140             __keydownHandler: function(e) {
141                 var code;
142 
143                 if (e.keyCode) {
144                     code = e.keyCode;
145                 } else if (e.which) {
146                     code = e.which;
147                 }
148 
149                 var visible = this.popupList.isVisible();
150 
151                 switch (code) {
152                     case rf.KEYS.DOWN:
153                         e.preventDefault();
154                         if (!visible) {
155                             this.__updateItems();
156                             this.__showPopup();
157                         } else {
158                             this.list.__selectNext();
159                         }
160                         break;
161 
162                     case rf.KEYS.UP:
163                         e.preventDefault();
164                         if (visible) {
165                             this.list.__selectPrev();
166                         }
167                         break;
168 
169                     case rf.KEYS.RETURN:
170                         e.preventDefault();
171                         if (visible) {
172                             this.list.__selectCurrent();
173                         }
174                         return false;
175                         break;
176 
177                     case rf.KEYS.TAB:
178                         break;
179 
180                     case rf.KEYS.ESC:
181                         e.preventDefault();
182                         if (visible) {
183                             this.__hidePopup();
184                         }
185                         break;
186 
187                     default:
188                         var _this = this;
189                         window.clearTimeout(this.changeTimerId);
190                         this.changeTimerId = window.setTimeout(function() {
191                             _this.__onChangeValue(e);
192                         }, this.changeDelay);
193                         break;
194                 }
195             },
196 
197             __onChangeValue: function(e) {
198                 var newValue = this.__getValue();
199                 if (newValue === this.previousValue) {
200                     return;
201                 }
202                 this.previousValue = newValue;
203                 if (!this.options.isAutocomplete ||
204                     (this.options.isCachedAjax || !this.options.ajaxMode) && this.cache && this.cache.isCached(newValue)) {
205                     this.__updateItems();
206                     if (this.isAutocomplete) {
207                         this.originalItems = this.list.__getItems();
208                     }
209 
210                     if (this.list.__getItems().length != 0) {
211                         this.container.removeClass("rf-sel-fld-err");
212                     } else {
213                         this.container.addClass("rf-sel-fld-err");
214                     }
215 
216                     if (!this.popupList.isVisible()) {
217                         this.__showPopup();
218                     }
219                 } else {
220                     if (newValue.length >= this.options.minChars) {
221                         if ((this.options.ajaxMode || this.options.lazyClientMode)) {
222                             this.callAjax(e);
223                         }
224                     } else {
225                         if (this.options.ajaxMode) {
226                             this.clearItems();
227                             this.__hidePopup();
228                         }
229                     }
230                 }
231             },
232 
233             clearItems: function() {
234                 this.list.removeAllItems();
235             },
236 
237             callAjax: function(event) {
238                 var _this = this;
239                 var _event = event;
240                 var ajaxSuccess = function (event) {
241                     updateItemsList.call(_this, _this.__getValue(), event.componentData && event.componentData[_this.id]);
242 
243                     if (_this.clientSelectItems.length != 0) {
244                         _this.__updateItems();
245                         _this.__showPopup();
246                     } else {
247                         _this.__hidePopup();
248                     }
249                 };
250 
251                 var ajaxError = function (event) {
252                     _this.__hidePopup();
253                     _this.clearItems();
254                 };
255 
256                 this.isFirstAjax = false;
257                 //caution: JSF submits inputs with empty names causing "WARNING: Parameters: Invalid chunk ignored." in Tomcat log
258                 var params = {};
259                 params[this.id + ".ajax"] = "1";
260                 rf.ajax(this.id, event, {parameters: params, error: ajaxError, complete:ajaxSuccess});
261 
262             },
263 
264             __blurHandler: function(e) {
265                 if (!this.isMouseDown) {
266                     var that = this;
267                     this.timeoutId = window.setTimeout(function() {
268                         if (that.input !== null) {
269                             that.onblur(e);
270                         }
271                     }, 200);
272                 } else {
273                     this.__setInputFocus();
274                     this.isMouseDown = false;
275                 }
276             },
277 
278             __onListMouseDown: function(e) {
279                 this.isMouseDown = true;
280             },
281 
282             __onMouseUp: function(e) {
283                 this.isMouseDown = false;
284                 this.__setInputFocus();
285             },
286 
287             __updateItems: function() {
288                 var newValue = this.__getValue();
289                 newValue = (newValue != this.defaultLabel) ? newValue : "";
290                 this.__updateItemsFromCache(newValue);
291 
292                 if (this.selectFirst) {
293                     this.list.__selectByIndex(0);
294                 }
295             },
296 
297             __updateItemsFromCache: function(value) {
298                 if (this.originalItems.length > 0 && this.enableManualInput || this.isAutocomplete) {
299                     var newItems = this.cache.getItems(value, this.filterFunction);
300                     var items = $(newItems);
301                     this.list.__unselectPrevious();
302                     this.list.__setItems(items);
303                     $(document.getElementById(this.id + "Items")).empty().append(items);
304                 }
305             },
306 
307             __getClientItemFromCache: function(inputLabel) {
308                 var value;
309                 var label;
310                 if (this.enableManualInput) {
311                     var items = this.cache.getItems(inputLabel, this.filterFunction);
312                     if (items && items.length > 0) {
313                         var first = $(items[0]);
314                         $.each(this.clientSelectItems, function() {
315                             if (this.id == first.attr("id")) {
316                                 label = this.label;
317                                 value = this.value;
318                                 return false;
319                             }
320                         });
321                     } else {
322                         label = inputLabel;
323                         value = "";
324                     }
325                 }
326 
327                 if (label) {
328                     return {'label': label, 'value': value};
329                 }
330             },
331 
332             __getClientItem: function(inputLabel) {
333                 var value;
334                 var label = inputLabel;
335                 $.each(this.clientSelectItems, function() {
336                     if (label == this.label) {
337                         value = this.value;
338                     }
339                 });
340 
341                 if (label && value) {
342                     return {'label': label, 'value': value};
343                 }
344             },
345 
346             __showPopup: function() {
347                 if (this.originalItems.length > 0) {
348                     this.popupList.show();
349                 }
350                 this.invokeEvent.call(this, "listshow", document.getElementById(this.id));
351             },
352 
353             __hidePopup: function() {
354                 this.popupList.hide();
355                 this.invokeEvent.call(this, "listhide", document.getElementById(this.id));
356             },
357 
358             showPopup: function() {
359                 if (!this.popupList.isVisible()) {
360                     this.__updateItems();
361                     this.__showPopup();
362                 }
363                 this.__setInputFocus();
364                 if (!this.focused) {
365                     if (this.__getValue() == this.defaultLabel) {
366                         this.__setValue("");
367                     }
368                     this.focusValue = this.selValueInput.val();
369                     this.focused = true;
370                     this.invokeEvent.call(this, "focus", document.getElementById(this.id));
371                 }
372             },
373 
374             hidePopup: function() {
375                 if (this.popupList.isVisible()) {
376                     this.__hidePopup();
377                     var inputLabel = this.__getValue();
378 
379                     if (!inputLabel || inputLabel == "") {
380                         this.__setValue(this.defaultLabel);
381                         this.selValueInput.val("");
382                     }
383 
384                     this.focused = false;
385                     this.invokeEvent.call(this, "blur", document.getElementById(this.id));
386                     if (this.focusValue != this.selValueInput.val()) {
387                         this.invokeEvent.call(this, "change", document.getElementById(this.id));
388                     }
389                 }
390             },
391 
392             processItem: function(item) {
393                 var key = $(item).attr("id");
394                 var label;
395                 $.each(this.clientSelectItems, function() {
396                     if (this.id == key) {
397                         label = this.label;
398                         return false;
399                     }
400                 });
401                 this.__setValue(label);
402                 this.__hidePopup();
403                 this.__setInputFocus();
404                 this.__save();
405 
406                 this.invokeEvent.call(this, "selectitem", document.getElementById(this.id));
407             },
408 
409             __save: function() {
410                 var value = "";
411                 var label = "";
412                 var inputLabel = this.__getValue();
413                 var clientSelectItem;
414 
415                 if (inputLabel && inputLabel != "") {
416                     if (this.enableManualInput) {
417                         clientSelectItem = this.__getClientItemFromCache(inputLabel);
418                     } else {
419                         clientSelectItem = this.__getClientItem(inputLabel);
420                     }
421 
422                     if (clientSelectItem) {
423                         label = clientSelectItem.label;
424                         value = clientSelectItem.value;
425                     }
426                 }
427 
428                 this.__setValue(label);
429                 this.selValueInput.val(value);
430             },
431 
432             onblur: function(e) {
433                 this.__hidePopup();
434                 var inputLabel = this.__getValue();
435 
436                 if (!inputLabel || inputLabel == "") {
437                     this.__setValue(this.defaultLabel);
438                     this.selValueInput.val("");
439                 }
440 
441                 this.focused = false;
442                 this.invokeEvent.call(this, "blur", document.getElementById(this.id), e);
443                 if (this.focusValue != this.selValueInput.val()) {
444                     this.invokeEvent.call(this, "change", document.getElementById(this.id), e);
445                 }
446             },
447 
448             getValue: function() {
449                 return this.selValueInput.val();
450             },
451 
452             setValue: function(value) {
453                 if (value == null || value == '') {
454                     this.__setValue('');
455                     this.__save();
456                     this.__updateItems();
457                     return;
458                 }
459                 var item;
460                 for (var i = 0; i < this.clientSelectItems.length; i++) {
461                     item = this.clientSelectItems[i];
462                     if (item.value == value) {
463                         this.__setValue(item.label);
464                         this.__save();
465                         this.list.__selectByIndex(i);
466                         return;
467                     }
468                 }
469             },
470 
471             getLabel: function() {
472                 return this.__getValue();
473             },
474 
475             destroy: function() {
476                 this.popupList.destroy();
477                 this.popupList = null;
478                 $super.destroy.call(this);
479             }
480         }
481     })());
482     
483     // client-side validation
484     rf.csv = rf.csv || {};
485     rf.csv.validateSelectLabelValue = function (input, id, params, msg) {
486         var value = $(document.getElementById(id + 'selValue')).val();
487         var label = $(document.getElementById(id + 'Input')).val();
488         
489         var defaultLabel = RichFaces.component(id).defaultLabel;
490         
491         if (!value && label && (label != defaultLabel)) {
492             throw rf.csv.getMessage(null, 'UISELECTONE_INVALID', [id, ""]);
493         }
494     };
495 
496 })(RichFaces.jQuery, window.RichFaces);