403 lines
13 KiB
HTML
403 lines
13 KiB
HTML
|
<!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>
|