1 /*
  2  * JBoss, Home of Professional Open Source
  3  * Copyright ${year}, Red Hat, Inc. and individual contributors
  4  * by the @authors tag. See the copyright.txt in the distribution for a
  5  * full listing of individual contributors.
  6  *
  7  * This is free software; you can redistribute it and/or modify it
  8  * under the terms of the GNU Lesser General Public License as
  9  * published by the Free Software Foundation; either version 2.1 of
 10  * the License, or (at your option) any later version.
 11  *
 12  * This software is distributed in the hope that it will be useful,
 13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 15  * Lesser General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU Lesser General Public
 18  * License along with this software; if not, write to the Free
 19  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 20  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 21  */
 22 (function($, rf) {
 23     rf.utils = rf.utils || {};
 24 
 25     rf.utils.addCSSText = function(cssText, elementId) {
 26         var style = $("<style></style>").attr({type: 'text/css', id: elementId}).appendTo("head");
 27         try {
 28             style.html(cssText);
 29         } catch (e) {
 30             //IE
 31             style[0].styleSheet.cssText = cssText;
 32         }
 33     };
 34     
 35     rf.utils.Ranges = function() {
 36         this.ranges = [];
 37     };
 38 
 39     rf.utils.Ranges.prototype = {
 40 
 41         add: function(index) {
 42             var i = 0;
 43             while (i < this.ranges.length && index >= this.ranges[i++][1]);
 44             i--;
 45             if (this.ranges[i - 1] && index == (this.ranges[i - 1][1] + 1)) {
 46                 if (index == (this.ranges[i][0] - 1)) {
 47                     this.ranges[i - 1][1] = this.ranges[i][1];
 48                     this.ranges.splice(i, 1);
 49                 } else {
 50                     this.ranges[i - 1][1]++;
 51                 }
 52             } else {
 53                 if (this.ranges[i]) {
 54                     if (this.ranges[i] && index == (this.ranges[i][0] - 1)) {
 55                         this.ranges[i][0]--;
 56                     } else {
 57                         if (index == (this.ranges[i][1] + 1)) {
 58                             this.ranges[i][1]++;
 59                         } else {
 60                             if (index < this.ranges[i][1]) {
 61                                 this.ranges.splice(i, 0, [index, index]);
 62                             } else {
 63                                 this.ranges.splice(i + 1, 0, [index, index]);
 64                             }
 65                         }
 66                     }
 67                 } else {
 68                     this.ranges.splice(i, 0, [index, index]);
 69                 }
 70             }
 71         },
 72 
 73         remove: function(index) {
 74             var i = 0;
 75             while (i < this.ranges.length && index > this.ranges[i++][1]);
 76             i--;
 77             if (this.ranges[i]) {
 78                 if (index == (this.ranges[i][1])) {
 79                     if (index == (this.ranges[i][0])) {
 80                         this.ranges.splice(i, 1);
 81                     } else {
 82                         this.ranges[i][1]--;
 83                     }
 84                 } else {
 85                     if (index == (this.ranges[i][0])) {
 86                         this.ranges[i][0]++;
 87                     } else {
 88                         this.ranges.splice(i + 1, 0, [index + 1, this.ranges[i][1]]);
 89                         this.ranges[i][1] = index - 1;
 90                     }
 91                 }
 92             }
 93         },
 94 
 95         clear: function() {
 96             this.ranges = [];
 97         },
 98 
 99         contains: function(index) {
100             var i = 0;
101             while (i < this.ranges.length && index >= this.ranges[i][0]) {
102                 if (index >= this.ranges[i][0] && index <= this.ranges[i][1]) {
103                     return true;
104                 } else {
105                     i++;
106                 }
107             }
108             return false;
109         },
110 
111         toString: function() {
112             var ret = new Array(this.ranges.length);
113             for (var i = 0; i < this.ranges.length; i++) {
114                 ret[i] = this.ranges[i].join();
115             }
116             return ret.join(";");
117         }
118     };
119 
120     var WIDTH_CLASS_NAME_BASE = "rf-edt-c-";
121     var MIN_WIDTH = 20;
122 
123     rf.ui = rf.ui || {};
124 
125     rf.ui.ExtendedDataTable = rf.BaseComponent.extendClass({
126 
127             name: "ExtendedDataTable",
128 
129             init: function (id, rowCount, ajaxFunction, options) {
130                 $super.constructor.call(this, id);
131                 this.ranges = new rf.utils.Ranges();
132                 this.rowCount = rowCount;
133                 this.ajaxFunction = ajaxFunction;
134                 this.options = options || {};
135                 this.element = this.attachToDom();
136                 this.newWidths = {};
137                 this.storeDomReferences();
138                 if (this.options['onready'] && typeof this.options['onready'] == 'function') {
139                     rf.Event.bind(this.element, "rich:ready", this.options['onready']);
140                 }
141                 this.resizeEventName = "resize.rf.edt." + this.id;
142                 $(document).ready($.proxy(this.initialize, this));
143                 this.activateResizeListener();
144                 $(this.scrollElement).bind("scroll", $.proxy(this.updateScrollPosition, this));
145                 this.bindHeaderHandlers();
146                 $(this.element).bind("rich:onajaxcomplete", $.proxy(this.ajaxComplete, this));
147                 
148                 this.resizeData = {};
149                 this.idOfReorderingColumn = "";
150                 this.timeoutId = null;
151             },
152 
153             storeDomReferences: function() {
154                 this.dragElement = document.getElementById(this.id + ":d");
155                 this.reorderElement = document.getElementById(this.id + ":r");
156                 this.reorderMarkerElement = document.getElementById(this.id + ":rm");
157                 this.widthInput = document.getElementById(this.id + ":wi");
158                 this.selectionInput = document.getElementById(this.id + ":si");
159 
160 
161                 this.header = $(this.element).children(".rf-edt-hdr");
162                 this.headerCells = this.header.find(".rf-edt-hdr-c");
163                 this.footerCells = $(this.element).children(".rf-edt-ftr").find(".rf-edt-ftr-c");
164                 this.resizerHolders = this.header.find(".rf-edt-rsz-cntr");
165 
166                 this.frozenHeaderPartElement = document.getElementById(this.id + ":frozenHeader");
167                 this.frozenColumnCount = this.frozenHeaderPartElement ? this.frozenHeaderPartElement.children[0].rows[0].cells.length : 0;//TODO Richfaces.firstDescendant;
168 
169                 this.headerElement = document.getElementById(this.id + ":header");
170                 this.footerElement = document.getElementById(this.id + ":footer");
171                 this.scrollElement = document.getElementById(this.id + ":scrl");
172                 this.scrollContentElement = document.getElementById(this.id + ":scrl-cnt");
173 
174             },
175 
176             getColumnPosition: function(id) {
177                 var position;
178                 for (var i = 0; i < this.headerCells.length; i++) {
179                     if (id == this.headerCells[i].className.match(new RegExp(WIDTH_CLASS_NAME_BASE + "([^\\W]*)"))[1]) {
180                         position = i;
181                     }
182                 }
183                 return position;
184             },
185 
186             setColumnPosition: function(id, position) {
187                 var colunmsOrder = "";
188                 var before;
189                 for (var i = 0; i < this.headerCells.length; i++) {
190                     var current = this.headerCells[i].className.match(new RegExp(WIDTH_CLASS_NAME_BASE + "([^\\W]*)"))[1];
191                     if (i == position) {
192                         if (before) {
193                             colunmsOrder += current + "," + id + ",";
194                         } else {
195                             colunmsOrder += id + "," + current + ",";
196                         }
197                     } else {
198                         if (id != current) {
199                             colunmsOrder += current + ",";
200                         } else {
201                             before = true;
202                         }
203                     }
204                 }
205                 this.ajaxFunction(null, {"rich:columnsOrder" : colunmsOrder}); // TODO Maybe, event model should be used here.
206             },
207 
208             setColumnWidth: function(columnId, width) {
209                 width = width + "px";
210                 var $table = $(document.getElementById(this.element.id));
211                 $table.find("." + WIDTH_CLASS_NAME_BASE + columnId).parent().css('width', width);
212                 $table.find("." + WIDTH_CLASS_NAME_BASE + columnId).css('width', width);
213                 this.newWidths[columnId] = width;
214                 var widthsArray = new Array();
215                 for (var id in this.newWidths) {
216                     widthsArray.push(id + ":" + this.newWidths[id]);
217                 }
218                 this.widthInput.value = widthsArray.toString();
219                 this.updateLayout();
220                 this.adjustResizers();
221                 this.ajaxFunction(); // TODO Maybe, event model should be used here.
222             },
223 
224             filter: function(colunmId, filterValue, isClear) {
225                 if (typeof(filterValue) == "undefined" || filterValue == null) {
226                     filterValue = "";
227                 }
228                 var map = {};
229                 map[this.id + "rich:filtering"] = colunmId + ":" + filterValue + ":" + isClear;
230                 this.ajaxFunction(null, map); // TODO Maybe, event model should be used here.
231             },
232 
233             clearFiltering: function() {
234                 this.filter("", "", true);
235             },
236 
237             sortHandler: function(event) {
238                 var sortHandle = $(event.data.sortHandle);
239                 var button = sortHandle.find('.rf-edt-srt-btn');
240                 var columnId = button.data('columnid');
241                 var sortOrder = button.hasClass('rf-edt-srt-asc') ? 'descending' : 'ascending';
242                 this.sort(columnId, sortOrder, false);
243             },
244 
245             filterHandler: function(event) {
246                 var filterHandle = $(event.data.filterHandle);
247                 var columnId = filterHandle.data('columnid');
248                 var filterValue = filterHandle.val();
249                 this.filter(columnId, filterValue, false);
250             },
251 
252 
253             sort: function(colunmId, sortOrder, isClear) {
254                 if (typeof(sortOrder) == "string") {
255                     sortOrder = sortOrder.toLowerCase();
256                 }
257                 var map = {}
258                 map[this.id + "rich:sorting"] = colunmId + ":" + sortOrder + ":" + isClear;
259                 this.ajaxFunction(null, map); // TODO Maybe, event model should be used here.
260             },
261 
262             clearSorting: function() {
263                 this.sort("", "", true);
264             },
265 
266             destroy: function() {
267                 $(window).unbind("resize", this.updateLayout);
268                 $(rf.getDomElement(this.id + ':st')).remove();
269                 $super.destroy.call(this);
270             },
271 
272             bindHeaderHandlers: function() {
273                 this.header.find(".rf-edt-rsz").bind("mousedown", $.proxy(this.beginResize, this));
274                 this.headerCells.bind("mousedown", $.proxy(this.beginReorder, this));
275                 var self = this;
276                 this.header.find(".rf-edt-c-srt").each(function() {
277                     $(this).bind("click", {sortHandle: this}, $.proxy(self.sortHandler, self));
278                 });
279                 this.header.find(".rf-edt-flt-i").each(function() {
280                     $(this).bind("blur", {filterHandle: this}, $.proxy(self.filterHandler, self));
281                 });
282             },
283 
284             updateLayout: function() {
285                 this.deActivateResizeListener();
286                 this.headerCells.height("auto");
287                 var headerCellHeight = 0;
288                 this.headerCells.each(function() {
289                     if (this.clientHeight > headerCellHeight) {
290                         headerCellHeight = this.clientHeight;
291                     }
292                 });
293                 this.headerCells.height(headerCellHeight + "px");
294                 this.footerCells.height("auto");
295                 var footerCellHeight = 0;
296                 this.footerCells.each(function() {
297                     if (this.clientHeight > footerCellHeight) {
298                         footerCellHeight = this.clientHeight;
299                     }
300                 });
301                 this.footerCells.height(footerCellHeight + "px");
302                 this.contentDivElement.css('width', 'auto');
303                 var offsetWidth = this.frozenHeaderPartElement ? this.frozenHeaderPartElement.offsetWidth : 0;
304                 var width = Math.max(0, this.element.clientWidth - offsetWidth);
305                 if (width) {
306                     this.parts.each(function() {
307                         this.style.width = "auto";
308                     });
309                     var contentWidth = this.parts.width();
310                     if (contentWidth > width) {
311                         this.contentDivElement.css('width', width + 'px');
312                     }
313                     this.contentDivElement.css('display', 'block');
314                     // update scroller and scroll-content
315                     if (contentWidth > width) {
316                         this.parts.each(function() {
317                             this.style.width = width + "px";
318                         });
319                         this.scrollElement.style.display = "block";
320                         this.scrollElement.style.overflowX = "scroll";
321                         this.scrollElement.style.width = width + "px";
322                         this.scrollContentElement.style.width = contentWidth + "px";
323                         this.updateScrollPosition();
324                     } else {
325                         this.parts.each(function() {
326                             this.style.width = "";
327                         });
328                         this.scrollElement.style.display = "none";
329                     }
330                 } else {
331                     this.contentDivElement.css('display', 'none');
332                 }
333                 var height = this.element.clientHeight;
334                 var el = this.element.firstChild;
335                 while (el && (!el.nodeName || el.nodeName.toUpperCase() != "TABLE")) {
336                     if (el.nodeName && el.nodeName.toUpperCase() == "DIV" && el != this.bodyElement) {
337                         height -= el.offsetHeight;
338                     }
339                     el = el.nextSibling;
340                 }
341                 if (this.bodyElement.offsetHeight > height || !this.contentElement) {
342                     this.bodyElement.style.height = height + "px";
343                 }
344                 this.activateResizeListener();
345             },
346 
347             adjustResizers: function() { //For IE7 only.
348                 var scrollLeft = this.scrollElement ? this.scrollElement.scrollLeft : 0;
349                 var clientWidth = this.element.clientWidth - 3;
350                 var i = 0;
351                 for (; i < this.frozenColumnCount; i++) {
352                     if (clientWidth > 0) {
353                         this.resizerHolders[i].style.display = "none";
354                         this.resizerHolders[i].style.display = "";
355                         clientWidth -= this.resizerHolders[i].offsetWidth;
356                     }
357                     if (clientWidth <= 0) {
358                         this.resizerHolders[i].style.display = "none";
359                     }
360                 }
361                 scrollLeft -= 3;
362                 for (; i < this.resizerHolders.length; i++) {
363                     if (clientWidth > 0) {
364                         this.resizerHolders[i].style.display = "none";
365                         if (scrollLeft > 0) {
366                             this.resizerHolders[i].style.display = "";
367                             scrollLeft -= this.resizerHolders[i].offsetWidth;
368                             if (scrollLeft > 0) {
369                                 this.resizerHolders[i].style.display = "none";
370                             } else {
371                                 clientWidth += scrollLeft;
372                             }
373                         } else {
374                             this.resizerHolders[i].style.display = "";
375                             clientWidth -= this.resizerHolders[i].offsetWidth;
376                         }
377                     }
378                     if (clientWidth <= 0) {
379                         this.resizerHolders[i].style.display = "none";
380                     }
381                 }
382             },
383 
384             updateScrollPosition: function() {
385                 if (this.scrollElement) {
386                     var scrollLeft = this.scrollElement.scrollLeft;
387                     this.parts.each(function() {
388                         this.scrollLeft = scrollLeft;
389                     });
390                 }
391                 this.adjustResizers();
392             },
393 
394             initialize: function() {
395                 this.deActivateResizeListener();
396                 if (! $(this.element).is(":visible")) {
397                     this.showOffscreen(this.element);
398                 }
399                 this.bodyElement = document.getElementById(this.id + ":b");
400                 this.bodyElement.tabIndex = -1; //TODO don't use tabIndex.
401                 this.contentDivElement = $(this.bodyElement).find('.rf-edt-cnt');
402                 var bodyJQuery = $(this.bodyElement);
403                 this.contentElement = bodyJQuery.children("div:not(.rf-edt-ndt):first")[0];
404                 if (this.contentElement) {
405                     this.spacerElement = this.contentElement.children[0];//TODO this.marginElement = Richfaces.firstDescendant(this.contentElement);
406                     this.dataTableElement = this.contentElement.lastChild;//TODO this.dataTableElement = Richfaces.lastDescendant(this.contentElement);
407                     this.tbodies = $(document.getElementById(this.id + ":tbf")).add(document.getElementById(this.id + ":tbn"));
408                     this.rows = this.tbodies[0].rows.length;
409                     this.rowHeight = this.dataTableElement.offsetHeight / this.rows;
410                     if (this.rowCount != this.rows) {
411                         this.contentElement.style.height = (this.rowCount * this.rowHeight) + "px";
412                     }
413                     bodyJQuery.bind("scroll", $.proxy(this.bodyScrollListener, this));
414                     if (this.options.selectionMode != "none") {
415                         this.tbodies.bind("click", $.proxy(this.selectionClickListener, this));
416                         bodyJQuery.bind(window.opera ? "keypress" : "keydown", $.proxy(this.selectionKeyDownListener, this));
417                         this.initializeSelection();
418                     }
419                 } else {
420                     this.spacerElement = null;
421                     this.dataTableElement = null;
422                 }
423                 var edt = this.element;
424                 this.parts = $(this.element).find(".rf-edt-cnt, .rf-edt-ftr-cnt").filter(function() {
425                     return $(this).parents('.rf-edt').get(0) === edt;
426                 });
427                 this.updateLayout();
428                 this.updateScrollPosition(); //TODO Restore horizontal scroll position
429                 if ($(this.element).data('offscreenElements')) {
430                     this.hideOffscreen(this.element);
431                 }
432                 this.activateResizeListener();
433                 $(this.element).trigger("rich:ready", this);
434             },
435 
436             showOffscreen: function(element) {
437                 var $element = $(element);
438                 var offscreenElements = $element.parents(":not(:visible)").addBack().toArray().reverse();
439                 var that = this;
440                 $.each(offscreenElements, function() {
441                     $this = $(this);
442                     if ($this.css('display') === 'none') {
443                         that.showOffscreenElement($(this));
444                     }
445                 })
446                 $element.data('offscreenElements', offscreenElements);
447             },
448 
449             hideOffscreen: function(element) {
450                 var $element = $(element);
451                 var offscreenElements = $element.data('offscreenElements');
452                 var that = this;
453                 $.each(offscreenElements, function() {
454                     $this = $(this);
455                     if ($this.data('offscreenOldValues')) {
456                         that.hideOffscreenElement($(this));
457                     }
458                 })
459                 $element.removeData('offscreenElements');
460             },
461 
462             showOffscreenElement: function($element) {
463                 var offscreenOldValues = {};
464                 offscreenOldValues.oldPosition = $element.css('position');
465                 offscreenOldValues.oldLeft = $element.css('left');
466                 offscreenOldValues.oldDisplay = $element.css('display');
467                 $element.css('position', 'absolute');
468                 $element.css('left', '-10000');
469                 $element.css('display', 'block');
470                 $element.data('offscreenOldValues', offscreenOldValues);
471             },
472 
473             hideOffscreenElement: function($element) {
474                 var offscreenOldValues = $element.data('offscreenOldValues');
475                 $element.css('display', offscreenOldValues.oldDisplay);
476                 $element.css('left', offscreenOldValues.oldLeft);
477                 $element.css('position', offscreenOldValues.oldPosition);
478                 $element.removeData('offscreenOldValues');
479             },
480 
481             drag: function(event) {
482                 $(this.dragElement).setPosition({left:Math.max(this.resizeData.left + MIN_WIDTH, event.pageX)});
483                 return false;
484             },
485 
486             beginResize: function(event) {
487                 var id = event.currentTarget.parentNode.className.match(new RegExp(WIDTH_CLASS_NAME_BASE + "([^\\W]*)"))[1];
488                 this.resizeData = {
489                     id : id,
490                     left : $(event.currentTarget).parent().offset().left
491                 };
492                 this.dragElement.style.height = this.element.offsetHeight + "px";
493                 $(this.dragElement).setPosition({top:$(this.element).offset().top, left:event.pageX});
494                 this.dragElement.style.display = "block";
495                 $(document).bind("mousemove", $.proxy(this.drag, this));
496                 $(document).one("mouseup", $.proxy(this.endResize, this));
497                 return false;
498             },
499 
500             endResize: function(event) {
501                 $(document).unbind("mousemove", this.drag);
502                 this.dragElement.style.display = "none";
503                 var width = Math.max(MIN_WIDTH, event.pageX - this.resizeData.left);
504                 this.setColumnWidth(this.resizeData.id, width);
505             },
506 
507             reorder: function(event) {
508                 $(this.reorderElement).setPosition(event, {offset:[5,5]});
509                 this.reorderElement.style.display = "block";
510                 return false;
511             },
512 
513             beginReorder: function(event) {
514                 if (!$(event.target).is("a, img, :input")) {
515                     this.idOfReorderingColumn = event.currentTarget.className.match(new RegExp(WIDTH_CLASS_NAME_BASE + "([^\\W]*)"))[1];
516                     $(document).bind("mousemove", $.proxy(this.reorder, this));
517                     this.headerCells.bind("mouseover", $.proxy(this.overReorder, this));
518                     $(document).one("mouseup", $.proxy(this.cancelReorder, this));
519                     return false;
520                 }
521             },
522 
523             overReorder: function(event) {
524                 if (this.idOfReorderingColumn != event.currentTarget.className.match(new RegExp(WIDTH_CLASS_NAME_BASE + "([^\\W]*)"))[1]) {
525                     var eventElement = $(event.currentTarget);
526                     var offset = eventElement.offset();
527                     $(this.reorderMarkerElement).setPosition({top:offset.top + eventElement.height(), left:offset.left - 5});
528                     this.reorderMarkerElement.style.display = "block";
529                     eventElement.one("mouseout", $.proxy(this.outReorder, this));
530                     eventElement.one("mouseup", $.proxy(this.endReorder, this));
531                 }
532             },
533 
534             outReorder: function(event) {
535                 this.reorderMarkerElement.style.display = "";
536                 $(event.currentTarget).unbind("mouseup", this.endReorder);
537             },
538 
539             endReorder: function(event) {
540                 this.reorderMarkerElement.style.display = "";
541                 $(event.currentTarget).unbind("mouseout", this.outReorder);
542                 var id = event.currentTarget.className.match(new RegExp(WIDTH_CLASS_NAME_BASE + "([^\\W]*)"))[1];
543                 var colunmsOrder = "";
544                 var _this = this;
545                 this.headerCells.each(function() {
546                     var i = this.className.match(new RegExp(WIDTH_CLASS_NAME_BASE + "([^\\W]*)"))[1];
547                     if (i == id) {
548                         colunmsOrder += _this.idOfReorderingColumn + "," + id + ",";
549                     } else if (i != _this.idOfReorderingColumn) {
550                         colunmsOrder += i + ",";
551                     }
552                 });
553                 this.ajaxFunction(event, {"rich:columnsOrder" : colunmsOrder}); // TODO Maybe, event model should be used here.
554             },
555 
556             cancelReorder: function(event) {
557                 $(document).unbind("mousemove", this.reorder);
558                 this.headerCells.unbind("mouseover", this.overReorder);
559                 this.reorderElement.style.display = "none";
560             },
561 
562             loadData: function(event) {
563                 var clientFirst = Math.round((this.bodyElement.scrollTop + this.bodyElement.clientHeight / 2) / this.rowHeight - this.rows / 2);
564                 if (clientFirst <= 0) {
565                     clientFirst = 0;
566                 } else {
567                     clientFirst = Math.min(this.rowCount - this.rows, clientFirst);
568                 }
569                 this.ajaxFunction(event, {"rich:clientFirst" : clientFirst});// TODO Maybe, event model should be used here.
570             },
571 
572             bodyScrollListener: function(event) {
573                 if (this.timeoutId) {
574                     window.clearTimeout(this.timeoutId);
575                     this.timeoutId = null;
576                 }
577                 if (Math.max(event.currentTarget.scrollTop - this.rowHeight, 0) < this.spacerElement.offsetHeight
578                     || Math.min(event.currentTarget.scrollTop + this.rowHeight + event.currentTarget.clientHeight, event.currentTarget.scrollHeight) > this.spacerElement.offsetHeight + this.dataTableElement.offsetHeight) {
579                     var _this = this;
580                     this.timeoutId = window.setTimeout(function (event) {
581                         _this.loadData(event)
582                     }, 1000);
583                 }
584             },
585 
586             showActiveRow: function() {
587                 if (this.bodyElement.scrollTop > this.activeIndex * this.rowHeight + this.spacerElement.offsetHeight) { //UP
588                     this.bodyElement.scrollTop = Math.max(this.bodyElement.scrollTop - this.rowHeight, 0);
589                 } else if (this.bodyElement.scrollTop + this.bodyElement.clientHeight
590                     < (this.activeIndex + 1) * this.rowHeight + this.spacerElement.offsetHeight) { //DOWN
591                     this.bodyElement.scrollTop = Math.min(this.bodyElement.scrollTop + this.rowHeight, this.bodyElement.scrollHeight - this.bodyElement.clientHeight);
592                 }
593             },
594 
595             selectRow: function(index) {
596                 this.ranges.add(index);
597                 for (var i = 0; i < this.tbodies.length; i++) {
598                     $(this.tbodies[i].rows[index]).addClass("rf-edt-r-sel");
599                 }
600             },
601 
602             deselectRow: function (index) {
603                 this.ranges.remove(index);
604                 for (var i = 0; i < this.tbodies.length; i++) {
605                     $(this.tbodies[i].rows[index]).removeClass("rf-edt-r-sel");
606                 }
607             },
608 
609             setActiveRow: function (index) {
610                 if (typeof this.activeIndex == "number") {
611                     for (var i = 0; i < this.tbodies.length; i++) {
612                         $(this.tbodies[i].rows[this.activeIndex]).removeClass("rf-edt-r-act");
613                     }
614 
615                 }
616                 this.activeIndex = index;
617                 for (var i = 0; i < this.tbodies.length; i++) {
618                     $(this.tbodies[i].rows[this.activeIndex]).addClass("rf-edt-r-act");
619                 }
620             },
621 
622             resetShiftRow: function () {
623                 if (typeof this.shiftIndex == "number") {
624                     for (var i = 0; i < this.tbodies.length; i++) {
625                         $(this.tbodies[i].rows[this.shiftIndex]).removeClass("rf-edt-r-sht");
626                     }
627 
628                 }
629                 this.shiftIndex = null;
630             },
631 
632             setShiftRow: function (index) {
633                 this.resetShiftRow();
634                 this.shiftIndex = index;
635                 if (typeof index == "number") {
636                     for (var i = 0; i < this.tbodies.length; i++) {
637                         $(this.tbodies[i].rows[this.shiftIndex]).addClass("rf-edt-r-sht");
638                     }
639                 }
640             },
641 
642             initializeSelection: function() {
643                 this.ranges.clear();
644                 var strings = this.selectionInput.value.split("|");
645                 this.activeIndex = strings[1] || null;
646                 this.shiftIndex = strings[2] || null;
647                 this.selectionFlag = null;
648                 var rows = this.tbodies[0].rows;
649                 for (var i = 0; i < rows.length; i++) {
650                     var row = $(rows[i]);
651                     if (row.hasClass("rf-edt-r-sel")) {
652                         this.ranges.add(row[0].rowIndex)
653                     }
654                     if (row.hasClass("rf-edt-r-act")) {
655                         this.activeIndex = row[0].rowIndex;
656                     }
657                     if (row.hasClass("rf-edt-r-sht")) {
658                         this.shiftIndex = row[0].rowIndex;
659                     }
660                 }
661                 this.writeSelection();
662             },
663 
664             writeSelection: function() {
665                 this.selectionInput.value = [this.ranges, this.activeIndex, this.shiftIndex, this.selectionFlag].join("|");
666             },
667 
668             selectRows: function(range) {
669                 if (typeof range == "number") {
670                     range = [range, range];
671                 }
672                 var changed;
673                 var i = 0;
674                 for (; i < range[0]; i++) {
675                     if (this.ranges.contains(i)) {
676                         this.deselectRow(i);
677                         changed = true;
678                     }
679                 }
680                 for (; i <= range[1]; i++) {
681                     if (!this.ranges.contains(i)) {
682                         this.selectRow(i);
683                         changed = true;
684                     }
685                 }
686                 for (; i < this.rows; i++) {
687                     if (this.ranges.contains(i)) {
688                         this.deselectRow(i);
689                         changed = true;
690                     }
691                 }
692                 this.selectionFlag = typeof this.shiftIndex == "string" ? this.shiftIndex : "x";
693                 return changed;
694             },
695 
696             processSlectionWithShiftKey: function(index) {
697                 if (this.shiftIndex == null) {
698                     this.setShiftRow(this.activeIndex != null ? this.activeIndex : index);
699                 }
700                 var range;
701                 if ("u" == this.shiftIndex) {
702                     range = [0, index];
703                 } else if ("d" == this.shiftIndex) {
704                     range = [index, this.rows - 1];
705                 } else if (index >= this.shiftIndex) {
706                     range = [this.shiftIndex, index];
707                 } else {
708                     range = [index, this.shiftIndex];
709                 }
710                 return this.selectRows(range);
711             },
712 
713             onbeforeselectionchange: function (event) {
714                 return !this.options.onbeforeselectionchange || this.options.onbeforeselectionchange.call(this.element, event) != false;
715             },
716 
717             onselectionchange: function (event, index, changed) {
718                 if (!event.shiftKey) {
719                     this.resetShiftRow();
720                 }
721                 if (this.activeIndex != index) {
722                     this.setActiveRow(index);
723                     this.showActiveRow();
724                 }
725                 if (changed) {
726                     this.writeSelection();
727                     if (this.options.onselectionchange) {
728                         this.options.onselectionchange.call(this.element, event);
729                     }
730                 }
731             },
732 
733             selectionClickListener: function (event) {
734                 if (!this.onbeforeselectionchange(event)) {
735                     return;
736                 }
737                 var changed;
738                 if (event.shiftKey || event.ctrlKey) {
739                     if (window.getSelection) { //TODO Try to find other way.
740                         window.getSelection().removeAllRanges();
741                     } else if (document.selection) {
742                         document.selection.empty();
743                     }
744                 }
745                 var tr = event.target;
746                 while (this.tbodies.index(tr.parentNode) == -1) {
747                     tr = tr.parentNode;
748                 }
749                 var index = tr.rowIndex;
750                 if (this.options.selectionMode == "single" || (this.options.selectionMode != "multipleKeyboardFree"
751                     && !event.shiftKey && !event.ctrlKey)) {
752                     changed = this.selectRows(index);
753                 } else if (this.options.selectionMode == "multipleKeyboardFree" || (!event.shiftKey && event.ctrlKey)) {
754                     if (this.ranges.contains(index)) {
755                         this.deselectRow(index);
756                     } else {
757                         this.selectRow(index);
758                     }
759                     changed = true;
760                 } else {
761                     changed = this.processSlectionWithShiftKey(index);
762                 }
763                 this.onselectionchange(event, index, changed);
764             },
765 
766             selectionKeyDownListener: function(event) {
767                 if (event.ctrlKey && this.options.selectionMode != "single" && (event.keyCode == 65 || event.keyCode == 97) //Ctrl-A
768                     && this.onbeforeselectionchange(event)) {
769                     this.selectRows([0, rows]);
770                     this.selectionFlag = "a";
771                     this.onselectionchange(event, this.activeIndex, true); //TODO Is there a way to know that selection haven't changed?
772                     event.preventDefault();
773                 } else {
774                     var index;
775                     if (event.keyCode == 38) { //UP
776                         index = -1;
777                     } else if (event.keyCode == 40) { //DOWN
778                         index = 1;
779                     }
780                     if (index != null && this.onbeforeselectionchange(event)) {
781                         if (typeof this.activeIndex == "number") {
782                             index += this.activeIndex;
783                             if (index >= 0 && index < this.rows) {
784                                 var changed;
785                                 if (this.options.selectionMode == "single" || (!event.shiftKey && !event.ctrlKey)) {
786                                     changed = this.selectRows(index);
787                                 } else if (event.shiftKey) {
788                                     changed = this.processSlectionWithShiftKey(index);
789                                 }
790                                 this.onselectionchange(event, index, changed);
791                             }
792                         }
793                         event.preventDefault();
794                     }
795                 }
796             },
797 
798             ajaxComplete: function (event, data) {
799                 this.storeDomReferences();
800                 if (data.reinitializeHeader) {
801                     this.bindHeaderHandlers();
802                     this.updateLayout();
803                 } else {
804                     this.selectionInput = document.getElementById(this.id + ":si");
805                     if (data.reinitializeBody) {
806                         this.rowCount = data.rowCount;
807                         this.initialize();
808                     } else if (this.options.selectionMode != "none") {
809                         this.initializeSelection();
810                     }
811                     if (this.spacerElement) {
812                         this.spacerElement.style.height = (data.first * this.rowHeight) + "px";
813                     }
814                 }
815                 
816                 // resize columns if necessary
817                 var $table = $(document.getElementById(this.element.id)),
818                     widthsArray = new Array();
819                 for (var id in this.newWidths) {
820                     $table.find("." + WIDTH_CLASS_NAME_BASE + id).css('width', this.newWidths[id])
821                         .parent().css('width', this.newWidths[id]);
822                     widthsArray.push(id + ":" + this.newWidths[id]);
823                 }
824                 this.widthInput.value = widthsArray.toString();
825                 this.updateLayout();
826                 this.adjustResizers();
827             },
828 
829             activateResizeListener: function() {
830                 if (typeof this.resizeEventName !== "undefined") {
831                     $(window).on(this.resizeEventName, $.proxy(this.updateLayout, this));
832                 }
833             },
834 
835             deActivateResizeListener: function() {
836                 if (typeof this.resizeEventName !== "undefined") {
837                     $(window).off(this.resizeEventName);
838                 }
839             },
840 
841             contextMenuAttach: function (menu) {
842                 var selector = "[id='" + this.element.id + "'] ";
843                 selector += (typeof menu.options.targetSelector === 'undefined')
844                     ?  ".rf-edt-b td" : menu.options.targetSelector;
845                 selector = $.trim(selector);
846                 rf.Event.bind(selector, menu.options.showEvent, $.proxy(menu.__showHandler, menu), menu);
847             },
848 
849             contextMenuShow: function (menu, event) {
850                 var tr = event.target;
851                 while (this.tbodies.index(tr.parentNode) == -1) {
852                     tr = tr.parentNode;
853                 }
854                 var index = tr.rowIndex;
855                 if (! this.ranges.contains(index) ) {
856                     this.selectionClickListener(event);
857                 }
858             }
859         });
860 
861     var $super = rf.ui.ExtendedDataTable.$super;
862 }(RichFaces.jQuery, window.RichFaces));
863