SenchaCon 2016: The Once and Future Grid - Nige White


Published on

The Ext JS Grid has been a powerhouse for years, used by thousands of enterprises to deliver robust, data-rich applications to the web. The Ext JS Grid for the Modern Toolkit builds on these years of experience and leverages the full power of HTML5 and CSS3 to provide an extremely flexible and efficient grid for the modern era. In this session, we'll explore some of the key architectural advantages of the Modern Grid. Come and see how you can take advantage of these capabilities to tame mountains of data and give your users the world-class experience they demand.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • Pre-4.2
    Refresh destroyed and then created the full view DOM.
    Row DOM completely replaced upon record mutation.
  • Templates can contain embedded code, so templates can call into their owning Component, or call other templates.
    Historically, we dumped a huge load of textual HTML into a Panel’s element to create the grid’s UI
    The reason it’s broken into nested template is to enable features such as grouping, row body, or anything else which needs extra DOM.
    The API gave explicit access to strings in the DOM generation buffer
  • The structure is transient. Changes regenerate HTML because of the HTML centric API of the classic grid
    Refreshing tips out old DOM, regenerates all rows.
    Even simple updates used to destroy active DOM prior to V5. Cell editor inside a cell would be destroyed, or a focused cell could be destroyed by data changes.
    Causes memory use and frequent garbage collection.
    Thousands of rows hurts performance
  • Because it was very HTML-centric, content required HTML and possible CSS skills.
    Internal elements were not configurable
    Internal elements were not themeable.
    See Phil Guerant’s talk Theming the Modern Toolkit at 3:40 today for more details
  • JavaScript is much FASTER than DOM
    Column updaters are passed the TD and may change it as the author sees fit – see later.
    Upon field change, we do create whole new rows through the whole template tree
    But ONLY for changed fields.
    Calculated fields can depend upon other calculated fields which can depend upon other fields. Calculations will be run in correct order – we’ll see this in action later
    … instead of renderers.
  • Rapidly updating DOM.
    50 changes per second thrown at the store!
    Timeline – still long frames. If part of a larger UI, would hurt its perf.
    Need to throttle changes to DOM. 5 times a second is fast enough for the human perception.
    Classic has throttledUpdate. – check timeline.
    Non-destructive update since 5.0. CellEditor undamaged.
    Test auto sort.. Use sortable: false on rapidly changing columns! Price is OK
  • Free for all
    Injects HTML.
    Mixes concerns. Appearance should come only from theme.
  • Because of the historical API we generate textual using the full render pathway.
    HTML. XTemplates have computational cost.
    HTML parsing has cost. We only generate the cells that have changed, so not full width rows.
    But because of renderer parameters, some columns MUST be generated even if not changed because renderer has such wide access.
    Walking the new tree and copying across only changed attributes or data of cells is suboptimal.
  • No minimal DOM update because we don’t know what other data the renderer uses, so we must ALWAYS update that column.
  • The only field being set in the demo is the price.
    All other fields are dependent, and the values in the record change when fields they are known to depend on change.
    Unlike renderers, your dependent data really exists.
  • Scroll.
    “Spare” rows are just derefenced and hopefully left to the garbage collector.
    New full rows are rendered through the pathway we’ve already seen and appended.
  • One row update cycle. Uncompressible now.
    Template overwrite = apply (string creation) + overwrite (HTML element creation). You can see the parse HTML call.
    Update columns which traverses the DOM is quite fast in comparison to parsing HTML and creating DOM
    It’s STILL amazingly fast! 7 - 12ms/flush. No long frames – no jank in large UI
    producesHTML: false opts out of HTML parsing
    (getPosition is grid navigation model
  • Modern components and widgets and ViewModels
    Because we use widgets at each level, Row and Cell, we can

    “Also is …” my word play ran out of steam
  • These are the things that it doesn’t do yet.
    These will be in the next major release
  • It’s all still there.
    Field dependencies are still useful
    Data binding tracks dependencies
    Binding is async and buffered, a ViewModel’s Scheduler can be configured with a tickDelay
  • Components and configs act as a kind of virtual DOM
    But a Higher-level of abstraction
    Not regenerated and differenced
    Setters and getters autogenerated
  • The generated setter is a bit more detailed
    You can provide an applier to “promote” the config object to its final form.
    If the applier returns undefined, then the property is not changed.
    This is so an applier can simply update the old property if the incoming just means reconfiguring.
    Example, setScrollable({y: false}) – the applier simply reconfigures the oldScrollable.
    Key is that updaters run only when config changes
  • Modern components and widgets
    “*” – we’ll expand on List later
  • A List is a dataview of components.
    Each component is bound to a record, and inherits a ViewModel from the List
    Grid’s components are of type gridrow
    Only enough gridrows are created to fill the visible area, plus a configurable buffer amount
  • Modern components and widgets
    “*” – we’ll expand on List later
  • Containers carry some overhead.
    A Row consists of a display:table-row element.
    A Cell consists of display:table-cell elements.
  • Modern components and widgets
    “*” – we’ll expand on List later
  • Modern components and widgets
    Rows are not containers. No add/remove methods or add/remove events.
    The grid
    “*” – we’ll expand on List later
  • The viewModel affects the grid Panel
    If you need docked components to update immediately, then contain the grid in a panel with a default ViewModel
  • This is instead of a recursive DOM comparison/update
    Cell’s updaters react as they see fit. Text cells do nothing but update the data of a text node
    Some cells have bound data to their innerCls
    The point is that only changed data gets updated.
    The record applier in the RowWidget will call setters which will then block non-changes
  • Eleven records flushing took less than 3ms
    It’s just configs being set by binding.
    Here’s a typical graph of two changed configurations.
    One is the trend field updating the sparkline, and the other is the price which ends in a simple set of the data of a textNode
  • Modern components and widgets
    “*” – we’ll expand on List later
  • Modern components and widgets
    “*” – we’ll expand on List later
  • A little like Widget column in classic, in that a config, the cell config determines what kind of widget to create under that column header
  • Column configuration drives the headferts
    And the cell config of each column drives the cells below it.
  • Cells only update what they need to update, when they need to update.
    Text JUST for text through dataIndex/binding
    Cell adds renderers, formatters and a tpl
    By default, they’ll encode any HTML.
    More secure. Data could contain “<div onmouseover=“doNefariousStuff”>data</div>
    Trees just have a fancy cell to display the first column. As in classic, a tree is a grid
  • First two entered in a CellEditor will execute the user’s Javascript code!
    Last one will Invoke a nefarious website.
    In Classic, HTML was assumed, and it was up to you, the developers to defend.
    In Modern, HTML is not allowed into a cell unless you opt in.
  • Enough Row components are created to fill the view
    A buffer config determines how many spares
    Limited by how many are needed to fill the visible zone, so there won’t be an excessive number.
  • Rows are moved and reconfigured as needed.
    Row widgets are recycled. None will be created unless the view changes height.
    Minimal change propagated into cells by configs.
  • Widgets encapsulate the HTML.
    Developers concentrate on behaviour and data.
    Designers concentrate on theming the widgets.
    But if you NEED HTML, it’s still there with a Component’s tpl. It’s just like a UIWebView in native apps.
    Preferably, create custom components to encapsulate your HTML needs which are configurable and themeable.
  • The value of “orderId” is the “id” value of the reference type
  • SenchaCon 2016: The Once and Future Grid - Nige White

    1. 1. The Once And Future Grid Nige White
    2. 2. The Grid That Was… XTemplates + cell renderers (simple DOM replacement)
    3. 3. 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
    4. 4. Challenges • DOM churn. • Changes replace DOM. Plugins, event handlers might “own” that DOM content. • Scaling to today’s big apps. Multi thousand row grids.
    5. 5. Shortcomings • View not configurable • View not themeable Header1 Header2 Header3 Data1.1 Data1.2 Data1.3 Data2.1 Data2.2 Data2.3
    6. 6. How have we mitigated these?
    7. 7. The Grid That Is… XTemplates + cell renderers + Throttled view updates + Cell updaters + Minimal re-render + Minimal DOM update + Field dependencies + Buffered renderer
    8. 8. Classic Demo
    9. 9. 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
    10. 10. Column updaters • Similar to renderers, but only called for update. Passed cell’s DOM updater: function(cellDom, value) { = value < 0 ? ‘red’ : ‘green’; }
    11. 11. 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
    12. 12. 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?
    13. 13. 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); // class sees this! if (trend.length > 10) { trend.shift(); } return trend; }
    14. 14. Buffered renderer
    15. 15. Flush cycle. Nine rows to update < 15ms
    16. 16. The Grid That Will Be… Ext.List + Configs + Data binding + Abstracted HTML away Already Is…
    17. 17. The Grid That Will Be… • Column locking • Keyboard navigability • Cell editing • Group collapsing • Group summaries
    18. 18. Where’s All The Magic Gone? XTemplates + cell renderers Cell updaters Minimal re-render Minimal DOM copy Throttled update Buffered Renderer Ext.List Ext.grid.Row Ext.grid.cell.* Binding → Configs VM Scheduler config List, infinite: true
    19. 19. Configs Ext.define('Ext.Widget', { config: { cls: null, ... }, ... updateCls: function (cls, oldCls) { this.element.replaceCls(oldCls, cls); } }); getCls setCls
    20. 20. 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); } } } Transform values Manage side-effects
    21. 21. Rows
    22. 22. Grid extends List Ext.define('Ext.grid.Grid', { extend: 'Ext.List', xtype: 'grid', defaultType: 'gridrow', infinite: true }); Create an Ext.grid.Row per visible record
    23. 23. Rows Are Containers of Cells
    24. 24. Not exactly…
    25. 25. Row extends Component Ext.define('Ext.grid.Row', { extend: 'Ext.Component', xtype: 'gridrow', ... });
    26. 26. Rows Are Cell Managers. Also RowBody Widget Managers.
    27. 27. add() and remove() - No Events - No Hierarchy – Yes ViewModels - Yes
    28. 28. Modern Demo
    29. 29. 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
    30. 30. Changes distributed selectively Row’s ViewModel Cell CellCell RowBodyWidget setValue(boundValue)setValue(boundValue) setInnerCls(boundValue) setRecord(boundRecord)
    31. 31. Flush cycle. Eleven rows to update < 3ms
    32. 32. Styling Rows
    33. 33. Classic items: [{ xtype: 'grid', viewConfig: { getRowClass: function (rec) { return rec.get("valid") ? "row-valid" : "row-error"; } } }
    34. 34. Modern items: [{ xtype: 'grid', itemConfig: { cls: 'row-class', viewModel: { type: 'row-model' }, bind: { cls: '{record.change > 0 ? "ticker-gain" : "ticker-loss”}’ } } }]
    35. 35. Cells
    36. 36. columns drive cell creation and order
    37. 37. Columns items: [{ xtype: 'grid', columns: [{ // xtype: 'column', text: 'Heading', cell: { // xtype: 'gridcell', cls: 'mycell' } }] Cell Configuratio n
    38. 38. ai Grid Columns bi ci A B C rows[i] headerxtype: 'grid', columns: [{ text: '...', cell: {...} }]
    39. 39. Base Text Boolean Cell Date Number Tree Widget Cell Classes
    40. 40. Faster. More secure. • Cell widget config updaters do minimal change. • Data is HTML encoded by default.
    41. 41. 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=""></img>
    42. 42. 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.
    43. 43. Row Modern Grid is managed Row Widgets Row Visible Spares Row 1 Row 2 Row 3
    44. 44. Modern Grid is managed Row Widgets scroll [0] [1] [2] [3] [4] setRecord(rec[5]) Row 4 Row 1 Row 2 Row 3 Row 0 [5] Row 5
    45. 45. 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.
    46. 46. 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