1(function ($) { 2 $.extend(true, window, { 3 Slick: { 4 Data: { 5 DataView: DataView, 6 Aggregators: { 7 Avg: AvgAggregator, 8 Min: MinAggregator, 9 Max: MaxAggregator, 10 Sum: SumAggregator 11 } 12 } 13 } 14 }); 15 16 17 /*** 18 * A sample Model implementation. 19 * Provides a filtered view of the underlying data. 20 * 21 * Relies on the data item having an "id" property uniquely identifying it. 22 */ 23 function DataView(options) { 24 var self = this; 25 26 var defaults = { 27 groupItemMetadataProvider: null, 28 inlineFilters: false 29 }; 30 31 32 // private 33 var idProperty = "id"; // property holding a unique row id 34 var items = []; // data by index 35 var rows = []; // data by row 36 var idxById = {}; // indexes by id 37 var rowsById = null; // rows by id; lazy-calculated 38 var filter = null; // filter function 39 var updated = null; // updated item ids 40 var suspend = false; // suspends the recalculation 41 var sortAsc = true; 42 var fastSortField; 43 var sortComparer; 44 var refreshHints = {}; 45 var prevRefreshHints = {}; 46 var filterArgs; 47 var filteredItems = []; 48 var compiledFilter; 49 var compiledFilterWithCaching; 50 var filterCache = []; 51 52 // grouping 53 var groupingInfoDefaults = { 54 getter: null, 55 formatter: null, 56 comparer: function(a, b) { 57 return (a.value === b.value ? 0 : 58 (a.value > b.value ? 1 : -1) 59 ); 60 }, 61 predefinedValues: [], 62 aggregators: [], 63 aggregateEmpty: false, 64 aggregateCollapsed: false, 65 aggregateChildGroups: false, 66 collapsed: false, 67 displayTotalsRow: true, 68 lazyTotalsCalculation: false 69 }; 70 var groupingInfos = []; 71 var groups = []; 72 var toggledGroupsByLevel = []; 73 var groupingDelimiter = ':|:'; 74 75 var pagesize = 0; 76 var pagenum = 0; 77 var totalRows = 0; 78 79 // events 80 var onRowCountChanged = new Slick.Event(); 81 var onRowsChanged = new Slick.Event(); 82 var onPagingInfoChanged = new Slick.Event(); 83 84 options = $.extend(true, {}, defaults, options); 85 86 87 function beginUpdate() { 88 suspend = true; 89 } 90 91 function endUpdate() { 92 suspend = false; 93 refresh(); 94 } 95 96 function setRefreshHints(hints) { 97 refreshHints = hints; 98 } 99 100 function setFilterArgs(args) { 101 filterArgs = args; 102 } 103 104 function updateIdxById(startingIndex) { 105 startingIndex = startingIndex || 0; 106 var id; 107 for (var i = startingIndex, l = items.length; i < l; i++) { 108 id = items[i][idProperty]; 109 if (id === undefined) { 110 throw new Error("Each data element must implement a unique 'id' property"); 111 } 112 idxById[id] = i; 113 } 114 } 115 116 function ensureIdUniqueness() { 117 var id; 118 for (var i = 0, l = items.length; i < l; i++) { 119 id = items[i][idProperty]; 120 if (id === undefined || idxById[id] !== i) { 121 throw new Error("Each data element must implement a unique 'id' property"); 122 } 123 } 124 } 125 126 function getItems() { 127 return items; 128 } 129 130 function setItems(data, objectIdProperty) { 131 if (objectIdProperty !== undefined) { 132 idProperty = objectIdProperty; 133 } 134 items = filteredItems = data; 135 idxById = {}; 136 updateIdxById(); 137 ensureIdUniqueness(); 138 refresh(); 139 } 140 141 function setPagingOptions(args) { 142 if (args.pageSize != undefined) { 143 pagesize = args.pageSize; 144 pagenum = pagesize ? Math.min(pagenum, Math.max(0, Math.ceil(totalRows / pagesize) - 1)) : 0; 145 } 146 147 if (args.pageNum != undefined) { 148 pagenum = Math.min(args.pageNum, Math.max(0, Math.ceil(totalRows / pagesize) - 1)); 149 } 150 151 onPagingInfoChanged.notify(getPagingInfo(), null, self); 152 153 refresh(); 154 } 155 156 function getPagingInfo() { 157 var totalPages = pagesize ? Math.max(1, Math.ceil(totalRows / pagesize)) : 1; 158 return {pageSize: pagesize, pageNum: pagenum, totalRows: totalRows, totalPages: totalPages, dataView: self}; 159 } 160 161 function sort(comparer, ascending) { 162 sortAsc = ascending; 163 sortComparer = comparer; 164 fastSortField = null; 165 if (ascending === false) { 166 items.reverse(); 167 } 168 items.sort(comparer); 169 if (ascending === false) { 170 items.reverse(); 171 } 172 idxById = {}; 173 updateIdxById(); 174 refresh(); 175 } 176 177 /*** 178 * Provides a workaround for the extremely slow sorting in IE. 179 * Does a [lexicographic] sort on a give column by temporarily overriding Object.prototype.toString 180 * to return the value of that field and then doing a native Array.sort(). 181 */ 182 function fastSort(field, ascending) { 183 sortAsc = ascending; 184 fastSortField = field; 185 sortComparer = null; 186 var oldToString = Object.prototype.toString; 187 Object.prototype.toString = (typeof field == "function") ? field : function () { 188 return this[field] 189 }; 190 // an extra reversal for descending sort keeps the sort stable 191 // (assuming a stable native sort implementation, which isn't true in some cases) 192 if (ascending === false) { 193 items.reverse(); 194 } 195 items.sort(); 196 Object.prototype.toString = oldToString; 197 if (ascending === false) { 198 items.reverse(); 199 } 200 idxById = {}; 201 updateIdxById(); 202 refresh(); 203 } 204 205 function reSort() { 206 if (sortComparer) { 207 sort(sortComparer, sortAsc); 208 } else if (fastSortField) { 209 fastSort(fastSortField, sortAsc); 210 } 211 } 212 213 function getFilteredItems(){ 214 return filteredItems; 215 } 216 217 218 function getFilter(){ 219 return filter; 220 } 221 222 function setFilter(filterFn) {
223 filter = filterFn; 224 if (options.inlineFilters) { 225 compiledFilter = compileFilter(); 226 compiledFilterWithCaching = compileFilterWithCaching(); 227 } 228 refresh(); 229 } 230 231 function getGrouping() { 232 return groupingInfos; 233 } 234 235 function setGrouping(groupingInfo) { 236 if (!options.groupItemMetadataProvider) { 237 options.groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider(); 238 } 239 240 groups = []; 241 toggledGroupsByLevel = []; 242 groupingInfo = groupingInfo || []; 243 groupingInfos = (groupingInfo instanceof Array) ? groupingInfo : [groupingInfo]; 244 245 for (var i = 0; i < groupingInfos.length; i++) { 246 var gi = groupingInfos[i] = $.extend(true, {}, groupingInfoDefaults, groupingInfos[i]); 247 gi.getterIsAFn = typeof gi.getter === "function"; 248 249 // pre-compile accumulator loops 250 gi.compiledAccumulators = []; 251 var idx = gi.aggregators.length; 252 while (idx--) { 253 gi.compiledAccumulators[idx] = compileAccumulatorLoop(gi.aggregators[idx]); 254 } 255 256 toggledGroupsByLevel[i] = {}; 257 } 258 259 refresh(); 260 } 261 262 /** 263 * @deprecated Please use {@link setGrouping}. 264 */ 265 function groupBy(valueGetter, valueFormatter, sortComparer) { 266 if (valueGetter == null) { 267 setGrouping([]); 268 return; 269 } 270 271 setGrouping({ 272 getter: valueGetter, 273 formatter: valueFormatter, 274 comparer: sortComparer 275 }); 276 } 277 278 /** 279 * @deprecated Please use {@link setGrouping}. 280 */ 281 function setAggregators(groupAggregators, includeCollapsed) { 282 if (!groupingInfos.length) { 283 throw new Error("At least one grouping must be specified before calling setAggregators()."); 284 } 285 286 groupingInfos[0].aggregators = groupAggregators; 287 groupingInfos[0].aggregateCollapsed = includeCollapsed; 288 289 setGrouping(groupingInfos); 290 } 291 292 function getItemByIdx(i) { 293 return items[i]; 294 } 295 296 function getIdxById(id) { 297 return idxById[id]; 298 } 299 300 function ensureRowsByIdCache() { 301 if (!rowsById) { 302 rowsById = {}; 303 for (var i = 0, l = rows.length; i < l; i++) { 304 rowsById[rows[i][idProperty]] = i; 305 } 306 } 307 } 308 309 function getRowByItem(item) { 310 ensureRowsByIdCache(); 311 return rowsById[item[idProperty]]; 312 } 313 314 function getRowById(id) { 315 ensureRowsByIdCache(); 316 return rowsById[id]; 317 } 318 319 function getItemById(id) { 320 return items[idxById[id]]; 321 } 322 323 function mapItemsToRows(itemArray) { 324 var rows = []; 325 ensureRowsByIdCache(); 326 for (var i = 0, l = itemArray.length; i < l; i++) { 327 var row = rowsById[itemArray[i][idProperty]]; 328 if (row != null) { 329 rows[rows.length] = row; 330 } 331 } 332 return rows; 333 } 334 335 function mapIdsToRows(idArray) { 336 var rows = []; 337 ensureRowsByIdCache(); 338 for (var i = 0, l = idArray.length; i < l; i++) { 339 var row = rowsById[idArray[i]]; 340 if (row != null) { 341 rows[rows.length] = row; 342 } 343 } 344 return rows; 345 } 346 347 function mapRowsToIds(rowArray) { 348 var ids = []; 349 for (var i = 0, l = rowArray.length; i < l; i++) { 350 if (rowArray[i] < rows.length) { 351 ids[ids.length] = rows[rowArray[i]][idProperty]; 352 } 353 } 354 return ids; 355 } 356 357 function updateItem(id, item) { 358 if (idxById[id] === undefined || id !== item[idProperty]) { 359 throw new Error("Invalid or non-matching id"); 360 } 361 items[idxById[id]] = item; 362 if (!updated) { 363 updated = {}; 364 } 365 updated[id] = true; 366 refresh(); 367 } 368 369 function insertItem(insertBefore, item) { 370 items.splice(insertBefore, 0, item); 371 updateIdxById(insertBefore); 372 refresh(); 373 } 374 375 function addItem(item) { 376 items.push(item); 377 updateIdxById(items.length - 1); 378 refresh(); 379 } 380 381 function deleteItem(id) { 382 var idx = idxById[id]; 383 if (idx === undefined) { 384 throw new Error("Invalid id"); 385 } 386 delete idxById[id]; 387 items.splice(idx, 1); 388 updateIdxById(idx); 389 refresh(); 390 } 391 392 function sortedAddItem(item) { 393 if(!sortComparer) { 394 throw new Error("sortedAddItem() requires a sort comparer, use sort()"); 395 } 396 insertItem(sortedIndex(item), item); 397 } 398 399 function sortedUpdateItem(id, item) { 400 if (idxById[id] === undefined || id !== item[idProperty]) { 401 throw new Error("Invalid or non-matching id " + idxById[id]); 402 } 403 if(!sortComparer) { 404 throw new Error("sortedUpdateItem() requires a sort comparer, use sort()"); 405 } 406 var oldItem = getItemById(id); 407 if(sortComparer(oldItem, item) !== 0) { 408 // item affects sorting -> must use sorted add 409 deleteItem(id); 410 sortedAddItem(item); 411 } 412 else { // update does not affect sorting -> regular update works fine 413 updateItem(id, item); 414 } 415 } 416 417 function sortedIndex(searchItem) { 418 var low = 0, high = items.length; 419 420 while (low < high) { 421 var mid = low + high >>> 1; 422 if (sortComparer(items[mid], searchItem) === -1) { 423 low = mid + 1; 424 } 425 else { 426 high = mid; 427 } 428 } 429 return low; 430 } 431 432 function getLength() { 433 return rows.length; 434 } 435 436 function getItem(i) { 437 var item = rows[i]; 438 439 // if this is a group row, make sure totals are calculated and update the title 440 if (item && item.__group && item.totals && !item.totals.initialized) { 441 var gi = groupingInfos[item.level]; 442 if (!gi.displayTotalsRow) { 443 calculateTotals(item.totals); 444 item.title = gi.formatter ? gi.formatter(item) : item.value; 445 } 446 } 447 // if this is a totals row, make sure it's calculated 448 else if (item && item.__groupTotals && !item.initialized) { 449 calculateTotals(item); 450 } 451 452 return item; 453 } 454 455 function getItemMetadata(i) { 456 var item = rows[i]; 457 if (item === undefined) { 458 return null; 459 } 460 461 // overrides for grouping rows 462 if (item.__group) { 463 return options.groupItemMetadataProvider.getGroupRowMetadata(item); 464 } 465 466 // overrides for totals rows 467 if (item.__groupTotals) { 468 return options.groupItemMetadataProvider.getTotalsRowMetadata(item); 469 } 470 471 return null; 472 } 473 474 function expandCollapseAllGroups(level, collapse) { 475 if (level == null) { 476 for (var i = 0; i < groupingInfos.length; i++) { 477 toggledGroupsByLevel[i] = {}; 478 groupingInfos[i].collapsed = collapse; 479 } 480 } else { 481 toggledGroupsByLevel[level] = {}; 482 groupingInfos[level].collapsed = collapse; 483 } 484 refresh(); 485 } 486 487 /** 488 * @param level {Number} Optional level to collapse. If not specified, applies to all levels. 489 */ 490 function collapseAllGroups(level) { 491 expandCollapseAllGroups(level, true); 492 } 493 494 /** 495 * @param level {Number} Optional level to expand. If not specified, applies to all levels. 496 */ 497 function expandAllGroups(level) { 498 expandCollapseAllGroups(level, false); 499 } 500 501 function expandCollapseGroup(level, groupingKey, collapse) { 502 toggledGroupsByLevel[level][groupingKey] = groupingInfos[level].collapsed ^ collapse; 503 refresh(); 504 } 505 506 /** 507 * @param varArgs Either a Slick.Group's "groupingKey" property, or a 508 * variable argument list of grouping values denoting a unique path to the row. For 509 * example, calling collapseGroup('high', '10%') will collapse the '10%' subgroup of 510 * the 'high' group. 511 */ 512 function collapseGroup(varArgs) { 513 var args = Array.prototype.slice.call(arguments); 514 var arg0 = args[0]; 515 if (args.length == 1 && arg0.indexOf(groupingDelimiter) != -1) { 516 expandCollapseGroup(arg0.split(groupingDelimiter).length - 1, arg0, true); 517 } else { 518 expandCollapseGroup(args.length - 1, args.join(groupingDelimiter), true); 519 } 520 } 521 522 /** 523 * @param varArgs Either a Slick.Group's "groupingKey" property, or a 524 * variable argument list of grouping values denoting a unique path to the row. For 525 * example, calling expandGroup('high', '10%') will expand the '10%' subgroup of 526 * the 'high' group. 527 */ 528 function expandGroup(varArgs) { 529 var args = Array.prototype.slice.call(arguments); 530 var arg0 = args[0]; 531 if (args.length == 1 && arg0.indexOf(groupingDelimiter) != -1) { 532 expandCollapseGroup(arg0.split(groupingDelimiter).length - 1, arg0, false); 533 } else { 534 expandCollapseGroup(args.length - 1, args.join(groupingDelimiter), false); 535 } 536 } 537 538 function getGroups() { 539 return groups; 540 } 541 542 function extractGroups(rows, parentGroup) { 543 var group; 544 var val; 545 var groups = []; 546 var groupsByVal = {}; 547 var r; 548 var level = parentGroup ? parentGroup.level + 1 : 0; 549 var gi = groupingInfos[level]; 550 551 for (var i = 0, l = gi.predefinedValues.length; i < l; i++) { 552 val = gi.predefinedValues[i]; 553 group = groupsByVal[val]; 554 if (!group) { 555 group = new Slick.Group(); 556 group.value = val; 557 group.level = level; 558 group.groupingKey = (parentGroup ? parentGroup.groupingKey + groupingDelimiter : '') + val; 559 groups[groups.length] = group; 560 groupsByVal[val] = group; 561 } 562 } 563 564 for (var i = 0, l = rows.length; i < l; i++) { 565 r = rows[i]; 566 val = gi.getterIsAFn ? gi.getter(r) : r[gi.getter]; 567 group = groupsByVal[val]; 568 if (!group) { 569 group = new Slick.Group(); 570 group.value = val; 571 group.level = level; 572 group.groupingKey = (parentGroup ? parentGroup.groupingKey + groupingDelimiter : '') + val; 573 groups[groups.length] = group; 574 groupsByVal[val] = group; 575 } 576
577 group.rows[group.count++] = r; 578 } 579 580 if (level < groupingInfos.length - 1) { 581 for (var i = 0; i < groups.length; i++) { 582 group = groups[i]; 583 group.groups = extractGroups(group.rows, group); 584 } 585 } 586 587 groups.sort(groupingInfos[level].comparer); 588 589 return groups; 590 } 591 592 function calculateTotals(totals) { 593 var group = totals.group; 594 var gi = groupingInfos[group.level]; 595 var isLeafLevel = (group.level == groupingInfos.length); 596 var agg, idx = gi.aggregators.length; 597 598 if (!isLeafLevel && gi.aggregateChildGroups) { 599 // make sure all the subgroups are calculated 600 var i = group.groups.length; 601 while (i--) { 602 if (!group.groups[i].totals.initialized) { 603 calculateTotals(group.groups[i].totals); 604 } 605 } 606 } 607 608 while (idx--) { 609 agg = gi.aggregators[idx]; 610 agg.init(); 611 if (!isLeafLevel && gi.aggregateChildGroups) { 612 gi.compiledAccumulators[idx].call(agg, group.groups); 613 } else { 614 gi.compiledAccumulators[idx].call(agg, group.rows); 615 } 616 agg.storeResult(totals); 617 } 618 totals.initialized = true; 619 } 620 621 function addGroupTotals(group) { 622 var gi = groupingInfos[group.level]; 623 var totals = new Slick.GroupTotals(); 624 totals.group = group; 625 group.totals = totals; 626 if (!gi.lazyTotalsCalculation) { 627 calculateTotals(totals); 628 } 629 } 630 631 function addTotals(groups, level) { 632 level = level || 0; 633 var gi = groupingInfos[level]; 634 var groupCollapsed = gi.collapsed; 635 var toggledGroups = toggledGroupsByLevel[level]; 636 var idx = groups.length, g; 637 while (idx--) { 638 g = groups[idx]; 639 640 if (g.collapsed && !gi.aggregateCollapsed) { 641 continue; 642 } 643 644 // Do a depth-first aggregation so that parent group aggregators can access subgroup totals. 645 if (g.groups) { 646 addTotals(g.groups, level + 1); 647 } 648 649 if (gi.aggregators.length && ( 650 gi.aggregateEmpty || g.rows.length || (g.groups && g.groups.length))) { 651 addGroupTotals(g); 652 } 653 654 g.collapsed = groupCollapsed ^ toggledGroups[g.groupingKey]; 655 g.title = gi.formatter ? gi.formatter(g) : g.value; 656 } 657 } 658 659 function flattenGroupedRows(groups, level) { 660 level = level || 0; 661 var gi = groupingInfos[level]; 662 var groupedRows = [], rows, gl = 0, g; 663 for (var i = 0, l = groups.length; i < l; i++) { 664 g = groups[i]; 665 groupedRows[gl++] = g; 666 667 if (!g.collapsed) { 668 rows = g.groups ? flattenGroupedRows(g.groups, level + 1) : g.rows; 669 for (var j = 0, jj = rows.length; j < jj; j++) { 670 groupedRows[gl++] = rows[j]; 671 } 672 } 673 674 if (g.totals && gi.displayTotalsRow && (!g.collapsed || gi.aggregateCollapsed)) { 675 groupedRows[gl++] = g.totals; 676 } 677 } 678 return groupedRows; 679 } 680 681 function getFunctionInfo(fn) { 682 var fnRegex = /^function[^(]*\(([^)]*)\)\s*{([\s\S]*)}$/; 683 var matches = fn.toString().match(fnRegex); 684 return { 685 params: matches[1].split(","), 686 body: matches[2] 687 }; 688 } 689 690 function compileAccumulatorLoop(aggregator) { 691 var accumulatorInfo = getFunctionInfo(aggregator.accumulate); 692 var fn = new Function( 693 "_items", 694 "for (var " + accumulatorInfo.params[0] + ", _i=0, _il=_items.length; _i<_il; _i++) {" + 695 accumulatorInfo.params[0] + " = _items[_i]; " + 696 accumulatorInfo.body + 697 "}" 698 ); 699 fn.displayName = fn.name = "compiledAccumulatorLoop"; 700 return fn; 701 } 702 703 function compileFilter() { 704 var filterInfo = getFunctionInfo(filter); 705 706 var filterPath1 = "{ continue _coreloop; }$1"; 707 var filterPath2 = "{ _retval[_idx++] = $item$; continue _coreloop; }$1"; 708 // make some allowances for minification - there's only so far we can go with RegEx 709 var filterBody = filterInfo.body 710 .replace(/return false\s*([;}]|\}|$)/gi, filterPath1) 711 .replace(/return!1([;}]|\}|$)/gi, filterPath1) 712 .replace(/return true\s*([;}]|\}|$)/gi, filterPath2) 713 .replace(/return!0([;}]|\}|$)/gi, filterPath2) 714 .replace(/return ([^;}]+?)\s*([;}]|$)/gi, 715 "{ if ($1) { _retval[_idx++] = $item$; }; continue _coreloop; }$2"); 716 717 // This preserves the function template code after JS compression, 718 // so that replace() commands still work as expected. 719 var tpl = [ 720 //"function(_items, _args) { ", 721 "var _retval = [], _idx = 0; ", 722 "var $item$, $args$ = _args; ", 723 "_coreloop: ", 724 "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ", 725 "$item$ = _items[_i]; ", 726 "$filter$; ", 727 "} ", 728 "return _retval; " 729 //"}" 730 ].join(""); 731 tpl = tpl.replace(/\$filter\$/gi, filterBody); 732 tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]); 733 tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]); 734 735 var fn = new Function("_items,_args", tpl); 736 fn.displayName = fn.name = "compiledFilter"; 737 return fn; 738 } 739 740 function compileFilterWithCaching() { 741 var filterInfo = getFunctionInfo(filter); 742 743 var filterPath1 = "{ continue _coreloop; }$1"; 744 var filterPath2 = "{ _cache[_i] = true;_retval[_idx++] = $item$; continue _coreloop; }$1"; 745 // make some allowances for minification - there's only so far we can go with RegEx 746 var filterBody = filterInfo.body 747 .replace(/return false\s*([;}]|\}|$)/gi, filterPath1) 748 .replace(/return!1([;}]|\}|$)/gi, filterPath1) 749 .replace(/return true\s*([;}]|\}|$)/gi, filterPath2) 750 .replace(/return!0([;}]|\}|$)/gi, filterPath2) 751 .replace(/return ([^;}]+?)\s*([;}]|$)/gi, 752 "{ if ((_cache[_i] = $1)) { _retval[_idx++] = $item$; }; continue _coreloop; }$2"); 753 754 // This preserves the function template code after JS compression, 755 // so that replace() commands still work as expected. 756 var tpl = [ 757 //"function(_items, _args, _cache) { ", 758 "var _retval = [], _idx = 0; ", 759 "var $item$, $args$ = _args; ", 760 "_coreloop: ", 761 "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ", 762 "$item$ = _items[_i]; ", 763 "if (_cache[_i]) { ", 764 "_retval[_idx++] = $item$; ", 765 "continue _coreloop; ", 766 "} ", 767 "$filter$; ", 768 "} ", 769 "return _retval; " 770 //"}" 771 ].join(""); 772 tpl = tpl.replace(/\$filter\$/gi, filterBody); 773 tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]); 774 tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]); 775 776 var fn = new Function("_items,_args,_cache", tpl); 777 fn.displayName = fn.name = "compiledFilterWithCaching"; 778 return fn; 779 } 780 781 function uncompiledFilter(items, args) { 782 var retval = [], idx = 0; 783 784 for (var i = 0, ii = items.length; i < ii; i++) { 785 if (filter(items[i], args)) { 786 retval[idx++] = items[i]; 787 } 788 } 789 790 return retval; 791 } 792 793 function uncompiledFilterWithCaching(items, args, cache) { 794 var retval = [], idx = 0, item; 795 796 for (var i = 0, ii = items.length; i < ii; i++) { 797 item = items[i]; 798 if (cache[i]) { 799 retval[idx++] = item; 800 }
800 else if (filter(item, args)) { 801 retval[idx++] = item; 802 cache[i] = true; 803 } 804 } 805 806 return retval; 807 } 808 809 function getFilteredAndPagedItems(items) { 810 if (filter) { 811 var batchFilter = options.inlineFilters ? compiledFilter : uncompiledFilter; 812 var batchFilterWithCaching = options.inlineFilters ? compiledFilterWithCaching : uncompiledFilterWithCaching; 813 814 if (refreshHints.isFilterNarrowing) { 815 filteredItems = batchFilter(filteredItems, filterArgs); 816 } else if (refreshHints.isFilterExpanding) { 817 filteredItems = batchFilterWithCaching(items, filterArgs, filterCache); 818 } else if (!refreshHints.isFilterUnchanged) { 819 filteredItems = batchFilter(items, filterArgs); 820 } 821 } else { 822 // special case: if not filtering and not paging, the resulting 823 // rows collection needs to be a copy so that changes due to sort 824 // can be caught 825 filteredItems = pagesize ? items : items.concat(); 826 } 827 828 // get the current page 829 var paged; 830 if (pagesize) { 831 if (filteredItems.length <= pagenum * pagesize) { 832 if (filteredItems.length === 0) { 833 pagenum = 0; 834 } else { 835 pagenum = Math.floor((filteredItems.length - 1) / pagesize); 836 } 837 } 838 paged = filteredItems.slice(pagesize * pagenum, pagesize * pagenum + pagesize); 839 } else { 840 paged = filteredItems; 841 } 842 return {totalRows: filteredItems.length, rows: paged}; 843 } 844 845 function getRowDiffs(rows, newRows) { 846 var item, r, eitherIsNonData, diff = []; 847 var from = 0, to = newRows.length; 848 849 if (refreshHints && refreshHints.ignoreDiffsBefore) { 850 from = Math.max(0, 851 Math.min(newRows.length, refreshHints.ignoreDiffsBefore)); 852 } 853 854 if (refreshHints && refreshHints.ignoreDiffsAfter) { 855 to = Math.min(newRows.length, 856 Math.max(0, refreshHints.ignoreDiffsAfter)); 857 } 858 859 for (var i = from, rl = rows.length; i < to; i++) { 860 if (i >= rl) { 861 diff[diff.length] = i; 862 } else { 863 item = newRows[i]; 864 r = rows[i]; 865 866 if ((groupingInfos.length && (eitherIsNonData = (item.__nonDataRow) || (r.__nonDataRow)) && 867 item.__group !== r.__group || 868 item.__group && !item.equals(r)) 869 || (eitherIsNonData && 870 // no good way to compare totals since they are arbitrary DTOs 871 // deep object comparison is pretty expensive 872 // always considering them 'dirty' seems easier for the time being 873 (item.__groupTotals || r.__groupTotals)) 874 || item[idProperty] != r[idProperty] 875 || (updated && updated[item[idProperty]]) 876 ) { 877 diff[diff.length] = i; 878 } 879 } 880 } 881 return diff; 882 } 883 884 function recalc(_items) { 885 rowsById = null; 886 887 if (refreshHints.isFilterNarrowing != prevRefreshHints.isFilterNarrowing || 888 refreshHints.isFilterExpanding != prevRefreshHints.isFilterExpanding) { 889 filterCache = []; 890 } 891 892 var filteredItems = getFilteredAndPagedItems(_items); 893 totalRows = filteredItems.totalRows; 894 var newRows = filteredItems.rows; 895 896 groups = []; 897 if (groupingInfos.length) { 898 groups = extractGroups(newRows); 899 if (groups.length) { 900 addTotals(groups); 901 newRows = flattenGroupedRows(groups); 902 } 903 } 904 905 var diff = getRowDiffs(rows, newRows); 906 907 rows = newRows; 908 909 return diff; 910 } 911 912 function refresh() { 913 if (suspend) { 914 return; 915 } 916 917 var countBefore = rows.length; 918 var totalRowsBefore = totalRows; 919 920 var diff = recalc(items, filter); // pass as direct refs to avoid closure perf hit 921 922 // if the current page is no longer valid, go to last page and recalc 923 // we suffer a performance penalty here, but the main loop (recalc) remains highly optimized 924 if (pagesize && totalRows < pagenum * pagesize) { 925 pagenum = Math.max(0, Math.ceil(totalRows / pagesize) - 1); 926 diff = recalc(items, filter); 927 } 928 929 updated = null; 930 prevRefreshHints = refreshHints; 931 refreshHints = {}; 932 933 if (totalRowsBefore !== totalRows) { 934 onPagingInfoChanged.notify(getPagingInfo(), null, self); 935 } 936 if (countBefore !== rows.length) { 937 onRowCountChanged.notify({previous: countBefore, current: rows.length, dataView: self}, null, self); 938 } 939 if (diff.length > 0) { 940 onRowsChanged.notify({rows: diff, dataView: self}, null, self); 941 } 942 } 943 944 /*** 945 * Wires the grid and the DataView together to keep row selection tied to item ids. 946 * This is useful since, without it, the grid only knows about rows, so if the items 947 * move around, the same rows stay selected instead of the selection moving along 948 * with the items. 949 * 950 * NOTE: This doesn't work with cell selection model. 951 * 952 * @param grid {Slick.Grid}
952 The grid to sync selection with. 953 * @param preserveHidden {Boolean} Whether to keep selected items that go out of the 954 * view due to them getting filtered out. 955 * @param preserveHiddenOnSelectionChange {Boolean} Whether to keep selected items 956 * that are currently out of the view (see preserveHidden) as selected when selection 957 * changes. 958 * @return {Slick.Event} An event that notifies when an internal list of selected row ids 959 * changes. This is useful since, in combination with the above two options, it allows 960 * access to the full list selected row ids, and not just the ones visible to the grid. 961 * @method syncGridSelection 962 */ 963 function syncGridSelection(grid, preserveHidden, preserveHiddenOnSelectionChange) { 964 var self = this; 965 var inHandler; 966 var selectedRowIds = self.mapRowsToIds(grid.getSelectedRows()); 967 var onSelectedRowIdsChanged = new Slick.Event(); 968 969 function setSelectedRowIds(rowIds) { 970 if (selectedRowIds.join(",") == rowIds.join(",")) { 971 return; 972 } 973 974 selectedRowIds = rowIds; 975 976 onSelectedRowIdsChanged.notify({ 977 "grid": grid, 978 "ids": selectedRowIds, 979 "dataView": self 980 }, new Slick.EventData(), self); 981 } 982 983 function update() { 984 if (selectedRowIds.length > 0) { 985 inHandler = true; 986 var selectedRows = self.mapIdsToRows(selectedRowIds); 987 if (!preserveHidden) { 988 setSelectedRowIds(self.mapRowsToIds(selectedRows)); 989 } 990 grid.setSelectedRows(selectedRows); 991 inHandler = false; 992 } 993 } 994 995 grid.onSelectedRowsChanged.subscribe(function(e, args) { 996 if (inHandler) { return; } 997 var newSelectedRowIds = self.mapRowsToIds(grid.getSelectedRows()); 998 if (!preserveHiddenOnSelectionChange || !grid.getOptions().multiSelect) { 999 setSelectedRowIds(newSelectedRowIds); 1000 } else { 1001 // keep the ones that are hidden 1002 var existing = $.grep(selectedRowIds, function(id) { return self.getRowById(id) === undefined; }); 1003 // add the newly selected ones 1004 setSelectedRowIds(existing.concat(newSelectedRowIds)); 1005 } 1006 }); 1007 1008 this.onRowsChanged.subscribe(update); 1009 1010 this.onRowCountChanged.subscribe(update); 1011 1012 return onSelectedRowIdsChanged; 1013 } 1014 1015 function syncGridCellCssStyles(grid, key) { 1016 var hashById; 1017 var inHandler; 1018 1019 // since this method can be called after the cell styles have been set, 1020 // get the existing ones right away 1021 storeCellCssStyles(grid.getCellCssStyles(key)); 1022 1023 function storeCellCssStyles(hash) { 1024 hashById = {}; 1025 for (var row in hash) { 1026 var id = rows[row][idProperty]; 1027 hashById[id] = hash[row]; 1028 } 1029 } 1030 1031 function update() { 1032 if (hashById) { 1033 inHandler = true; 1034 ensureRowsByIdCache(); 1035 var newHash = {}; 1036 for (var id in hashById) { 1037 var row = rowsById[id]; 1038 if (row != undefined) { 1039 newHash[row] = hashById[id]; 1040 } 1041 } 1042 grid.setCellCssStyles(key, newHash); 1043 inHandler = false; 1044 } 1045 } 1046 1047 grid.onCellCssStylesChanged.subscribe(function(e, args) { 1048 if (inHandler) { return; } 1049 if (key != args.key) { return; } 1050 if (args.hash) { 1051 storeCellCssStyles(args.hash); 1052 } else { 1053 grid.onCellCssStylesChanged.unsubscribe(styleChanged); 1054 self.onRowsChanged.unsubscribe(update); 1055 self.onRowCountChanged.unsubscribe(update); 1056 } 1057 }); 1058 1059 this.onRowsChanged.subscribe(update); 1060 1061 this.onRowCountChanged.subscribe(update); 1062 } 1063 1064 $.extend(this, { 1065 // methods 1066 "beginUpdate": beginUpdate, 1067 "endUpdate": endUpdate, 1068 "setPagingOptions": setPagingOptions, 1069 "getPagingInfo": getPagingInfo, 1070 "getItems": getItems, 1071 "setItems": setItems, 1072 "setFilter": setFilter, 1073 "getFilter": getFilter, 1074 "getFilteredItems": getFilteredItems, 1075 "sort": sort, 1076 "fastSort": fastSort, 1077 "reSort": reSort, 1078 "setGrouping": setGrouping, 1079 "getGrouping": getGrouping, 1080 "groupBy": groupBy, 1081 "setAggregators": setAggregators, 1082 "collapseAllGroups": collapseAllGroups, 1083 "expandAllGroups": expandAllGroups, 1084 "collapseGroup": collapseGroup, 1085 "expandGroup": expandGroup, 1086 "getGroups": getGroups, 1087 "getIdxById": getIdxById, 1088 "getRowByItem": getRowByItem, 1089 "getRowById": getRowById, 1090 "getItemById": getItemById, 1091 "getItemByIdx": getItemByIdx, 1092 "mapItemsToRows": mapItemsToRows, 1093 "mapRowsToIds": mapRowsToIds, 1094 "mapIdsToRows": mapIdsToRows, 1095 "setRefreshHints": setRefreshHints, 1096 "setFilterArgs": setFilterArgs, 1097 "refresh": refresh, 1098 "updateItem": updateItem, 1099 "insertItem": insertItem, 1100 "addItem": addItem, 1101 "deleteItem": deleteItem, 1102 "sortedAddItem": sortedAddItem, 1103 "sortedUpdateItem": sortedUpdateItem, 1104 "syncGridSelection": syncGridSelection, 1105 "syncGridCellCssStyles": syncGridCellCssStyles, 1106 1107 // data provider methods 1108 "getLength": getLength, 1109 "getItem": getItem, 1110 "getItemMetadata": getItemMetadata, 1111 1112 // events 1113 "onRowCountChanged": onRowCountChanged, 1114 "onRowsChanged": onRowsChanged, 1115 "onPagingInfoChanged": onPagingInfoChanged 1116 }); 1117 } 1118 1119 function AvgAggregator(field) { 1120 this.field_ = field; 1121 1122 this.init = function () { 1123 this.count_ = 0; 1124 this.nonNullCount_ = 0; 1125 this.sum_ = 0; 1126 }; 1127 1128 this.accumulate = function (item) { 1129 var val = item[this.field_]; 1130 this.count_++; 1131 if (val != null && val !== "" && !isNaN(val)) { 1132 this.nonNullCount_++; 1133 this.sum_ += parseFloat(val); 1134 } 1135 }; 1136 1137 this.storeResult = function (groupTotals) { 1138 if (!groupTotals.avg) { 1139 groupTotals.avg = {}; 1140 } 1141 if (this.nonNullCount_ != 0) { 1142 groupTotals.avg[this.field_] = this.sum_ / this.nonNullCount_; 1143 } 1144 }; 1145 } 1146 1147 function MinAggregator(field) { 1148 this.field_ = field; 1149 1150 this.init = function () { 1151 this.min_ = null; 1152 }; 1153 1154 this.accumulate = function (item) { 1155 var val = item[this.field_]; 1156 if (val != null && val !== "" && !isNaN(val)) { 1157 if (this.min_ == null || val < this.min_) { 1158 this.min_ = val; 1159 } 1160 } 1161 }; 1162 1163 this.storeResult = function (groupTotals) { 1164 if (!groupTotals.min) { 1165 groupTotals.min = {}; 1166 } 1167 groupTotals.min[this.field_] = this.min_; 1168 } 1169 } 1170 1171 function MaxAggregator(field) { 1172 this.field_ = field; 1173 1174 this.init = function () { 1175 this.max_ = null; 1176 }; 1177 1178 this.accumulate = function (item) { 1179 var val = item[this.field_]; 1180 if (val != null && val !== "" && !isNaN(val)) { 1181 if (this.max_ == null || val > this.max_) { 1182 this.max_ = val; 1183 } 1184 } 1185 }; 1186 1187 this.storeResult = function (groupTotals) { 1188 if (!groupTotals.max) { 1189 groupTotals.max = {}; 1190 } 1191 groupTotals.max[this.field_] = this.max_; 1192 } 1193 } 1194 1195 function SumAggregator(field) { 1196 this.field_ = field; 1197 1198 this.init = function () { 1199 this.sum_ = null; 1200 }; 1201 1202 this.accumulate = function (item) { 1203 var val = item[this.field_]; 1204 if (val != null && val !== "" && !isNaN(val)) { 1205 this.sum_ += parseFloat(val); 1206 } 1207 }; 1208 1209 this.storeResult = function (groupTotals) { 1210 if (!groupTotals.sum) { 1211 groupTotals.sum = {}; 1212 } 1213 groupTotals.sum[this.field_] = this.sum_; 1214 } 1215 } 1216 1217 // TODO: add more built-in aggregators 1218 // TODO: merge common aggregators in one to prevent needles iterating 1219 1220})(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.