Skip to content

Commit 3db2c64

Browse files
committed
- boolean properties are reflected as boolean attributes: fixes #240
- creating a property binding causes property-> attar reflection: fixes #239
1 parent fb1d1b3 commit 3db2c64

3 files changed

Lines changed: 195 additions & 167 deletions

File tree

src/instance/attributes.js

Lines changed: 94 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,94 @@
1-
/*
2-
* Copyright 2013 The Polymer Authors. All rights reserved.
3-
* Use of this source code is governed by a BSD-style
4-
* license that can be found in the LICENSE file.
5-
*/
6-
(function(scope) {
7-
8-
// magic words
9-
10-
var PUBLISHED = '__published';
11-
var INSTANCE_ATTRIBUTES = '__instance_attributes';
12-
13-
// instance api for attributes
14-
15-
var attributes = {
16-
PUBLISHED: PUBLISHED,
17-
INSTANCE_ATTRIBUTES: INSTANCE_ATTRIBUTES,
18-
copyInstanceAttributes: function () {
19-
var a$ = this[INSTANCE_ATTRIBUTES];
20-
for (var k in a$) {
21-
this.setAttribute(k, a$[k]);
22-
}
23-
},
24-
// for each attribute on this, deserialize value to property as needed
25-
takeAttributes: function() {
26-
for (var i=0, a$=this.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) {
27-
this.attributeToProperty(a.name, a.value);
28-
}
29-
},
30-
// if attribute 'name' is mapped to a property, deserialize
31-
// 'value' into that property
32-
attributeToProperty: function(name, value) {
33-
// try to match this attribute to a property (attributes are
34-
// all lower-case, so this is case-insensitive search)
35-
var name = this.propertyForAttribute(name);
36-
if (name) {
37-
// filter out 'mustached' values, these are to be
38-
// replaced with bound-data and are not yet values
39-
// themselves
40-
if (value.search(scope.bindPattern) >= 0) {
41-
return;
42-
}
43-
// get original value
44-
var defaultValue = this[name];
45-
// deserialize Boolean or Number values from attribute
46-
var value = this.deserializeValue(value, defaultValue);
47-
// only act if the value has changed
48-
if (value !== defaultValue) {
49-
// install new value (has side-effects)
50-
this[name] = value;
51-
}
52-
}
53-
},
54-
// return the published property matching name, or undefined
55-
propertyForAttribute: function(name) {
56-
// matchable properties must be published
57-
var properties = Object.keys(this[PUBLISHED]);
58-
// search for a matchable property
59-
return properties[properties.map(lowerCase).indexOf(name.toLowerCase())];
60-
},
61-
// convert representation of 'stringValue' based on type of 'defaultValue'
62-
deserializeValue: function(stringValue, defaultValue) {
63-
return scope.deserializeValue(stringValue, defaultValue);
64-
},
65-
serializeValue: function(value) {
66-
if (typeof value != 'object' && value !== undefined) {
67-
return value;
68-
}
69-
},
70-
propertyToAttribute: function(name) {
71-
if (Object.keys(this[PUBLISHED]).indexOf(name) >= 0) {
72-
var serializedValue = this.serializeValue(this[name]);
73-
if (serializedValue !== undefined) {
74-
this.setAttribute(name, serializedValue);
75-
}
76-
}
77-
}
78-
};
79-
80-
var lowerCase = String.prototype.toLowerCase.call.bind(
81-
String.prototype.toLowerCase);
82-
83-
// exports
84-
85-
scope.api.instance.attributes = attributes;
86-
87-
})(Polymer);
1+
/*
2+
* Copyright 2013 The Polymer Authors. All rights reserved.
3+
* Use of this source code is governed by a BSD-style
4+
* license that can be found in the LICENSE file.
5+
*/
6+
(function(scope) {
7+
8+
// magic words
9+
10+
var PUBLISHED = '__published';
11+
var INSTANCE_ATTRIBUTES = '__instance_attributes';
12+
13+
// instance api for attributes
14+
15+
var attributes = {
16+
PUBLISHED: PUBLISHED,
17+
INSTANCE_ATTRIBUTES: INSTANCE_ATTRIBUTES,
18+
copyInstanceAttributes: function () {
19+
var a$ = this[INSTANCE_ATTRIBUTES];
20+
for (var k in a$) {
21+
this.setAttribute(k, a$[k]);
22+
}
23+
},
24+
// for each attribute on this, deserialize value to property as needed
25+
takeAttributes: function() {
26+
for (var i=0, a$=this.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) {
27+
this.attributeToProperty(a.name, a.value);
28+
}
29+
},
30+
// if attribute 'name' is mapped to a property, deserialize
31+
// 'value' into that property
32+
attributeToProperty: function(name, value) {
33+
// try to match this attribute to a property (attributes are
34+
// all lower-case, so this is case-insensitive search)
35+
var name = this.propertyForAttribute(name);
36+
if (name) {
37+
// filter out 'mustached' values, these are to be
38+
// replaced with bound-data and are not yet values
39+
// themselves
40+
if (value && value.search(scope.bindPattern) >= 0) {
41+
return;
42+
}
43+
// get original value
44+
var defaultValue = this[name];
45+
// deserialize Boolean or Number values from attribute
46+
var value = this.deserializeValue(value, defaultValue);
47+
// only act if the value has changed
48+
if (value !== defaultValue) {
49+
// install new value (has side-effects)
50+
this[name] = value;
51+
}
52+
}
53+
},
54+
// return the published property matching name, or undefined
55+
propertyForAttribute: function(name) {
56+
// matchable properties must be published
57+
var properties = Object.keys(this[PUBLISHED]);
58+
// search for a matchable property
59+
return properties[properties.map(lowerCase).indexOf(name.toLowerCase())];
60+
},
61+
// convert representation of 'stringValue' based on type of 'defaultValue'
62+
deserializeValue: function(stringValue, defaultValue) {
63+
return scope.deserializeValue(stringValue, defaultValue);
64+
},
65+
serializeValue: function(value) {
66+
if (typeof value != 'object' && value !== undefined) {
67+
return value;
68+
}
69+
},
70+
propertyToAttribute: function(name) {
71+
if (Object.keys(this[PUBLISHED]).indexOf(name) >= 0) {
72+
var serializedValue = this.serializeValue(this[name]);
73+
// boolean properties must reflect as boolean attributes
74+
if (typeof this.__proto__[name] === 'boolean') {
75+
if (serializedValue) {
76+
this.setAttribute(name, '');
77+
} else {
78+
this.removeAttribute(name);
79+
}
80+
} else if (serializedValue !== undefined) {
81+
this.setAttribute(name, serializedValue);
82+
}
83+
}
84+
}
85+
};
86+
87+
var lowerCase = String.prototype.toLowerCase.call.bind(
88+
String.prototype.toLowerCase);
89+
90+
// exports
91+
92+
scope.api.instance.attributes = attributes;
93+
94+
})(Polymer);

