1 (function ($, rf) {
  2 
  3     rf.ui = rf.ui || {};
  4 
  5     rf.ui.List = function(id, options) {
  6         $super.constructor.call(this, id);
  7         this.namespace = this.namespace || "." + rf.Event.createNamespace(this.name, this.id);
  8         this.attachToDom();
  9         var mergedOptions = $.extend({}, defaultOptions, options);
 10         this.list = $(document.getElementById(id));
 11         this.selectListener = mergedOptions.selectListener;
 12         this.selectItemCss = mergedOptions.selectItemCss;
 13         this.selectItemCssMarker = mergedOptions.selectItemCss.split(" ", 1)[0];
 14         this.scrollContainer = $(mergedOptions.scrollContainer);
 15         this.itemCss = mergedOptions.itemCss.split(" ", 1)[0]; // we only need one of the item css classes to identify the item
 16         this.listCss = mergedOptions.listCss;
 17         this.clickRequiredToSelect = mergedOptions.clickRequiredToSelect;
 18         this.index = -1;
 19         this.disabled = mergedOptions.disabled;
 20 
 21         this.focusKeeper = $(document.getElementById(id + "FocusKeeper"));
 22         this.focusKeeper.focused = false;
 23 
 24         this.isMouseDown = false;
 25         this.list
 26             .bind("mousedown", $.proxy(this.__onMouseDown, this))
 27             .bind("mouseup", $.proxy(this.__onMouseUp, this));
 28 
 29 
 30         bindEventHandlers.call(this);
 31         if (mergedOptions.focusKeeperEnabled) {
 32             bindFocusEventHandlers.call(this);
 33         }
 34 
 35         this.__updateItemsList(); // initialize this.items
 36         if (mergedOptions.clientSelectItems !== null) {
 37             this.__storeClientSelectItems(mergedOptions.clientSelectItems);
 38         }
 39     };
 40 
 41     rf.BaseComponent.extend(rf.ui.List);
 42     var $super = rf.ui.List.$super;
 43 
 44     var defaultOptions = {
 45         clickRequiredToSelect: false,
 46         disabled : false,
 47         selectListener : false,
 48         clientSelectItems : null,
 49         focusKeeperEnabled : true
 50     };
 51 
 52     var bindEventHandlers = function () {
 53         var handlers = {};
 54         handlers["click" + this.namespace] = $.proxy(this.onClick, this);
 55         handlers["dblclick" + this.namespace] = $.proxy(this.onDblclick, this);
 56         this.list.on("mouseover" + this.namespace, "."+this.itemCss, $.proxy(onMouseOver, this));
 57         rf.Event.bind(this.list, handlers, this);
 58     };
 59 
 60     var bindFocusEventHandlers = function () {
 61         var focusEventHandlers = {};
 62         focusEventHandlers["keydown" + this.namespace] = $.proxy(this.__keydownHandler, this);
 63         focusEventHandlers["blur" + this.namespace] = $.proxy(this.__blurHandler, this);
 64         focusEventHandlers["focus" + this.namespace] = $.proxy(this.__focusHandler, this);
 65         rf.Event.bind(this.focusKeeper, focusEventHandlers, this);
 66     };
 67 
 68     var onMouseOver = function(e) {
 69         var item = $(e.target);
 70         if (item && !this.clickRequiredToSelect && !this.disabled) {
 71             this.__select(item);
 72         }
 73     };
 74 
 75     $.extend(rf.ui.List.prototype, ( function () {
 76 
 77         return{
 78 
 79             name : "list",
 80 
 81             processItem: function(item) {
 82                 if (this.selectListener.processItem && typeof this.selectListener.processItem == 'function') {
 83                     this.selectListener.processItem(item);
 84                 }
 85             },
 86 
 87             isSelected: function(item) {
 88                 return item.hasClass(this.selectItemCssMarker);
 89             },
 90 
 91             selectItem: function(item) {
 92                 if (this.selectListener.selectItem && typeof this.selectListener.selectItem == 'function') {
 93                     this.selectListener.selectItem(item);
 94                 } else {
 95                     item.addClass(this.selectItemCss);
 96                     rf.Event.fire(this, "selectItem", item);
 97                 }
 98                 this.__scrollToSelectedItem(this);
 99             },
100 
101             unselectItem: function(item) {
102                 if (this.selectListener.unselectItem && typeof this.selectListener.unselectItem == 'function') {
103                     this.selectListener.unselectItem(item);
104                 } else {
105                     item.removeClass(this.selectItemCss);
106                     rf.Event.fire(this, "unselectItem", item);
107                 }
108             },
109 
110             __focusHandler : function(e) {
111                 if (! this.focusKeeper.focused) {
112                     this.focusKeeper.focused = true;
113                     rf.Event.fire(this, "listfocus" + this.namespace, e);
114                 }
115             },
116 
117             __blurHandler: function(e) {
118                 if (!this.isMouseDown) {
119                     var that = this;
120                     this.timeoutId = window.setTimeout(function() {
121                         that.focusKeeper.focused = false;
122                         that.invokeEvent.call(that, "blur", document.getElementById(that.id), e);
123                         rf.Event.fire(that, "listblur" + that.namespace, e);
124                     }, 200);
125                 } else {
126                     this.isMouseDown = false;
127                 }
128             },
129 
130             __onMouseDown: function(e) {
131                 this.isMouseDown = true;
132             },
133 
134             __onMouseUp: function(e) {
135                 this.isMouseDown = false;
136             },
137 
138             __keydownHandler: function(e) {
139                 if (e.isDefaultPrevented()) return;
140                 if (e.metaKey || e.ctrlKey) return;
141 
142                 var code;
143                 if (e.keyCode) {
144                     code = e.keyCode;
145                 } else if (e.which) {
146                     code = e.which;
147                 }
148 
149                 switch (code) {
150                     case rf.KEYS.DOWN:
151                         e.preventDefault();
152                         this.__selectNext();
153                         break;
154 
155                     case rf.KEYS.UP:
156                         e.preventDefault();
157                         this.__selectPrev();
158                         break;
159 
160                     case rf.KEYS.HOME:
161                         e.preventDefault();
162                         this.__selectByIndex(0);
163                         break;
164 
165                     case rf.KEYS.END:
166                         e.preventDefault();
167                         this.__selectByIndex(this.items.length - 1);
168                         break;
169 
170                     default:
171                         break;
172                 }
173             },
174 
175             onClick: function(e) {
176                 this.setFocus();
177                 var item = this.__getItem(e);
178                 if (!item) return;
179                 this.processItem(item);
180                 var clickModified = e.metaKey || e.ctrlKey;
181                 if (!this.disabled) {
182                     this.__select(item, clickModified && this.clickRequiredToSelect);
183                 }
184             },
185 
186             onDblclick: function(e) {
187                 this.setFocus();
188                 var item = this.__getItem(e);
189                 if (!item) return;
190                 this.processItem(item);
191                 if (!this.disabled) {
192                     this.__select(item, false);
193                 }
194             },
195 
196             currentSelectItem: function() {
197                 if (this.items && this.index != -1) {
198                     return $(this.items[this.index]);
199                 }
200             },
201 
202             getSelectedItemIndex: function() {
203                 return this.index;
204             },
205 
206             removeItems: function(items) {
207                 $(items).detach();
208                 this.__updateItemsList();
209                 rf.Event.fire(this, "removeitems", items);
210             },
211 
212             removeAllItems: function() {
213                 var items = this.__getItems();
214                 this.removeItems(items);
215                 return items;
216             },
217 
218             addItems: function(items) {
219                 var parentContainer = this.scrollContainer;
220                 parentContainer.append(items);
221                 this.__updateItemsList();
222                 rf.Event.fire(this, "additems", items);
223             },
224 
225             move: function(items, step) {
226                 if (step === 0) {
227                     return;
228                 }
229                 var that = this;
230                 if (step > 0) {
231                     items = $(items.get().reverse());
232                 }
233                 items.each(function(i) {
234                     var index = that.items.index(this);
235                     var indexNew = index + step;
236                     var existingItem = that.items[indexNew];
237                     if (step < 0) {
238                         $(this).insertBefore(existingItem);
239                     } else {
240                         $(this).insertAfter(existingItem);
241                     }
242                     that.index = that.index + step;
243                     that.__updateItemsList();
244                 });
245                 rf.Event.fire(this, "moveitems", items);
246             },
247 
248             getItemByIndex: function(i) {
249                 if (i >= 0 && i < this.items.length) {
250                     return this.items[i];
251                 }
252             },
253 
254             getClientSelectItemByIndex: function(i) {
255                 if (i >= 0 && i < this.items.length) {
256                     return $(this.items[i]).data('clientSelectItem');
257                 }
258             },
259 
260             resetSelection: function() {
261                 var item = this.currentSelectItem();
262                 if (item) {
263                     this.unselectItem($(item));
264                 }
265                 this.index = -1;
266             },
267 
268             isList: function(target) {
269                 var parentId = target.parents("." + this.listCss).attr("id");
270                 return (parentId && (parentId == this.getId()));
271             },
272 
273             length: function() {
274                 return this.items.length;
275             },
276 
277             __updateIndex: function(item) {
278                 if (item === null) {
279                     this.index = -1;
280                 } else {
281                     var index = this.items.index(item);
282                     if (index < 0) {
283                         index = 0;
284                     } else if (index >= this.items.length) {
285                         index = this.items.length - 1;
286                     }
287                     this.index = index;
288                 }
289             },
290 
291             __updateItemsList: function () {
292                 return (this.items = this.list.find("." + this.itemCss));
293             },
294 
295             __storeClientSelectItems: function(clientSelectItems) {
296                 var clientSelectItemsMap = [];
297                 $.each(clientSelectItems, function(i) {
298                     clientSelectItemsMap[this.id] = this;
299                 });
300                 this.items.each(function (i)  {
301                     var item = $(this);
302                     var id = item.attr("id");
303                     var clientSelectItem = clientSelectItemsMap[id];
304                     item.data('clientSelectItem', clientSelectItem);
305                 })
306             },
307 
308             __select: function(item, clickModified) {
309                 var index = this.items.index(item);
310                 this.__selectByIndex(index, clickModified);
311             },
312 
313             __selectByIndex: function(index, clickModified) {
314                 if (! this.__isSelectByIndexValid(index)) {
315                     return;
316                 }
317 
318                 if (!this.clickRequiredToSelect && this.index == index) {
319                     return; // do nothing if re-selecting the same item
320                 }
321 
322                 var oldIndex = this.__unselectPrevious();
323 
324                 if (this.clickRequiredToSelect && oldIndex == index) {
325                     return; //do nothing after unselecting item
326                 }
327 
328                 this.index = this.__sanitizeSelectedIndex(index);
329 
330                 var item = this.items.eq(this.index);
331                 if (this.isSelected(item)) {
332                     this.unselectItem(item);
333                 } else {
334                     this.selectItem(item);
335                 }
336             },
337 
338             __isSelectByIndexValid: function(index) {
339                 if (this.items.length == 0) {
340                     return false;
341                 }
342                 if (index == undefined) {
343                     this.index = -1;
344                     return false;
345                 }
346                 return true;
347             },
348 
349             __sanitizeSelectedIndex: function(index) {
350                 var sanitizedIndex;
351                 if (index < 0) {
352                     sanitizedIndex = 0;
353                 } else if (index >= this.items.length) {
354                     sanitizedIndex = this.items.length - 1;
355                 } else {
356                     sanitizedIndex = index;
357                 }
358                 return sanitizedIndex;
359             },
360 
361             __unselectPrevious: function() {
362                 var oldIndex = this.index;
363                 if (oldIndex != -1) {
364                     var item = this.items.eq(oldIndex);
365                     this.unselectItem(item);
366                     this.index = -1;
367                 }
368                 return oldIndex;
369             },
370 
371             __selectItemByValue: function(value) {
372                 var item = null;
373                 this.resetSelection();
374                 var that = this;
375                 this.__getItems().each(function( i ) {
376                     if ($(this).data('clientSelectItem').value == value) {
377                         that.__selectByIndex(i);
378                         item = $(this);
379                         return false; //break
380                     }
381                 });
382                 return item;
383             },
384 
385             csvEncodeValues: function() {
386                 var encoded = new Array();
387                 this.__getItems().each(function( index ) {
388                     encoded.push($(this).data('clientSelectItem').value);
389                 });
390                 return encoded.join(",");
391             },
392 
393             __selectCurrent: function() {
394                 var item;
395                 if (this.items && this.index >= 0) {
396                     item = this.items.eq(this.index);
397                     this.processItem(item);
398                 }
399             },
400 
401             __getAdjacentIndex: function(offset) {
402                 var index = this.index + offset;
403                 if (index < 0) {
404                     index = this.items.length - 1;
405                 } else if (index >= this.items.length) {
406                     index = 0;
407                 }
408                 return index;
409             },
410 
411             __selectPrev: function() {
412                 this.__selectByIndex(this.__getAdjacentIndex(-1));
413             },
414 
415             __selectNext: function() {
416                 this.__selectByIndex(this.__getAdjacentIndex(1));
417             },
418 
419             __getItem: function(e) {
420                 return $(e.target).closest("." + this.itemCss, e.currentTarget).get(0);
421             },
422 
423             __getItems: function () {
424                 return this.items;
425             },
426 
427             __setItems: function(items) {
428                 this.items = items;
429             },
430 
431             __scrollToSelectedItem : function() {
432                 if (this.scrollContainer) {
433                     var offset = 0;
434 
435                     this.items.slice(0, this.index).each(function() {
436                         offset += this.offsetHeight;
437                     });
438 
439                     var parentContainer = this.scrollContainer;
440                     if (offset < parentContainer.scrollTop()) {
441                         parentContainer.scrollTop(offset);
442                     } else {
443                         offset += this.items.get(this.index).offsetHeight;
444                         if (offset - parentContainer.scrollTop() > parentContainer.get(0).clientHeight) {
445                             parentContainer.scrollTop(offset - parentContainer.innerHeight());
446                         }
447                     }
448                 }
449             },
450 
451             setFocus : function() {
452     		    this.focusKeeper.focus();
453 	        }
454 
455         }
456     })());
457 
458 })(RichFaces.jQuery, window.RichFaces);
459