      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