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.__updateItemsFromCache(newValue, true);
206                     this.originalItems = this.list.__getItems();
207 
208                     if (this.list.__getItems().length != 0) {
209                         this.container.removeClass("rf-sel-fld-err");
210                     } else {
211                         this.container.addClass("rf-sel-fld-err");
212                     }
213 
214                     if (!this.popupList.isVisible()) {
215                         this.__showPopup();
216                     }
217                 } else {
218                     if (newValue.length >= this.options.minChars) {
219                         if ((this.options.ajaxMode || this.options.lazyClientMode)) {
220                             this.callAjax(e);
221                         }
222                     } else {
223                         if (this.options.ajaxMode) {
224                             this.clearItems();
225                             this.__hidePopup();
226                         }
227                     }
228                 }
229             },
230 
231             clearItems: function() {
232                 this.list.removeAllItems();
233             },
234 
235             callAjax: function(event) {
236                 var _this = this;
237                 var _event = event;
238                 var ajaxSuccess = function (event) {
239                     updateItemsList.call(_this, _this.__getValue(), event.componentData && event.componentData[_this.id]);
240 
241                     if (_this.clientSelectItems.length != 0) {
242                         _this.__updateItems();
243                         _this.__showPopup();
244                     } else {
245                         _this.__hidePopup();
246                     }
247                 };
248 
249                 var ajaxError = function (event) {
250                     _this.__hidePopup();
251                     _this.clearItems();
252                 };
253 
254                 this.isFirstAjax = false;
255                 //caution: JSF submits inputs with empty names causing "WARNING: Parameters: Invalid chunk ignored." in Tomcat log
256                 var params = {};
257                 params[this.id + ".ajax"] = "1";
258                 rf.ajax(this.id, event, {parameters: params, error: ajaxError, complete:ajaxSuccess});
259 
260             },
261 
262             __blurHandler: function(e) {
263                 if (!this.isMouseDown) {
264                     var that = this;
265                     this.timeoutId = window.setTimeout(function() {
266                         if (that.input !== null) {
267                             that.onblur(e);
268                         }
269                     }, 200);
270                 } else {
271                     this.__setInputFocus();
272                     this.isMouseDown = false;
273                 }
274             },
275 
276             __onListMouseDown: function(e) {
277                 this.isMouseDown = true;
278             },
279 
280             __onMouseUp: function(e) {
281                 this.isMouseDown = false;
282                 this.__setInputFocus();
283             },
284 
285             __updateItems: function() {
286                 var newValue = this.__getValue();
287                 newValue = (newValue != this.defaultLabel) ? newValue : "";
288                 this.__updateItemsFromCache(newValue);
289 
290                 if (this.selectFirst) {
291                     this.list.__selectByIndex(0);
292                 }
293             },
294 
295             __updateItemsFromCache: function(value, isAutocomplete) {
296                 if (this.originalItems.length > 0 && this.enableManualInput || isAutocomplete) {
297                     var newItems = this.cache.getItems(value, this.filterFunction);
298                     var items = $(newItems);
299                     this.list.__unselectPrevious();
300                     this.list.__setItems(items);
301                     $(document.getElementById(this.id + "Items")).empty().append(items);
302                 }
303             },
304 
305             __getClientItemFromCache: function(inputLabel) {
306                 var value;
307                 var label;
308                 if (this.enableManualInput) {
309                     var items = this.cache.getItems(inputLabel, this.filterFunction);
310                     if (items && items.length > 0) {
311                         var first = $(items[0]);
312                         $.each(this.clientSelectItems, function() {
313                             if (this.id == first.attr("id")) {
314                                 label = this.label;
315                                 value = this.value;
316                                 return false;
317                             }
318                         });
319                     } else {
320                         label = inputLabel;
321                         value = "";
322                     }
323                 }
324 
325                 if (label) {
326                     return {'label': label, 'value': value};
327                 }
328             },
329 
330             __getClientItem: function(inputLabel) {
331                 var value;
332                 var label = inputLabel;
333                 $.each(this.clientSelectItems, function() {
334                     if (label == this.label) {
335                         value = this.value;
336                     }
337                 });
338 
339                 if (label && value) {
340                     return {'label': label, 'value': value};
341                 }
342             },
343 
344             __showPopup: function() {
345                 if (this.originalItems.length > 0) {
346                     this.popupList.show();
347                 }
348                 this.invokeEvent.call(this, "listshow", document.getElementById(this.id));
349             },
350 
351             __hidePopup: function() {
352                 this.popupList.hide();
353                 this.invokeEvent.call(this, "listhide", document.getElementById(this.id));
354             },
355 
356             showPopup: function() {
357                 if (!this.popupList.isVisible()) {
358                     this.__updateItems();
359                     this.__showPopup();
360                 }
361                 this.__setInputFocus();
362                 if (!this.focused) {
363                     if (this.__getValue() == this.defaultLabel) {
364                         this.__setValue("");
365                     }
366                     this.focusValue = this.selValueInput.val();
367                     this.focused = true;
368                     this.invokeEvent.call(this, "focus", document.getElementById(this.id));
369                 }
370             },
371 
372             hidePopup: function() {
373                 if (this.popupList.isVisible()) {
374                     this.__hidePopup();
375                     var inputLabel = this.__getValue();
376 
377                     if (!inputLabel || inputLabel == "") {
378                         this.__setValue(this.defaultLabel);
379                         this.selValueInput.val("");
380                     }
381 
382                     this.focused = false;
383                     this.invokeEvent.call(this, "blur", document.getElementById(this.id));
384                     if (this.focusValue != this.selValueInput.val()) {
385                         this.invokeEvent.call(this, "change", document.getElementById(this.id));
386                     }
387                 }
388             },
389 
390             processItem: function(item) {
391                 var key = $(item).attr("id");
392                 var label;
393                 $.each(this.clientSelectItems, function() {
394                     if (this.id == key) {
395                         label = this.label;
396                         return false;
397                     }
398                 });
399                 this.__setValue(label);
400                 this.__hidePopup();
401                 this.__setInputFocus();
402                 this.__save();
403 
404                 this.invokeEvent.call(this, "selectitem", document.getElementById(this.id));
405             },
406 
407             __save: function() {
408                 var value = "";
409                 var label = "";
410                 var inputLabel = this.__getValue();
411                 var clientSelectItem;
412 
413                 if (inputLabel && inputLabel != "") {
414                     if (this.enableManualInput) {
415                         clientSelectItem = this.__getClientItemFromCache(inputLabel);
416                     } else {
417                         clientSelectItem = this.__getClientItem(inputLabel);
418                     }
419 
420                     if (clientSelectItem) {
421                         label = clientSelectItem.label;
422                         value = clientSelectItem.value;
423                     }
424                 }
425 
426                 this.__setValue(label);
427                 this.selValueInput.val(value);
428             },
429 
430             onblur: function(e) {
431                 this.__hidePopup();
432                 var inputLabel = this.__getValue();
433 
434                 if (!inputLabel || inputLabel == "") {
435                     this.__setValue(this.defaultLabel);
436                     this.selValueInput.val("");
437                 }
438 
439                 this.focused = false;
440                 this.invokeEvent.call(this, "blur", document.getElementById(this.id), e);
441                 if (this.focusValue != this.selValueInput.val()) {
442                     this.invokeEvent.call(this, "change", document.getElementById(this.id), e);
443                 }
444             },
445 
446             getValue: function() {
447                 return this.selValueInput.val();
448             },
449 
450             setValue: function(value) {
451                 if (value == null || value == '') {
452                     this.__setValue('');
453                     this.__save();
454                     this.__updateItems();
455                     return;
456                 }
457                 var item;
458                 for (var i = 0; i < this.clientSelectItems.length; i++) {
459                     item = this.clientSelectItems[i];
460                     if (item.value == value) {
461                         this.__setValue(item.label);
462                         this.__save();
463                         this.list.__selectByIndex(i);
464                         return;
465                     }
466                 }
467             },
468 
469             getLabel: function() {
470                 return this.__getValue();
471             },
472 
473             destroy: function() {
474                 this.popupList.destroy();
475                 this.popupList = null;
476                 $super.destroy.call(this);
477             }
478         }
479     })());
480     
481     // client-side validation
482     rf.csv = rf.csv || {};
483     rf.csv.validateSelectLabelValue = function (input, id, params, msg) {
484         var value = $(document.getElementById(id + 'selValue')).val();
485         var label = $(document.getElementById(id + 'Input')).val();
486         
487         var defaultLabel = RichFaces.component(id).defaultLabel;
488         
489         if (!value && label && (label != defaultLabel)) {
490             throw rf.csv.getMessage(null, 'UISELECTONE_INVALID', [id, ""]);
491         }
492     };
493 
494 })(RichFaces.jQuery, window.RichFaces);