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             var clientSelectItemsMap = [];
 38             $.each(mergedOptions.clientSelectItems, function(i) {
 39                 clientSelectItemsMap[this.id] = this;
 40             });
 41             this.__storeClientSelectItems(this.items, clientSelectItemsMap);
 42         }
 43     };
 44 
 45     rf.BaseComponent.extend(rf.ui.List);
 46     var $super = rf.ui.List.$super;
 47 
 48     var defaultOptions = {
 49         clickRequiredToSelect: false,
 50         disabled : false,
 51         selectListener : false,
 52         clientSelectItems : null,
 53         focusKeeperEnabled : true
 54     };
 55 
 56     var bindEventHandlers = function () {
 57         var handlers = {};
 58         handlers["click" + this.namespace] = $.proxy(this.onClick, this);
 59         handlers["dblclick" + this.namespace] = $.proxy(this.onDblclick, this);
 60         this.list.on("mouseover" + this.namespace, "."+this.itemCss, $.proxy(onMouseOver, this));
 61         rf.Event.bind(this.list, handlers, this);
 62     };
 63 
 64     var bindFocusEventHandlers = function () {
 65         var focusEventHandlers = {};
 66         focusEventHandlers["keydown" + this.namespace] = $.proxy(this.__keydownHandler, this);
 67         focusEventHandlers["blur" + this.namespace] = $.proxy(this.__blurHandler, this);
 68         focusEventHandlers["focus" + this.namespace] = $.proxy(this.__focusHandler, this);
 69         rf.Event.bind(this.focusKeeper, focusEventHandlers, this);
 70     };
 71 
 72     var onMouseOver = function(e) {
 73         var item = $(e.target);
 74         if (item && !this.clickRequiredToSelect && !this.disabled) {
 75             this.__select(item);
 76         }
 77     };
 78 
 79     $.extend(rf.ui.List.prototype, ( function () {
 80 
 81         return{
 82 
 83             name : "list",
 84 
 85             processItem: function(item) {
 86                 if (this.selectListener.processItem && typeof this.selectListener.processItem == 'function') {
 87                     this.selectListener.processItem(item);
 88                 }
 89             },
 90 
 91             isSelected: function(item) {
 92                 return item.hasClass(this.selectItemCssMarker);
 93             },
 94 
 95             selectItem: function(item) {
 96                 if (this.selectListener.selectItem && typeof this.selectListener.selectItem == 'function') {
 97                     this.selectListener.selectItem(item);
 98                 } else {
 99                     item.addClass(this.selectItemCss);
100                     rf.Event.fire(this, "selectItem", item);
101                 }
102                 this.__scrollToSelectedItem(this);
103             },
104 
105             unselectItem: function(item) {
106                 if (this.selectListener.unselectItem && typeof this.selectListener.unselectItem == 'function') {
107                     this.selectListener.unselectItem(item);
108                 } else {
109                     item.removeClass(this.selectItemCss);
110                     rf.Event.fire(this, "unselectItem", item);
111                 }
112             },
113 
114             __focusHandler : function(e) {
115                 if (! this.focusKeeper.focused) {
116                     this.focusKeeper.focused = true;
117                     rf.Event.fire(this, "listfocus" + this.namespace, e);
118                 }
119             },
120 
121             __blurHandler: function(e) {
122                 if (!this.isMouseDown) {
123                     var that = this;
124                     this.timeoutId = window.setTimeout(function() {
125                         that.focusKeeper.focused = false;
126                         that.invokeEvent.call(that, "blur", document.getElementById(that.id), e);
127                         rf.Event.fire(that, "listblur" + that.namespace, e);
128                     }, 200);
129                 } else {
130                     this.isMouseDown = false;
131                 }
132             },
133 
134             __onMouseDown: function(e) {
135                 this.isMouseDown = true;
136             },
137 
138             __onMouseUp: function(e) {
139                 this.isMouseDown = false;
140             },
141 
142             __keydownHandler: function(e) {
143                 if (e.isDefaultPrevented()) return;
144                 if (e.metaKey || e.ctrlKey) return;
145 
146                 var code;
147                 if (e.keyCode) {
148                     code = e.keyCode;
149                 } else if (e.which) {
150                     code = e.which;
151                 }
152 
153                 switch (code) {
154                     case rf.KEYS.DOWN:
155                         e.preventDefault();
156                         this.__selectNext();
157                         break;
158 
159                     case rf.KEYS.UP:
160                         e.preventDefault();
161                         this.__selectPrev();
162                         break;
163 
164                     case rf.KEYS.HOME:
165                         e.preventDefault();
166                         this.__selectByIndex(0);
167                         break;
168 
169                     case rf.KEYS.END:
170                         e.preventDefault();
171                         this.__selectByIndex(this.items.length - 1);
172                         break;
173 
174                     default:
175                         break;
176                 }
177             },
178 
179             onClick: function(e) {
180                 this.setFocus();
181                 var item = this.__getItem(e);
182                 if (!item) return;
183                 this.processItem(item);
184                 var clickModified = e.metaKey || e.ctrlKey;
185                 if (!this.disabled) {
186                     this.__select(item, clickModified && this.clickRequiredToSelect);
187                 }
188             },
189 
190             onDblclick: function(e) {
191                 this.setFocus();
192                 var item = this.__getItem(e);
193                 if (!item) return;
194                 this.processItem(item);
195                 if (!this.disabled) {
196                     this.__select(item, false);
197                 }
198             },
199 
200             currentSelectItem: function() {
201                 if (this.items && this.index != -1) {
202                     return $(this.items[this.index]);
203                 }
204             },
205 
206             getSelectedItemIndex: function() {
207                 return this.index;
208             },
209 
210             removeItems: function(items) {
211                 $(items).detach();
212                 this.__updateItemsList();
213                 rf.Event.fire(this, "removeitems", items);
214             },
215 
216             removeAllItems: function() {
217                 var items = this.__getItems();
218                 this.removeItems(items);
219                 return items;
220             },
221 
222             addItems: function(items) {
223                 var parentContainer = this.scrollContainer;
224                 parentContainer.append(items);
225                 this.__updateItemsList();
226                 rf.Event.fire(this, "additems", items);
227             },
228 
229             move: function(items, step) {
230                 if (step === 0) {
231                     return;
232                 }
233                 var that = this;
234                 if (step > 0) {
235                     items = $(items.get().reverse());
236                 }
237                 items.each(function(i) {
238                     var index = that.items.index(this);
239                     var indexNew = index + step;
240                     var existingItem = that.items[indexNew];
241                     if (step < 0) {
242                         $(this).insertBefore(existingItem);
243                     } else {
244                         $(this).insertAfter(existingItem);
245                     }
246                     that.index = that.index + step;
247                     that.__updateItemsList();
248                 });
249                 rf.Event.fire(this, "moveitems", items);
250             },
251 
252             getItemByIndex: function(i) {
253                 if (i >= 0 && i < this.items.length) {
254                     return this.items[i];
255                 }
256             },
257 
258             getClientSelectItemByIndex: function(i) {
259                 if (i >= 0 && i < this.items.length) {
260                     return $(this.items[i]).data('clientSelectItem');
261                 }
262             },
263 
264             resetSelection: function() {
265                 var item = this.currentSelectItem();
266                 if (item) {
267                     this.unselectItem($(item));
268                 }
269                 this.index = -1;
270             },
271 
272             isList: function(target) {
273                 var parentId = target.parents("." + this.listCss).attr("id");
274                 return (parentId && (parentId == this.getId()));
275             },
276 
277             length: function() {
278                 return this.items.length;
279             },
280 
281             __updateIndex: function(item) {
282                 if (item === null) {
283                     this.index = -1;
284                 } else {
285                     var index = this.items.index(item);
286                     if (index < 0) {
287                         index = 0;
288                     } else if (index >= this.items.length) {
289                         index = this.items.length - 1;
290                     }
291                     this.index = index;
292                 }
293             },
294 
295             __updateItemsList: function () {
296                 return (this.items = this.list.find("." + this.itemCss));
297             },
298 
299             __storeClientSelectItems: function(items, clientSelectItemsMap) {
300                 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