From 3db2c64e4508d7d6c1100d50c6ebac6af0dcdc7a Mon Sep 17 00:00:00 2001 From: Steve Orvell Date: Thu, 15 Aug 2013 16:32:37 -0700 Subject: [PATCH] - boolean properties are reflected as boolean attributes: fixes #240 - creating a property binding causes property-> attar reflection: fixes #239 --- src/instance/attributes.js | 181 +++++++++++++++------------- src/instance/mdv.js | 4 + test/html/prop-attr-reflection.html | 177 +++++++++++++++------------ 3 files changed, 195 insertions(+), 167 deletions(-) diff --git a/src/instance/attributes.js b/src/instance/attributes.js index 57bee66eda..7d63567010 100644 --- a/src/instance/attributes.js +++ b/src/instance/attributes.js @@ -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= 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= 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); diff --git a/src/instance/mdv.js b/src/instance/mdv.js index 01b76552dd..96c55e84ab 100644 --- a/src/instance/mdv.js +++ b/src/instance/mdv.js @@ -27,6 +27,10 @@ var observer = this.bindProperty(property, model, path); // stick path on observer so it's available via this.bindings observer.path = path; + // reflect bound property to attribute when binding + // to ensure binding is not left on attribute if property + // does not update due to not changing. + this.propertyToAttribute(name); return this.bindings[name] = observer; } else { return this.super(arguments); diff --git a/test/html/prop-attr-reflection.html b/test/html/prop-attr-reflection.html index f773e671e1..7f17f342e9 100644 --- a/test/html/prop-attr-reflection.html +++ b/test/html/prop-attr-reflection.html @@ -1,80 +1,97 @@ - - - - publish attributes - - - - - - - - - - - - - - - - - - - + + + + publish attributes + + + + + + + + + + + + + + + + + + + + + + + + +