Upload
sencha
View
123
Download
1
Embed Size (px)
Citation preview
The Once And Future Grid
Nige White
The Grid That Was…
XTemplates + cell renderers
(simple DOM replacement)
Classic Grid is a DataView with a BIG template
Four nested templates with embedded code.
1. The standard Component tpl which calls…
2. renderRows which uses
3. itemTemplate which uses
4. rowTemplate which uses
5. cellTemplate which calls…
6. Cell renderers
Challenges
• DOM churn.
• Changes replace DOM. Plugins, event handlers might “own” that DOM content.
• Scaling to today’s big apps. Multi thousand row grids.
Shortcomings
• View not configurable
• View not themeable
Header1 Header2 Header3
Data1.1 Data1.2 Data1.3
Data2.1 Data2.2 Data2.3
How have we mitigated these?
The Grid That Is…
XTemplates + cell renderers + Throttled view updates
+ Cell updaters
+ Minimal re-render
+ Minimal DOM update
+ Field dependencies
+ Buffered renderer
Classic Demo
Throttling/batching of DOM updates
View config:
{throttledUpdate: true
}Flushes batched changes every Ext.view.Table.updateDelay milliseconds
No long frames, small pulse of update activity every 200ms
Column updaters
• Similar to renderers, but only called for update. Passed cell’s DOM
updater: function(cellDom, value) {cellDom.firstChild.style.color = value < 0 ? ‘red’ : ‘green’;
}
Minimal DOM update
<tr class=“x-grid-row”><td class=“x-grid-cell”>
<div class=“x-grid-inner”>OldData
</div></td>
</tr>
<tr class=“x-grid-row”><td class=“x-grid-cell”>
<div class=“x-grid-inner”>NewData
</div></td>
</tr>
New, detached DOM Existing grid row
• Only the changed columns used to render detached row DOM• For each row:• Recursively update changed DOM attributes or text content
Field dependencies
• Relying on renderers to produce dependent values is unreliable.
• The calculated data does not really exist, so cannot be used.
• Renderers which use their full argument list mean no minimal DOM rerender
How do those fields change when we just set the new price?
Field dependencies // Depends on price field. Called whenever that is set{ name: 'trend', calculate: function(data) { // Avoid circular dependency by hiding the read of trend value var trend = data['trend'] || (data['trend'] = []);
trend.push(data.price); // Ext.data.Model class sees this! if (trend.length > 10) { trend.shift(); } return trend;}
Buffered renderer
Flush cycle. Nine rows to update
< 15ms
The Grid That Will Be…
Ext.List+ Configs
+ Data binding
+ Abstracted HTML away
Already Is…
The Grid That Will Be…
• Column locking
• Keyboard navigability
• Cell editing
• Group collapsing
• Group summaries
Where’s All The Magic Gone?
XTemplates + cell renderers
Cell updatersMinimal re-render
Minimal DOM copy
Throttled update
Buffered Renderer
Ext.ListExt.grid.Row
Ext.grid.cell.*
Binding → Configs
VM Scheduler config
List, infinite: true
Configs
Ext.define('Ext.Widget', {
config: { cls: null, ... }, ...
updateCls: function (cls, oldCls) { this.element.replaceCls(oldCls, cls); }});
getCls
setCls
On changes only!
Config generated setter
setCls: function (value) { var oldValue = this._cls; if (this.applyCls) { value = this.applyCls(value, oldValue); } if (value !== undefined && value !== oldValue) { this._cls = value; if (this.updateCls) { this.updateCls(value, oldValue); } } }
Transformvalues
Manageside-effects
Rows
Grid extends List
Ext.define('Ext.grid.Grid', { extend: 'Ext.List', xtype: 'grid',
defaultType: 'gridrow', infinite: true});
Create anExt.grid.Row
per visible record
Rows Are Containers of Cells
Not exactly…
Row extends Component
Ext.define('Ext.grid.Row', { extend: 'Ext.Component', xtype: 'gridrow',
...});
Rows Are Cell Managers.Also RowBody Widget Managers.
add() and remove() - No
Events - No
Hierarchy – Yes
ViewModels - Yes
Modern Demo
Throttling/batching of DOM updates
View config:
viewModel: { scheduler: { tickDelay: 200 }}Flushes batched changes every 200 milliseconds
No long frames, even smaller pulse of update activity every 200ms
Changes distributed selectivelyRow’s ViewModel
Cell CellCell
RowBodyWidget
setValue(boundValue)setValue(boundValue) setInnerCls(boundValue)
setRecord(boundRecord)
Flush cycle. Eleven rows to update
< 3ms
Styling Rows
Classic
items: [{ xtype: 'grid', viewConfig: { getRowClass: function (rec) { return rec.get("valid") ? "row-valid" : "row-error"; } }}
Modernitems: [{ xtype: 'grid', itemConfig: { cls: 'row-class', viewModel: { type: 'row-model' }, bind: { cls: '{record.change > 0 ? "ticker-gain" : "ticker-loss”}’ } }}]
Cells
columnsdrive cell
creation and order
Columns
items: [{ xtype: 'grid', columns: [{ // xtype: 'column', text: 'Heading', cell: { // xtype: 'gridcell', cls: 'mycell' } }]
CellConfiguratio
n
ai
Grid Columns
bi ci
A B C
rows[i]
headerxtype: 'grid',columns: [{ text: '...', cell: {...}}]
Base
Text
Boolean
Cell
Date
Number
TreeWidget
Cell Classes
Faster. More secure.
• Cell widget config updaters do minimal change.
• Data is HTML encoded by default.
htmlEncode: true by default
<div style="position:fixed;top:0; left:0;width:10000px;height:10000px;z-index:10" onmouseover="alert('Hack!')"></div>
<img src="\\" onerror="alert('hack')"</img>
<img src="http://evil.org/"></img>
What if we’re not rapidly changing?
Scrolling data is exactly that when we are using buffered rendering.
New DOM has to be created to show rows in the direction of scroll.
Row
Modern Grid is managed Row Widgets
Row
VisibleSpares
Row 1Row 2Row 3
Modern Grid is managed Row Widgets
scro
ll[0]
[1]
[2]
[3]
[4]setRecord(rec[5])
Row 4
Row 1Row 2Row 3
Row 0
[5]
Row 5
Conclusion
• Less openly HTML centred concept.
• Widgets abstract away from HTML, each can carry CSS classes for theming.
• HTML + CSS power still there for decorative purposes – tpl config.
• Analogous to UIWebView.
• If you need different appearance and behaviour, prefer a custom component.
Please Take the Survey in the Mobile App
• Navigate to this session in the mobile app
• Click on “Evaluate Session”
• Respondents will be entered into a drawing to win one of five $50 Amazon gift cards