      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