diff --git a/gruntfile.js b/gruntfile.js index 145c1e3913..ec1a24ec5e 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -38,6 +38,7 @@ module.exports = function(grunt) { "declaration/events.js", "declaration/properties.js", "declaration/attributes.js", + "declaration/prototype.js", "declaration/polymer-element.js", "deprecated.js" ].map(function(n) { @@ -140,7 +141,10 @@ module.exports = function(grunt) { '../CustomElements', '../PointerEvents', '../PointerGestures', - '../mdv' + '../polymer-expressions', + '../observe-js', + '../NodeBind', + '../TemplateInstances' ] }, dest: 'build.log', diff --git a/polymer.js b/polymer.js index 3f8574ff47..42bb26986c 100644 --- a/polymer.js +++ b/polymer.js @@ -31,6 +31,7 @@ var modules = [ "declaration/events.js", "declaration/properties.js", "declaration/attributes.js", + "declaration/prototype.js", "declaration/polymer-element.js", "deprecated.js" ].map(function(n) { @@ -50,10 +51,10 @@ var script = document.querySelector('script[src*="' + thisFile + '"]'); var src = script.attributes.src.value; var basePath = src.slice(0, src.indexOf(thisFile)); -if (!window.Loader) { +if (!window.PolymerLoader) { var path = basePath + 'tools/loader/loader.js'; document.write(''); } -document.write(''); +document.write(''); })(); diff --git a/src/declaration/polymer-element.js b/src/declaration/polymer-element.js index 9bb69e7590..fadc396efc 100644 --- a/src/declaration/polymer-element.js +++ b/src/declaration/polymer-element.js @@ -7,21 +7,11 @@ // imports - var extend = Polymer.extend; + var extend = scope.extend; var apis = scope.api.declaration; // imperative implementation: Polymer() - // maps tag names to prototypes - var prototypesByName = {}; - - function getRegisteredPrototype(name) { - return prototypesByName[name]; - } - - // elements waiting for prototype, by name - var waitPrototype = {}; - // specify an 'own' prototype for tag `name` function element(name, prototype) { //console.log('registering [' + name + ']'); @@ -31,71 +21,9 @@ notifyPrototype(name); } - function notifyPrototype(name) { - if (waitPrototype[name]) { - waitPrototype[name].registerWhenReady(); - delete waitPrototype[name]; - } - } - - // elements waiting for super, by name - var waitSuper = {}; - - function notifySuper(name) { - registered[name] = true; - var waiting = waitSuper[name]; - if (waiting) { - waiting.forEach(function(w) { - w.registerWhenReady(); - }); - delete waitSuper[name]; - } - } - - // track document.register'ed tag names - - var registered = {}; - - function isRegistered(name) { - return registered[name]; - } - - // returns a prototype that chains to or HTMLElement - function generatePrototype(tag) { - return Object.create(HTMLElement.getPrototypeForTag(tag)); - } - - // On platforms that do not support __proto__ (IE10), the prototype chain - // of a custom element is simulated via installation of __proto__. - // Although custom elements manages this, we install it here so it's - // available during desugaring. - function ensurePrototypeTraversal(prototype) { - if (!Object.__proto__) { - var ancestor = Object.getPrototypeOf(prototype); - prototype.__proto__ = ancestor; - if (scope.isBase(ancestor)) { - ancestor.__proto__ = Object.getPrototypeOf(ancestor); - } - } - } - - function whenImportsLoaded(doThis) { - if (window.HTMLImports && !HTMLImports.readyTime) { - addEventListener('HTMLImportsLoaded', doThis); - } else { - doThis(); - } - } - // declarative implementation: - var prototype = generatePrototype(); - - extend(prototype, { - // TODO(sjmiles): temporary BC - readyCallback: function() { - this.createdCallback(); - }, + var prototype = extend(Object.create(HTMLElement.prototype), { createdCallback: function() { // fetch the element name this.name = this.getAttribute('name'); @@ -108,48 +36,22 @@ if (!getRegisteredPrototype(name)) { // then wait for a prototype waitPrototype[name] = this; - // TODO(sjmiles): 'noscript' gambit is mutually exclusive - // with 'async' gambit below - // // if explicitly marked as 'noscript' if (this.hasAttribute('noscript')) { - // go async to allow children to parse - setTimeout(function() { - // register with the default prototype - element(name, null); - }, 0); - } - // TODO(sjmiles): 'async' gambit is deemed too dangerous - // because it changes the timing of noscript elements - // in import from 'timeout 0' to 'HTMLImportsReady' - /* - // if we are not explicitly async... - if (!this.hasAttribute('async')) { - // then we expect the script to be registered - // by end of microtask(-ish) and can otherwise - // consider this element to have no script - // - // TODO(sjmiles): - // we have to wait for end-of-microtask because - // native CE upgrades polymer-element (any custom - // element, really) *before* it's children are - // parsed, and it's common for the script to - // exist as a child of the polymer-element - // - // additionally, there is a massive asynchrony - // between parsing HTML in imports and executing - // script that foils the end of microtask gambit - // Waiting on HTMLImportsLoaded signal solves - // both timing problems for imports loaded - // at startup under the import polyfill - whenImportsLoaded(function() { - if (!getRegisteredPrototype(name)) { - console.warn('giving up waiting for script for [' + name + ']'); - element(name, null); - } - }); + // TODO(sorvell): CustomElements polyfill awareness: + // noscript elements should upgrade in logical order + // script injection ensures this under native custom elements; + // under imports + ce polyfill, scripts run before upgrades + // dependencies should be ready at upgrade time so register + // prototype at this time. + if (window.CustomElements && !CustomElements.useNative) { + element(name); + } else { + var script = document.createElement('script'); + script.textContent = 'Polymer(\'' + name + '\');'; + this.appendChild(script); + } } - */ return; } // fetch our extendee name @@ -162,7 +64,7 @@ return; } } - // TODO(sjmiles): HTMLImports polyfill awareness + // TODO(sjmiles): HTMLImports polyfill awareness: // elements in the main document are likely to parse // in advance of elements in imports because the // polyfill parser is simulated @@ -177,7 +79,6 @@ } }, register: function(name, extendee) { - //console.log('register', name, extendee); // build prototype combining extendee, Polymer base, and named api this.prototype = this.generateCustomPrototype(name, extendee); // backref @@ -188,7 +89,6 @@ // Potentially remove when spec bug is addressed. // https://www.w3.org/Bugs/Public/show_bug.cgi?id=21407 this.addResolvePathApi(); - ensurePrototypeTraversal(this.prototype); // declarative features this.desugar(); // under ShadowDOMPolyfill, transforms to approximate missing CSS features @@ -219,57 +119,6 @@ // cache the list of custom prototype names for faster reflection this.cacheProperties(); }, - // prototype marshaling - // build prototype combining extendee, Polymer base, and named api - generateCustomPrototype: function (name, extnds) { - // basal prototype - var prototype = this.generateBasePrototype(extnds); - // mixin registered custom api - return this.addNamedApi(prototype, name); - }, - // build prototype combining extendee, Polymer base, and named api - generateBasePrototype: function(extnds) { - // create a prototype based on tag-name extension - var prototype = generatePrototype(extnds); - // insert base api in inheritance chain (if needed) - return this.ensureBaseApi(prototype); - }, - // install Polymer instance api into prototype chain, as needed - ensureBaseApi: function(prototype) { - if (!prototype.PolymerBase) { - Object.keys(scope.api.instance).forEach(function(n) { - extend(prototype, scope.api.instance[n]); - }); - prototype = Object.create(prototype); - } - // inherit publishing meta-data - this.inheritAttributesObjects(prototype); - // inherit event delegates - this.inheritDelegates(prototype); - // return buffed-up prototype - return prototype; - }, - // mix api registered to 'name' into 'prototype' - addNamedApi: function(prototype, name) { - // combine custom api into prototype - return extend(prototype, getRegisteredPrototype(name)); - }, - // make a fresh object that inherits from a prototype object - inheritObject: function(prototype, name) { - // copy inherited properties onto a new object - prototype[name] = extend({}, Object.getPrototypeOf(prototype)[name]); - }, - // register 'prototype' to custom element 'name', store constructor - registerPrototype: function(name) { - // register the custom type - this.ctor = document.register(name, { - prototype: this.prototype - }); - // constructor shenanigans - this.prototype.constructor = this.ctor; - // register the prototype with HTMLElement for name lookup - HTMLElement.register(name, this.prototype); - }, // if a named constructor is requested in element, map a reference // to the constructor to the given symbol publishConstructor: function() { @@ -280,6 +129,8 @@ } }); + // semi-pluggable APIs + // TODO(sjmiles): should be fully pluggable Object.keys(apis).forEach(function(n) { extend(prototype, apis[n]); }); @@ -288,7 +139,61 @@ document.register('polymer-element', {prototype: prototype}); - // namespace shenanigans so we can expose our scope on the registration function + // utility and bookkeeping + + // maps tag names to prototypes + var prototypesByName = {}; + + function getRegisteredPrototype(name) { + return prototypesByName[name]; + } + + // elements waiting for prototype, by name + var waitPrototype = {}; + + function notifyPrototype(name) { + if (waitPrototype[name]) { + waitPrototype[name].registerWhenReady(); + delete waitPrototype[name]; + } + } + + // elements waiting for super, by name + var waitSuper = {}; + + function notifySuper(name) { + registered[name] = true; + var waiting = waitSuper[name]; + if (waiting) { + waiting.forEach(function(w) { + w.registerWhenReady(); + }); + delete waitSuper[name]; + } + } + + // track document.register'ed tag names + + var registered = {}; + + function isRegistered(name) { + return registered[name]; + } + + function whenImportsLoaded(doThis) { + if (window.HTMLImports && !HTMLImports.readyTime) { + addEventListener('HTMLImportsLoaded', doThis); + } else { + doThis(); + } + } + + // exports + + scope.getRegisteredPrototype = getRegisteredPrototype; + + // namespace shenanigans so we can expose our scope on the registration + // function // TODO(sjmiles): find a way to do this that is less terrible // copy window.Polymer properties onto `element()` diff --git a/src/declaration/prototype.js b/src/declaration/prototype.js new file mode 100644 index 0000000000..0b7ac527b2 --- /dev/null +++ b/src/declaration/prototype.js @@ -0,0 +1,100 @@ +/* + * Copyright 2013 The Polymer Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ +(function(scope) { + + // imports + + var api = scope.api; + var isBase = scope.isBase; + var extend = scope.extend; + + // returns a prototype that chains to or HTMLElement + function generatePrototype(tag) { + return Object.create(HTMLElement.getPrototypeForTag(tag)); + } + + // On platforms that do not support __proto__ (IE10), the prototype chain + // of a custom element is simulated via installation of __proto__. + // Although custom elements manages this, we install it here so it's + // available during desugaring. + function ensurePrototypeTraversal(prototype) { + if (!Object.__proto__) { + var ancestor = Object.getPrototypeOf(prototype); + prototype.__proto__ = ancestor; + if (isBase(ancestor)) { + ancestor.__proto__ = Object.getPrototypeOf(ancestor); + } + } + } + + // declarative implementation: + + var prototype = generatePrototype(); + + // prototype api + + var prototype = { + // prototype marshaling + // build prototype combining extendee, Polymer base, and named api + generateCustomPrototype: function (name, extnds) { + // basal prototype + var prototype = this.generateBasePrototype(extnds); + // mixin registered custom api + this.addNamedApi(prototype, name); + // x-platform fixups + ensurePrototypeTraversal(prototype); + return prototype; + }, + // build prototype combining extendee, Polymer base, and named api + generateBasePrototype: function(extnds) { + // create a prototype based on tag-name extension + var prototype = generatePrototype(extnds); + // insert base api in inheritance chain (if needed) + return this.ensureBaseApi(prototype); + }, + // install Polymer instance api into prototype chain, as needed + ensureBaseApi: function(prototype) { + if (!prototype.PolymerBase) { + Object.keys(api.instance).forEach(function(n) { + extend(prototype, api.instance[n]); + }); + prototype = Object.create(prototype); + } + // inherit publishing meta-data + this.inheritAttributesObjects(prototype); + // inherit event delegates + this.inheritDelegates(prototype); + // return buffed-up prototype + return prototype; + }, + // mix api registered to 'name' into 'prototype' + addNamedApi: function(prototype, name) { + // combine custom api into prototype + return extend(prototype, scope.getRegisteredPrototype(name)); + }, + // make a fresh object that inherits from a prototype object + inheritObject: function(prototype, name) { + // copy inherited properties onto a new object + prototype[name] = extend({}, Object.getPrototypeOf(prototype)[name]); + }, + // register 'prototype' to custom element 'name', store constructor + registerPrototype: function(name) { + // register the custom type + this.ctor = document.register(name, { + prototype: this.prototype + }); + // constructor shenanigans + this.prototype.constructor = this.ctor; + // register the prototype with HTMLElement for name lookup + HTMLElement.register(name, this.prototype); + } + }; + + // exports + + api.declaration.prototype = prototype; + +})(Polymer); diff --git a/src/instance/base.js b/src/instance/base.js index 09289253f7..5ee15a26cf 100644 --- a/src/instance/base.js +++ b/src/instance/base.js @@ -10,7 +10,11 @@ job: Polymer.job, super: Polymer.super, // user entry point for constructor-like initialization + created: function() { + }, + // TODO(sorvell): temporary BC ready: function() { + }, // TODO(sjmiles): temporary BC readyCallback: function() { @@ -40,7 +44,9 @@ // when polyfilling Object.observe //this.asyncUnbindAll(); // user initialization + // TODO(sorvell): bc this.ready(); + this.created(); }, insertedCallback: function() { this._enteredDocumentCallback(); diff --git a/src/instance/mdv.js b/src/instance/mdv.js index 59f42e0d58..01b76552dd 100644 --- a/src/instance/mdv.js +++ b/src/instance/mdv.js @@ -11,7 +11,7 @@ // use an MDV syntax - var mdv_syntax = new ExpressionSyntax(); + var mdv_syntax = new PolymerExpressions(); // element api supporting mdv diff --git a/test/html/bind-object-repeat.html b/test/html/bind-object-repeat.html index f083a1437f..a908a2d2ba 100644 --- a/test/html/bind-object-repeat.html +++ b/test/html/bind-object-repeat.html @@ -46,26 +46,22 @@ + + + + + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + + + + + + + + \ No newline at end of file diff --git a/test/html/element-import/elements.js b/test/html/element-import/elements.js new file mode 100644 index 0000000000..180694f037 --- /dev/null +++ b/test/html/element-import/elements.js @@ -0,0 +1,13 @@ +Polymer('x-foo', { + ready: function() { + this.style.color = 'blue'; + } +}); + +Polymer('x-bar', { + ready: function() { + this.style.padding = '4px'; + this.style.backgroundColor = 'orange'; + this.super(); + } +}); diff --git a/test/html/element-import/import.html b/test/html/element-import/import.html new file mode 100644 index 0000000000..3c9becc09b --- /dev/null +++ b/test/html/element-import/import.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/html/element-import/x-quux.js b/test/html/element-import/x-quux.js new file mode 100644 index 0000000000..cad4883e7a --- /dev/null +++ b/test/html/element-import/x-quux.js @@ -0,0 +1,6 @@ +Polymer('x-quux', { + ready: function() { + this.style.fontSize = '24px'; + this.super(); + } +}); diff --git a/test/html/element-registration.html b/test/html/element-registration.html index 523d35708e..98640209e1 100644 --- a/test/html/element-registration.html +++ b/test/html/element-registration.html @@ -40,13 +40,10 @@ - + - diff --git a/test/html/prop-attr-reflection.html b/test/html/prop-attr-reflection.html index b404c6ad84..f773e671e1 100644 --- a/test/html/prop-attr-reflection.html +++ b/test/html/prop-attr-reflection.html @@ -36,38 +36,44 @@ var xfoo = document.querySelector('x-foo'); xfoo.foo = 5; Platform.flush(); - assert.equal(String(xfoo.foo), xfoo.getAttribute('foo'), 'attribute reflects property as string'); - xfoo.setAttribute('foo', '27'); - assert.equal(xfoo.foo, xfoo.getAttribute('foo'), 'property reflects attribute'); - // - xfoo.baz = 'Hello'; - Platform.flush(); - assert.equal(xfoo.baz, xfoo.getAttribute('baz'), 'attribute reflects property'); - // - var xbar = document.querySelector('x-bar'); - // - xbar.foo = 'foo!'; - xbar.zot = 27; - xbar.zim = true; - xbar.str = 'str!'; - xbar.obj = {hello: 'world'}; - Platform.flush(); - assert.equal(xbar.foo, xbar.getAttribute('foo'), 'inherited published property is reflected'); - assert.equal(String(xbar.zot), xbar.getAttribute('zot'), 'attribute reflects property as number'); - assert.equal(String(xbar.zim), xbar.getAttribute('zim'), 'attribute reflects property as boolean'); - assert.equal(xbar.str, xbar.getAttribute('str'), 'attribute reflects property as published string'); - assert.isFalse(xbar.hasAttribute('obj'), 'attribute does not reflect object property'); - xbar.setAttribute('foo', 'foo!!'); - xbar.setAttribute('zot', 54); - xbar.setAttribute('zim', 'false'); - xbar.setAttribute('str', 'str!!'); - xbar.setAttribute('obj', "{'hello': 'world'}"); - assert.equal(xbar.foo, xbar.getAttribute('foo'), 'property reflects attribute as string'); - assert.equal(xbar.zot, 54, 'property reflects attribute as number'); - assert.equal(xbar.zim, false, 'property reflects attribute as boolean'); - assert.equal(xbar.str, 'str!!', 'property reflects attribute as published string'); - assert.deepEqual(xbar.obj, {hello: 'world'}, 'property reflects attribute as object'); - done(); + Platform.endOfMicrotask(function() { + assert.equal(String(xfoo.foo), xfoo.getAttribute('foo'), 'attribute reflects property as string'); + xfoo.setAttribute('foo', '27'); + assert.equal(xfoo.foo, xfoo.getAttribute('foo'), 'property reflects attribute'); + // + xfoo.baz = 'Hello'; + Platform.flush(); + Platform.endOfMicrotask(function() { + assert.equal(xfoo.baz, xfoo.getAttribute('baz'), 'attribute reflects property'); + // + var xbar = document.querySelector('x-bar'); + // + xbar.foo = 'foo!'; + xbar.zot = 27; + xbar.zim = true; + xbar.str = 'str!'; + xbar.obj = {hello: 'world'}; + Platform.flush(); + setTimeout(function() { + assert.equal(xbar.foo, xbar.getAttribute('foo'), 'inherited published property is reflected'); + assert.equal(String(xbar.zot), xbar.getAttribute('zot'), 'attribute reflects property as number'); + assert.equal(String(xbar.zim), xbar.getAttribute('zim'), 'attribute reflects property as boolean'); + assert.equal(xbar.str, xbar.getAttribute('str'), 'attribute reflects property as published string'); + assert.isFalse(xbar.hasAttribute('obj'), 'attribute does not reflect object property'); + xbar.setAttribute('foo', 'foo!!'); + xbar.setAttribute('zot', 54); + xbar.setAttribute('zim', 'false'); + xbar.setAttribute('str', 'str!!'); + xbar.setAttribute('obj', "{'hello': 'world'}"); + assert.equal(xbar.foo, xbar.getAttribute('foo'), 'property reflects attribute as string'); + assert.equal(xbar.zot, 54, 'property reflects attribute as number'); + assert.equal(xbar.zim, false, 'property reflects attribute as boolean'); + assert.equal(xbar.str, 'str!!', 'property reflects attribute as published string'); + assert.deepEqual(xbar.obj, {hello: 'world'}, 'property reflects attribute as object'); + done(); + }); + }); + }); }); diff --git a/test/html/template-distribute-dynamic.html b/test/html/template-distribute-dynamic.html index e50a27b081..3f1f2844b5 100644 --- a/test/html/template-distribute-dynamic.html +++ b/test/html/template-distribute-dynamic.html @@ -30,17 +30,16 @@ diff --git a/test/html/unbind.html b/test/html/unbind.html index 265857ccfd..3870e9e734 100644 --- a/test/html/unbind.html +++ b/test/html/unbind.html @@ -33,7 +33,6 @@ diff --git a/test/js/bindMDV.js b/test/js/bindMDV.js index d3b0fd7164..2902cfd930 100644 --- a/test/js/bindMDV.js +++ b/test/js/bindMDV.js @@ -23,18 +23,17 @@ suite('bindMDV', function() { test.bar = 5; Platform.flush(); - setTimeout(function() { + Platform.endOfMicrotask(function() { assert.equal(a.getAttribute('foo'), 5); - // test.bar = 8; Platform.flush(); - setTimeout(function() { + Platform.endOfMicrotask(function() { assert.equal(a.getAttribute('foo'), 8); done(); }); }); }); - + test('bindModel bind input', function(done) { var test = document.createElement('div'); var fragment = parseAndBindHTML('', test); @@ -43,14 +42,16 @@ suite('bindMDV', function() { test.bar = 'hello'; Platform.flush(); - setTimeout(function() { + Platform.endOfMicrotask(function() { assert.equal(a.value, 'hello'); done(); }); }); + }); + htmlSuite('unbind', function() { htmlTest('html/template-distribute-dynamic.html'); htmlTest('html/unbind.html'); -}); \ No newline at end of file +}); diff --git a/test/js/register.js b/test/js/register.js index 00ea17f2a4..6bc8c217e9 100644 --- a/test/js/register.js +++ b/test/js/register.js @@ -46,10 +46,8 @@ htmlSuite('element callbacks', function() { htmlTest('html/callbacks.html'); }); -htmlSuite('element script', function() { - htmlTest('html/element-script.html'); -}); - htmlSuite('element registration', function() { + htmlTest('html/element-script.html'); htmlTest('html/element-registration.html'); + htmlTest('html/element-import.html'); }); diff --git a/tools b/tools index b853b33b56..03ce077cc6 160000 --- a/tools +++ b/tools @@ -1 +1 @@ -Subproject commit b853b33b567fd2c67f51be3eae7022e4eded07f4 +Subproject commit 03ce077cc6676eda5d87baa185a1519c273430c5 diff --git a/workbench/events/index.html b/workbench/events/index.html index bf50c4fde5..e1cfe5dd0b 100644 --- a/workbench/events/index.html +++ b/workbench/events/index.html @@ -21,7 +21,7 @@

Custom element using declarative events

without event path, only host event fires.) - + - - + +
- +