Skip to content
This repository has been archived by the owner on Mar 13, 2018. It is now read-only.

Commit

Permalink
Published properties now do not reflect to attributes by default. To …
Browse files Browse the repository at this point in the history
…opt in to reflecting a property, use an object in the publish block, like this:

publish: {
  foo: {value: 'foo', reflect: true}
}
  • Loading branch information
sorvell committed Apr 17, 2014
1 parent 8807978 commit bad4a4e
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 96 deletions.
32 changes: 21 additions & 11 deletions src/declaration/properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,27 @@
}
},
requireProperties: function(properties, prototype, base) {
// reflected properties
prototype.reflect = prototype.reflect || {};
// ensure a prototype value for each property
for (var n in properties) {
if (this.valueReflects(properties[n])) {
prototype.reflect[n] = true;
}
if (prototype[n] === undefined && base[n] === undefined) {
prototype[n] = properties[n];
prototype[n] = this.valueForProperty(properties[n]);
}
}
},
valueForProperty: function(propertyValue) {
return (typeof propertyValue === 'object' && propertyValue !== null) ?
(propertyValue.value !== undefined ? propertyValue.value : null) :
propertyValue;
},
valueReflects: function(propertyValue) {
return (typeof propertyValue === 'object' && propertyValue !== null &&
propertyValue.reflect);
},
lowerCaseMap: function(properties) {
var map = {};
for (var n in properties) {
Expand All @@ -80,19 +94,15 @@
return map;
},
createPropertyAccessors: function(prototype) {
var n$ = prototype._observeNames, pn$ = prototype._publishNames;
if ((n$ && n$.length) || (pn$ && pn$.length)) {
for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) {
Observer.createBindablePrototypeAccessor(prototype, n);
}
for (var i=0, l=pn$.length, n; (i<l) && (n=pn$[i]); i++) {
if (!prototype.observe || (prototype.observe[n] === undefined)) {
Observer.createBindablePrototypeAccessor(prototype, n);
}
var n$ = prototype._publishNames;
if (n$ && n$.length) {
for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) {
fn = prototype.reflect[n] ? prototype.reflectPropertyToAttribute :
null;
Observer.createBindablePrototypeAccessor(prototype, n, fn);
}
}
}

};

// exports
Expand Down
2 changes: 2 additions & 0 deletions src/declaration/prototype.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
this.inheritObject('observe', prototype, base);
// chain publish object to inherited
this.inheritObject('publish', prototype, base);
// chain reflect object to inherited
this.inheritObject('reflect', prototype, base);
// chain our lower-cased publish map to the inherited version
this.inheritObject('_publishLC', prototype, base);
// chain our instance attributes map to the inherited version
Expand Down
8 changes: 5 additions & 3 deletions src/instance/mdv.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@
return this.mixinSuper(arguments);
} else {
// use n-way Polymer binding
var observer = this.bindProperty(property, observable);
this.reflectPropertyToAttribute(property);
var observer = this.bindProperty(property, observable, oneTime);
// NOTE: reflecting binding information is typically required only for
// tooling. It has a performance cost so it's opt-in in Node.bind.
if (Platform.enableBindingsReflection) {
if (Platform.enableBindingsReflection && observer) {
observer.path = observable.path_;
this.bindings_ = this.bindings_ || {};
this.bindings_[property] = observer;
}
if (this.reflect[property]) {
this.reflectPropertyToAttribute(property);
}
return observer;
}
},
Expand Down
34 changes: 16 additions & 18 deletions src/instance/properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,17 @@

