Skip to content

Commit

Permalink
enable 'publishing' system for attributes, add tests, fresh builds
Browse files Browse the repository at this point in the history
  • Loading branch information
Scott J. Miles committed Apr 15, 2013
1 parent 6e66b2b commit ececd01
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 176 deletions.
209 changes: 118 additions & 91 deletions src/attrs.js
Original file line number Diff line number Diff line change
@@ -1,92 +1,119 @@
/*
* Copyright 2013 The Toolkitchen 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() {

// imports

var bindPattern = Toolkit.bindPattern;

var publishAttributes = function(inAttributes, inDefinition) {
if (inAttributes) {
/*
var pd = conventions.PUBLISH_DIRECTIVE;
// need a publish block to extend
var pub = inDefinition[pd] = inDefinition[pd] || {};
// use the value of the attributes-attribute
var a$ = inAttributes.value;
// attributes='a b c' or attributes='a,b,c'
var names = a$.split(a$.indexOf(',') >= 0 ? ',' : ' ');
// record each name for publishing
names.forEach(function(p) {
pub[p.trim()] = null;
});
*/
}
};

function takeAttributes() {
// for each attribute
forEach(this.attributes, function(a) {
// try to match this attribute to a property (attributess are
// all lower-case, so this is case-insensitive search)
var name = propertyForAttribute.call(this, a.name);
if (name) {
// filter out 'mustached' values, these are to be
// replaced with bound-data and are not yet values
// themselves
if (a.value.search(bindPattern) >= 0) {
return;
}
// get original value
var defaultValue = this[name];
// deserialize Boolean or Number values from attribute
var value = deserializeValue(a.value, defaultValue);
//console.log('takeAttributes: ', a.name, a.value);
// only act if the value has changed
if (value !== defaultValue) {
// install new value (has side-effects)
this[name] = value;
}
}
}, this);
};

// find the public property identified by inAttributeName
function propertyForAttribute(inAttributeName) {
// specifically search the __proto__ (as opposed to getPrototypeOf)
// __proto__ is simulated on platforms which don't support it naturally
// TODO(sjmiles): I'm reluctant to search the entire namespace
// but this set is too small. Perhaps in 'full public' mode, one
// must declare 'attributable properties'.
var properties = Object.keys(this.__proto__);
//var properties = Object.keys(Object.getPrototypeOf(this));
for (var i=0, n; (n=properties[i]); i++) {
if (n.toLowerCase() == inAttributeName) {
return n;
}
}
};

function deserializeValue(inValue, inDefaultValue) {
var inferredType = typeof inDefaultValue;
switch (inValue) {
case '':
case 'true':
return inferredType == 'boolean' ? true : inValue;
case 'false':
return inferredType == 'boolean' ? false : inValue;
}
return isNaN(inValue) ? inValue : parseFloat(inValue);
}

// exports

Toolkit.takeAttributes = takeAttributes;
Toolkit.propertyForAttribute = propertyForAttribute;
Toolkit.publishAttributes = publishAttributes;

/*
* Copyright 2013 The Toolkitchen 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() {

// imports

var bindPattern = Toolkit.bindPattern;

// constants

var published$ = '__published';
var attributes$ = 'attributes';
var attrProps$ = 'publish';
//var attrProps$ = 'attributeDefaults';

var publishAttributes = function(inElement, inPrototype) {
var published = {};
// merge attribute names from 'attributes' attribute
var attributes = inElement.getAttribute(attributes$);
if (attributes) {
// attributes='a b c' or attributes='a,b,c'
var names = attributes.split(attributes.indexOf(',') >= 0 ? ',' : ' ');
// record each name for publishing
names.forEach(function(p) {
published[p.trim()] = null;
});
}
// combine with 'publish' object from prototype'
published = mixin(published, inPrototype[attrProps$]);
// install actual properties on the prototype
Object.keys(published).forEach(function(p) {
inPrototype[p] = published[p];
});
// combine with inherited published properties
inPrototype[published$] = mixin(
{},
inElement.options.prototype[published$],
published);
};

function takeAttributes() {
// for each attribute
forEach(this.attributes, function(a) {
// try to match this attribute to a property (attributess are
// all lower-case, so this is case-insensitive search)
var name = propertyForAttribute.call(this, a.name);
if (name) {
// filter out 'mustached' values, these are to be
// replaced with bound-data and are not yet values
// themselves
if (a.value.search(bindPattern) >= 0) {
return;
}
// get original value
var defaultValue = this[name];
// deserialize Boolean or Number values from attribute
var value = deserializeValue(a.value, defaultValue);
//console.log('takeAttributes: ', a.name, a.value);
// only act if the value has changed
if (value !== defaultValue) {
// install new value (has side-effects)
this[name] = value;
}
}
}, this);
};

var lowerCase = String.prototype.toLowerCase.call.bind(
String.prototype.toLowerCase);

// return the published property matching name, or undefined
function propertyForAttribute(name) {
// matchable properties must be published
var properties = Object.keys(this[published$]);
// search for a matchable property
return properties[properties.map(lowerCase).indexOf(name)];
};

/*
// find the public property identified by inAttributeName
function propertyForAttribute(inAttributeName) {
// specifically search the __proto__ (as opposed to getPrototypeOf)
// __proto__ is simulated on platforms which don't support it naturally
// TODO(sjmiles): I'm reluctant to search the entire namespace
// but this set is too small. Perhaps in 'full public' mode, one
// must declare 'attributable properties'.
var properties = Object.keys(this.__proto__);
//var properties = Object.keys(Object.getPrototypeOf(this));
for (var i=0, n; (n=properties[i]); i++) {
if (n.toLowerCase() == inAttributeName) {
return n;
}
}
};
*/

