Autopsy Of A Widget

5,454 views

Published on

With modern JavaScript frameworks like ExtJS, SproutCore or qooxdoo, it is possible to create very rich user interfaces using only open web standards. But how do they do it? How do they work internally?

In this talk I will open the thorax of a simple qooxdoo widget and look at the various layers and building blocks used to implement it. You will learn how the widget is represented in the DOM, how events are handled, and how the layout engine works. Often there is more than a single way to achieve something. In those cases the different options and qooxdoo's specific design decision will be presented.

Published in: Technology
0 Comments
10 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
5,454
On SlideShare
0
From Embeds
0
Number of Embeds
281
Actions
Shares
0
Downloads
92
Comments
0
Likes
10
Embeds 0
No embeds

No notes for slide

Autopsy Of A Widget

  1. Autopsy of a Widget The Internals of a qooxdoo Widget by Fabian Jakobs @fjakobs
  2. Web Application
  3. Browser as Render Engine
  4. Primitives Image Text Rectangle
  5. Tree of Primitives •DOM tree •Events •CSS for styling •JavaScript for behavior
  6. The Body Widget
  7. The Widget •Spinner •Number input •Composed widget new qx.ui.form.Spinner(20);
  8. Static Structure: Composed Widgets
  9. Goal •Create complex Widgets by composing Widgets •Use known GUI Concepts •Abstraction over the Browser
  10. Goal •Create complex Widgets by composing Widgets •Use known GUI Concepts •Abstraction over the Browser
  11. qx.Class.define("qx.ui.form.Spinner", { extend : qx.ui.core.Widget, construct : function(value) { this.base(arguments); } });
  12. qx.Class.define("qx.ui.form.Spinner", { extend : qx.ui.core.Widget, construct : function(value) { this.base(arguments); var layout = new qx.ui.layout.Grid(); layout.setRowFlex(0, 1); layout.setRowFlex(1, 1); layout.setColumnFlex(1, 1); this._setLayout(layout); } });
  13. qx.Class.define("qx.ui.form.Spinner", { extend : qx.ui.core.Widget, construct : function(value) { this.base(arguments); var layout = new qx.ui.layout.Grid(); layout.setRowFlex(0, 1); layout.setRowFlex(1, 1); layout.setColumnFlex(1, 1); this._setLayout(layout); this._input = new qx.ui.form.TextField(value.toString()); this._add(this._input, {row: 0, column: 0, rowSpan: 2}); this._buttonUp = new qx.ui.form.Button(); this._buttonUp.setIcon("decoration/arrows/up-small.png"); this._add(this._buttonUp, {row: 0, column: 1}); this._buttonDown = new qx.ui.form.Button(); this._buttonDown.setIcon("decoration/arrows/down-small.png"); this._add(this._buttonDown, {row: 1, column: 1}); } });
  14. Demo http://demo.qooxdoo.org/current/playground
  15. DOM Structure
  16. DOM Structure new qx.ui.form.TextField("Anatomy");
  17. DOM Structure new qx.ui.form.TextField("Anatomy"); <div style="position: absolute; left: 10px; top: 10px; width: 102px; height: 22px;"> <input type="text" spellcheck="false" tabindex="1" style='position: absolute; z-index: 10; left: 6px; top: 4px; width: 90px; height: 15px; ...'> <div style="position: absolute; top: 0pt; left: 0pt; z-index: 5;"> ... </div> </div>
  18. Container <div style="position: absolute; left: 10px; top: 10px; width: 102px; height: 22px;"> <input type="text" spellcheck="false" tabindex="1" style='position: absolute; z-index: 10; left: 6px; top: 4px; width: 90px; height: 15px; ...'> <div style="position: absolute; top: 0pt; left: 0pt; z-index: 5;"> ... </div> </div>
  19. Container Decorator <div style="position: absolute; left: 10px; top: 10px; width: 102px; height: 22px;"> <input type="text" spellcheck="false" tabindex="1" style='position: absolute; z-index: 10; left: 6px; top: 4px; width: 90px; height: 15px; ...'> <div style="position: absolute; top: 0pt; left: 0pt; z-index: 5;"> ... </div> </div>
  20. Container Decorator Content <div style="position: absolute; left: 10px; top: 10px; width: 102px; height: 22px;"> <input type="text" spellcheck="false" tabindex="1" style='position: absolute; z-index: 10; left: 6px; top: 4px; width: 90px; height: 15px; ...'> <div style="position: absolute; top: 0pt; left: 0pt; z-index: 5;"> ... </div> </div>
  21. Summary •2-3 DOM elements •Absolute positioning •Fixed sizes •No explicit padding <div style="position: absolute; left: 10px; top: 10px; width: 102px; height: 22px;"> <input type="text" spellcheck="false" tabindex="1" style='position: absolute; z-index: 10; left: 6px; top: 4px; width: 90px; height: 15px; ...'> <div style="position: absolute; top: 0pt; left: 0pt; z-index: 5;"> ... </div> </div>
  22. DOM Structure Pro Con Box Model Independence 2-3 DOM Elements Flexible Styling Size Computation in JS No Cross Browser Issues
  23. Theming
  24. The Guts: Working With The DOM
  25. new qx.ui.form.TextField("Anatomy"); ??? <div style="position: absolute; left: 10px; top: 10px; width: 102px; height: 22px;"> <input type="text" spellcheck="false" tabindex="1" style='position: absolute; z-index: 10; left: 6px; top: 4px; width: 90px; height: 15px; ...'> <div style="position: absolute; top: 0pt; left: 0pt; z-index: 5;"> ... </div> </div>
  26. The DOM is a Mess •Many cross browser issues IE element.style.filter = "alpha(opacity=" + (opacity * 100) + ");"; others element.style.opacity = opacity;
  27. The DOM is a Mess •Many cross browser issues var el = document.createElement("div"); document.body.appendChild(el); •Small mistakes can degrade el.style.position = absolute; // more style changes performance
  28. The DOM is a Mess •Many cross browser issues var el = document.createElement("div"); document.body.appendChild(el); •Small mistakes can degrade el.style.position = absolute; // more style changes performance var el = document.createElement("div"); el.style.position = absolute; // more style changes document.body.appendChild(el);
  29. The DOM is a Mess •Many cross browser issues •Small mistakes can degrade performance var width = parseInt(el.style.width); var height = parseInt(el.style.height); •Reading styles/attributes is slow
  30. The DOM is a Mess •Many cross browser issues •Small mistakes can degrade performance var width = parseInt(el.style.width); var height = parseInt(el.style.height); •Reading styles/attributes is slow
  31. DOM Wrapper •Wrap DOM elements •Lazy DOM element creation •Batch DOM manipulations •Cache styles/attributes
  32. qx.Class.define("qx.ui.core.Widget", Container { extend : qx.core.Object, construct : function() { this.base(arguments); this._container = new qx.html.Element("div"); this._content = new qx.html.Element("div").setStyles({ position: "absolute", zIndex: 10, overflow: "hidden" }); this._container.add(this._content); this._decorator = new qx.html.Element("div").setStyles({ position: "absolute", zIndex: 10 }); this._container.add(this._decorator); } });
  33. qx.Class.define("qx.ui.core.Widget", Container { extend : qx.core.Object, Decorator construct : function() { this.base(arguments); this._container = new qx.html.Element("div"); this._content = new qx.html.Element("div").setStyles({ position: "absolute", zIndex: 10, overflow: "hidden" }); this._container.add(this._content); this._decorator = new qx.html.Element("div").setStyles({ position: "absolute", zIndex: 10 }); this._container.add(this._decorator); } });
  34. qx.Class.define("qx.ui.core.Widget", Container { extend : qx.core.Object, Decorator construct : function() Content { this.base(arguments); this._container = new qx.html.Element("div"); this._content = new qx.html.Element("div").setStyles({ position: "absolute", zIndex: 10, overflow: "hidden" }); this._container.add(this._content); this._decorator = new qx.html.Element("div").setStyles({ position: "absolute", zIndex: 10 }); this._container.add(this._decorator); } });
  35. DOM Wrapper Pro Con Cross browser API Memory overhead Performance Additional complexity Keeps widget code clean
  36. Dynamic Structure: Layouts
  37. Initial Sizes
  38. Initial Sizes
  39. Initial Sizes
  40. Dynamic Update
  41. Layout •Compute widgets sizes based on •Available space •Layout constraints •Preferred widget sizes •Widget size constraints
  42. Algorithm 2 Passes
  43. Algorithm 2 Passes 1st Pass Compute Preferred Size
  44. Algorithm 2 Passes 1st Pass 2nd Pass Compute Preferred Size Render Size
  45. 1st Pass Spinner Grid getSizeHint() getSizeHint() getLayout() getLayoutChildren() computeLayout(hints) TextField Button Button getSizeHint() getSizeHint() getSizeHint() measureTextHeight() getImageSize() getImageSize()
  46. 1st Pass Spinner.getSizeHint = function() { Spinner Grid return this.getLayout().getSizeHint(); } getSizeHint() getSizeHint() getLayout() getLayoutChildren() computeLayout(hints) TextField Button Button getSizeHint() getSizeHint() getSizeHint() measureTextHeight() getImageSize() getImageSize()
  47. 1st Pass Grid.getSizeHint = function() { Spinner Grid var childrenHints = []; var children =getSizeHint() this.getLayoutChildren(); getSizeHint() for (var i=0; i<children.length; i++) { getLayout() childrenHints.push(children[i].getSizeHint()); getLayoutChildren() } computeLayout(hints) return this.computeLayout(childrenHints); } TextField Button Button getSizeHint() getSizeHint() getSizeHint() measureTextHeight() getImageSize() getImageSize()
  48. 1st Pass Spinner Grid getSizeHint() TextField.getSizeHint = function() getSizeHint() { getLayout() getLayoutChildren() return { height: this.measureTextHeight(), computeLayout(hints) width: 100 } Button.getSizeHint = function() { } return this.getImageSize() } TextField Button Button getSizeHint() getSizeHint() getSizeHint() measureTextHeight() getImageSize() getImageSize()
  49. 2nd Pass Spinner Grid renderLayout(bounds) renderLayout() getLayout() getLayoutChildren() updateWidgetSize(bounds) computeChildrenSizes(bounds) TextField Button Button renderLayout() renderLayout() renderLayout() updateWidgetSize(bounds) updateWidgetSize(bounds) updateWidgetSize(bounds)
  50. 2nd Pass Spinner.renderLayout = function(bounds) Spinner { Grid this.updateWidgetSize(bounds); renderLayout(bounds) renderLayout() this.getLayout().renderLayout(bounds); getLayout() } getLayoutChildren() updateWidgetSize(bounds) computeChildrenSizes(bounds) TextField Button Button renderLayout() renderLayout() renderLayout() updateWidgetSize(bounds) updateWidgetSize(bounds) updateWidgetSize(bounds)
  51. 2nd Pass Grid.renderLayout = function(bounds) { Spinner Grid var sizes = this.computeChildrenSizes(bounds); renderLayout(bounds) var children = this.getLayoutChildren(); renderLayout() getLayout() for (var i=0; i<children.length; i++) { children[i].renderLayout(childrenSizes[i]); getLayoutChildren() } updateWidgetSize(bounds) computeChildrenSizes(bounds) } TextField Button Button renderLayout() renderLayout() renderLayout() updateWidgetSize(bounds) updateWidgetSize(bounds) updateWidgetSize(bounds)
  52. 2nd Pass Spinner Grid renderLayout(bounds) renderLayout() getLayout() getLayoutChildren() updateWidgetSize(bounds) computeChildrenSizes(bounds) Widget.renderLayout = function(bounds) { this.updateWidgetSize(bounds); } TextField Button Button renderLayout() renderLayout() renderLayout() updateWidgetSize(bounds) updateWidgetSize(bounds) updateWidgetSize(bounds)
  53. Layout Summary •Reflow computation in JavaScript •Large collection of layout managers •Highly optimized
  54. Layout Summary •Reflow computation in JavaScript •Large collection of layout managers •Highly optimized „If a feature is missing we can easily add it!“
  55. Layout Pro Con Cross Browser Slower than CSS layouts Custom Layout Managers Complex Implementation Highly Customizable Sensible Default Sizes
  56. Events
  57. „DOM events are a huge stinking pile of mess!“
  58. Different API IE W3C DOM Events attachEvent(...) addEventListener(...) e.returnValue = false e.preventDefault() e.cancelBubble = true e.stopPropagation() ... ...
  59. „It‘s getting worse“
  60. Different Features •Event capturing phase •Mouse capturing (only IE) •Both features are highly desirable
  61. „Worst ...“
  62. Different Behavior •Different mouse event sequences •keyCode and charCode depend on •browser •operating system •locale •...
  63. Goal qx.Class.define("qx.ui.form.Spinner", { extend : qx.ui.core.Widget, construct : function(value) { this.base(arguments); // ... this._buttonUp.addListener("click", this._increaseValue, this); this._buttonDown.addListener("click", this._decreaseValue, this); }, members : { _increaseValue : function() { /* .. */}, _decreaseValue : function() { /* .. */}, } });
  64. Naive e.onclick = onClick e.onkeyPress = onKeyPress
  65. Mouse Handler
  66. listen Mouse Handler
  67. listen Mouse Handler Registration
  68. listen Mouse Handler Registration
  69. listen Mouse Handler Registration qx.event.Registration.addListener(element, "click", function(e) { alert("you clicked me"); });
  70. listen Mouse Handler Registration qx.event.Registration.addListener(element, "click", function(e) { alert("you clicked me"); });
  71. listen Mouse Handler Registration qx.event.Registration.addListener(element, "click", function(e) { alert("you clicked me"); });
  72. listen Mouse Handler Registration qx.event.Registration.addListener(element, "click", function(e) { alert("you clicked me"); });
  73. listen Mouse Handler Registration qx.event.Registration.addListener(element, "click", function(e) { alert("you clicked me"); });
  74. listen Mouse Handler Registration qx.event.Registration.addListener(element, "click", function(e) { alert("you clicked me"); });
  75. listen Mouse Handler Registration qx.event.Registration.addListener(element, "click", function(e) { alert("you clicked me"); });
  76. listen Mouse Handler Registration qx.event.Registration.addListener(element, "click", function(e) { alert("you clicked me"); });
  77. listen Mouse Handler Registration qx.event.Registration.addListener(element, "click", function(e) { alert("you clicked me"); });
  78. Events Pro Con W3C DOM API Performance Mouse capturing Code Size Unified behavior
  79. Summary
  80. Composed Widgets DOM Structure Theming DOM Wrapper Layout Events
  81. Image Reference •Anatomy exhibit •University of Michigan Health Sciences Libraries Rare Book Room •http://www.flickr.com/photos/ rosefirerising/sets/ 72157606558666166
  82. Demo •http://bit.ly/39RDkP •http://bit.ly/2sllO4 •http://bit.ly/1fAevz •http://bit.ly/38ZUYR •http://bit.ly/1Rrb9O

×