src/instance/mdv.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
var observer = this.bindProperty(property, model, path);
2828
// stick path on observer so it's available via this.bindings
2929
observer.path = path;
30+
// reflect bound property to attribute when binding
31+
// to ensure binding is not left on attribute if property
32+
// does not update due to not changing.
33+
this.propertyToAttribute(name);
3034
return this.bindings[name] = observer;
3135
} else {
3236
return this.super(arguments);
Lines changed: 97 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,97 @@
1-
<!doctype html>
2-
<html>
3-
<head>
4-
<title>publish attributes</title>
5-
<script src="../../polymer.js"></script>
6-
<script src="../../tools/test/htmltest.js"></script>
7-
<script src="../../node_modules/chai/chai.js"></script>
8-
</head>
9-
<body>
10-
11-
<x-foo></x-foo>
12-
<polymer-element name="x-foo" attributes="foo baz">
13-
<script>
14-
Polymer('x-foo');
15-
</script>
16-
</polymer-element>
17-
18-
<x-bar></x-bar>
19-
<polymer-element name="x-bar" extends="x-foo">
20-
<script>
21-
Polymer('x-bar', {
22-
publish: {
23-
zot: 3,
24-
zim: false,
25-
str: 'str',
26-
obj: null
27-
}
28-
});
29-
</script>
30-
</polymer-element>
31-
32-
<script>
33-
var assert = chai.assert;
34-
document.addEventListener('WebComponentsReady', function() {
35-
//
36-
var xfoo = document.querySelector('x-foo');
37-
xfoo.foo = 5;
38-
Platform.flush();
39-
Platform.endOfMicrotask(function() {
40-
assert.equal(String(xfoo.foo), xfoo.getAttribute('foo'), 'attribute reflects property as string');
41-
xfoo.setAttribute('foo', '27');
42-
assert.equal(xfoo.foo, xfoo.getAttribute('foo'), 'property reflects attribute');
43-
//
44-
xfoo.baz = 'Hello';
45-
Platform.flush();
46-
Platform.endOfMicrotask(function() {
47-
assert.equal(xfoo.baz, xfoo.getAttribute('baz'), 'attribute reflects property');
48-
//
49-
var xbar = document.querySelector('x-bar');
50-
//
51-
xbar.foo = 'foo!';
52-
xbar.zot = 27;
53-
xbar.zim = true;
54-
xbar.str = 'str!';
55-
xbar.obj = {hello: 'world'};
56-
Platform.flush();
57-
setTimeout(function() {
58-
assert.equal(xbar.foo, xbar.getAttribute('foo'), 'inherited published property is reflected');
59-
assert.equal(String(xbar.zot), xbar.getAttribute('zot'), 'attribute reflects property as number');
60-
assert.equal(String(xbar.zim), xbar.getAttribute('zim'), 'attribute reflects property as boolean');
61-
assert.equal(xbar.str, xbar.getAttribute('str'), 'attribute reflects property as published string');
62-
assert.isFalse(xbar.hasAttribute('obj'), 'attribute does not reflect object property');
63-
xbar.setAttribute('foo', 'foo!!');
64-
xbar.setAttribute('zot', 54);
65-
xbar.setAttribute('zim', 'false');
66-
xbar.setAttribute('str', 'str!!');
67-
xbar.setAttribute('obj', "{'hello': 'world'}");
68-
assert.equal(xbar.foo, xbar.getAttribute('foo'), 'property reflects attribute as string');
69-
assert.equal(xbar.zot, 54, 'property reflects attribute as number');
70-
assert.equal(xbar.zim, false, 'property reflects attribute as boolean');
71-
assert.equal(xbar.str, 'str!!', 'property reflects attribute as published string');
72-
assert.deepEqual(xbar.obj, {hello: 'world'}, 'property reflects attribute as object');
73-
done();
74-
});
75-
});
76-
});
77-
});
78-
</script>
79-
</body>
80-
</html>
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>publish attributes</title>
5+
<script src="../../polymer.js"></script>
6+
<script src="../../tools/test/htmltest.js"></script>
7+
<script src="../../node_modules/chai/chai.js"></script>
8+
</head>
9+
<body>
10+
11+
<x-foo></x-foo>
12+
<polymer-element name="x-foo" attributes="foo baz">
13+
<script>
14+
Polymer('x-foo');
15+
</script>
16+
</polymer-element>
17+
18+
<x-bar></x-bar>
19+
<polymer-element name="x-bar" extends="x-foo" attributes="zot zim str obj">
20+
<script>
21+
Polymer('x-bar', {
22+
zot: 3,
23+
zim: false,
24+
str: 'str',
25+
obj: null
26+
});
27+
</script>
28+
</polymer-element>
29+
30+
<x-compose></x-compose>
31+
<polymer-element name="x-compose">
32+
<template>
33+
<x-bar id="bar" zim="{{zim}}"></x-bar>
34+
</template>
35+
<script>
36+
Polymer('x-compose', {
37+
zim: false
38+
});
39+
</script>
40+
</polymer-element>
41+
42+
<script>
43+
var assert = chai.assert;
44+
document.addEventListener('WebComponentsReady', function() {
45+
var xcompose = document.querySelector('x-compose');
46+
var xfoo = document.querySelector('x-foo');
47+
xfoo.foo = 5;
48+
Platform.flush();
49+
Platform.endOfMicrotask(function() {
50+
assert.isFalse(xcompose.$.bar.hasAttribute('zim'), 'attribute bound to property updates when binding is made');
51+
52+
assert.equal(String(xfoo.foo), xfoo.getAttribute('foo'), 'attribute reflects property as string');
53+
xfoo.setAttribute('foo', '27');
54+
assert.equal(xfoo.foo, xfoo.getAttribute('foo'), 'property reflects attribute');
55+
//
56+
xfoo.baz = 'Hello';
57+
Platform.flush();
58+
Platform.endOfMicrotask(function() {
59+
assert.equal(xfoo.baz, xfoo.getAttribute('baz'), 'attribute reflects property');
60+
//
61+
var xbar = document.querySelector('x-bar');
62+
//
63+
xbar.foo = 'foo!';
64+
xbar.zot = 27;
65+
xbar.zim = true;
66+
xbar.str = 'str!';
67+
xbar.obj = {hello: 'world'};
68+
Platform.flush();
69+
Platform.endOfMicrotask(function() {
70+
assert.equal(xbar.foo, xbar.getAttribute('foo'), 'inherited published property is reflected');
71+
assert.equal(String(xbar.zot), xbar.getAttribute('zot'), 'attribute reflects property as number');
72+
assert.equal('', xbar.getAttribute('zim'), 'attribute reflects true valued boolean property as having attribute');
73+
assert.equal(xbar.str, xbar.getAttribute('str'), 'attribute reflects property as published string');
74+
assert.isFalse(xbar.hasAttribute('obj'), 'attribute does not reflect object property');
75+
xbar.setAttribute('foo', 'foo!!');
76+
xbar.setAttribute('zot', 54);
77+
xbar.setAttribute('zim', 'false');
78+
xbar.setAttribute('str', 'str!!');
79+
xbar.setAttribute('obj', "{'hello': 'world'}");
80+
assert.equal(xbar.foo, xbar.getAttribute('foo'), 'property reflects attribute as string');
81+
assert.equal(xbar.zot, 54, 'property reflects attribute as number');
82+
assert.equal(xbar.zim, false, 'property reflects attribute as boolean');
83+
assert.equal(xbar.str, 'str!!', 'property reflects attribute as published string');
84+
assert.deepEqual(xbar.obj, {hello: 'world'}, 'property reflects attribute as object');
85+
xbar.zim = false;
86+
Platform.flush();
87+
Platform.endOfMicrotask(function() {
88+
assert.isFalse(xbar.hasAttribute('zim'), 'attribute reflects false valued boolean property as NOT having attribute');
89+
done();
90+
});
91+
});
92+
});
93+
});
94+
});
95+
</script>
96+
</body>
97+
</html>

0 commit comments

Comments
 (0)