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