function deserializeValue(inValue, inDefaultValue) {
var inferredType = typeof inDefaultValue;
switch (inValue) {
case '':
case 'true':
return inferredType == 'boolean' ? true : inValue;
case 'false':
return inferredType == 'boolean' ? false : inValue;
}
return isNaN(inValue) ? inValue : parseFloat(inValue);
}

// exports

Toolkit.takeAttributes = takeAttributes;
Toolkit.publishAttributes = publishAttributes;
Toolkit.propertyForAttribute = propertyForAttribute;

})();
19 changes: 13 additions & 6 deletions src/register.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
if (inElement == window) {
return;
}
// catch common mistake of omitting 'this' in call to register
if (!inElement || !(inElement instanceof HTMLElement)) {
throw "First argument to Toolkit.register must be an HTMLElement";
}
// TODO(sjmiles): it's not obvious at this point whether inElement
// will chain to another toolkit element, so we just copy base boilerplate
// anyway
Expand All @@ -35,17 +39,18 @@
this.super();
installTemplate.call(this, inElement);
};
// parse attribute-attributes
Toolkit.publishAttributes(inElement.attributes.attribute);
// parse declared on-* delegates into imperative form
Toolkit.parseHostEvents(inElement.attributes, prototype);
// parse attribute-attributes
Toolkit.publishAttributes(inElement, prototype);
// install external stylesheets as if they are inline
Toolkit.installSheets(inElement);
Toolkit.shimStyling(inElement);
// invoke element.register
inElement.register({prototype: prototype});
// logging
logFlags.comps && console.log("initialized component " + inElement.options.name);
logFlags.comps &&
console.log("Toolkit: element registered" + inElement.options.name);
};

function installTemplate(inElement) {
Expand Down Expand Up @@ -94,15 +99,17 @@
};

var base = {
isToolkitElement: true,
super: $super,
// object on prototype used mindfully
attributes: {},

This comment has been minimized.

Copy link
@sorvell

sorvell Apr 16, 2013

Contributor

This overwrites the instance attributes when used with ShadowDOM polyfill. Can we remove?

This comment has been minimized.

Copy link
@sjmiles

sjmiles Apr 16, 2013

Contributor

Yes, that's vestigal.

isToolkitElement: true,
readyCallback: function() {
// invoke closed 'installTemplate'
this.installTemplate();
// invoke boilerplate 'instanceReady'
instanceReady.call(this);
},
// MDV binding
// MDV binding
bind: function() {
Toolkit.bind.apply(this, arguments);
},
Expand Down Expand Up @@ -132,7 +139,7 @@
};

// user utility

function findDistributedTarget(inTarget, inNodes) {
// find first ancestor of target (including itself) that
// is in inNodes, if any
Expand Down
3 changes: 1 addition & 2 deletions test/html/bind-object-repeat.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@
</head>
<body>
<x-bind-obj></x-bind-obj>
<element name="x-foo">
<element name="x-foo" attributes="obj">
<template>
<p>obj.foo is {{obj.foo}}</p>
</template>
<script>
Toolkit.register(this, {
obj: null,
objChanged: function() {
console.log('x-foo', this.obj);
}
Expand Down
74 changes: 74 additions & 0 deletions test/html/publish-attributes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<!doctype html>
<html>
<head>
<title>publish attributes</title>
<script src="../../platform/platform.js"></script>
<script src="../../toolkit.js"></script>
<script src="../../tools/test/htmltest.js"></script>
<script src="../../node_modules/chai/chai.js"></script>
</head>
<body>

<element name="x-foo" attributes="Foo baz" constructor="XFoo">
<script>
Toolkit.register(this);
</script>
</element>

<element name="x-bar" extends="x-foo" attributes="Bar" constructor="XBar">
<script>
Toolkit.register(this);
</script>
</element>

<element name="x-zot" extends="x-bar" constructor="XZot">
<script>
Toolkit.register(this, {
publish: {
zot: 3
}
});
</script>
</element>

<element name="x-squid" extends="x-zot" attributes="squid" constructor="XSquid">
<script>
Toolkit.register(this, {
publish: {
baz: 13,
zot: 5,
squid: 7
}
});
</script>
</element>

<script>
var assert = chai.assert;
document.addEventListener('WebComponentsReady', function() {
//
assert.deepEqual(
XFoo.prototype.__published,
{Foo: null, baz: null});
assert.deepEqual(
XBar.prototype.__published,
{Foo: null, baz: null, Bar: null});
assert.deepEqual(
XZot.prototype.__published,
{Foo: null, baz: null, Bar: null, zot: 3});
assert.deepEqual(
XSquid.prototype.__published,
{Foo: null, baz: 13, Bar: null, zot: 5, squid: 7});
//
assert.equal(
Toolkit.propertyForAttribute.call(XFoo.prototype, 'foo'),
'Foo');
XFoo.prototype.baz = true;
assert.isUndefined(
Toolkit.propertyForAttribute.call(XFoo.prototype, 'splat'));
//
done();
});
</script>
</body>
</html>
Loading

0 comments on commit ececd01

Please sign in to comment.