Skip to content

Commit

Permalink
Adds optimized bulk operations to DataView (#572)
Browse files Browse the repository at this point in the history
* Add simple bulk update operations

* Added bulk suspend option

* Use Map polyfill for better performance

* Ensure we clear the delete id lookup

* Fix issue on Map polyfill migration

* Tests WIP

* Fixes and tests for new add operations

* Tests for bulk operations

* Example added

* Some additions to make performance difference more visible.

* Moved map and added some docs+comments

* Added cypress test for performance

* Moved exports of all files consistently to the bottom

Co-authored-by: KUSCHNY Daniel <[email protected]>
  • Loading branch information
Danielku15 and kusc-leica authored Feb 2, 2021
1 parent 32e03d5 commit 17967c9
Show file tree
Hide file tree
Showing 13 changed files with 1,978 additions and 317 deletions.
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

0 comments on commit 17967c9

Please sign in to comment.