      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