Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds optimized bulk operations to DataView #572

Merged
merged 14 commits into from
Feb 2, 2021
Merged
101 changes: 101 additions & 0 deletions cypress/integration/example-optimizing-updates.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/// <reference types="Cypress" />

describe('Example - Optimizing Updates', () => {
const titles = ['#', 'Severity', 'Time', 'Message'];

beforeEach(() => {
// create a console.log spy for later use
cy.window().then((win) => {
cy.spy(win.console, "log");
});
});

it('should display Example Multi-grid Basic', () => {
cy.visit(`${Cypress.config('baseExampleUrl')}/example-optimizing-updates.html`);
cy.get('.options-panel > b').should('contain', 'Description:');
cy.contains('This page demonstrates how the bulk update operations ');
});

it('should have exact Column Titles in the grid', () => {
cy.get('#myGrid')
.find('.slick-header-columns')
.children()
.each(($child, index) => expect($child.text()).to.eq(titles[index]));
});

it('should show initial rows', () => {
cy.get('#pager')
.find('.slick-pager-status')
.should('contain', 'Showing all 300 rows');
});

it('should update the rows on inefficient click', () => {
cy.visit(`${Cypress.config('baseExampleUrl')}/example-optimizing-updates.html`);

cy.get('#myGrid')
.find('.slick-row')
.each(($child, index) => {
const message = $child.find('.cell-message').text();
const number = parseInt(message.substring("Log Entry ".length));
expect(number).to.be.lessThan(1000)
});

cy.get('.options-panel button')
.contains('inefficient')
.click();

cy.get('#myGrid')
.find('.slick-row')
.each(($child, index) => {
const message = $child.find('.cell-message').text();
const number = parseInt(message.substring("Log Entry ".length));
expect(number).to.be.greaterThan(90000)
});
});

it('should update the rows on efficient click', () => {
cy.visit(`${Cypress.config('baseExampleUrl')}/example-optimizing-updates.html`);

cy.get('#myGrid')
.find('.slick-row')
.each(($child, index) => {
const message = $child.find('.cell-message').text();
const number = parseInt(message.substring("Log Entry ".length));
expect(number).to.be.lessThan(1000)
});

cy.get('.options-panel button')
.contains('efficient')
.click();

cy.get('#myGrid')
.find('.slick-row')
.each(($child, index) => {
const message = $child.find('.cell-message').text();
const number = parseInt(message.substring("Log Entry ".length));
expect(number).to.be.greaterThan(90000)
});
});

it('should need less time on efficient than inefficient', () => {
cy.visit(`${Cypress.config('baseExampleUrl')}/example-optimizing-updates.html`);

cy.get('#duration').invoke('text', '').should('be.empty');
cy.get('.options-panel button')
.contains('(inefficient)')
.click();
cy.get('#duration').should('not.be.empty').then($duration => {
let inEfficientTime = parseInt($duration.text());

cy.get('#duration').invoke('text', '').should('be.empty');
cy.get('.options-panel button')
.contains('(efficient)')
.click();
cy.get('#duration').should('not.be.empty').then($duration2 => {
let efficientTime = parseInt($duration2.text());
expect(efficientTime).to.be.lessThan(inEfficientTime / 2);
});
});

});
});
182 changes: 182 additions & 0 deletions examples/example-optimizing-updates.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<link rel="shortcut icon" type="image/ico" href="favicon.ico" />
<title>SlickGrid example: Optimizing Updates</title>
<link rel="stylesheet" href="../slick.grid.css" type="text/css"/>
<link rel="stylesheet" href="../css/smoothness/jquery-ui.css" type="text/css"/>
<link rel="stylesheet" href="../controls/slick.pager.css" type="text/css"/>
<link rel="stylesheet" href="examples.css" type="text/css"/>
<style>
.cell-title {
font-weight: bold;
}

.cell-effort-driven {
text-align: center;
}

.cell-selection {
border-right-color: silver;
border-right-style: solid;
background: #f5f5f5;
color: gray;
text-align: right;
font-size: 10px;
}
</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">
<b>Description:</b>
<hr/>

<p>
This page demonstrates how the bulk update operations can be used efficiently
delete and insert new rows into the table. The example simulates a log viewer
where for each log severity always 100 items are shown in a ring-buffer fashion.
If you use the browser profiler to compare the two different operations below
you will notice the significant performance difference which can be crucial for
systems where a lot of new events are provided from an external source and
then updated.
</p>
<h2>Controls</h2>
<button onclick="simulateLogs(100000, false, true)">Add 100000 entries of each severity (inefficient)</button>
<button onclick="simulateLogs(100000, true, true)">Add 100000 entries of each severity (efficient)</button>
<div>
Last update took: <span id="duration"></span>
</div>
<h2>View Source:</h2>
<ul>
<li><a href="https://github.com/6pac/SlickGrid/blob/master/examples/example-optimizing-updates.html" target="_sourcewindow"> View the source for this example on Github</a></li>
</ul>
</div>
</div>

