-
Notifications
You must be signed in to change notification settings - Fork 423
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds optimized bulk operations to DataView (#572)
* 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
1 parent
32e03d5
commit 17967c9
Showing
13 changed files
with
1,978 additions
and
317 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); | ||
|
||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.