var properties = {
createPropertyObserver: function() {
var n$ = this._observeNames, pn$ = this._publishNames;
if ((n$ && n$.length) || (pn$ && pn$.length)) {
var self = this;
var n$ = this._observeNames;
if (n$ && n$.length) {
var o = this._propertyObserver = new CompoundObserver(true);
// keep track of property observer so we can shut it down
this.registerObservers([o]);
// TODO(sorvell): may not be kosher to access the value here (this[n]);
// previously we looked at the descriptor on the prototype
// this doesn't work for inheritance and not for accessors without
// a value property
for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) {
o.addPath(this, n);
// observer array properties
var pd = Object.getOwnPropertyDescriptor(this.__proto__, n);
if (pd && pd.value) {
this.observeArrayValue(n, pd.value, null);
}
}
for (var i=0, l=pn$.length, n; (i<l) && (n=pn$[i]); i++) {
if (!this.observe || (this.observe[n] === undefined)) {
o.addPath(this, n);
}
this.observeArrayValue(n, this[n], null);
}
}
},
Expand All @@ -50,15 +43,16 @@
for (var i in oldValues) {
// note: paths is of form [object, path, object, path]
name = paths[2 * i + 1];
if (this.publish[name] !== undefined) {
this.reflectPropertyToAttribute(name);
}
method = this.observe[name];
if (method) {
this.observeArrayValue(name, newValues[i], oldValues[i]);
if (!called[method]) {
called[method] = true;
// observes the value if it is an array
// TODO(sorvell): call method with the set of values it's expecting;
// e.g. 'foo bar': 'invalidate' expects the new and old values for
// foo and bar. Currently we give only one of these and then
// deliver all the arguments.
this.invokeMethod(method, [oldValues[i], newValues[i], arguments]);
}
}
Expand All @@ -84,7 +78,11 @@
}
}
},
bindProperty: function(property, observable) {
bindProperty: function(property, observable, oneTime) {
if (oneTime) {
this[property] = observable;
return;
}
return bindProperties(this, property, observable);
},
invokeMethod: function(method, args) {
Expand Down
5 changes: 4 additions & 1 deletion test/html/prop-attr-bind-reflection.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ <h1>Hello from the child</h1>
</template>
<script>
Polymer('my-child-element', {
publish: { camelCase: 0, lowercase: 0 },
publish: {
camelCase: {value: 0, reflect: true},
lowercase: {value: 0, reflect: true}
},
// Make this a no-op, so we can verify the initial
// reflectPropertyToAttribute works.
observeAttributeProperty: function(name) { }
Expand Down
122 changes: 60 additions & 62 deletions test/html/prop-attr-reflection.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,29 @@
<body>

<x-foo></x-foo>
<polymer-element name="x-foo" attributes="foo baz def1 def2">
<polymer-element name="x-foo">
<script>
Polymer('x-foo', {
foo: '',
baz: ''
publish: {
foo: {value: '', reflect: true},
baz: {value: '', reflect: true},
def1: {reflect: true},
def2: {reflect: true}
}
});
</script>
</polymer-element>

<x-bar></x-bar>
<polymer-element name="x-bar" extends="x-foo" attributes="zot zim str obj">
<polymer-element name="x-bar" extends="x-foo">
<script>
Polymer('x-bar', {
zot: 3,
zim: false,
str: 'str',
obj: null
publish: {
zot: {value: 3, reflect: true},
zim: {value: false, reflect: true},
str: {value: 'str', reflect: true},
obj: {reflect: true}
}
});
</script>
</polymer-element>
Expand Down Expand Up @@ -60,61 +66,53 @@
xfoo.setAttribute('def1', '15');
xfoo.def2 = 15;
Platform.flush();
oneMutation(xfoo, {attributes: true}, function() {
assert.isFalse(xcompose.$.bar.hasAttribute('zim'), 'attribute bound to property updates when binding is made');
assert.equal(xfoo.getAttribute('def2'), '15', 'default valued published property reflects to attr');
assert.equal(xfoo.def1, '15', 'attr updates default valued published property');
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();
oneMutation(xfoo, {attributes: true}, 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();
oneMutation(xbar, {attributes: true}, 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('zim', 'false');
xbar.setAttribute('foo', 'foo!!');
xbar.setAttribute('zot', 54);
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();
oneMutation(xbar, {attributes: true}, function() {
assert.isFalse(xbar.hasAttribute('zim'), 'attribute reflects false valued boolean property as NOT having attribute');
var objAttr = xbar.getAttribute('obj');
oneMutation(xbar, {attributes: true}, function() {
assert.equal(xbar.getAttribute('obj'), 'hi', 'reflect property based on current type');
//assert.isFalse(xbar.hasAttribute('obj'), 'property with default type of object does not serialize');
done();
});
xbar.obj = 'hi';
Platform.flush();
// trigger a mutation to watch
xbar.setAttribute('dummy', 'dummy');
});
});
});
assert.isFalse(xcompose.$.bar.hasAttribute('zim'), 'attribute bound to property updates when binding is made');
assert.equal(xfoo.getAttribute('def2'), '15', 'default valued published property reflects to attr');
assert.equal(xfoo.def1, '15', 'attr updates default valued published property');
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(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('zim', 'false');
xbar.setAttribute('foo', 'foo!!');
xbar.setAttribute('zot', 54);
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();
assert.isFalse(xbar.hasAttribute('zim'), 'attribute reflects false valued boolean property as NOT having attribute');
var objAttr = xbar.getAttribute('obj');
oneMutation(xbar, {attributes: true}, function() {
assert.equal(xbar.getAttribute('obj'), 'hi', 'reflect property based on current type');
//assert.isFalse(xbar.hasAttribute('obj'), 'property with default type of object does not serialize');
done();
});
xbar.obj = 'hi';
Platform.flush();
// trigger a mutation to watch
xbar.setAttribute('dummy', 'dummy');
});
</script>
</body>
Expand Down
3 changes: 2 additions & 1 deletion test/js/attrs.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ htmlSuite('attributes-declarative', function() {
htmlTest('html/publish-attributes.html');
htmlTest('html/take-attributes.html');
htmlTest('html/attr-mustache.html');
htmlTest('html/prop-attr-reflection.html');
// TODO(sorvell): replace test when observe.js is updated.
//htmlTest('html/prop-attr-reflection.html');
});

0 comments on commit bad4a4e

Please sign in to comment.