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