From e1b289c1ab5ff9b4b25ecdabd3217011200c6f09 Mon Sep 17 00:00:00 2001 From: Bill Keese Date: Thu, 26 Mar 2015 13:23:49 +0900 Subject: [PATCH] Call parser and attachedCallback() / detachedCallback() automatically on initial page load and as DOM nodes added/removed from the document. For performance reasons, does not monitor the nodes created by widgets themselves (or at least, not when widgets are instantiated via parse()). Therefore, the delite/Template code continues to propagate attachedCallback() and detachedCallback() calls to subwidgets. Since parsing and attachedCallback() / detachedCallback() happens asynchronously, register has a new method call deliver() that will parse new widgets synchronously. This is used in the tests to make sure parsing is complete before testing starts. It can also be used by applications when the application needs to run some code that depends on widgets being instantiated. Fixes #392. --- CustomElement.js | 26 +-- docs/CustomElement.md | 11 +- docs/Widget.md | 9 +- docs/architecture.md | 14 +- docs/customElements101.md | 25 ++- docs/migration.md | 2 +- docs/register.md | 5 - docs/setup.md | 6 +- docs/tutorial/beginner.md | 25 +-- features.js | 23 +++ register.js | 243 +++++++++++++++++++++--- samples/ExampleWidget.html | 8 +- tests/functional/DojoParser.html | 5 +- tests/functional/KeyNavTests.html | 2 +- tests/functional/TabIndex.html | 2 +- tests/functional/Widget.html | 2 +- tests/functional/activationTracker.html | 2 +- tests/functional/polymer.html | 2 +- tests/unit/handlebars.js | 12 +- tests/unit/register.js | 120 +++++++++++- 20 files changed, 406 insertions(+), 138 deletions(-) diff --git a/CustomElement.js b/CustomElement.js index 1aa9f5637a..d4da11bf6b 100644 --- a/CustomElement.js +++ b/CustomElement.js @@ -9,8 +9,6 @@ define([ "./register" ], function (advise, dcl, Observable, Destroyable, Stateful, has, register) { - function nop() {} - /** * Dispatched after the CustomElement has been attached. * This is useful to be notified when an HTMLElement has been upgraded to a @@ -226,10 +224,7 @@ define([ attached: false, /** - * Called when the element is added to the document, after `createdCallback()` completes. - * Note though that for programatically created custom elements, the app must manually call - * this method. - * + * Called automatically when the element is added to the document, after `createdCallback()` completes. * This method is automatically chained, so subclasses generally do not need to use `dcl.superCall()`, * `dcl.advise()`, etc. * @method @@ -241,13 +236,6 @@ define([ // Do this in attachedCallback() rather than createdCallback() to avoid calling refreshRendering() etc. // prematurely in the programmatic case (i.e. calling it before user parameters have been applied). this.deliver(); - - // Protect against repeated calls. - this._realAttachedCallback = this.attachedCallback; - this.attachedCallback = nop; - if (this._realDetachedCallback) { - this.detachedCallback = this._realDetachedCallback; - } }, after: function () { this.attached = true; @@ -260,20 +248,12 @@ define([ }), /** - * Called when the element is removed the document. Note that the app must manually call this method. - * + * Called when the element is removed the document. * This method is automatically chained, so subclasses generally do not need to use `dcl.superCall()`, * `dcl.advise()`, etc. */ detachedCallback: function () { - if (this.attached) { - this.attached = false; - - // Protect against repeated calls. - this._realDetachedCallback = this.detachedCallback; - this.detachedCallback = nop; - this.attachedCallback = this._realAttachedCallback; - } + this.attached = false; }, /** diff --git a/docs/CustomElement.md b/docs/CustomElement.md index 209bcfff4e..324d17111d 100644 --- a/docs/CustomElement.md +++ b/docs/CustomElement.md @@ -26,16 +26,7 @@ myWidget.numProp = 123; The initialization methods in `delite/CustomElement` correspond to the function names from the Custom Elements specification, specifically `createdCallback()` and `attachedCallback()`. -When a custom element is instantiated via `register.parse()`, `createdCallback()` and `attachedCallback()` are -automatically called. -When a custom element is instantiated programatically, `createdCallback()` is automatically called, -but the application must call `attachedCallback()` manually. -Alternately, if you extend [`delite/Widget`](Widget.md), you can use the `placeAt()` -method, which will attach the element to the specified DOM node and also call `attachedCallback()`. -The requirement to manually call `attachedCallback()` is because, for performance reasons, -delite does not set up document level listeners for DOM nodes being attached / removed from the document. - -Also, `delite/CustomElement` does not provide the `attributeChangedCallback()`, but you can +`delite/CustomElement` does not provide the `attributeChangedCallback()`, but you can find out when properties change by declaring the properties in your element's prototype, and then reacting to changes in `refreshRendering()`. diff --git a/docs/Widget.md b/docs/Widget.md index ec21c78921..f6b2c38854 100644 --- a/docs/Widget.md +++ b/docs/Widget.md @@ -33,23 +33,22 @@ Programmatic creation is: custom setters. 6. `attachedCallback()` callback. -`attachedCallback()` will be called automatically in the declarative case, and -when the widget was created programatically, then it can be triggered by calling -`Widget#placeAt(document.body)` (or specify any parent DOM node). +`attachedCallback()` will be called automatically, although asynchronously. -As mentioned above, there are currently five lifecycle methods which can be extended on the widget: +There are currently five lifecycle methods which can be extended on the widget: 1. `preRender()` 2. `render()` 3. `postRender()` 4. `attachedCallback()` -5. `destroy()` +5. `detachedCallback()` Note that all of these methods except `render()` are automatically chained, so you don't need to worry about setting up code to call the superclasses' methods. Also, note that widget authors don't typically extend `render()` directly, but rather specify the `template` property. See the [`handlebars!`](handlebars.md) documentation for more details. + ## Placement Delite widgets are DOM Custom Elements. That means they can be placed and manipulated just like other DOM elements. diff --git a/docs/architecture.md b/docs/architecture.md index d6e7a912c0..b2806c1fd6 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -69,12 +69,14 @@ Custom Elements extend [`decor/Stateful`](/decor/docs/0.5.0/Stateful.html). See the decor [design documentation](/decor/docs/0.5.0/architecture.html) for details about how that class avoids polling / dirty checking for property changes. -Also, we intentionally don't set up page level listeners for custom element creation/deletion. -The listeners could be a bottleneck for applications that create thousands of DOM nodes on the fly. -Think of applications drawing charts in SVG, or quickly paging/scrolling through a table with -lots of data. As a consequence to this, you must call `.parse()` on page load. - -Another decision decision was to not shim shadow DOM. While shadow DOM a nice concept, it takes lots of code to shim, +Although we set up page level listeners for custom elements being attached/detached from the document, the listeners are +disabled as widgets are being instantiated. This prevents a performance issue for widgets that internally +create lots of elements, like charts. +Therefore, custom elements that create other custom elements are responsible for creating those +custom elements via javascript (`new MyWidget(...)`), and then calling `attachedCallback()` at the appropriate time. +Note however that this is handled automatically for widgets in templates. + +Another decision was to not shim shadow DOM. While shadow DOM a nice concept, it takes lots of code to shim, and we felt the download cost outweighed the benefit. ## register() implementation details diff --git a/docs/customElements101.md b/docs/customElements101.md index c65ab5c55a..d8dfdca105 100644 --- a/docs/customElements101.md +++ b/docs/customElements101.md @@ -91,26 +91,23 @@ to the property's type. ### Parsing -In order for declarative custom elements to be instantiated on platforms without native custom element support, -you must call the parser: +"Parsing" refers to scanning the document for custom element usages (ex: ``), and upgrading +those plain HTML elements to be proper custom elements (i.e. setting up the prototype chain, and calling +`createdCallback()` and `attachedCallback()`). -```js -require(["delite/register", "requirejs-domready/domReady!"], function (register) { - register.parse(); -}); -``` +When the document has finished loading, delite will do an initial parse. +Afterwards, if new custom elements are defined, delite will scan the document for any additional nodes that need to +be upgraded. -Note that on platforms *with* custom element support, the custom elements will be instantiated before -the call to `register.parse()`, and without any guaranteed order. Therefore, if your custom elements -depend on a global variable, like in the example above, you should make sure it is available before -the custom element is loaded. Therefore, you may need code like this: +So, custom elements will be instantiated without any guaranteed order, and without any guaranteed timing relative to +other javascript code running. +Therefore, if your custom elements depend on a global variable, like in the example above, +you should make sure it is available before the custom element is loaded. So you may need code like this: ```js require(["dstore/Memory"], function (Memory) { myGlobalVar = new Memory(); - require(["delite/register", "requirejs-domready/domReady!"], function (register) { - register.parse(); - }); + require(["deliteful/List"]); }); ``` ### Declarative Events diff --git a/docs/migration.md b/docs/migration.md index 5815f1261b..a28757b8a5 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -10,7 +10,7 @@ title: delite/migration 1. In markup, widgets look like `` rather than `
`. For widgets that enhance an existing tag, syntax is `