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