1/** 2 * @license 3 * (c) 2009-2013 Michael Leibman 4 * michael{dot}leibman{at}gmail{dot}com 5 * http://github.com/mleibman/slickgrid 6 * 7 * Distributed under MIT license. 8 * All rights reserved. 9 * 10 * SlickGrid v2.2 11 * 12 * NOTES: 13 * Cell/row DOM manipulations are done directly bypassing jQuery's DOM manipulation methods. 14 * This increases the speed dramatically, but can only be done safely because there are no event handlers 15 * or data associated with any cell/row DOM nodes. Cell editors must make sure they implement .destroy() 16 * and do proper cleanup. 17 */ 18 19// make sure required JavaScript modules are loaded 20if (typeof jQuery === "undefined") { 21 throw "SlickGrid requires jquery module to be loaded"; 22} 23if (!jQuery.fn.drag) { 24 throw "SlickGrid requires jquery.event.drag module to be loaded"; 25} 26if (typeof Slick === "undefined") { 27 throw "slick.core.js not loaded"; 28} 29 30 31(function ($) { 32 // Slick.Grid 33 $.extend(true, window, { 34 Slick: { 35 Grid: SlickGrid 36 } 37 }); 38 39 // shared across all grids on the page 40 var scrollbarDimensions; 41 var maxSupportedCssHeight; // browser's breaking point 42 43 ////////////////////////////////////////////////////////////////////////////////////////////// 44 // SlickGrid class implementation (available as Slick.Grid) 45 46 /** 47 * Creates a new instance of the grid. 48 * @class SlickGrid 49 * @constructor 50 * @param {Node} container Container node to create the grid in. 51 * @param {Array,Object} data An array of objects for databinding. 52 * @param {Array} columns An array of column definitions. 53 * @param {Object} options Grid options. 54 **/ 55 function SlickGrid(container, data, columns, options) { 56 // settings 57 var defaults = { 58 explicitInitialization: false, 59 rowHeight: 25, 60 defaultColumnWidth: 80, 61 enableAddRow: false, 62 leaveSpaceForNewRows: false, 63 editable: false, 64 autoEdit: true, 65 enableCellNavigation: true, 66 enableColumnReorder: true, 67 asyncEditorLoading: false, 68 asyncEditorLoadDelay: 100, 69 forceFitColumns: false, 70 enableAsyncPostRender: false, 71 asyncPostRenderDelay: 50, 72 autoHeight: false, 73 editorLock: Slick.GlobalEditorLock, 74 showHeaderRow: false, 75 headerRowHeight: 25, 76 showTopPanel: false, 77 topPanelHeight: 25, 78 formatterFactory: null, 79 editorFactory: null, 80 cellFlashingCssClass: "flashing", 81 selectedCellCssClass: "selected", 82 multiSelect: true, 83 enableTextSelectionOnCells: false, 84 dataItemColumnValueExtractor: null, 85 fullWidthRows: false, 86 multiColumnSort: false, 87 defaultFormatter: defaultFormatter, 88 forceSyncScrolling: false, 89 addNewRowCssClass: "new-row" 90 }; 91 92 var columnDefaults = { 93 name: "", 94 resizable: true, 95 sortable: false, 96 minWidth: 30, 97 rerenderOnResize: false, 98 headerCssClass: null, 99 defaultSortAsc: true, 100 focusable: true, 101 selectable: true 102 }; 103 104 // scroller 105 var th; // virtual height 106 var h; // real scrollable height 107 var ph; // page height 108 var n; // number of pages 109 var cj; // "jumpiness" coefficient 110 111 var page = 0; // current page 112 var offset = 0; // current page offset 113 var vScrollDir = 1; 114 115 // private 116 var initialized = false; 117 var $container; 118 var uid = "slickgrid_" + Math.round(1000000 * Math.random()); 119 var self = this; 120 var $focusSink, $focusSink2; 121 var $headerScroller; 122 var $headers; 123 var $headerRow, $headerRowScroller, $headerRowSpacer; 124 var $topPanelScroller; 125 var $topPanel; 126 var $viewport; 127 var $canvas; 128 var $style; 129 var $boundAncestors; 130 var stylesheet, columnCssRulesL, columnCssRulesR; 131 var viewportH, viewportW; 132 var canvasWidth; 133 var viewportHasHScroll, viewportHasVScroll; 134 var headerColumnWidthDiff = 0, headerColumnHeightDiff = 0, // border+padding 135 cellWidthDiff = 0, cellHeightDiff = 0; 136 var absoluteColumnMinWidth; 137 138 var tabbingDirection = 1; 139 var activePosX; 140 var activeRow, activeCell; 141 var activeCellNode = null; 142 var currentEditor = null; 143 var serializedEditorValue; 144 var editController; 145 146 var rowsCache = {}; 147 var renderedRows = 0; 148 var numVisibleRows; 149 var prevScrollTop = 0; 150 var scrollTop = 0; 151 var lastRenderedScrollTop = 0; 152 var lastRenderedScrollLeft = 0; 153 var prevScrollLeft = 0; 154 var scrollLeft = 0; 155 156 var selectionModel; 157 var selectedRows = []; 158 159 var plugins = [];
160 var cellCssClasses = {}; 161 162 var columnsById = {}; 163 var sortColumns = []; 164 var columnPosLeft = []; 165 var columnPosRight = []; 166 167 168 // async call handles 169 var h_editorLoader = null; 170 var h_render = null; 171 var h_postrender = null; 172 var postProcessedRows = {}; 173 var postProcessToRow = null; 174 var postProcessFromRow = null; 175 176 // perf counters 177 var counter_rows_rendered = 0; 178 var counter_rows_removed = 0; 179 180 // These two variables work around a bug with inertial scrolling in Webkit/Blink on Mac. 181 // See http://crbug.com/312427. 182 var rowNodeFromLastMouseWheelEvent; // this node must not be deleted while inertial scrolling 183 var zombieRowNodeFromLastMouseWheelEvent; // node that was hidden instead of getting deleted 184 185 186 ////////////////////////////////////////////////////////////////////////////////////////////// 187 // Initialization 188 189 function init() { 190 $container = $(container); 191 if ($container.length < 1) { 192 throw new Error("SlickGrid requires a valid container, " + container + " does not exist in the DOM."); 193 } 194 195 // calculate these only once and share between grid instances 196 maxSupportedCssHeight = maxSupportedCssHeight || getMaxSupportedCssHeight(); 197 scrollbarDimensions = scrollbarDimensions || measureScrollbar(); 198 199 options = $.extend({}, defaults, options); 200 validateAndEnforceOptions(); 201 columnDefaults.width = options.defaultColumnWidth; 202 203 columnsById = {}; 204 for (var i = 0; i < columns.length; i++) { 205 var m = columns[i] = $.extend({}, columnDefaults, columns[i]); 206 columnsById[m.id] = i; 207 if (m.minWidth && m.width < m.minWidth) { 208 m.width = m.minWidth; 209 } 210 if (m.maxWidth && m.width > m.maxWidth) { 211 m.width = m.maxWidth; 212 } 213 } 214 215 // validate loaded JavaScript modules against requested options 216 if (options.enableColumnReorder && !$.fn.sortable) { 217 throw new Error("SlickGrid's 'enableColumnReorder = true' option requires jquery-ui.sortable module to be loaded"); 218 } 219 220 editController = { 221 "commitCurrentEdit": commitCurrentEdit, 222 "cancelCurrentEdit": cancelCurrentEdit 223 }; 224 225 $container 226 .empty() 227 .css("overflow", "hidden") 228 .css("outline", 0) 229 .addClass(uid) 230 .addClass("ui-widget"); 231 232 // set up a positioning container if needed 233 if (!/relative|absolute|fixed/.test($container.css("position"))) { 234 $container.css("position", "relative"); 235 } 236 237 $focusSink = $("<div tabIndex='0' hideFocus style='position:fixed;width:0;height:0;top:0;left:0;outline:0;'></div>").appendTo($container); 238 239 $headerScroller = $("<div class='slick-header ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container); 240 $headers = $("<div class='slick-header-columns' style='left:-1000px' />").appendTo($headerScroller); 241 $headers.width(getHeadersWidth()); 242 243 $headerRowScroller = $("<div class='slick-headerrow ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container); 244 $headerRow = $("<div class='slick-headerrow-columns' />").appendTo($headerRowScroller); 245 $headerRowSpacer = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>") 246 .css("width", getCanvasWidth() + scrollbarDimensions.width + "px") 247 .appendTo($headerRowScroller); 248 249 $topPanelScroller = $("<div class='slick-top-panel-scroller ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container); 250 $topPanel = $("<div class='slick-top-panel' style='width:10000px' />").appendTo($topPanelScroller); 251 252 if (!options.showTopPanel) { 253 $topPanelScroller.hide(); 254 } 255 256 if (!options.showHeaderRow) { 257 $headerRowScroller.hide(); 258 } 259 260 $viewport = $("<div class='slick-viewport' style='width:100%;overflow:auto;outline:0;position:relative;;'>").appendTo($container); 261 $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto"); 262 263 $canvas = $("<div class='grid-canvas' />").appendTo($viewport); 264 265 $focusSink2 = $focusSink.clone().appendTo($container); 266 267 if (!options.explicitInitialization) { 268 finishInitialization(); 269 } 270 } 271 272 function finishInitialization() { 273 if (!initialized) { 274 initialized = true; 275 276 viewportW = parseFloat($.css($container[0], "width", true)); 277 278 // header columns and cells may have different padding/border skewing width calculations (box-sizing, hello?) 279 // calculate the diff so we can set consistent sizes 280 measureCellPaddingAndBorder(); 281 282 // for usability reasons, all text selection in SlickGrid is disabled 283 // with the exception of input and textarea elements (selection must 284 // be enabled there so that editors work as expected); note that 285 // selection in grid cells (grid body) is already unavailable in 286 // all browsers except IE 287 disableSelection($headers); // disable all text selection in header (including input and textarea) 288 289 if (!options.enableTextSelectionOnCells) { 290 // disable text selection in grid cells except in input and textarea elements 291 // (this is IE-specific, because selectstart event will only fire in IE) 292 $viewport.bind("selectstart.ui", function (event) { 293 return $(event.target).is("input,textarea"); 294 }); 295 } 296 297 updateColumnCaches(); 298 createColumnHeaders(); 299 setupColumnSort(); 300 createCssRules();
301 resizeCanvas(); 302 bindAncestorScrollEvents(); 303 304 $container 305 .bind("resize.slickgrid", resizeCanvas); 306 $viewport 307 //.bind("click", handleClick) 308 .bind("scroll", handleScroll); 309 $headerScroller 310 .bind("contextmenu", handleHeaderContextMenu) 311 .bind("click", handleHeaderClick) 312 .delegate(".slick-header-column", "mouseenter", handleHeaderMouseEnter) 313 .delegate(".slick-header-column", "mouseleave", handleHeaderMouseLeave); 314 $headerRowScroller 315 .bind("scroll", handleHeaderRowScroll); 316 $focusSink.add($focusSink2) 317 .bind("keydown", handleKeyDown); 318 $canvas 319 .bind("keydown", handleKeyDown) 320 .bind("click", handleClick) 321 .bind("dblclick", handleDblClick) 322 .bind("contextmenu", handleContextMenu) 323 .bind("draginit", handleDragInit) 324 .bind("dragstart", {distance: 3}, handleDragStart) 325 .bind("drag", handleDrag) 326 .bind("dragend", handleDragEnd) 327 .delegate(".slick-cell", "mouseenter", handleMouseEnter) 328 .delegate(".slick-cell", "mouseleave", handleMouseLeave); 329 330 // Work around http://crbug.com/312427. 331 if (navigator.userAgent.toLowerCase().match(/webkit/) && 332 navigator.userAgent.toLowerCase().match(/macintosh/)) { 333 $canvas.bind("mousewheel", handleMouseWheel); 334 } 335 } 336 } 337 338 function registerPlugin(plugin) { 339 plugins.unshift(plugin); 340 plugin.init(self); 341 } 342 343 function unregisterPlugin(plugin) { 344 for (var i = plugins.length; i >= 0; i--) { 345 if (plugins[i] === plugin) { 346 if (plugins[i].destroy) { 347 plugins[i].destroy(); 348 } 349 plugins.splice(i, 1); 350 break; 351 } 352 } 353 } 354 355 function setSelectionModel(model) { 356 if (selectionModel) { 357 selectionModel.onSelectedRangesChanged.unsubscribe(handleSelectedRangesChanged); 358 if (selectionModel.destroy) { 359 selectionModel.destroy(); 360 } 361 } 362 363 selectionModel = model; 364 if (selectionModel) { 365 selectionModel.init(self); 366 selectionModel.onSelectedRangesChanged.subscribe(handleSelectedRangesChanged); 367 } 368 } 369 370 function getSelectionModel() { 371 return selectionModel; 372 } 373 374 function getCanvasNode() { 375 return $canvas[0]; 376 } 377 378 function measureScrollbar() { 379 var $c = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").appendTo("body"); 380 var dim = { 381 width: $c.width() - $c[0].clientWidth, 382 height: $c.height() - $c[0].clientHeight 383 }; 384 $c.remove(); 385 return dim; 386 } 387 388 function getHeadersWidth() { 389 var headersWidth = 0; 390 for (var i = 0, ii = columns.length; i < ii; i++) { 391 var width = columns[i].width; 392 headersWidth += width; 393 } 394 headersWidth += scrollbarDimensions.width; 395 return Math.max(headersWidth, viewportW) + 1000; 396 } 397 398 function getCanvasWidth() { 399 var availableWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW; 400 var rowWidth = 0; 401 var i = columns.length; 402 while (i--) { 403 rowWidth += columns[i].width; 404 } 405 return options.fullWidthRows ? Math.max(rowWidth, availableWidth) : rowWidth; 406 } 407 408 function updateCanvasWidth(forceColumnWidthsUpdate) { 409 var oldCanvasWidth = canvasWidth; 410 canvasWidth = getCanvasWidth(); 411 412 if (canvasWidth != oldCanvasWidth) { 413 $canvas.width(canvasWidth); 414 $headerRow.width(canvasWidth); 415 $headers.width(getHeadersWidth()); 416 viewportHasHScroll = (canvasWidth > viewportW - scrollbarDimensions.width); 417 } 418 419 $headerRowSpacer.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0)); 420 421 if (canvasWidth != oldCanvasWidth || forceColumnWidthsUpdate) { 422 applyColumnWidths(); 423 } 424 } 425 426 function disableSelection($target) { 427 if ($target && $target.jquery) { 428 $target 429 .attr("unselectable", "on") 430 .css("MozUserSelect", "none") 431 .bind("selectstart.ui", function () { 432 return false; 433 }); // from jquery:ui.core.js 1.7.2 434 } 435 } 436 437 function getMaxSupportedCssHeight() { 438 var supportedHeight = 1000000; 439 // FF reports the height back but still renders blank after ~6M px 440 var testUpTo = navigator.userAgent.toLowerCase().match(/firefox/) ? 6000000 : 1000000000; 441 var div = $("<div style='display:none' />").appendTo(document.body); 442 443 while (true) { 444 var test = supportedHeight * 2; 445 div.css("height", test); 446 if (test > testUpTo || div.height() !== test) { 447 break; 448 } else { 449 supportedHeight = test; 450 } 451 } 452 453 div.remove(); 454 return supportedHeight; 455 } 456 457 // TODO: this is static. need to handle page mutation.
458 function bindAncestorScrollEvents() { 459 var elem = $canvas[0]; 460 while ((elem = elem.parentNode) != document.body && elem != null) { 461 // bind to scroll containers only 462 if (elem == $viewport[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight) { 463 var $elem = $(elem); 464 if (!$boundAncestors) { 465 $boundAncestors = $elem; 466 } else { 467 $boundAncestors = $boundAncestors.add($elem); 468 } 469 $elem.bind("scroll." + uid, handleActiveCellPositionChange); 470 } 471 } 472 } 473 474 function unbindAncestorScrollEvents() { 475 if (!$boundAncestors) { 476 return; 477 } 478 $boundAncestors.unbind("scroll." + uid); 479 $boundAncestors = null; 480 } 481 482 function updateColumnHeader(columnId, title, toolTip) { 483 if (!initialized) { return; } 484 var idx = getColumnIndex(columnId); 485 if (idx == null) { 486 return; 487 } 488 489 var columnDef = columns[idx]; 490 var $header = $headers.children().eq(idx); 491 if ($header) { 492 if (title !== undefined) { 493 columns[idx].name = title; 494 } 495 if (toolTip !== undefined) { 496 columns[idx].toolTip = toolTip; 497 } 498 499 trigger(self.onBeforeHeaderCellDestroy, { 500 "node": $header[0], 501 "column": columnDef 502 }); 503 504 $header 505 .attr("title", toolTip || "") 506 .children().eq(0).html(title); 507 508 trigger(self.onHeaderCellRendered, { 509 "node": $header[0], 510 "column": columnDef 511 }); 512 } 513 } 514 515 function getHeaderRow() { 516 return $headerRow[0]; 517 } 518 519 function getHeaderRowColumn(columnId) { 520 var idx = getColumnIndex(columnId); 521 var $header = $headerRow.children().eq(idx); 522 return $header && $header[0]; 523 } 524 525 function createColumnHeaders() { 526 function onMouseEnter() { 527 $(this).addClass("ui-state-hover"); 528 } 529 530 function onMouseLeave() { 531 $(this).removeClass("ui-state-hover"); 532 } 533 534 $headers.find(".slick-header-column") 535 .each(function() { 536 var columnDef = $(this).data("column"); 537 if (columnDef) { 538 trigger(self.onBeforeHeaderCellDestroy, { 539 "node": this, 540 "column": columnDef 541 }); 542 } 543 }); 544 $headers.empty(); 545 $headers.width(getHeadersWidth()); 546 547 $headerRow.find(".slick-headerrow-column") 548 .each(function() { 549 var columnDef = $(this).data("column"); 550 if (columnDef) { 551 trigger(self.onBeforeHeaderRowCellDestroy, { 552 "node": this, 553 "column": columnDef 554 }); 555 } 556 }); 557 $headerRow.empty(); 558 559 for (var i = 0; i < columns.length; i++) { 560 var m = columns[i]; 561 562 var header = $("<div class='ui-state-default slick-header-column' />") 563 .html("<span class='slick-column-name'>" + m.name + "</span>") 564 .width(m.width - headerColumnWidthDiff) 565 .attr("id", "" + uid + m.id) 566 .attr("title", m.toolTip || "") 567 .data("column", m) 568 .addClass(m.headerCssClass || "") 569 .appendTo($headers); 570 571 if (options.enableColumnReorder || m.sortable) { 572 header 573 .on('mouseenter', onMouseEnter) 574 .on('mouseleave', onMouseLeave); 575 } 576 577 if (m.sortable) { 578 header.addClass("slick-header-sortable"); 579 header.append("<span class='slick-sort-indicator' />"); 580 } 581 582 trigger(self.onHeaderCellRendered, { 583 "node": header[0], 584 "column": m 585 }); 586 587 if (options.showHeaderRow) { 588 var headerRowCell = $("<div class='ui-state-default slick-headerrow-column l" + i + " r" + i + "'></div>") 589 .data("column", m) 590 .appendTo($headerRow); 591 592 trigger(self.onHeaderRowCellRendered, { 593 "node": headerRowCell[0], 594 "column": m 595 }); 596 } 597 } 598 599 setSortColumns(sortColumns); 600 setupColumnResize(); 601 if (options.enableColumnReorder) { 602 setupColumnReorder(); 603 } 604 } 605 606 function setupColumnSort() { 607 $headers.click(function (e) { 608 // temporary workaround for a bug in jQuery 1.7.1 (http://bugs.jquery.com/ticket/11328) 609 e.metaKey = e.metaKey || e.ctrlKey; 610 611 if ($(e.target).hasClass("slick-resizable-handle")) { 612 return; 613 } 614 615 var $col = $(e.target).closest(".slick-header-column"); 616 if (!$col.length) { 617 return; 618 } 619 620 var column = $col.data("column"); 621 if (column.sortable) { 622 if (!getEditorLock().commitCurrentEdit()) { 623 return; 624 } 625 626 var sortOpts = null; 627 var i = 0; 628 for (; i < sortColumns.length; i++) { 629 if (sortColumns[i].columnId == column.id) { 630 sortOpts = sortColumns[i]; 631 sortOpts.sortAsc = !sortOpts.sortAsc; 632 break; 633 } 634 } 635 636 if (e.metaKey && options.multiColumnSort) { 637 if (sortOpts) { 638 sortColumns.splice(i, 1); 639 } 640 } 641 else { 642 if ((!e.shiftKey && !e.metaKey) || !options.multiColumnSort) { 643 sortColumns = []; 644 } 645 646 if (!sortOpts) { 647 sortOpts = { columnId: column.id, sortAsc: column.defaultSortAsc }; 648 sortColumns.push(sortOpts); 649 } else if (sortColumns.length == 0) { 650 sortColumns.push(sortOpts); 651 } 652 } 653 654 setSortColumns(sortColumns); 655 656 if (!options.multiColumnSort) { 657 trigger(self.onSort, { 658 multiColumnSort: false, 659 sortCol: column, 660 sortAsc: sortOpts.sortAsc}, e); 661 } else { 662 trigger(self.onSort, { 663 multiColumnSort: true, 664 sortCols: $.map(sortColumns, function(col) { 665 return {sortCol: columns[getColumnIndex(col.columnId)], sortAsc: col.sortAsc }; 666 })}, e); 667 } 668 } 669 }); 670 } 671 672 function setupColumnReorder() { 673 $headers.filter(":ui-sortable").sortable("destroy"); 674 $headers.sortable({ 675 containment: "parent", 676 distance: 3, 677 axis: "x", 678 cursor: "default", 679 tolerance: "intersection", 680 helper: "clone", 681 placeholder: "slick-sortable-placeholder ui-state-default slick-header-column", 682 start: function (e, ui) { 683 ui.placeholder.width(ui.helper.outerWidth() - headerColumnWidthDiff); 684 $(ui.helper).addClass("slick-header-column-active"); 685 }, 686 beforeStop: function (e, ui) { 687 $(ui.helper).removeClass("slick-header-column-active"); 688 }, 689 stop: function (e) { 690 if (!getEditorLock().commitCurrentEdit()) { 691 $(this).sortable("cancel"); 692 return; 693 } 694 695 var reorderedIds = $headers.sortable("toArray"); 696 var reorderedColumns = []; 697 for (var i = 0; i < reorderedIds.length; i++) { 698 reorderedColumns.push(columns[getColumnIndex(reorderedIds[i].replace(uid, ""))]); 699 } 700 setColumns(reorderedColumns); 701 702 trigger(self.onColumnsReordered, {}); 703 e.stopPropagation(); 704 setupColumnResize(); 705 } 706 }); 707 } 708 709 function setupColumnResize() { 710 var $col, j, c, pageX, columnElements, minPageX, maxPageX, firstResizable, lastResizable; 711 columnElements = $headers.children();
712 columnElements.find(".slick-resizable-handle").remove(); 713 columnElements.each(function (i, e) { 714 if (columns[i].resizable) { 715 if (firstResizable === undefined) { 716 firstResizable = i; 717 } 718 lastResizable = i; 719 } 720 }); 721 if (firstResizable === undefined) { 722 return; 723 } 724 columnElements.each(function (i, e) { 725 if (i < firstResizable || (options.forceFitColumns && i >= lastResizable)) { 726 return; 727 } 728 $col = $(e); 729 $("<div class='slick-resizable-handle' />") 730 .appendTo(e) 731 .bind("dragstart", function (e, dd) { 732 if (!getEditorLock().commitCurrentEdit()) { 733 return false; 734 } 735 pageX = event.pageX; 736 $(this).parent().addClass("slick-header-column-active"); 737 var shrinkLeewayOnRight = null, stretchLeewayOnRight = null; 738 // lock each column's width option to current width 739 columnElements.each(function (i, e) { 740 columns[i].previousWidth = $(e).outerWidth(); 741 }); 742 if (options.forceFitColumns) { 743 shrinkLeewayOnRight = 0; 744 stretchLeewayOnRight = 0; 745 // colums on right affect maxPageX/minPageX 746 for (j = i + 1; j < columnElements.length; j++) { 747 c = columns[j]; 748 if (c.resizable) { 749 if (stretchLeewayOnRight !== null) { 750 if (c.maxWidth) { 751 stretchLeewayOnRight += c.maxWidth - c.previousWidth; 752 } else { 753 stretchLeewayOnRight = null; 754 } 755 } 756 shrinkLeewayOnRight += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth); 757 } 758 } 759 } 760 var shrinkLeewayOnLeft = 0, stretchLeewayOnLeft = 0; 761 for (j = 0; j <= i; j++) { 762 // columns on left only affect minPageX 763 c = columns[j]; 764 if (c.resizable) { 765 if (stretchLeewayOnLeft !== null) { 766 if (c.maxWidth) { 767 stretchLeewayOnLeft += c.maxWidth - c.previousWidth; 768 } else { 769 stretchLeewayOnLeft = null; 770 } 771 } 772 shrinkLeewayOnLeft += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth); 773 } 774 } 775 if (shrinkLeewayOnRight === null) { 776 shrinkLeewayOnRight = 100000; 777 } 778 if (shrinkLeewayOnLeft === null) { 779 shrinkLeewayOnLeft = 100000; 780 } 781 if (stretchLeewayOnRight === null) { 782 stretchLeewayOnRight = 100000; 783 } 784 if (stretchLeewayOnLeft === null) { 785 stretchLeewayOnLeft = 100000; 786 } 787 maxPageX = pageX + Math.min(shrinkLeewayOnRight, stretchLeewayOnLeft); 788 minPageX = pageX - Math.min(shrinkLeewayOnLeft, stretchLeewayOnRight); 789 }) 790 .bind("drag", function (e, dd) { 791 var actualMinWidth, d = Math.min(maxPageX, Math.max(minPageX, event.pageX)) - pageX, x; 792 if (d < 0) { // shrink column 793 x = d; 794 for (j = i; j >= 0; j--) { 795 c = columns[j]; 796 if (c.resizable) { 797 actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth); 798 if (x && c.previousWidth + x < actualMinWidth) { 799 x += c.previousWidth - actualMinWidth; 800 c.width = actualMinWidth; 801 } else { 802 c.width = c.previousWidth + x; 803 x = 0; 804 } 805 } 806 } 807 808 if (options.forceFitColumns) { 809 x = -d; 810 for (j = i + 1; j < columnElements.length; j++) { 811 c = columns[j]; 812 if (c.resizable) { 813 if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) { 814 x -= c.maxWidth - c.previousWidth; 815 c.width = c.maxWidth; 816 } else { 817 c.width = c.previousWidth + x; 818 x = 0; 819 } 820 } 821 } 822 } 823 } else { // stretch column 824 x = d; 825 for (j = i; j >= 0; j--) { 826 c = columns[j]; 827 if (c.resizable) { 828 if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) { 829 x -= c.maxWidth - c.previousWidth; 830 c.width = c.maxWidth; 831 } else { 832 c.width = c.previousWidth + x; 833 x = 0; 834 } 835 } 836 } 837 838 if (options.forceFitColumns) { 839 x = -d; 840 for (j = i + 1; j < columnElements.length; j++) { 841 c = columns[j]; 842 if (c.resizable) { 843 actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth); 844 if (x && c.previousWidth + x < actualMinWidth) { 845 x += c.previousWidth - actualMinWidth; 846 c.width = actualMinWidth; 847 } else { 848 c.width = c.previousWidth + x; 849 x = 0; 850 } 851 } 852 } 853 } 854 } 855 applyColumnHeaderWidths(); 856 if (options.syncColumnCellResize) { 857 applyColumnWidths(); 858 } 859 }) 860 .bind("dragend", function (e, dd) { 861 var newWidth; 862 $(this).parent().removeClass("slick-header-column-active"); 863 for (j = 0; j < columnElements.length; j++) { 864 c = columns[j]; 865 newWidth = $(columnElements[j]).outerWidth(); 866 867 if (c.previousWidth !== newWidth && c.rerenderOnResize) { 868 invalidateAllRows(); 869 } 870 } 871 updateCanvasWidth(true); 872 render(); 873 trigger(self.onColumnsResized, {}); 874 }); 875 }); 876 } 877 878 function getVBoxDelta($el) { 879 var p = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"]; 880 var delta = 0; 881 $.each(p, function (n, val) { 882 delta += parseFloat($el.css(val)) || 0; 883 }); 884 return delta; 885 } 886 887 function measureCellPaddingAndBorder() { 888 var el; 889 var h = ["borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight"]; 890 var v = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"]; 891 892 el = $("<div class='ui-state-default slick-header-column' style='visibility:hidden'>-</div>").appendTo($headers); 893 headerColumnWidthDiff = headerColumnHeightDiff = 0; 894 if (el.css("box-sizing") != "border-box" && el.css("-moz-box-sizing") != "border-box" && el.css("-webkit-box-sizing") != "border-box") { 895 $.each(h, function (n, val) { 896 headerColumnWidthDiff += parseFloat(el.css(val)) || 0; 897 }); 898 $.each(v, function (n, val) { 899 headerColumnHeightDiff += parseFloat(el.css(val)) || 0; 900 }); 901 } 902 el.remove(); 903 904 var r = $("<div class='slick-row' />").appendTo($canvas); 905 el = $("<div class='slick-cell' id='' style='visibility:hidden'>-</div>").appendTo(r); 906 cellWidthDiff = cellHeightDiff = 0; 907 if (el.css("box-sizing") != "border-box" && el.css("-moz-box-sizing") != "border-box" && el.css("-webkit-box-sizing") != "border-box") { 908 $.each(h, function (n, val) { 909 cellWidthDiff += parseFloat(el.css(val)) || 0; 910 }); 911 $.each(v, function (n, val) { 912 cellHeightDiff += parseFloat(el.css(val)) || 0; 913 }); 914 } 915 r.remove(); 916 917 absoluteColumnMinWidth = Math.max(headerColumnWidthDiff, cellWidthDiff); 918 } 919 920 function createCssRules() { 921 $style = $("<style type='text/css' rel='stylesheet' />").appendTo($("head")); 922 var rowHeight = (options.rowHeight - cellHeightDiff); 923 var rules = [ 924 "." + uid + " .slick-header-column { left: 1000px; }", 925 "." + uid + " .slick-top-panel { height:" + options.topPanelHeight + "px; }", 926 "." + uid + " .slick-headerrow-columns { height:" + options.headerRowHeight + "px; }", 927 "." + uid + " .slick-cell { height:" + rowHeight + "px; }", 928 "." + uid + " .slick-row { height:" + options.rowHeight + "px; }" 929 ]; 930 931 for (var i = 0; i < columns.length; i++) { 932 rules.push("." + uid + " .l" + i + " { }"); 933 rules.push("." + uid + " .r" + i + " { }"); 934 } 935 936 if ($style[0].styleSheet) { // IE 937 $style[0].styleSheet.cssText = rules.join(" "); 938 } else { 939 $style[0].appendChild(document.createTextNode(rules.join(" "))); 940 } 941 } 942 943 function getColumnCssRules(idx) { 944 if (!stylesheet) { 945 var sheets = document.styleSheets; 946 for (var i = 0; i < sheets.length; i++) { 947 if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) { 948 stylesheet = sheets[i];
949 break; 950 } 951 } 952 953 if (!stylesheet) { 954 throw new Error("Cannot find stylesheet."); 955 } 956 957 // find and cache column CSS rules 958 columnCssRulesL = []; 959 columnCssRulesR = []; 960 var cssRules = (stylesheet.cssRules || stylesheet.rules); 961 var matches, columnIdx; 962 for (var i = 0; i < cssRules.length; i++) { 963 var selector = cssRules[i].selectorText; 964 if (matches = /\.l\d+/.exec(selector)) { 965 columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10); 966 columnCssRulesL[columnIdx] = cssRules[i]; 967 } else if (matches = /\.r\d+/.exec(selector)) { 968 columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10); 969 columnCssRulesR[columnIdx] = cssRules[i]; 970 } 971 } 972 } 973 974 return { 975 "left": columnCssRulesL[idx], 976 "right": columnCssRulesR[idx] 977 }; 978 } 979 980 function removeCssRules() { 981 $style.remove(); 982 stylesheet = null; 983 } 984 985 function destroy() { 986 getEditorLock().cancelCurrentEdit(); 987 988 trigger(self.onBeforeDestroy, {}); 989 990 var i = plugins.length; 991 while(i--) { 992 unregisterPlugin(plugins[i]); 993 } 994 995 if (options.enableColumnReorder) { 996 $headers.filter(":ui-sortable").sortable("destroy"); 997 } 998 999 unbindAncestorScrollEvents(); 1000 $container.unbind(".slickgrid"); 1001 removeCssRules(); 1002 1003 $canvas.unbind("draginit dragstart dragend drag"); 1004 $container.empty().removeClass(uid); 1005 } 1006 1007 1008 ////////////////////////////////////////////////////////////////////////////////////////////// 1009 // General 1010 1011 function trigger(evt, args, e) { 1012 e = e || new Slick.EventData(); 1013 args = args || {}; 1014 args.grid = self; 1015 return evt.notify(args, e, self); 1016 } 1017 1018 function getEditorLock() { 1019 return options.editorLock; 1020 } 1021 1022 function getEditController() { 1023 return editController; 1024 } 1025 1026 function getColumnIndex(id) { 1027 return columnsById[id]; 1028 } 1029 1030 function autosizeColumns() { 1031 var i, c, 1032 widths = [], 1033 shrinkLeeway = 0, 1034 total = 0, 1035 prevTotal, 1036 availWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW; 1037 1038 for (i = 0; i < columns.length; i++) { 1039 c = columns[i]; 1040 widths.push(c.width); 1041 total += c.width; 1042 if (c.resizable) { 1043 shrinkLeeway += c.width - Math.max(c.minWidth, absoluteColumnMinWidth); 1044 } 1045 } 1046 1047 // shrink 1048 prevTotal = total; 1049 while (total > availWidth && shrinkLeeway) { 1050 var shrinkProportion = (total - availWidth) / shrinkLeeway; 1051 for (i = 0; i < columns.length && total > availWidth; i++) { 1052 c = columns[i]; 1053 var width = widths[i]; 1054 if (!c.resizable || width <= c.minWidth || width <= absoluteColumnMinWidth) { 1055 continue; 1056 } 1057 var absMinWidth = Math.max(c.minWidth, absoluteColumnMinWidth); 1058 var shrinkSize = Math.floor(shrinkProportion * (width - absMinWidth)) || 1; 1059 shrinkSize = Math.min(shrinkSize, width - absMinWidth); 1060 total -= shrinkSize; 1061 shrinkLeeway -= shrinkSize; 1062 widths[i] -= shrinkSize; 1063 } 1064 if (prevTotal <= total) { // avoid infinite loop 1065 break; 1066 } 1067 prevTotal = total; 1068 } 1069 1070 // grow 1071 prevTotal = total; 1072 while (total < availWidth) { 1073 var growProportion = availWidth / total; 1074 for (i = 0; i < columns.length && total < availWidth; i++) { 1075 c = columns[i]; 1076 var currentWidth = widths[i]; 1077 var growSize; 1078 1079 if (!c.resizable || c.maxWidth <= currentWidth) { 1080 growSize = 0; 1081 } else { 1082 growSize = Math.min(Math.floor(growProportion * currentWidth) - currentWidth, (c.maxWidth - currentWidth) || 1000000) || 1; 1083 } 1084 total += growSize; 1085 widths[i] += growSize; 1086 } 1087 if (prevTotal >= total) { // avoid infinite loop 1088 break; 1089 } 1090 prevTotal = total; 1091 } 1092 1093 var reRender = false; 1094 for (i = 0; i < columns.length; i++) { 1095 if (columns[i].rerenderOnResize && columns[i].width != widths[i]) {
1096 reRender = true; 1097 } 1098 columns[i].width = widths[i]; 1099 } 1100 1101 applyColumnHeaderWidths(); 1102 updateCanvasWidth(true); 1103 if (reRender) { 1104 invalidateAllRows(); 1105 render(); 1106 } 1107 } 1108 1109 function applyColumnHeaderWidths() { 1110 if (!initialized) { return; } 1111 var h; 1112 for (var i = 0, headers = $headers.children(), ii = headers.length; i < ii; i++) { 1113 h = $(headers[i]); 1114 if (h.width() !== columns[i].width - headerColumnWidthDiff) { 1115 h.width(columns[i].width - headerColumnWidthDiff); 1116 } 1117 } 1118 1119 updateColumnCaches(); 1120 } 1121 1122 function applyColumnWidths() { 1123 var x = 0, w, rule; 1124 for (var i = 0; i < columns.length; i++) { 1125 w = columns[i].width; 1126 1127 rule = getColumnCssRules(i); 1128 rule.left.style.left = x + "px"; 1129 rule.right.style.right = (canvasWidth - x - w) + "px"; 1130 1131 x += columns[i].width; 1132 } 1133 } 1134 1135 function setSortColumn(columnId, ascending) { 1136 setSortColumns([{ columnId: columnId, sortAsc: ascending}]); 1137 } 1138 1139 function setSortColumns(cols) { 1140 sortColumns = cols; 1141 1142 var headerColumnEls = $headers.children(); 1143 headerColumnEls 1144 .removeClass("slick-header-column-sorted") 1145 .find(".slick-sort-indicator") 1146 .removeClass("slick-sort-indicator-asc slick-sort-indicator-desc"); 1147 1148 $.each(sortColumns, function(i, col) { 1149 if (col.sortAsc == null) { 1150 col.sortAsc = true; 1151 } 1152 var columnIndex = getColumnIndex(col.columnId); 1153 if (columnIndex != null) { 1154 headerColumnEls.eq(columnIndex) 1155 .addClass("slick-header-column-sorted") 1156 .find(".slick-sort-indicator") 1157 .addClass(col.sortAsc ? "slick-sort-indicator-asc" : "slick-sort-indicator-desc"); 1158 } 1159 }); 1160 } 1161 1162 function getSortColumns() { 1163 return sortColumns; 1164 } 1165 1166 function handleSelectedRangesChanged(e, ranges) { 1167 selectedRows = []; 1168 var hash = {}; 1169 for (var i = 0; i < ranges.length; i++) { 1170 for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) { 1171 if (!hash[j]) { // prevent duplicates 1172 selectedRows.push(j); 1173 hash[j] = {}; 1174 } 1175 for (var k = ranges[i].fromCell; k <= ranges[i].toCell; k++) { 1176 if (canCellBeSelected(j, k)) { 1177 hash[j][columns[k].id] = options.selectedCellCssClass; 1178 } 1179 } 1180 } 1181 } 1182 1183 setCellCssStyles(options.selectedCellCssClass, hash); 1184 1185 trigger(self.onSelectedRowsChanged, {rows: getSelectedRows()}, e); 1186 } 1187 1188 function getColumns() { 1189 return columns; 1190 } 1191 1192 function updateColumnCaches() { 1193 // Pre-calculate cell boundaries. 1194 columnPosLeft = []; 1195 columnPosRight = []; 1196 var x = 0; 1197 for (var i = 0, ii = columns.length; i < ii; i++) { 1198 columnPosLeft[i] = x; 1199 columnPosRight[i] = x + columns[i].width; 1200 x += columns[i].width; 1201 } 1202 } 1203 1204 function setColumns(columnDefinitions) { 1205 columns = columnDefinitions; 1206 1207 columnsById = {}; 1208 for (var i = 0; i < columns.length; i++) { 1209 var m = columns[i] = $.extend({}, columnDefaults, columns[i]); 1210 columnsById[m.id] = i; 1211 if (m.minWidth && m.width < m.minWidth) { 1212 m.width = m.minWidth; 1213 } 1214 if (m.maxWidth && m.width > m.maxWidth) { 1215 m.width = m.maxWidth; 1216 } 1217 } 1218 1219 updateColumnCaches(); 1220 1221 if (initialized) { 1222 invalidateAllRows(); 1223 createColumnHeaders(); 1224 removeCssRules(); 1225 createCssRules(); 1226 resizeCanvas(); 1227 applyColumnWidths(); 1228 handleScroll(); 1229 } 1230 } 1231 1232 function getOptions() { 1233 return options; 1234 } 1235 1236 function setOptions(args) { 1237 if (!getEditorLock().commitCurrentEdit()) { 1238 return; 1239 } 1240 1241 makeActiveCellNormal(); 1242 1243 if (options.enableAddRow !== args.enableAddRow) { 1244 invalidateRow(getDataLength()); 1245 } 1246 1247 options = $.extend(options, args); 1248 validateAndEnforceOptions(); 1249 1250 $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto"); 1251 render(); 1252 } 1253 1254 function validateAndEnforceOptions() { 1255 if (options.autoHeight) { 1256 options.leaveSpaceForNewRows = false; 1257 } 1258 } 1259 1260 function setData(newData, scrollToTop) { 1261 data = newData; 1262 invalidateAllRows(); 1263 updateRowCount(); 1264 if (scrollToTop) { 1265 scrollTo(0); 1266 } 1267 } 1268 1269 function getData() { 1270 return data; 1271 } 1272 1273 function getDataLength() { 1274 if (data.getLength) { 1275 return data.getLength(); 1276 } else { 1277 return data.length; 1278 } 1279 } 1280 1281 function getDataLengthIncludingAddNew() { 1282 return getDataLength() + (options.enableAddRow ? 1 : 0); 1283 } 1284 1285 function getDataItem(i) { 1286 if (data.getItem) { 1287 return data.getItem(i); 1288 } else { 1289 return data[i]; 1290 } 1291 } 1292
1293 function getTopPanel() { 1294 return $topPanel[0]; 1295 } 1296 1297 function setTopPanelVisibility(visible) { 1298 if (options.showTopPanel != visible) { 1299 options.showTopPanel = visible; 1300 if (visible) { 1301 $topPanelScroller.slideDown("fast", resizeCanvas); 1302 } else { 1303 $topPanelScroller.slideUp("fast", resizeCanvas); 1304 } 1305 } 1306 } 1307 1308 function setHeaderRowVisibility(visible) { 1309 if (options.showHeaderRow != visible) { 1310 options.showHeaderRow = visible; 1311 if (visible) { 1312 $headerRowScroller.slideDown("fast", resizeCanvas); 1313 } else { 1314 $headerRowScroller.slideUp("fast", resizeCanvas); 1315 } 1316 } 1317 } 1318 1319 function getContainerNode() { 1320 return $container.get(0); 1321 } 1322 1323 ////////////////////////////////////////////////////////////////////////////////////////////// 1324 // Rendering / Scrolling 1325 1326 function getRowTop(row) { 1327 return options.rowHeight * row - offset; 1328 } 1329 1330 function getRowFromPosition(y) { 1331 return Math.floor((y + offset) / options.rowHeight); 1332 } 1333 1334 function scrollTo(y) { 1335 y = Math.max(y, 0); 1336 y = Math.min(y, th - viewportH + (viewportHasHScroll ? scrollbarDimensions.height : 0)); 1337 1338 var oldOffset = offset; 1339 1340 page = Math.min(n - 1, Math.floor(y / ph)); 1341 offset = Math.round(page * cj); 1342 var newScrollTop = y - offset; 1343 1344 if (offset != oldOffset) { 1345 var range = getVisibleRange(newScrollTop); 1346 cleanupRows(range); 1347 updateRowPositions(); 1348 } 1349 1350 if (prevScrollTop != newScrollTop) { 1351 vScrollDir = (prevScrollTop + oldOffset < newScrollTop + offset) ? 1 : -1; 1352 $viewport[0].scrollTop = (lastRenderedScrollTop = scrollTop = prevScrollTop = newScrollTop); 1353 1354 trigger(self.onViewportChanged, {}); 1355 } 1356 } 1357 1358 function defaultFormatter(row, cell, value, columnDef, dataContext) { 1359 if (value == null) { 1360 return ""; 1361 } else { 1362 return (value + "").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"); 1363 } 1364 } 1365 1366 function getFormatter(row, column) { 1367 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row); 1368 1369 // look up by id, then index 1370 var columnOverrides = rowMetadata && 1371 rowMetadata.columns && 1372 (rowMetadata.columns[column.id] || rowMetadata.columns[getColumnIndex(column.id)]); 1373 1374 return (columnOverrides && columnOverrides.formatter) || 1375 (rowMetadata && rowMetadata.formatter) || 1376 column.formatter || 1377 (options.formatterFactory && options.formatterFactory.getFormatter(column)) || 1378 options.defaultFormatter; 1379 } 1380 1381 function getEditor(row, cell) { 1382 var column = columns[cell]; 1383 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row); 1384 var columnMetadata = rowMetadata && rowMetadata.columns; 1385 1386 if (columnMetadata && columnMetadata[column.id] && columnMetadata[column.id].editor !== undefined) { 1387 return columnMetadata[column.id].editor; 1388 } 1389 if (columnMetadata && columnMetadata[cell] && columnMetadata[cell].editor !== undefined) { 1390 return columnMetadata[cell].editor; 1391 } 1392 1393 return column.editor || (options.editorFactory && options.editorFactory.getEditor(column)); 1394 } 1395 1396 function getDataItemValueForColumn(item, columnDef) { 1397 if (options.dataItemColumnValueExtractor) { 1398 return options.dataItemColumnValueExtractor(item, columnDef); 1399 } 1400 return item[columnDef.field]; 1401 } 1402
1403 function appendRowHtml(stringArray, row, range, dataLength) { 1404 var d = getDataItem(row); 1405 var dataLoading = row < dataLength && !d; 1406 var rowCss = "slick-row" + 1407 (dataLoading ? " loading" : "") + 1408 (row === activeRow ? " active" : "") + 1409 (row % 2 == 1 ? " odd" : " even"); 1410 1411 if (!d) { 1412 rowCss += " " + options.addNewRowCssClass; 1413 } 1414 1415 var metadata = data.getItemMetadata && data.getItemMetadata(row); 1416 1417 if (metadata && metadata.cssClasses) { 1418 rowCss += " " + metadata.cssClasses; 1419 } 1420 1421 stringArray.push("<div class='ui-widget-content " + rowCss + "' style='top:" + getRowTop(row) + "px'>"); 1422 1423 var colspan, m; 1424 for (var i = 0, ii = columns.length; i < ii; i++) { 1425 m = columns[i]; 1426 colspan = 1; 1427 if (metadata && metadata.columns) { 1428 var columnData = metadata.columns[m.id] || metadata.columns[i]; 1429 colspan = (columnData && columnData.colspan) || 1; 1430 if (colspan === "*") { 1431 colspan = ii - i; 1432 } 1433 } 1434 1435 // Do not render cells outside of the viewport. 1436 if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) { 1437 if (columnPosLeft[i] > range.rightPx) { 1438 // All columns to the right are outside the range. 1439 break; 1440 } 1441 1442 appendCellHtml(stringArray, row, i, colspan, d); 1443 } 1444 1445 if (colspan > 1) { 1446 i += (colspan - 1); 1447 } 1448 } 1449 1450 stringArray.push("</div>"); 1451 } 1452 1453 function appendCellHtml(stringArray, row, cell, colspan, item) { 1454 var m = columns[cell]; 1455 var cellCss = "slick-cell l" + cell + " r" + Math.min(columns.length - 1, cell + colspan - 1) + 1456 (m.cssClass ? " " + m.cssClass : ""); 1457 if (row === activeRow && cell === activeCell) { 1458 cellCss += (" active"); 1459 } 1460 1461 // TODO: merge them together in the setter 1462 for (var key in cellCssClasses) { 1463 if (cellCssClasses[key][row] && cellCssClasses[key][row][m.id]) { 1464 cellCss += (" " + cellCssClasses[key][row][m.id]); 1465 } 1466 } 1467 1468 stringArray.push("<div class='" + cellCss + "'>"); 1469 1470 // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet) 1471 if (item) { 1472 var value = getDataItemValueForColumn(item, m); 1473 stringArray.push(getFormatter(row, m)(row, cell, value, m, item)); 1474 } 1475 1476 stringArray.push("</div>"); 1477 1478 rowsCache[row].cellRenderQueue.push(cell); 1479 rowsCache[row].cellColSpans[cell] = colspan; 1480 } 1481 1482 1483 function cleanupRows(rangeToKeep) { 1484 for (var i in rowsCache) { 1485 if (((i = parseInt(i, 10)) !== activeRow) && (i < rangeToKeep.top || i > rangeToKeep.bottom)) { 1486 removeRowFromCache(i); 1487 } 1488 } 1489 } 1490
1491 function invalidate() { 1492 updateRowCount(); 1493 invalidateAllRows(); 1494 render(); 1495 } 1496 1497 function invalidateAllRows() { 1498 if (currentEditor) { 1499 makeActiveCellNormal(); 1500 } 1501 for (var row in rowsCache) { 1502 removeRowFromCache(row); 1503 } 1504 } 1505 1506 function removeRowFromCache(row) { 1507 var cacheEntry = rowsCache[row]; 1508 if (!cacheEntry) { 1509 return; 1510 } 1511 1512 if (rowNodeFromLastMouseWheelEvent == cacheEntry.rowNode) { 1513 cacheEntry.rowNode.style.display = 'none'; 1514 zombieRowNodeFromLastMouseWheelEvent = rowNodeFromLastMouseWheelEvent; 1515 } else { 1516 $canvas[0].removeChild(cacheEntry.rowNode); 1517 } 1518 1519 delete rowsCache[row]; 1520 delete postProcessedRows[row]; 1521 renderedRows--; 1522 counter_rows_removed++; 1523 } 1524 1525 function invalidateRows(rows) { 1526 var i, rl; 1527 if (!rows || !rows.length) { 1528 return; 1529 } 1530 vScrollDir = 0; 1531 for (i = 0, rl = rows.length; i < rl; i++) { 1532 if (currentEditor && activeRow === rows[i]) { 1533 makeActiveCellNormal(); 1534 } 1535 if (rowsCache[rows[i]]) { 1536 removeRowFromCache(rows[i]); 1537 } 1538 } 1539 } 1540
1541 function invalidateRow(row) { 1542 invalidateRows([row]); 1543 } 1544 1545 function updateCell(row, cell) { 1546 var cellNode = getCellNode(row, cell); 1547 if (!cellNode) { 1548 return; 1549 } 1550 1551 var m = columns[cell], d = getDataItem(row); 1552 if (currentEditor && activeRow === row && activeCell === cell) { 1553 currentEditor.loadValue(d); 1554 } else { 1555 cellNode.innerHTML = d ? getFormatter(row, m)(row, cell, getDataItemValueForColumn(d, m), m, d) : ""; 1556 invalidatePostProcessingResults(row); 1557 } 1558 } 1559 1560 function updateRow(row) { 1561 var cacheEntry = rowsCache[row]; 1562 if (!cacheEntry) { 1563 return; 1564 } 1565 1566 ensureCellNodesInRowsCache(row); 1567 1568 var d = getDataItem(row); 1569 1570 for (var columnIdx in cacheEntry.cellNodesByColumnIdx) { 1571 if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) { 1572 continue; 1573 } 1574 1575 columnIdx = columnIdx | 0; 1576 var m = columns[columnIdx], 1577 node = cacheEntry.cellNodesByColumnIdx[columnIdx]; 1578 1579 if (row === activeRow && columnIdx === activeCell && currentEditor) { 1580 currentEditor.loadValue(d); 1581 } else if (d) { 1582 node.innerHTML = getFormatter(row, m)(row, columnIdx, getDataItemValueForColumn(d, m), m, d); 1583 } else { 1584 node.innerHTML = ""; 1585 } 1586 } 1587 1588 invalidatePostProcessingResults(row); 1589 } 1590 1591 function getViewportHeight() { 1592 return parseFloat($.css($container[0], "height", true)) - 1593 parseFloat($.css($container[0], "paddingTop", true)) - 1594 parseFloat($.css($container[0], "paddingBottom", true)) - 1595 parseFloat($.css($headerScroller[0], "height")) - getVBoxDelta($headerScroller) - 1596 (options.showTopPanel ? options.topPanelHeight + getVBoxDelta($topPanelScroller) : 0) - 1597 (options.showHeaderRow ? options.headerRowHeight + getVBoxDelta($headerRowScroller) : 0); 1598 } 1599 1600 function resizeCanvas() { 1601 if (!initialized) { return; } 1602 if (options.autoHeight) { 1603 viewportH = options.rowHeight * getDataLengthIncludingAddNew(); 1604 } else { 1605 viewportH = getViewportHeight(); 1606 } 1607 1608 numVisibleRows = Math.ceil(viewportH / options.rowHeight); 1609 viewportW = parseFloat($.css($container[0], "width", true)); 1610 if (!options.autoHeight) { 1611 $viewport.height(viewportH); 1612 } 1613 1614 if (options.forceFitColumns) { 1615 autosizeColumns(); 1616 } 1617 1618 updateRowCount(); 1619 handleScroll(); 1620 // Since the width has changed, force the render() to reevaluate virtually rendered cells. 1621 lastRenderedScrollLeft = -1; 1622 render(); 1623 } 1624 1625 function updateRowCount() { 1626 if (!initialized) { return; } 1627 1628 var dataLengthIncludingAddNew = getDataLengthIncludingAddNew(); 1629 var numberOfRows = dataLengthIncludingAddNew + 1630 (options.leaveSpaceForNewRows ? numVisibleRows - 1 : 0); 1631 1632 var oldViewportHasVScroll = viewportHasVScroll; 1633 // with autoHeight, we do not need to accommodate the vertical scroll bar 1634 viewportHasVScroll = !options.autoHeight && (numberOfRows * options.rowHeight > viewportH); 1635 1636 makeActiveCellNormal(); 1637 1638 // remove the rows that are now outside of the data range 1639 // this helps avoid redundant calls to .removeRow() when the size of the data decreased by thousands of rows 1640 var l = dataLengthIncludingAddNew - 1; 1641 for (var i in rowsCache) { 1642 if (i >= l) { 1643 removeRowFromCache(i); 1644 } 1645 } 1646 1647 if (activeCellNode && activeRow > l) { 1648 resetActiveCell(); 1649 } 1650 1651 var oldH = h; 1652 th = Math.max(options.rowHeight * numberOfRows, viewportH - scrollbarDimensions.height); 1653 if (th < maxSupportedCssHeight) { 1654 // just one page 1655 h = ph = th; 1656 n = 1; 1657 cj = 0; 1658 } else { 1659 // break into pages 1660 h = maxSupportedCssHeight; 1661 ph = h / 100; 1662 n = Math.floor(th / ph); 1663 cj = (th - h) / (n - 1); 1664 } 1665 1666 if (h !== oldH) { 1667 $canvas.css("height", h); 1668 scrollTop = $viewport[0].scrollTop; 1669 } 1670 1671 var oldScrollTopInRange = (scrollTop + offset <= th - viewportH); 1672 1673 if (th == 0 || scrollTop == 0) { 1674 page = offset = 0; 1675 } else if (oldScrollTopInRange) { 1676 // maintain virtual position 1677 scrollTo(scrollTop + offset); 1678 } else { 1679 // scroll to bottom 1680 scrollTo(th - viewportH); 1681 } 1682 1683 if (h != oldH && options.autoHeight) { 1684 resizeCanvas(); 1685 } 1686 1687 if (options.forceFitColumns && oldViewportHasVScroll != viewportHasVScroll) { 1688 autosizeColumns(); 1689 } 1690 updateCanvasWidth(false); 1691 } 1692 1693 function getVisibleRange(viewportTop, viewportLeft) { 1694 if (viewportTop == null) { 1695 viewportTop = scrollTop; 1696 } 1697 if (viewportLeft == null) { 1698 viewportLeft = scrollLeft; 1699 } 1700 1701 return { 1702 top: getRowFromPosition(viewportTop), 1703 bottom: getRowFromPosition(viewportTop + viewportH) + 1, 1704 leftPx: viewportLeft, 1705 rightPx: viewportLeft + viewportW 1706 }; 1707 } 1708 1709 function getRenderedRange(viewportTop, viewportLeft) { 1710 var range = getVisibleRange(viewportTop, viewportLeft); 1711 var buffer = Math.round(viewportH / options.rowHeight); 1712 var minBuffer = 3; 1713 1714 if (vScrollDir == -1) { 1715 range.top -= buffer; 1716 range.bottom += minBuffer; 1717 } else if (vScrollDir == 1) { 1718 range.top -= minBuffer; 1719 range.bottom += buffer; 1720 } else { 1721 range.top -= minBuffer; 1722 range.bottom += minBuffer; 1723 } 1724 1725 range.top = Math.max(0, range.top); 1726 range.bottom = Math.min(getDataLengthIncludingAddNew() - 1, range.bottom); 1727 1728 range.leftPx -= viewportW; 1729 range.rightPx += viewportW; 1730 1731 range.leftPx = Math.max(0, range.leftPx); 1732 range.rightPx = Math.min(canvasWidth, range.rightPx); 1733 1734 return range; 1735 } 1736 1737 function ensureCellNodesInRowsCache(row) { 1738 var cacheEntry = rowsCache[row]; 1739 if (cacheEntry) { 1740 if (cacheEntry.cellRenderQueue.length) {
1741 var lastChild = cacheEntry.rowNode.lastChild; 1742 while (cacheEntry.cellRenderQueue.length) { 1743 var columnIdx = cacheEntry.cellRenderQueue.pop(); 1744 cacheEntry.cellNodesByColumnIdx[columnIdx] = lastChild; 1745 lastChild = lastChild.previousSibling; 1746 } 1747 } 1748 } 1749 } 1750 1751 function cleanUpCells(range, row) { 1752 var totalCellsRemoved = 0; 1753 var cacheEntry = rowsCache[row]; 1754 1755 // Remove cells outside the range. 1756 var cellsToRemove = []; 1757 for (var i in cacheEntry.cellNodesByColumnIdx) { 1758 // I really hate it when people mess with Array.prototype. 1759 if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(i)) { 1760 continue; 1761 } 1762 1763 // This is a string, so it needs to be cast back to a number. 1764 i = i | 0; 1765 1766 var colspan = cacheEntry.cellColSpans[i]; 1767 if (columnPosLeft[i] > range.rightPx || 1768 columnPosRight[Math.min(columns.length - 1, i + colspan - 1)] < range.leftPx) { 1769 if (!(row == activeRow && i == activeCell)) { 1770 cellsToRemove.push(i); 1771 } 1772 } 1773 } 1774 1775 var cellToRemove; 1776 while ((cellToRemove = cellsToRemove.pop()) != null) { 1777 cacheEntry.rowNode.removeChild(cacheEntry.cellNodesByColumnIdx[cellToRemove]); 1778 delete cacheEntry.cellColSpans[cellToRemove]; 1779 delete cacheEntry.cellNodesByColumnIdx[cellToRemove]; 1780 if (postProcessedRows[row]) { 1781 delete postProcessedRows[row][cellToRemove]; 1782 } 1783 totalCellsRemoved++; 1784 } 1785 } 1786 1787 function cleanUpAndRenderCells(range) { 1788 var cacheEntry; 1789 var stringArray = []; 1790 var processedRows = []; 1791 var cellsAdded; 1792 var totalCellsAdded = 0; 1793 var colspan; 1794 1795 for (var row = range.top, btm = range.bottom; row <= btm; row++) { 1796 cacheEntry = rowsCache[row]; 1797 if (!cacheEntry) { 1798 continue; 1799 } 1800 1801 // cellRenderQueue populated in renderRows() needs to be cleared first 1802 ensureCellNodesInRowsCache(row); 1803 1804 cleanUpCells(range, row); 1805 1806 // Render missing cells. 1807 cellsAdded = 0; 1808 1809 var metadata = data.getItemMetadata && data.getItemMetadata(row); 1810 metadata = metadata && metadata.columns; 1811 1812 var d = getDataItem(row); 1813 1814 // TODO: shorten this loop (index? heuristics? binary search?) 1815 for (var i = 0, ii = columns.length; i < ii; i++) { 1816 // Cells to the right are outside the range. 1817 if (columnPosLeft[i] > range.rightPx) { 1818 break; 1819 } 1820 1821 // Already rendered. 1822 if ((colspan = cacheEntry.cellColSpans[i]) != null) { 1823 i += (colspan > 1 ? colspan - 1 : 0); 1824 continue; 1825 } 1826 1827 colspan = 1; 1828 if (metadata) { 1829 var columnData = metadata[columns[i].id] || metadata[i]; 1830 colspan = (columnData && columnData.colspan) || 1; 1831 if (colspan === "*") { 1832 colspan = ii - i; 1833 } 1834 } 1835 1836 if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) { 1837 appendCellHtml(stringArray, row, i, colspan, d); 1838 cellsAdded++; 1839 } 1840 1841 i += (colspan > 1 ? colspan - 1 : 0); 1842 } 1843 1844 if (cellsAdded) { 1845 totalCellsAdded += cellsAdded; 1846 processedRows.push(row); 1847 } 1848 } 1849 1850 if (!stringArray.length) { 1851 return; 1852 } 1853 1854 var x = document.createElement("div"); 1855 x.innerHTML = stringArray.join(""); 1856 1857 var processedRow; 1858 var node; 1859 while ((processedRow = processedRows.pop()) != null) { 1860 cacheEntry = rowsCache[processedRow]; 1861 var columnIdx; 1862 while ((columnIdx = cacheEntry.cellRenderQueue.pop()) != null) { 1863 node = x.lastChild; 1864 cacheEntry.rowNode.appendChild(node); 1865 cacheEntry.cellNodesByColumnIdx[columnIdx] = node; 1866 } 1867 } 1868 } 1869 1870 function renderRows(range) { 1871 var parentNode = $canvas[0],
1872 stringArray = [], 1873 rows = [], 1874 needToReselectCell = false, 1875 dataLength = getDataLength(); 1876 1877 for (var i = range.top, ii = range.bottom; i <= ii; i++) { 1878 if (rowsCache[i]) { 1879 continue; 1880 } 1881 renderedRows++; 1882 rows.push(i); 1883 1884 // Create an entry right away so that appendRowHtml() can 1885 // start populatating it. 1886 rowsCache[i] = { 1887 "rowNode": null, 1888 1889 // ColSpans of rendered cells (by column idx). 1890 // Can also be used for checking whether a cell has been rendered. 1891 "cellColSpans": [], 1892 1893 // Cell nodes (by column idx). Lazy-populated by ensureCellNodesInRowsCache(). 1894 "cellNodesByColumnIdx": [], 1895 1896 // Column indices of cell nodes that have been rendered, but not yet indexed in 1897 // cellNodesByColumnIdx. These are in the same order as cell nodes added at the 1898 // end of the row. 1899 "cellRenderQueue": [] 1900 }; 1901 1902 appendRowHtml(stringArray, i, range, dataLength); 1903 if (activeCellNode && activeRow === i) { 1904 needToReselectCell = true; 1905 } 1906 counter_rows_rendered++; 1907 } 1908 1909 if (!rows.length) { return; } 1910 1911 var x = document.createElement("div"); 1912 x.innerHTML = stringArray.join(""); 1913 1914 for (var i = 0, ii = rows.length; i < ii; i++) { 1915 rowsCache[rows[i]].rowNode = parentNode.appendChild(x.firstChild); 1916 } 1917 1918 if (needToReselectCell) { 1919 activeCellNode = getCellNode(activeRow, activeCell); 1920 } 1921 } 1922 1923 function startPostProcessing() { 1924 if (!options.enableAsyncPostRender) { 1925 return; 1926 } 1927 clearTimeout(h_postrender); 1928 h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay); 1929 } 1930
1931 function invalidatePostProcessingResults(row) { 1932 delete postProcessedRows[row]; 1933 postProcessFromRow = Math.min(postProcessFromRow, row); 1934 postProcessToRow = Math.max(postProcessToRow, row); 1935 startPostProcessing(); 1936 } 1937 1938 function updateRowPositions() { 1939 for (var row in rowsCache) { 1940 rowsCache[row].rowNode.style.top = getRowTop(row) + "px"; 1941 } 1942 } 1943 1944 function render() { 1945 if (!initialized) { return; } 1946 var visible = getVisibleRange(); 1947 var rendered = getRenderedRange(); 1948 1949 // remove rows no longer in the viewport 1950 cleanupRows(rendered); 1951 1952 // add new rows & missing cells in existing rows 1953 if (lastRenderedScrollLeft != scrollLeft) { 1954 cleanUpAndRenderCells(rendered); 1955 } 1956 1957 // render missing rows 1958 renderRows(rendered); 1959 1960 postProcessFromRow = visible.top; 1961 postProcessToRow = Math.min(getDataLengthIncludingAddNew() - 1, visible.bottom); 1962 startPostProcessing(); 1963 1964 lastRenderedScrollTop = scrollTop; 1965 lastRenderedScrollLeft = scrollLeft; 1966 h_render = null; 1967 } 1968 1969 function handleHeaderRowScroll() { 1970 var scrollLeft = $headerRowScroller[0].scrollLeft; 1971 if (scrollLeft != $viewport[0].scrollLeft) { 1972 $viewport[0].scrollLeft = scrollLeft; 1973 } 1974 } 1975 1976 function handleScroll() { 1977 scrollTop = $viewport[0].scrollTop; 1978 scrollLeft = $viewport[0].scrollLeft; 1979 var vScrollDist = Math.abs(scrollTop - prevScrollTop); 1980 var hScrollDist = Math.abs(scrollLeft - prevScrollLeft); 1981 1982 if (hScrollDist) { 1983 prevScrollLeft = scrollLeft; 1984 $headerScroller[0].scrollLeft = scrollLeft; 1985 $topPanelScroller[0].scrollLeft = scrollLeft; 1986 $headerRowScroller[0].scrollLeft = scrollLeft; 1987 } 1988 1989 if (vScrollDist) { 1990 vScrollDir = prevScrollTop < scrollTop ? 1 : -1; 1991 prevScrollTop = scrollTop; 1992 1993 // switch virtual pages if needed 1994 if (vScrollDist < viewportH) { 1995 scrollTo(scrollTop + offset); 1996 } else { 1997 var oldOffset = offset; 1998 if (h == viewportH) { 1999 page = 0; 2000 } else { 2001 page = Math.min(n - 1, Math.floor(scrollTop * ((th - viewportH) / (h - viewportH)) * (1 / ph))); 2002 } 2003 offset = Math.round(page * cj); 2004 if (oldOffset != offset) { 2005 invalidateAllRows(); 2006 } 2007 } 2008 } 2009 2010 if (hScrollDist || vScrollDist) { 2011 if (h_render) { 2012 clearTimeout(h_render); 2013 } 2014 2015 if (Math.abs(lastRenderedScrollTop - scrollTop) > 20 || 2016 Math.abs(lastRenderedScrollLeft - scrollLeft) > 20) { 2017 if (options.forceSyncScrolling || ( 2018 Math.abs(lastRenderedScrollTop - scrollTop) < viewportH && 2019 Math.abs(lastRenderedScrollLeft - scrollLeft) < viewportW)) { 2020 render(); 2021 } else { 2022 h_render = setTimeout(render, 50); 2023 } 2024 2025 trigger(self.onViewportChanged, {}); 2026 } 2027 } 2028 2029 trigger(self.onScroll, {scrollLeft: scrollLeft, scrollTop: scrollTop}); 2030 } 2031 2032 function asyncPostProcessRows() { 2033 var dataLength = getDataLength(); 2034 while (postProcessFromRow <= postProcessToRow) { 2035 var row = (vScrollDir >= 0) ? postProcessFromRow++ : postProcessToRow--; 2036 var cacheEntry = rowsCache[row]; 2037 if (!cacheEntry || row >= dataLength) { 2038 continue; 2039 } 2040 2041 if (!postProcessedRows[row]) { 2042 postProcessedRows[row] = {}; 2043 } 2044 2045 ensureCellNodesInRowsCache(row); 2046 for (var columnIdx in cacheEntry.cellNodesByColumnIdx) { 2047 if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) { 2048 continue; 2049 } 2050 2051 columnIdx = columnIdx | 0; 2052 2053 var m = columns[columnIdx]; 2054 if (m.asyncPostRender && !postProcessedRows[row][columnIdx]) { 2055 var node = cacheEntry.cellNodesByColumnIdx[columnIdx]; 2056 if (node) { 2057 m.asyncPostRender(node, row, getDataItem(row), m); 2058 } 2059 postProcessedRows[row][columnIdx] = true; 2060 } 2061 } 2062 2063 h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay); 2064 return; 2065 } 2066 } 2067 2068 function updateCellCssStylesOnRenderedRows(addedHash, removedHash) { 2069 var node, columnId, addedRowHash, removedRowHash; 2070 for (var row in rowsCache) { 2071 removedRowHash = removedHash && removedHash[row]; 2072 addedRowHash = addedHash && addedHash[row]; 2073 2074 if (removedRowHash) { 2075 for (columnId in removedRowHash) { 2076 if (!addedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) { 2077 node = getCellNode(row, getColumnIndex(columnId)); 2078 if (node) { 2079 $(node).removeClass(removedRowHash[columnId]); 2080 } 2081 } 2082 } 2083 } 2084 2085 if (addedRowHash) { 2086 for (columnId in addedRowHash) { 2087 if (!removedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) { 2088 node = getCellNode(row, getColumnIndex(columnId)); 2089 if (node) { 2090 $(node).addClass(addedRowHash[columnId]); 2091 } 2092 } 2093 } 2094 } 2095 } 2096 } 2097 2098 function addCellCssStyles(key, hash) {
2099 if (cellCssClasses[key]) { 2100 throw "addCellCssStyles: cell CSS hash with key '" + key + "' already exists."; 2101 } 2102 2103 cellCssClasses[key] = hash; 2104 updateCellCssStylesOnRenderedRows(hash, null); 2105 2106 trigger(self.onCellCssStylesChanged, { "key": key, "hash": hash }); 2107 } 2108 2109 function removeCellCssStyles(key) { 2110 if (!cellCssClasses[key]) { 2111 return; 2112 } 2113 2114 updateCellCssStylesOnRenderedRows(null, cellCssClasses[key]); 2115 delete cellCssClasses[key]; 2116 2117 trigger(self.onCellCssStylesChanged, { "key": key, "hash": null }); 2118 } 2119 2120 function setCellCssStyles(key, hash) { 2121 var prevHash = cellCssClasses[key]; 2122 2123 cellCssClasses[key] = hash; 2124 updateCellCssStylesOnRenderedRows(hash, prevHash); 2125 2126 trigger(self.onCellCssStylesChanged, { "key": key, "hash": hash }); 2127 } 2128 2129 function getCellCssStyles(key) { 2130 return cellCssClasses[key]; 2131 } 2132 2133 function flashCell(row, cell, speed) { 2134 speed = speed || 100; 2135 if (rowsCache[row]) { 2136 var $cell = $(getCellNode(row, cell)); 2137 2138 function toggleCellClass(times) { 2139 if (!times) { 2140 return; 2141 } 2142 setTimeout(function () { 2143 $cell.queue(function () { 2144 $cell.toggleClass(options.cellFlashingCssClass).dequeue(); 2145 toggleCellClass(times - 1); 2146 }); 2147 }, 2148 speed); 2149 } 2150 2151 toggleCellClass(4); 2152 } 2153 } 2154 2155 ////////////////////////////////////////////////////////////////////////////////////////////// 2156 // Interactivity 2157 2158 function handleMouseWheel(e) { 2159 var rowNode = $(e.target).closest(".slick-row")[0]; 2160 if (rowNode != rowNodeFromLastMouseWheelEvent) { 2161 if (zombieRowNodeFromLastMouseWheelEvent && zombieRowNodeFromLastMouseWheelEvent != rowNode) { 2162 $canvas[0].removeChild(zombieRowNodeFromLastMouseWheelEvent); 2163 zombieRowNodeFromLastMouseWheelEvent = null; 2164 } 2165 rowNodeFromLastMouseWheelEvent = rowNode; 2166 } 2167 } 2168 2169 function handleDragInit(e, dd) { 2170 var cell = getCellFromEvent(e); 2171 if (!cell || !cellExists(cell.row, cell.cell)) { 2172 return false; 2173 } 2174 2175 var retval = trigger(self.onDragInit, dd, e); 2176 if (e.isImmediatePropagationStopped()) { 2177 return retval; 2178 } 2179 2180 // if nobody claims to be handling drag'n'drop by stopping immediate propagation, 2181 // cancel out of it 2182 return false; 2183 } 2184 2185 function handleDragStart(e, dd) { 2186 var cell = getCellFromEvent(e); 2187 if (!cell || !cellExists(cell.row, cell.cell)) { 2188 return false; 2189 } 2190 2191 var retval = trigger(self.onDragStart, dd, e); 2192 if (e.isImmediatePropagationStopped()) { 2193 return retval; 2194 } 2195 2196 return false; 2197 } 2198 2199 function handleDrag(e, dd) { 2200 return trigger(self.onDrag, dd, e); 2201 } 2202 2203 function handleDragEnd(e, dd) { 2204 trigger(self.onDragEnd, dd, e); 2205 } 2206 2207 function handleKeyDown(e) { 2208 trigger(self.onKeyDown, {row: activeRow, cell: activeCell}, e); 2209 var handled = e.isImmediatePropagationStopped(); 2210 2211 if (!handled) { 2212 if (!e.shiftKey && !e.altKey && !e.ctrlKey) { 2213 if (e.which == 27) { 2214 if (!getEditorLock().isActive()) { 2215 return; // no editing mode to cancel, allow bubbling and default processing (exit without cancelling the event) 2216 } 2217 cancelEditAndSetFocus(); 2218 } else if (e.which == 34) { 2219 navigatePageDown(); 2220 handled = true; 2221 } else if (e.which == 33) { 2222 navigatePageUp(); 2223 handled = true; 2224 } else if (e.which == 37) { 2225 handled = navigateLeft(); 2226 } else if (e.which == 39) { 2227 handled = navigateRight(); 2228 } else if (e.which == 38) { 2229 handled = navigateUp(); 2230 } else if (e.which == 40) { 2231 handled = navigateDown(); 2232 } else if (e.which == 9) { 2233 handled = navigateNext(); 2234 } else if (e.which == 13) { 2235 if (options.editable) { 2236 if (currentEditor) { 2237 // adding new row 2238 if (activeRow === getDataLength()) { 2239 navigateDown(); 2240 } else { 2241 commitEditAndSetFocus(); 2242 } 2243 } else { 2244 if (getEditorLock().commitCurrentEdit()) { 2245 makeActiveCellEditable(); 2246 } 2247 } 2248 } 2249 handled = true; 2250 } 2251 } else if (e.which == 9 && e.shiftKey && !e.ctrlKey && !e.altKey) { 2252 handled = navigatePrev(); 2253 } 2254 } 2255 2256 if (handled) { 2257 // the event has been handled so don't let parent element (bubbling/propagation) or browser (default) handle it 2258 e.stopPropagation(); 2259 e.preventDefault(); 2260 try { 2261 e.originalEvent.keyCode = 0; // prevent default behaviour for special keys in IE browsers (F3, F5, etc.) 2262 } 2263 // ignore exceptions - setting the original event's keycode throws access denied exception for "Ctrl" 2264 // (hitting control key only, nothing else), "Shift" (maybe others) 2265 catch (error) { 2266 } 2267 } 2268 } 2269 2270 function handleClick(e) { 2271 if (!currentEditor) { 2272 // if this click resulted in some cell child node getting focus, 2273 // don't steal it back - keyboard events will still bubble up 2274 // IE9+ seems to default DIVs to tabIndex=0 instead of -1, so check for cell clicks directly. 2275 if (e.target != document.activeElement || $(e.target).hasClass("slick-cell")) { 2276 setFocus(); 2277 } 2278 } 2279 2280 var cell = getCellFromEvent(e); 2281 if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) { 2282 return; 2283 } 2284 2285 trigger(self.onClick, {row: cell.row, cell: cell.cell}, e); 2286 if (e.isImmediatePropagationStopped()) { 2287 return; 2288 } 2289 2290 if ((activeCell != cell.cell || activeRow != cell.row) && canCellBeActive(cell.row, cell.cell)) { 2291 if (!getEditorLock().isActive() || getEditorLock().commitCurrentEdit()) { 2292 scrollRowIntoView(cell.row, false);
2293 setActiveCellInternal(getCellNode(cell.row, cell.cell)); 2294 } 2295 } 2296 } 2297 2298 function handleContextMenu(e) { 2299 var $cell = $(e.target).closest(".slick-cell", $canvas); 2300 if ($cell.length === 0) { 2301 return; 2302 } 2303 2304 // are we editing this cell? 2305 if (activeCellNode === $cell[0] && currentEditor !== null) { 2306 return; 2307 } 2308 2309 trigger(self.onContextMenu, {}, e); 2310 } 2311 2312 function handleDblClick(e) { 2313 var cell = getCellFromEvent(e); 2314 if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) { 2315 return; 2316 } 2317 2318 trigger(self.onDblClick, {row: cell.row, cell: cell.cell}, e); 2319 if (e.isImmediatePropagationStopped()) { 2320 return; 2321 } 2322 2323 if (options.editable) { 2324 gotoCell(cell.row, cell.cell, true); 2325 } 2326 } 2327 2328 function handleHeaderMouseEnter(e) { 2329 trigger(self.onHeaderMouseEnter, { 2330 "column": $(this).data("column") 2331 }, e); 2332 } 2333 2334 function handleHeaderMouseLeave(e) { 2335 trigger(self.onHeaderMouseLeave, { 2336 "column": $(this).data("column") 2337 }, e); 2338 } 2339 2340 function handleHeaderContextMenu(e) { 2341 var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns"); 2342 var column = $header && $header.data("column"); 2343 trigger(self.onHeaderContextMenu, {column: column}, e); 2344 } 2345 2346 function handleHeaderClick(e) { 2347 var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns"); 2348 var column = $header && $header.data("column"); 2349 if (column) { 2350 trigger(self.onHeaderClick, {column: column}, e); 2351 } 2352 } 2353 2354 function handleMouseEnter(e) { 2355 trigger(self.onMouseEnter, {}, e); 2356 } 2357 2358 function handleMouseLeave(e) { 2359 trigger(self.onMouseLeave, {}, e); 2360 } 2361 2362 function cellExists(row, cell) { 2363 return !(row < 0 || row >= getDataLength() || cell < 0 || cell >= columns.length); 2364 } 2365 2366 function getCellFromPoint(x, y) { 2367 var row = getRowFromPosition(y); 2368 var cell = 0; 2369 2370 var w = 0; 2371 for (var i = 0; i < columns.length && w < x; i++) { 2372 w += columns[i].width; 2373 cell++; 2374 } 2375 2376 if (cell < 0) { 2377 cell = 0; 2378 } 2379 2380 return {row: row, cell: cell - 1}; 2381 } 2382 2383 function getCellFromNode(cellNode) { 2384 // read column number from .l<columnNumber> CSS class 2385 var cls = /l\d+/.exec(cellNode.className); 2386 if (!cls) { 2387 throw "getCellFromNode: cannot get cell - " + cellNode.className; 2388 } 2389 return parseInt(cls[0].substr(1, cls[0].length - 1), 10); 2390 } 2391 2392 function getRowFromNode(rowNode) { 2393 for (var row in rowsCache) { 2394 if (rowsCache[row].rowNode === rowNode) { 2395 return row | 0; 2396 } 2397 } 2398 2399 return null; 2400 } 2401 2402 function getCellFromEvent(e) { 2403 var $cell = $(e.target).closest(".slick-cell", $canvas); 2404 if (!$cell.length) { 2405 return null; 2406 } 2407 2408 var row = getRowFromNode($cell[0].parentNode); 2409 var cell = getCellFromNode($cell[0]); 2410 2411 if (row == null || cell == null) { 2412 return null; 2413 } else { 2414 return { 2415 "row": row, 2416 "cell": cell 2417 }; 2418 } 2419 } 2420 2421 function getCellNodeBox(row, cell) { 2422 if (!cellExists(row, cell)) { 2423 return null; 2424 } 2425 2426 var y1 = getRowTop(row); 2427 var y2 = y1 + options.rowHeight - 1; 2428 var x1 = 0; 2429 for (var i = 0; i < cell; i++) { 2430 x1 += columns[i].width; 2431 } 2432 var x2 = x1 + columns[cell].width; 2433 2434 return { 2435 top: y1, 2436 left: x1, 2437 bottom: y2, 2438 right: x2 2439 }; 2440 } 2441 2442 ////////////////////////////////////////////////////////////////////////////////////////////// 2443 // Cell switching 2444 2445 function resetActiveCell() { 2446 setActiveCellInternal(null, false); 2447 } 2448 2449 function setFocus() { 2450 if (tabbingDirection == -1) { 2451 $focusSink[0].focus(); 2452 } else { 2453 $focusSink2[0].focus(); 2454 } 2455 } 2456 2457 function scrollCellIntoView(row, cell, doPaging) { 2458 scrollRowIntoView(row, doPaging); 2459 2460 var colspan = getColspan(row, cell); 2461 var left = columnPosLeft[cell], 2462 right = columnPosRight[cell + (colspan > 1 ? colspan - 1 : 0)], 2463 scrollRight = scrollLeft + viewportW; 2464 2465 if (left < scrollLeft) { 2466 $viewport.scrollLeft(left); 2467 handleScroll(); 2468 render(); 2469 } else if (right > scrollRight) { 2470 $viewport.scrollLeft(Math.min(left, right - $viewport[0].clientWidth)); 2471 handleScroll(); 2472 render(); 2473 } 2474 } 2475 2476 function setActiveCellInternal(newCell, opt_editMode) { 2477 if (activeCellNode !== null) { 2478 makeActiveCellNormal(); 2479 $(activeCellNode).removeClass("active"); 2480 if (rowsCache[activeRow]) { 2481 $(rowsCache[activeRow].rowNode).removeClass("active"); 2482 } 2483 } 2484 2485 var activeCellChanged = (activeCellNode !== newCell); 2486 activeCellNode = newCell; 2487 2488 if (activeCellNode != null) { 2489 activeRow = getRowFromNode(activeCellNode.parentNode); 2490 activeCell = activePosX = getCellFromNode(activeCellNode); 2491 2492 if (opt_editMode == null) { 2493 opt_editMode = (activeRow == getDataLength()) || options.autoEdit; 2494 } 2495 2496 $(activeCellNode).addClass("active"); 2497 $(rowsCache[activeRow].rowNode).addClass("active"); 2498 2499 if (options.editable && opt_editMode && isCellPotentiallyEditable(activeRow, activeCell)) { 2500 clearTimeout(h_editorLoader); 2501 2502 if (options.asyncEditorLoading) { 2503 h_editorLoader = setTimeout(function () { 2504 makeActiveCellEditable(); 2505 }, options.asyncEditorLoadDelay); 2506 } else { 2507 makeActiveCellEditable(); 2508 } 2509 } 2510 } else { 2511 activeRow = activeCell = null; 2512 } 2513 2514 if (activeCellChanged) { 2515 trigger(self.onActiveCellChanged, getActiveCell()); 2516 } 2517 } 2518 2519 function clearTextSelection() { 2520 if (document.selection && document.selection.empty) { 2521 try { 2522 //IE fails here if selected element is not in dom 2523 document.selection.empty(); 2524 } catch (e) { } 2525 } else if (window.getSelection) { 2526 var sel = window.getSelection(); 2527 if (sel && sel.removeAllRanges) { 2528 sel.removeAllRanges(); 2529 } 2530 } 2531 } 2532 2533 function isCellPotentiallyEditable(row, cell) { 2534 var dataLength = getDataLength(); 2535 // is the data for this row loaded? 2536 if (row < dataLength && !getDataItem(row)) { 2537 return false; 2538 } 2539 2540 // are we in the Add New row? can we create new from this cell? 2541 if (columns[cell].cannotTriggerInsert && row >= dataLength) { 2542 return false; 2543 } 2544 2545 // does this cell have an editor? 2546 if (!getEditor(row, cell)) { 2547 return false; 2548 } 2549 2550 return true; 2551 } 2552 2553 function makeActiveCellNormal() { 2554 if (!currentEditor) { 2555 return; 2556 } 2557 trigger(self.onBeforeCellEditorDestroy, {editor: currentEditor}); 2558 currentEditor.destroy(); 2559 currentEditor = null; 2560 2561 if (activeCellNode) { 2562 var d = getDataItem(activeRow); 2563 $(activeCellNode).removeClass("editable invalid"); 2564 if (d) { 2565 var column = columns[activeCell]; 2566 var formatter = getFormatter(activeRow, column); 2567 activeCellNode.innerHTML = formatter(activeRow, activeCell, getDataItemValueForColumn(d, column), column, d); 2568 invalidatePostProcessingResults(activeRow); 2569 } 2570 } 2571 2572 // if there previously was text selected on a page (such as selected text in the edit cell just removed), 2573 // IE can't set focus to anything else correctly 2574 if (navigator.userAgent.toLowerCase().match(/msie/)) { 2575 clearTextSelection(); 2576 } 2577 2578 getEditorLock().deactivate(editController); 2579 } 2580 2581 function makeActiveCellEditable(editor) { 2582 if (!activeCellNode) { 2583 return; 2584 } 2585 if (!options.editable) { 2586 throw "Grid : makeActiveCellEditable : should never get called when options.editable is false"; 2587 } 2588 2589 // cancel pending async call if there is one 2590 clearTimeout(h_editorLoader); 2591 2592 if (!isCellPotentiallyEditable(activeRow, activeCell)) { 2593 return; 2594 } 2595 2596 var columnDef = columns[activeCell]; 2597 var item = getDataItem(activeRow); 2598 2599 if (trigger(self.onBeforeEditCell, {row: activeRow, cell: activeCell, item: item, column: columnDef}) === false) { 2600 setFocus(); 2601 return; 2602 } 2603 2604 getEditorLock().activate(editController); 2605 $(activeCellNode).addClass("editable"); 2606 2607 // don't clear the cell if a custom editor is passed through 2608 if (!editor) { 2609 activeCellNode.innerHTML = ""; 2610 } 2611 2612 currentEditor = new (editor || getEditor(activeRow, activeCell))({ 2613 grid: self,
2614 gridPosition: absBox($container[0]), 2615 position: absBox(activeCellNode), 2616 container: activeCellNode, 2617 column: columnDef, 2618 item: item || {}, 2619 commitChanges: commitEditAndSetFocus, 2620 cancelChanges: cancelEditAndSetFocus 2621 }); 2622 2623 if (item) { 2624 currentEditor.loadValue(item); 2625 } 2626 2627 serializedEditorValue = currentEditor.serializeValue(); 2628 2629 if (currentEditor.position) { 2630 handleActiveCellPositionChange(); 2631 } 2632 } 2633 2634 function commitEditAndSetFocus() { 2635 // if the commit fails, it would do so due to a validation error 2636 // if so, do not steal the focus from the editor 2637 if (getEditorLock().commitCurrentEdit()) { 2638 setFocus(); 2639 if (options.autoEdit) { 2640 navigateDown(); 2641 } 2642 } 2643 } 2644 2645 function cancelEditAndSetFocus() { 2646 if (getEditorLock().cancelCurrentEdit()) { 2647 setFocus(); 2648 } 2649 } 2650 2651 function absBox(elem) { 2652 var box = { 2653 top: elem.offsetTop, 2654 left: elem.offsetLeft, 2655 bottom: 0, 2656 right: 0, 2657 width: $(elem).outerWidth(), 2658 height: $(elem).outerHeight(), 2659 visible: true}; 2660 box.bottom = box.top + box.height; 2661 box.right = box.left + box.width; 2662 2663 // walk up the tree 2664 var offsetParent = elem.offsetParent; 2665 while ((elem = elem.parentNode) != document.body) { 2666 if (box.visible && elem.scrollHeight != elem.offsetHeight && $(elem).css("overflowY") != "visible") { 2667 box.visible = box.bottom > elem.scrollTop && box.top < elem.scrollTop + elem.clientHeight; 2668 } 2669 2670 if (box.visible && elem.scrollWidth != elem.offsetWidth && $(elem).css("overflowX") != "visible") { 2671 box.visible = box.right > elem.scrollLeft && box.left < elem.scrollLeft + elem.clientWidth; 2672 } 2673 2674 box.left -= elem.scrollLeft; 2675 box.top -= elem.scrollTop; 2676 2677 if (elem === offsetParent) { 2678 box.left += elem.offsetLeft; 2679 box.top += elem.offsetTop; 2680 offsetParent = elem.offsetParent; 2681 } 2682 2683 box.bottom = box.top + box.height; 2684 box.right = box.left + box.width; 2685 } 2686 2687 return box; 2688 } 2689 2690 function getActiveCellPosition() { 2691 return absBox(activeCellNode); 2692 } 2693 2694 function getGridPosition() { 2695 return absBox($container[0]) 2696 } 2697 2698 function handleActiveCellPositionChange() { 2699 if (!activeCellNode) { 2700 return; 2701 } 2702 2703 trigger(self.onActiveCellPositionChanged, {}); 2704 2705 if (currentEditor) { 2706 var cellBox = getActiveCellPosition(); 2707 if (currentEditor.show && currentEditor.hide) { 2708 if (!cellBox.visible) { 2709 currentEditor.hide(); 2710 } else { 2711 currentEditor.show(); 2712 } 2713 } 2714 2715 if (currentEditor.position) { 2716 currentEditor.position(cellBox); 2717 } 2718 } 2719 } 2720 2721 function getCellEditor() { 2722 return currentEditor; 2723 } 2724 2725 function getActiveCell() { 2726 if (!activeCellNode) { 2727 return null; 2728 } else { 2729 return {row: activeRow, cell: activeCell}; 2730 } 2731 } 2732 2733 function getActiveCellNode() { 2734 return activeCellNode; 2735 } 2736 2737 function scrollRowIntoView(row, doPaging) { 2738 var rowAtTop = row * options.rowHeight; 2739 var rowAtBottom = (row + 1) * options.rowHeight - viewportH + (viewportHasHScroll ? scrollbarDimensions.height : 0); 2740 2741 // need to page down? 2742 if ((row + 1) * options.rowHeight > scrollTop + viewportH + offset) { 2743 scrollTo(doPaging ? rowAtTop : rowAtBottom); 2744 render(); 2745 } 2746 // or page up? 2747 else if (row * options.rowHeight < scrollTop + offset) { 2748 scrollTo(doPaging ? rowAtBottom : rowAtTop); 2749 render(); 2750 } 2751 } 2752 2753 function scrollRowToTop(row) { 2754 scrollTo(row * options.rowHeight); 2755 render(); 2756 } 2757 2758 function scrollPage(dir) { 2759 var deltaRows = dir * numVisibleRows; 2760 scrollTo((getRowFromPosition(scrollTop) + deltaRows) * options.rowHeight); 2761 render(); 2762 2763 if (options.enableCellNavigation && activeRow != null) { 2764 var row = activeRow + deltaRows; 2765 var dataLengthIncludingAddNew = getDataLengthIncludingAddNew(); 2766 if (row >= dataLengthIncludingAddNew) { 2767 row = dataLengthIncludingAddNew - 1; 2768 } 2769 if (row < 0) { 2770 row = 0; 2771 } 2772 2773 var cell = 0, prevCell = null; 2774 var prevActivePosX = activePosX; 2775 while (cell <= activePosX) { 2776 if (canCellBeActive(row, cell)) { 2777 prevCell = cell; 2778 } 2779 cell += getColspan(row, cell); 2780 } 2781 2782 if (prevCell !== null) {
2783 setActiveCellInternal(getCellNode(row, prevCell)); 2784 activePosX = prevActivePosX; 2785 } else { 2786 resetActiveCell(); 2787 } 2788 } 2789 } 2790 2791 function navigatePageDown() { 2792 scrollPage(1); 2793 } 2794 2795 function navigatePageUp() { 2796 scrollPage(-1); 2797 } 2798 2799 function getColspan(row, cell) { 2800 var metadata = data.getItemMetadata && data.getItemMetadata(row); 2801 if (!metadata || !metadata.columns) { 2802 return 1; 2803 } 2804 2805 var columnData = metadata.columns[columns[cell].id] || metadata.columns[cell]; 2806 var colspan = (columnData && columnData.colspan); 2807 if (colspan === "*") { 2808 colspan = columns.length - cell; 2809 } else { 2810 colspan = colspan || 1; 2811 } 2812 2813 return colspan; 2814 } 2815 2816 function findFirstFocusableCell(row) { 2817 var cell = 0; 2818 while (cell < columns.length) { 2819 if (canCellBeActive(row, cell)) { 2820 return cell; 2821 } 2822 cell += getColspan(row, cell); 2823 } 2824 return null; 2825 } 2826 2827 function findLastFocusableCell(row) { 2828 var cell = 0; 2829 var lastFocusableCell = null; 2830 while (cell < columns.length) { 2831 if (canCellBeActive(row, cell)) { 2832 lastFocusableCell = cell; 2833 } 2834 cell += getColspan(row, cell); 2835 } 2836 return lastFocusableCell; 2837 } 2838 2839 function gotoRight(row, cell, posX) { 2840 if (cell >= columns.length) { 2841 return null; 2842 } 2843 2844 do { 2845 cell += getColspan(row, cell); 2846 } 2847 while (cell < columns.length && !canCellBeActive(row, cell)); 2848 2849 if (cell < columns.length) { 2850 return { 2851 "row": row, 2852 "cell": cell, 2853 "posX": cell 2854 }; 2855 } 2856 return null; 2857 } 2858 2859 function gotoLeft(row, cell, posX) { 2860 if (cell <= 0) { 2861 return null; 2862 } 2863 2864 var firstFocusableCell = findFirstFocusableCell(row); 2865 if (firstFocusableCell === null || firstFocusableCell >= cell) { 2866 return null; 2867 } 2868 2869 var prev = { 2870 "row": row, 2871 "cell": firstFocusableCell, 2872 "posX": firstFocusableCell 2873 }; 2874 var pos; 2875 while (true) { 2876 pos = gotoRight(prev.row, prev.cell, prev.posX); 2877 if (!pos) { 2878 return null; 2879 } 2880 if (pos.cell >= cell) { 2881 return prev; 2882 } 2883 prev = pos; 2884 } 2885 } 2886 2887 function gotoDown(row, cell, posX) { 2888 var prevCell; 2889 var dataLengthIncludingAddNew = getDataLengthIncludingAddNew(); 2890 while (true) { 2891 if (++row >= dataLengthIncludingAddNew) { 2892 return null; 2893 } 2894 2895 prevCell = cell = 0; 2896 while (cell <= posX) { 2897 prevCell = cell; 2898 cell += getColspan(row, cell); 2899 } 2900 2901 if (canCellBeActive(row, prevCell)) { 2902 return { 2903 "row": row, 2904 "cell": prevCell, 2905 "posX": posX 2906 }; 2907 } 2908 } 2909 } 2910 2911 function gotoUp(row, cell, posX) { 2912 var prevCell; 2913 while (true) { 2914 if (--row < 0) { 2915 return null; 2916 } 2917 2918 prevCell = cell = 0; 2919 while (cell <= posX) { 2920 prevCell = cell; 2921 cell += getColspan(row, cell); 2922 } 2923 2924 if (canCellBeActive(row, prevCell)) { 2925 return { 2926 "row": row, 2927 "cell": prevCell, 2928 "posX": posX 2929 }; 2930 } 2931 } 2932 } 2933 2934 function gotoNext(row, cell, posX) { 2935 if (row == null && cell == null) { 2936 row = cell = posX = 0; 2937 if (canCellBeActive(row, cell)) { 2938 return { 2939 "row": row, 2940 "cell": cell, 2941 "posX": cell 2942 }; 2943 } 2944 } 2945 2946 var pos = gotoRight(row, cell, posX); 2947 if (pos) { 2948 return pos; 2949 } 2950 2951 var firstFocusableCell = null; 2952 var dataLengthIncludingAddNew = getDataLengthIncludingAddNew(); 2953 while (++row < dataLengthIncludingAddNew) { 2954 firstFocusableCell = findFirstFocusableCell(row); 2955 if (firstFocusableCell !== null) { 2956 return { 2957 "row": row, 2958 "cell": firstFocusableCell, 2959 "posX": firstFocusableCell 2960 }; 2961 } 2962 } 2963 return null; 2964 } 2965 2966 function gotoPrev(row, cell, posX) { 2967 if (row == null && cell == null) { 2968 row = getDataLengthIncludingAddNew() - 1; 2969 cell = posX = columns.length - 1; 2970 if (canCellBeActive(row, cell)) { 2971 return { 2972 "row": row, 2973 "cell": cell, 2974 "posX": cell 2975 }; 2976 } 2977 } 2978 2979 var pos; 2980 var lastSelectableCell; 2981 while (!pos) { 2982 pos = gotoLeft(row, cell, posX); 2983 if (pos) { 2984 break; 2985 } 2986 if (--row < 0) { 2987 return null; 2988 } 2989 2990 cell = 0; 2991 lastSelectableCell = findLastFocusableCell(row); 2992 if (lastSelectableCell !== null) { 2993 pos = { 2994 "row": row, 2995 "cell": lastSelectableCell, 2996 "posX": lastSelectableCell 2997 }; 2998 } 2999 } 3000 return pos; 3001 } 3002 3003 function navigateRight() { 3004 return navigate("right"); 3005 } 3006 3007 function navigateLeft() { 3008 return navigate("left"); 3009 } 3010 3011 function navigateDown() { 3012 return navigate("down"); 3013 } 3014 3015 function navigateUp() { 3016 return navigate("up"); 3017 } 3018 3019 function navigateNext() { 3020 return navigate("next"); 3021 } 3022 3023 function navigatePrev() { 3024 return navigate("prev"); 3025 } 3026 3027 /** 3028 * @param {string} dir Navigation direction. 3029 * @return {boolean} Whether navigation resulted in a change of active cell. 3030 */ 3031 function navigate(dir) { 3032 if (!options.enableCellNavigation) { 3033 return false; 3034 } 3035 3036 if (!activeCellNode && dir != "prev" && dir != "next") { 3037 return false; 3038 } 3039 3040 if (!getEditorLock().commitCurrentEdit()) { 3041 return true; 3042 } 3043 setFocus(); 3044 3045 var tabbingDirections = { 3046 "up": -1, 3047 "down": 1, 3048 "left": -1, 3049 "right": 1,
3050 "prev": -1, 3051 "next": 1 3052 }; 3053 tabbingDirection = tabbingDirections[dir]; 3054 3055 var stepFunctions = { 3056 "up": gotoUp, 3057 "down": gotoDown, 3058 "left": gotoLeft, 3059 "right": gotoRight, 3060 "prev": gotoPrev, 3061 "next": gotoNext 3062 }; 3063 var stepFn = stepFunctions[dir]; 3064 var pos = stepFn(activeRow, activeCell, activePosX); 3065 if (pos) { 3066 var isAddNewRow = (pos.row == getDataLength()); 3067 scrollCellIntoView(pos.row, pos.cell, !isAddNewRow); 3068 setActiveCellInternal(getCellNode(pos.row, pos.cell)); 3069 activePosX = pos.posX; 3070 return true; 3071 } else { 3072 setActiveCellInternal(getCellNode(activeRow, activeCell)); 3073 return false; 3074 } 3075 } 3076 3077 function getCellNode(row, cell) { 3078 if (rowsCache[row]) { 3079 ensureCellNodesInRowsCache(row); 3080 return rowsCache[row].cellNodesByColumnIdx[cell]; 3081 } 3082 return null; 3083 } 3084 3085 function setActiveCell(row, cell) { 3086 if (!initialized) { return; } 3087 if (row > getDataLength() || row < 0 || cell >= columns.length || cell < 0) { 3088 return; 3089 } 3090 3091 if (!options.enableCellNavigation) { 3092 return; 3093 } 3094 3095 scrollCellIntoView(row, cell, false); 3096 setActiveCellInternal(getCellNode(row, cell), false); 3097 } 3098 3099 function canCellBeActive(row, cell) { 3100 if (!options.enableCellNavigation || row >= getDataLengthIncludingAddNew() || 3101 row < 0 || cell >= columns.length || cell < 0) { 3102 return false; 3103 } 3104 3105 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row); 3106 if (rowMetadata && typeof rowMetadata.focusable === "boolean") { 3107 return rowMetadata.focusable; 3108 } 3109 3110 var columnMetadata = rowMetadata && rowMetadata.columns; 3111 if (columnMetadata && columnMetadata[columns[cell].id] && typeof columnMetadata[columns[cell].id].focusable === "boolean") { 3112 return columnMetadata[columns[cell].id].focusable; 3113 } 3114 if (columnMetadata && columnMetadata[cell] && typeof columnMetadata[cell].focusable === "boolean") { 3115 return columnMetadata[cell].focusable; 3116 } 3117 3118 return columns[cell].focusable; 3119 } 3120 3121 function canCellBeSelected(row, cell) { 3122 if (row >= getDataLength() || row < 0 || cell >= columns.length || cell < 0) { 3123 return false; 3124 } 3125 3126 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row); 3127 if (rowMetadata && typeof rowMetadata.selectable === "boolean") { 3128 return rowMetadata.selectable; 3129 } 3130 3131 var columnMetadata = rowMetadata && rowMetadata.columns && (rowMetadata.columns[columns[cell].id] || rowMetadata.columns[cell]); 3132 if (columnMetadata && typeof columnMetadata.selectable === "boolean") { 3133 return columnMetadata.selectable; 3134 } 3135 3136 return columns[cell].selectable; 3137 } 3138 3139 function gotoCell(row, cell, forceEdit) { 3140 if (!initialized) { return; } 3141 if (!canCellBeActive(row, cell)) { 3142 return; 3143 } 3144 3145 if (!getEditorLock().commitCurrentEdit()) { 3146 return; 3147 } 3148 3149 scrollCellIntoView(row, cell, false); 3150 3151 var newCell = getCellNode(row, cell); 3152 3153 // if selecting the 'add new' row, start editing right away 3154 setActiveCellInternal(newCell, forceEdit || (row === getDataLength()) || options.autoEdit); 3155 3156 // if no editor was created, set the focus back on the grid 3157 if (!currentEditor) { 3158 setFocus(); 3159 } 3160 } 3161 3162 3163 ////////////////////////////////////////////////////////////////////////////////////////////// 3164 // IEditor implementation for the editor lock 3165 3166 function commitCurrentEdit() { 3167 var item = getDataItem(activeRow); 3168 var column = columns[activeCell]; 3169 3170 if (currentEditor) { 3171 if (currentEditor.isValueChanged()) { 3172 var validationResults = currentEditor.validate(); 3173 3174 if (validationResults.valid) { 3175 if (activeRow < getDataLength()) { 3176 var editCommand = { 3177 row: activeRow, 3178 cell: activeCell, 3179 editor: currentEditor, 3180 serializedValue: currentEditor.serializeValue(), 3181 prevSerializedValue: serializedEditorValue, 3182 execute: function () { 3183 this.editor.applyValue(item, this.serializedValue); 3184 updateRow(this.row); 3185 trigger(self.onCellChange, { 3186 row: activeRow, 3187 cell: activeCell, 3188 item: item 3189 }); 3190 }, 3191 undo: function () { 3192 this.editor.applyValue(item, this.prevSerializedValue); 3193 updateRow(this.row); 3194 trigger(self.onCellChange, { 3195 row: activeRow, 3196 cell: activeCell, 3197 item: item 3198 }); 3199 } 3200 }; 3201 3202 if (options.editCommandHandler) { 3203 makeActiveCellNormal(); 3204 options.editCommandHandler(item, column, editCommand); 3205 } else { 3206 editCommand.execute(); 3207 makeActiveCellNormal(); 3208 } 3209 3210 } else { 3211 var newItem = {}; 3212 currentEditor.applyValue(newItem, currentEditor.serializeValue()); 3213 makeActiveCellNormal(); 3214 trigger(self.onAddNewRow, {item: newItem, column: column}); 3215 } 3216 3217 // check whether the lock has been re-acquired by event handlers 3218 return !getEditorLock().isActive(); 3219 } else { 3220 // Re-add the CSS class to trigger transitions, if any. 3221 $(activeCellNode).removeClass("invalid"); 3222 $(activeCellNode).width(); // force layout 3223 $(activeCellNode).addClass("invalid"); 3224 3225 trigger(self.onValidationError, { 3226 editor: currentEditor, 3227 cellNode: activeCellNode, 3228 validationResults: validationResults, 3229 row: activeRow, 3230 cell: activeCell, 3231 column: column 3232 }); 3233 3234 currentEditor.focus(); 3235 return false; 3236 } 3237 } 3238 3239 makeActiveCellNormal(); 3240 } 3241 return true; 3242 } 3243 3244 function cancelCurrentEdit() { 3245 makeActiveCellNormal(); 3246 return true; 3247 } 3248 3249 function rowsToRanges(rows) { 3250 var ranges = []; 3251 var lastCell = columns.length - 1; 3252 for (var i = 0; i < rows.length; i++) { 3253 ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell)); 3254 } 3255 return ranges; 3256 } 3257 3258 function getSelectedRows() { 3259 if (!selectionModel) { 3260 throw "Selection model is not set"; 3261 } 3262 return selectedRows; 3263 } 3264 3265 function setSelectedRows(rows) { 3266 if (!selectionModel) { 3267 throw "Selection model is not set"; 3268 } 3269 selectionModel.setSelectedRanges(rowsToRanges(rows)); 3270 } 3271 3272
3273 ////////////////////////////////////////////////////////////////////////////////////////////// 3274 // Debug 3275 3276 this.debug = function () { 3277 var s = ""; 3278 3279 s += ("\n" + "counter_rows_rendered: " + counter_rows_rendered); 3280 s += ("\n" + "counter_rows_removed: " + counter_rows_removed); 3281 s += ("\n" + "renderedRows: " + renderedRows); 3282 s += ("\n" + "numVisibleRows: " + numVisibleRows); 3283 s += ("\n" + "maxSupportedCssHeight: " + maxSupportedCssHeight); 3284 s += ("\n" + "n(umber of pages): " + n); 3285 s += ("\n" + "(current) page: " + page); 3286 s += ("\n" + "page height (ph): " + ph); 3287 s += ("\n" + "vScrollDir: " + vScrollDir); 3288 3289 alert(s); 3290 }; 3291 3292 // a debug helper to be able to access private members 3293 this.eval = function (expr) { 3294 return eval(expr); 3295 }; 3296 3297 ////////////////////////////////////////////////////////////////////////////////////////////// 3298 // Public API 3299 3300 $.extend(this, { 3301 "slickGridVersion": "2.1", 3302 3303 // Events 3304 "onScroll": new Slick.Event(), 3305 "onSort": new Slick.Event(), 3306 "onHeaderMouseEnter": new Slick.Event(), 3307 "onHeaderMouseLeave": new Slick.Event(), 3308 "onHeaderContextMenu": new Slick.Event(), 3309 "onHeaderClick": new Slick.Event(), 3310 "onHeaderCellRendered": new Slick.Event(), 3311 "onBeforeHeaderCellDestroy": new Slick.Event(), 3312 "onHeaderRowCellRendered": new Slick.Event(), 3313 "onBeforeHeaderRowCellDestroy": new Slick.Event(), 3314 "onMouseEnter": new Slick.Event(), 3315 "onMouseLeave": new Slick.Event(), 3316 "onClick": new Slick.Event(), 3317 "onDblClick": new Slick.Event(), 3318 "onContextMenu": new Slick.Event(), 3319 "onKeyDown": new Slick.Event(), 3320 "onAddNewRow": new Slick.Event(), 3321 "onValidationError": new Slick.Event(), 3322 "onViewportChanged": new Slick.Event(), 3323 "onColumnsReordered": new Slick.Event(), 3324 "onColumnsResized": new Slick.Event(), 3325 "onCellChange": new Slick.Event(), 3326 "onBeforeEditCell": new Slick.Event(), 3327 "onBeforeCellEditorDestroy": new Slick.Event(), 3328 "onBeforeDestroy": new Slick.Event(), 3329 "onActiveCellChanged": new Slick.Event(), 3330 "onActiveCellPositionChanged": new Slick.Event(), 3331 "onDragInit": new Slick.Event(), 3332 "onDragStart": new Slick.Event(), 3333 "onDrag": new Slick.Event(), 3334 "onDragEnd": new Slick.Event(), 3335 "onSelectedRowsChanged": new Slick.Event(), 3336 "onCellCssStylesChanged": new Slick.Event(), 3337 3338 // Methods 3339 "registerPlugin": registerPlugin, 3340 "unregisterPlugin": unregisterPlugin, 3341 "getColumns": getColumns, 3342 "setColumns": setColumns, 3343 "getColumnIndex": getColumnIndex, 3344 "updateColumnHeader": updateColumnHeader, 3345 "setSortColumn": setSortColumn, 3346 "setSortColumns": setSortColumns, 3347 "getSortColumns": getSortColumns, 3348 "autosizeColumns": autosizeColumns, 3349 "getOptions": getOptions, 3350 "setOptions": setOptions, 3351 "getData": getData, 3352 "getDataLength": getDataLength, 3353 "getDataItem": getDataItem, 3354 "setData": setData, 3355 "getSelectionModel": getSelectionModel, 3356 "setSelectionModel": setSelectionModel, 3357 "getSelectedRows": getSelectedRows, 3358 "setSelectedRows": setSelectedRows, 3359 "getContainerNode": getContainerNode, 3360 3361 "render": render, 3362 "invalidate": invalidate, 3363 "invalidateRow": invalidateRow, 3364 "invalidateRows": invalidateRows, 3365 "invalidateAllRows": invalidateAllRows, 3366 "updateCell": updateCell, 3367 "updateRow": updateRow, 3368 "getViewport": getVisibleRange, 3369 "getRenderedRange": getRenderedRange, 3370 "resizeCanvas": resizeCanvas, 3371 "updateRowCount": updateRowCount, 3372 "scrollRowIntoView": scrollRowIntoView, 3373 "scrollRowToTop": scrollRowToTop, 3374 "scrollCellIntoView": scrollCellIntoView, 3375 "getCanvasNode": getCanvasNode, 3376 "focus": setFocus, 3377 3378 "getCellFromPoint": getCellFromPoint, 3379 "getCellFromEvent": getCellFromEvent, 3380 "getActiveCell": getActiveCell, 3381 "setActiveCell": setActiveCell, 3382 "getActiveCellNode": getActiveCellNode, 3383 "getActiveCellPosition": getActiveCellPosition, 3384 "resetActiveCell": resetActiveCell, 3385 "editActiveCell": makeActiveCellEditable, 3386 "getCellEditor": getCellEditor, 3387 "getCellNode": getCellNode, 3388 "getCellNodeBox": getCellNodeBox, 3389 "canCellBeSelected": canCellBeSelected, 3390 "canCellBeActive": canCellBeActive, 3391 "navigatePrev": navigatePrev, 3392 "navigateNext": navigateNext, 3393 "navigateUp": navigateUp, 3394 "navigateDown": navigateDown, 3395 "navigateLeft": navigateLeft, 3396 "navigateRight": navigateRight, 3397 "navigatePageUp": navigatePageUp, 3398 "navigatePageDown": navigatePageDown, 3399 "gotoCell": gotoCell, 3400 "getTopPanel": getTopPanel, 3401 "setTopPanelVisibility": setTopPanelVisibility, 3402 "setHeaderRowVisibility": setHeaderRowVisibility, 3403 "getHeaderRow": getHeaderRow, 3404 "getHeaderRowColumn": getHeaderRowColumn, 3405 "getGridPosition": getGridPosition, 3406 "flashCell": flashCell, 3407 "addCellCssStyles": addCellCssStyles, 3408 "setCellCssStyles": setCellCssStyles, 3409 "removeCellCssStyles": removeCellCssStyles, 3410 "getCellCssStyles": getCellCssStyles, 3411 3412 "init": finishInitialization, 3413 "destroy": destroy, 3414 3415 // IEditor implementation 3416 "getEditorLock": getEditorLock, 3417 "getEditController": getEditController 3418 });
3419 3420 init(); 3421 } 3422}(jQuery));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.