<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>SlickGrid example: Grouping</title> <link rel="stylesheet" href="../slick.grid.css" type="text/css"/> <link rel="stylesheet" href="../controls/slick.pager.css" type="text/css"/> <link rel="stylesheet" href="../css/smoothness/jquery-ui-1.8.16.custom.css" type="text/css"/> <link rel="stylesheet" href="examples.css" type="text/css"/> <link rel="stylesheet" href="../controls/slick.columnpicker.css" type="text/css"/> <style> .cell-effort-driven { text-align: center; } .slick-group-title[level='0'] { font-weight: bold; } .slick-group-title[level='1'] { text-decoration: underline; } .slick-group-title[level='2'] { font-style: italic; } </style> </head> <body> <div style="position:relative"> <div style="width:600px;"> <div class="grid-header" style="width:100%"> <label>SlickGrid</label> </div> <div id="myGrid" style="width:100%;height:500px;"></div> <div id="pager" style="width:100%;height:20px;"></div> </div> <div class="options-panel" style="width:450px;"> <b>Options:</b> <hr/> <div style="padding:6px;"> <label style="width:200px;float:left">Show tasks with % at least: </label> <div style="padding:2px;"> <div style="width:100px;display:inline-block;" id="pcSlider"></div> </div> <br/><br/> <button onclick="loadData(50)">50 rows</button> <button onclick="loadData(50000)">50k rows</button> <button onclick="loadData(500000)">500k rows</button> <hr/> <button onclick="dataView.setGrouping([])">Clear grouping</button> <br/> <button onclick="groupByDuration()">Group by duration & sort groups by value</button> <br/> <button onclick="groupByDurationOrderByCount(false)">Group by duration & sort groups by count</button> <br/> <button onclick="groupByDurationOrderByCount(true)">Group by duration & sort groups by count, aggregate collapsed </button> <br/> <br/> <button onclick="groupByDurationEffortDriven()">Group by duration then effort-driven</button> <br/> <button onclick="groupByDurationEffortDrivenPercent()">Group by duration then effort-driven then percent.</button> <br/> <br/> <button onclick="dataView.collapseAllGroups()">Collapse all groups</button> <br/> <button onclick="dataView.expandAllGroups()">Expand all groups</button> <br/> </div> <hr/> <h2>Demonstrates:</h2> <ul> <li> Fully dynamic and interactive multi-level grouping with filtering and aggregates over <b>50'000</b> items<br> Each grouping level can have its own aggregates (over child rows, child groups, or all descendant rows).<br> Personally, this is just the coolest slickest thing I've ever seen done with DHTML grids! </li> </ul> <h2>View Source:</h2> <ul> <li><A href="https://github.com/mleibman/SlickGrid/blob/gh-pages/examples/example-grouping.html" target="_sourcewindow"> View the source for this example on Github</a></li> </ul> </div> </div> <script src="../lib/firebugx.js"></script> <script src="../lib/jquery-1.7.min.js"></script> <script src="../lib/jquery-ui-1.8.16.custom.min.js"></script> <script src="../lib/jquery.event.drag-2.2.js"></script> <script src="../slick.core.js"></script> <script src="../slick.formatters.js"></script> <script src="../slick.editors.js"></script> <script src="../plugins/slick.cellrangedecorator.js"></script> <script src="../plugins/slick.cellrangeselector.js"></script> <script src="../plugins/slick.cellselectionmodel.js"></script> <script src="../slick.grid.js"></script> <script src="../slick.groupitemmetadataprovider.js"></script> <script src="../slick.dataview.js"></script> <script src="../controls/slick.pager.js"></script> <script src="../controls/slick.columnpicker.js"></script> <script> var dataView; var grid; var data = []; var columns = [ {id: "sel", name: "#", field: "num", cssClass: "cell-selection", width: 40, resizable: false, selectable: false, focusable: false }, {id: "title", name: "Title", field: "title", width: 70, minWidth: 50, cssClass: "cell-title", sortable: true, editor: Slick.Editors.Text}, {id: "duration", name: "Duration", field: "duration", width: 70, sortable: true, groupTotalsFormatter: sumTotalsFormatter}, {id: "%", name: "% Complete", field: "percentComplete", width: 80, formatter: Slick.Formatters.PercentCompleteBar, sortable: true, groupTotalsFormatter: avgTotalsFormatter}, {id: "start", name: "Start", field: "start", minWidth: 60, sortable: true}, {id: "finish", name: "Finish", field: "finish", minWidth: 60, sortable: true}, {id: "cost", name: "Cost", field: "cost", width: 90, sortable: true, groupTotalsFormatter: sumTotalsFormatter}, {id: "effort-driven", name: "Effort Driven", width: 80, minWidth: 20, maxWidth: 80, cssClass: "cell-effort-driven", field: "effortDriven", formatter: Slick.Formatters.Checkmark, sortable: true} ]; var options = { enableCellNavigation: true, editable: true }; var sortcol = "title"; var sortdir = 1; var percentCompleteThreshold = 0; var prevPercentCompleteThreshold = 0; function avgTotalsFormatter(totals, columnDef) { var val = totals.avg && totals.avg[columnDef.field]; if (val != null) { return "avg: " + Math.round(val) + "%"; } return ""; } function sumTotalsFormatter(totals, columnDef) { var val = totals.sum && totals.sum[columnDef.field]; if (val != null) { return "total: " + ((Math.round(parseFloat(val)*100)/100)); } return ""; } function myFilter(item, args) { return item["percentComplete"] >= args.percentComplete; } function percentCompleteSort(a, b) { return a["percentComplete"] - b["percentComplete"]; } function comparer(a, b) { var x = a[sortcol], y = b[sortcol]; return (x == y ? 0 : (x > y ? 1 : -1)); } function groupByDuration() { dataView.setGrouping({ getter: "duration", formatter: function (g) { return "Duration: " + g.value + " <span style='color:green'>(" + g.count + " items)</span>"; }, aggregators: [ new Slick.Data.Aggregators.Avg("percentComplete"), new Slick.Data.Aggregators.Sum("cost") ], aggregateCollapsed: false, lazyTotalsCalculation: true }); } function groupByDurationOrderByCount(aggregateCollapsed) { dataView.setGrouping({ getter: "duration", formatter: function (g) { return "Duration: " + g.value + " <span style='color:green'>(" + g.count + " items)</span>"; }, comparer: function (a, b) { return a.count - b.count; }, aggregators: [ new Slick.Data.Aggregators.Avg("percentComplete"), new Slick.Data.Aggregators.Sum("cost") ], aggregateCollapsed: aggregateCollapsed, lazyTotalsCalculation: true }); } function groupByDurationEffortDriven() { dataView.setGrouping([ { getter: "duration", formatter :function (g) { return "Duration: " + g.value + " <span style='color:green'>(" + g.count + " items)</span>"; }, aggregators: [ new Slick.Data.Aggregators.Sum("duration"), new Slick.Data.Aggregators.Sum("cost") ], aggregateCollapsed: true, lazyTotalsCalculation: true }, { getter: "effortDriven", formatter :function (g) { return "Effort-Driven: " + (g.value ? "True" : "False") + " <span style='color:green'>(" + g.count + " items)</span>"; }, aggregators: [ new Slick.Data.Aggregators.Avg("percentComplete"), new Slick.Data.Aggregators.Sum("cost") ], collapsed: true, lazyTotalsCalculation: true } ]); } function groupByDurationEffortDrivenPercent() { dataView.setGrouping([ { getter: "duration", formatter: function (g) { return "Duration: " + g.value + " <span style='color:green'>(" + g.count + " items)</span>"; }, aggregators: [ new Slick.Data.Aggregators.Sum("duration"), new Slick.Data.Aggregators.Sum("cost") ], aggregateCollapsed: true, lazyTotalsCalculation: true }, { getter: "effortDriven", formatter: function (g) { return "Effort-Driven: " + (g.value ? "True" : "False") + " <span style='color:green'>(" + g.count + " items)</span>"; }, aggregators :[ new Slick.Data.Aggregators.Sum("duration"), new Slick.Data.Aggregators.Sum("cost") ], lazyTotalsCalculation: true }, { getter: "percentComplete", formatter: function (g) { return "% Complete: " + g.value + " <span style='color:green'>(" + g.count + " items)</span>"; }, aggregators: [ new Slick.Data.Aggregators.Avg("percentComplete") ], aggregateCollapsed: true, collapsed: true, lazyTotalsCalculation: true } ]); } function loadData(count) { var someDates = ["01/01/2009", "02/02/2009", "03/03/2009"]; data = []; // prepare the data for (var i = 0; i < count; i++) { var d = (data[i] = {}); d["id"] = "id_" + i; d["num"] = i; d["title"] = "Task " + i; d["duration"] = Math.round(Math.random() * 30); d["percentComplete"] = Math.round(Math.random() * 100); d["start"] = someDates[ Math.floor((Math.random()*2)) ]; d["finish"] = someDates[ Math.floor((Math.random()*2)) ]; d["cost"] = Math.round(Math.random() * 10000) / 100; d["effortDriven"] = (i % 5 == 0); } dataView.setItems(data); } $(".grid-header .ui-icon") .addClass("ui-state-default ui-corner-all") .mouseover(function (e) { $(e.target).addClass("ui-state-hover") }) .mouseout(function (e) { $(e.target).removeClass("ui-state-hover") }); $(function () { var groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider(); dataView = new Slick.Data.DataView({ groupItemMetadataProvider: groupItemMetadataProvider, inlineFilters: true }); grid = new Slick.Grid("#myGrid", dataView, columns, options); // register the group item metadata provider to add expand/collapse group handlers grid.registerPlugin(groupItemMetadataProvider); grid.setSelectionModel(new Slick.CellSelectionModel()); var pager = new Slick.Controls.Pager(dataView, grid, $("#pager")); var columnpicker = new Slick.Controls.ColumnPicker(columns, grid, options); grid.onSort.subscribe(function (e, args) { sortdir = args.sortAsc ? 1 : -1; sortcol = args.sortCol.field; if ($.browser.msie && $.browser.version <= 8) { // using temporary Object.prototype.toString override // more limited and does lexicographic sort only by default, but can be much faster var percentCompleteValueFn = function () { var val = this["percentComplete"]; if (val < 10) { return "00" + val; } else if (val < 100) { return "0" + val; } else { return val; } }; // use numeric sort of % and lexicographic for everything else dataView.fastSort((sortcol == "percentComplete") ? percentCompleteValueFn : sortcol, args.sortAsc); } else { // using native sort with comparer // preferred method but can be very slow in IE with huge datasets dataView.sort(comparer, args.sortAsc); } }); // wire up model events to drive the grid dataView.onRowCountChanged.subscribe(function (e, args) { grid.updateRowCount(); grid.render(); }); dataView.onRowsChanged.subscribe(function (e, args) { grid.invalidateRows(args.rows); grid.render(); }); var h_runfilters = null; // wire up the slider to apply the filter to the model $("#pcSlider,#pcSlider2").slider({ "range": "min", "slide": function (event, ui) { Slick.GlobalEditorLock.cancelCurrentEdit(); if (percentCompleteThreshold != ui.value) { window.clearTimeout(h_runfilters); h_runfilters = window.setTimeout(filterAndUpdate, 10); percentCompleteThreshold = ui.value; } } }); function filterAndUpdate() { var isNarrowing = percentCompleteThreshold > prevPercentCompleteThreshold; var isExpanding = percentCompleteThreshold < prevPercentCompleteThreshold; var renderedRange = grid.getRenderedRange(); dataView.setFilterArgs({ percentComplete: percentCompleteThreshold }); dataView.setRefreshHints({ ignoreDiffsBefore: renderedRange.top, ignoreDiffsAfter: renderedRange.bottom + 1, isFilterNarrowing: isNarrowing, isFilterExpanding: isExpanding }); dataView.refresh(); prevPercentCompleteThreshold = percentCompleteThreshold; } // initialize the model after all the events have been hooked up dataView.beginUpdate(); dataView.setFilter(myFilter); dataView.setFilterArgs({ percentComplete: percentCompleteThreshold }); loadData(50); groupByDuration(); dataView.endUpdate(); $("#gridContainer").resizable(); }) </script> </body> </html>