-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- boolean properties are reflected as boolean attributes: fixes #240
- creating a property binding causes property-> attar reflection: fixes #239
- Loading branch information
Showing
3 changed files
with
195 additions
and
167 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,87 +1,94 @@ | ||
/* | ||
* 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) { | ||
|
||
// magic words | ||
|
||
var PUBLISHED = '__published'; | ||
var INSTANCE_ATTRIBUTES = '__instance_attributes'; | ||
|
||
// instance api for attributes | ||
|
||
var attributes = { | ||
PUBLISHED: PUBLISHED, | ||
INSTANCE_ATTRIBUTES: INSTANCE_ATTRIBUTES, | ||
copyInstanceAttributes: function () { | ||
var a$ = this[INSTANCE_ATTRIBUTES]; | ||
for (var k in a$) { | ||
this.setAttribute(k, a$[k]); | ||
} | ||
}, | ||
// for each attribute on this, deserialize value to property as needed | ||
takeAttributes: function() { | ||
for (var i=0, a$=this.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) { | ||
this.attributeToProperty(a.name, a.value); | ||
} | ||
}, | ||
// if attribute 'name' is mapped to a property, deserialize | ||
// 'value' into that property | ||
attributeToProperty: function(name, value) { | ||
// try to match this attribute to a property (attributes are | ||
// all lower-case, so this is case-insensitive search) | ||
var name = this.propertyForAttribute(name); | ||
if (name) { | ||
// filter out 'mustached' values, these are to be | ||
// replaced with bound-data and are not yet values | ||
// themselves | ||
if (value.search(scope.bindPattern) >= 0) { | ||
return; | ||
} | ||
// get original value | ||
var defaultValue = this[name]; | ||
// deserialize Boolean or Number values from attribute | ||
var value = this.deserializeValue(value, defaultValue); | ||
// only act if the value has changed | ||
if (value !== defaultValue) { | ||
// install new value (has side-effects) | ||
this[name] = value; | ||
} | ||
} | ||
}, | ||
// return the published property matching name, or undefined | ||
propertyForAttribute: function(name) { | ||
// matchable properties must be published | ||
var properties = Object.keys(this[PUBLISHED]); | ||
// search for a matchable property | ||
return properties[properties.map(lowerCase).indexOf(name.toLowerCase())]; | ||
}, | ||
// convert representation of 'stringValue' based on type of 'defaultValue' | ||
deserializeValue: function(stringValue, defaultValue) { | ||
return scope.deserializeValue(stringValue, defaultValue); | ||
}, | ||
serializeValue: function(value) { | ||
if (typeof value != 'object' && value !== undefined) { | ||
return value; | ||
} | ||
}, | ||
propertyToAttribute: function(name) { | ||
if (Object.keys(this[PUBLISHED]).indexOf(name) >= 0) { | ||
var serializedValue = this.serializeValue(this[name]); | ||
if (serializedValue !== undefined) { | ||
this.setAttribute(name, serializedValue); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
var lowerCase = String.prototype.toLowerCase.call.bind( | ||
String.prototype.toLowerCase); | ||
|
||
// exports | ||
|
||
scope.api.instance.attributes = attributes; | ||
|
||
})(Polymer); | ||
/* | ||
* 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) { | ||
|
||
// magic words | ||
|
||
var PUBLISHED = '__published'; | ||
var INSTANCE_ATTRIBUTES = '__instance_attributes'; | ||
|
||
// instance api for attributes | ||
|
||
var attributes = { | ||
PUBLISHED: PUBLISHED, | ||
INSTANCE_ATTRIBUTES: INSTANCE_ATTRIBUTES, | ||
copyInstanceAttributes: function () { | ||
var a$ = this[INSTANCE_ATTRIBUTES]; | ||
for (var k in a$) { | ||
this.setAttribute(k, a$[k]); | ||
} | ||
}, | ||
// for each attribute on this, deserialize value to property as needed | ||
takeAttributes: function() { | ||
for (var i=0, a$=this.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) { | ||
this.attributeToProperty(a.name, a.value); | ||
} | ||
}, | ||
// if attribute 'name' is mapped to a property, deserialize | ||
// 'value' into that property | ||
attributeToProperty: function(name, value) { | ||
// try to match this attribute to a property (attributes are | ||
// all lower-case, so this is case-insensitive search) | ||
var name = this.propertyForAttribute(name); | ||
if (name) { | ||
// filter out 'mustached' values, these are to be | ||
// replaced with bound-data and are not yet values | ||
// themselves | ||
if (value && value.search(scope.bindPattern) >= 0) { | ||
return; | ||
} | ||
// get original value | ||
var defaultValue = this[name]; | ||
// deserialize Boolean or Number values from attribute | ||
var value = this.deserializeValue(value, defaultValue); | ||
// only act if the value has changed | ||
if (value !== defaultValue) { | ||
// install new value (has side-effects) | ||
this[name] = value; | ||
} | ||
} | ||
}, | ||
// return the published property matching name, or undefined | ||
propertyForAttribute: function(name) { | ||
// matchable properties must be published | ||
var properties = Object.keys(this[PUBLISHED]); | ||
// search for a matchable property | ||
return properties[properties.map(lowerCase).indexOf(name.toLowerCase())]; | ||
}, | ||
// convert representation of 'stringValue' based on type of 'defaultValue' | ||
deserializeValue: function(stringValue, defaultValue) { | ||
return scope.deserializeValue(stringValue, defaultValue); | ||
}, | ||
serializeValue: function(value) { | ||
if (typeof value != 'object' && value !== undefined) { | ||
return value; | ||
} | ||
}, | ||
propertyToAttribute: function(name) { | ||
if (Object.keys(this[PUBLISHED]).indexOf(name) >= 0) { | ||
var serializedValue = this.serializeValue(this[name]); | ||
// boolean properties must reflect as boolean attributes | ||
if (typeof this.__proto__[name] === 'boolean') { | ||
if (serializedValue) { | ||
this.setAttribute(name, ''); | ||
} else { | ||
this.removeAttribute(name); | ||
} | ||
} else if (serializedValue !== undefined) { | ||
this.setAttribute(name, serializedValue); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
var lowerCase = String.prototype.toLowerCase.call.bind( | ||
String.prototype.toLowerCase); | ||
|
||
// exports | ||
|
||
scope.api.instance.attributes = attributes; | ||
|
||
})(Polymer); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,80 +1,97 @@ | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<title>publish attributes</title> | ||
<script src="../../polymer.js"></script> | ||
<script src="../../tools/test/htmltest.js"></script> | ||
<script src="../../node_modules/chai/chai.js"></script> | ||
</head> | ||
<body> | ||
|
||
<x-foo></x-foo> | ||
<polymer-element name="x-foo" attributes="foo baz"> | ||
<script> | ||
Polymer('x-foo'); | ||
</script> | ||
</polymer-element> | ||
|
||
<x-bar></x-bar> | ||
<polymer-element name="x-bar" extends="x-foo"> | ||
<script> | ||
Polymer('x-bar', { | ||
publish: { | ||
zot: 3, | ||
zim: false, | ||
str: 'str', | ||
obj: null | ||
} | ||
}); | ||
</script> | ||
</polymer-element> | ||
|
||
<script> | ||
var assert = chai.assert; | ||
document.addEventListener('WebComponentsReady', function() { | ||
// | ||
var xfoo = document.querySelector('x-foo'); | ||
xfoo.foo = 5; | ||
Platform.flush(); | ||
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(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
</script> | ||
</body> | ||
</html> | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<title>publish attributes</title> | ||
<script src="../../polymer.js"></script> | ||
<script src="../../tools/test/htmltest.js"></script> | ||
<script src="../../node_modules/chai/chai.js"></script> | ||
</head> | ||
<body> | ||
|
||
<x-foo></x-foo> | ||
<polymer-element name="x-foo" attributes="foo baz"> | ||
<script> | ||
Polymer('x-foo'); | ||
</script> | ||
</polymer-element> | ||
|
||
<x-bar></x-bar> | ||
<polymer-element name="x-bar" extends="x-foo" attributes="zot zim str obj"> | ||
<script> | ||
Polymer('x-bar', { | ||
zot: 3, | ||
zim: false, | ||
str: 'str', | ||
obj: null | ||
}); | ||
</script> | ||
</polymer-element> | ||
|
||
<x-compose></x-compose> | ||
<polymer-element name="x-compose"> | ||
<template> | ||
<x-bar id="bar" zim="{{zim}}"></x-bar> | ||
</template> | ||
<script> | ||
Polymer('x-compose', { | ||
zim: false | ||
}); | ||
</script> | ||
</polymer-element> | ||
|
||
<script> | ||
var assert = chai.assert; | ||
document.addEventListener('WebComponentsReady', function() { | ||
var xcompose = document.querySelector('x-compose'); | ||
var xfoo = document.querySelector('x-foo'); | ||
xfoo.foo = 5; | ||
Platform.flush(); | ||
Platform.endOfMicrotask(function() { | ||
assert.isFalse(xcompose.$.bar.hasAttribute('zim'), 'attribute bound to property updates when binding is made'); | ||
|
||
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(); | ||
Platform.endOfMicrotask(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('', xbar.getAttribute('zim'), 'attribute reflects true valued boolean property as having attribute'); | ||
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'); | ||
xbar.zim = false; | ||
Platform.flush(); | ||
Platform.endOfMicrotask(function() { | ||
assert.isFalse(xbar.hasAttribute('zim'), 'attribute reflects false valued boolean property as NOT having attribute'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
</script> | ||
</body> | ||
</html> |