<script src="../lib/jquery-1.12.4.min.js"></script>
<script src="../lib/jquery-ui.min.js"></script>
<script src="../lib/jquery.event.drag-2.3.0.js"></script>

<script src="../slick.core.js"></script>
<script src="../slick.formatters.js"></script>
<script src="../slick.grid.js"></script>
<script src="../slick.dataview.js"></script>
<script src="../controls/slick.pager.js"></script>

<script>
var dataView;
var grid;
var data = [];
var columns = [
{id: "sel", name: "#", field: "num", behavior: "select", cssClass: "cell-selection", width: 40, resizable: false, selectable: false },
{id: "severity", name: "Severity", field: "severity", width: 120, minWidth: 120, cssClass: "cell-severity"},
{id: "time", name: "Time", field: "time", width: 120, minWidth: 120, cssClass: "cell-time"},
{id: "message", name: "Message", field: "message", minWidth: 300, cssClass: "cell-message"},
];

var options = {
editable: false,
enableAddRow: false,
enableCellNavigation: true
};

var eventIndex = 0;
var severities = ['Info', 'Warning', 'Error'];
var maxItemsPerSeverity = 100;

function LogEntry() {
eventIndex++;
this.id = "id_" + eventIndex;
this.severity = severities[eventIndex % severities.length];
this.time = new Date().toISOString().substring(0,19);
this.message = "Log Entry " + eventIndex;
}

// our ringbuffer to keep only 10 items per severity
var logsPerSeverity = {};
for (var i = 0; i < severities.length; i++) {
logsPerSeverity[severities[i]] = [];
}

// the logic to simulate new events

function simulateLogs(count, efficient, update) {
var chunks = 10;
var start = new Date();
for(var i = 0; i < chunks; i++) {
simulateLogChunk(count / chunks, efficient);
}
var end = new Date();
if (update) {
$('#duration').text(end - start + 'ms');
}
}
function simulateLogChunk(count, efficient, update) {
if(efficient) {
dataView.beginUpdate(true); // efficient bulk update
var logs = []; // we will first collect all items to be added
for (var i = 0; i < count; i++) {
var log = new LogEntry();
var group = logsPerSeverity[log.severity];
if(group.length > maxItemsPerSeverity) {
dataView.deleteItem(group.pop().id);
}

group.unshift(log);
logs[i] = log;
}
// then we insert all items in one go.
dataView.insertItems(0, logs);
dataView.endUpdate();
} else {
dataView.beginUpdate(); // just a normal update
for (var i = 0; i < count; i++) {
var log = new LogEntry();
var group = logsPerSeverity[log.severity];
if(group.length > maxItemsPerSeverity) {
try {
dataView.deleteItem(group.pop().id);
} catch(e) {
// item not yet fully inserted
}
}

group.unshift(log);
dataView.insertItem(0, log); // individual inserts
}
dataView.endUpdate();
}
}

$(function () {
dataView = new Slick.Data.DataView({ inlineFilters: true });
grid = new Slick.Grid("#myGrid", dataView, columns, options);
var pager = new Slick.Controls.Pager(dataView, grid, $("#pager"));

// 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();
});

// prepare some data
simulateLogs(severities.length * maxItemsPerSeverity, true)
})
</script>
</body>
</html>
1 change: 1 addition & 0 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ <h2>Data-Centric</h2>
<li><a href="example-totals-via-data-provider.html">Implementing a totals row via a data provider</a></li>
<li>(most comprehensive) <a href="example4-model.html">Using a filtered data view to drive the grid</a></li>
<li><a href="example-optimizing-dataview.html">Optimizing DataView for 500’000 rows</a></li>
<li><a href="example-optimizing-updates.html">Optimizing DataView for updates</a></li>
<li><a href="example6-ajax-loading.html">AJAX-loading data with search</a></li>
<li><a href="example6-ajax-loading-yahoo.html">AJAX-loading data, second example</a></li>
<li><a href="example13-getItem-sorting.html">Sorting by an index, getItem method</a></li>
Expand Down
14 changes: 7 additions & 7 deletions slick.compositeeditor.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
(function ($) {
$.extend(true, window, {
Slick: {
CompositeEditor: CompositeEditor
}
});


/***
* A composite SlickGrid editor factory.
* Generates an editor that is composed of multiple editors for given columns.
Expand Down Expand Up @@ -260,4 +253,11 @@
editor.prototype = this;
return editor;
}

// exports
$.extend(true, window, {
Slick: {
CompositeEditor: CompositeEditor
}
});
})(jQuery);
Loading