UNPKG

five-bells-visualization

Version:
251 lines (230 loc) 8.5 kB
<!-- @license Copyright (c) 2014 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt --> <link rel="import" href="../lib/case-map.html"> <script> /** * Changes to an object sub-field (aka "path") via a binding * (e.g. `<x-foo value="{{item.subfield}}"`) will notify other elements bound to * the same object automatically. * * When modifying a sub-field of an object imperatively * (e.g. `this.item.subfield = 42`), in order to have the new value propagated * to other elements, a special `setPathValue(path, value)` API is provided. * `setPathValue` sets the object field at the path specified, and then notifies the * binding system so that other elements bound to the same path will update. * * Example: * * Polymer({ * * is: 'x-date', * * properties: { * date: { * type: Object, * notify: true * } * }, * * attached: function() { * this.date = {}; * setInterval(function() { * var d = new Date(); * // Required to notify elements bound to date of changes to sub-fields * // this.date.seconds = d.getSeconds(); <-- Will not notify * this.setPathValue('date.seconds', d.getSeconds()); * this.setPathValue('date.minutes', d.getMinutes()); * this.setPathValue('date.hours', d.getHours() % 12); * }.bind(this), 1000); * } * * }); * * Allows bindings to `date` sub-fields to update on changes: * * <x-date date="{{date}}"></x-date> * * Hour: <span>{{date.hours}}</span> * Min: <span>{{date.minutes}}</span> * Sec: <span>{{date.seconds}}</span> * * @class data feature: path notification */ Polymer.Base._addFeature({ /** Notify that a path has changed. For example: this.item.user.name = 'Bob'; this.notifyPath('item.user.name', this.item.user.name); Returns true if notification actually took place, based on a dirty check of whether the new value was already known */ notifyPath: function(path, value, fromAbove) { var old = this._propertySet(path, value); // manual dirty checking for now... if (old !== value) { // console.group((this.localName || this.dataHost.id + '-' + this.dataHost.dataHost.index) + '#' + (this.id || this.index) + ' ' + path, value); // Take path effects at this level for exact path matches, // and notify down for any bindings to a subset of this path this._pathEffector(path, value); // Send event to notify the path change upwards // Optimization: don't notify up if we know the notification // is coming from above already (avoid wasted event dispatch) if (!fromAbove) { // TODO(sorvell): should only notify if notify: true? this._notifyPath(path, value); } // console.groupEnd((this.localName || this.dataHost.id + '-' + this.dataHost.dataHost.index) + '#' + (this.id || this.index) + ' ' + path, value); } }, /** Convienence method for setting a value to a path and calling notify path */ setPathValue: function(path, value) { var parts = path.split('.'); if (parts.length > 1) { var last = parts.pop(); var prop = this; while (parts.length) { prop = prop[parts.shift()]; if (!prop) { return; } } // TODO(kschaaf): want dirty-check here? // if (prop[last] !== value) { prop[last] = value; this.notifyPath(path, value); // } } else { this[path] = value; } }, getPathValue: function(path, root) { var parts = path.split('.'); var last = parts.pop(); var prop = root || this; while (parts.length) { prop = prop[parts.shift()]; if (!prop) { return; } } return prop[last]; }, // TODO(kschaaf): This machine can be optimized to memoize compiled path // effectors as new paths are notified for performance, since it involves // a fair amount of runtime lookup _pathEffector: function(path, value) { // get root property var model = this._modelForPath(path); // search property effects of the root property for 'annotation' effects var fx$ = this._propertyEffects[model]; if (fx$) { fx$.forEach(function(fx) { var fxFn = this[fx.kind + 'PathEffect']; if (fxFn) { fxFn.call(this, path, value, fx.effect); } }, this); } // notify runtime-bound paths if (this._boundPaths) { this._notifyBoundPaths(path, value); } }, annotationPathEffect: function(path, value, effect) { if (effect.value === path || effect.value.indexOf(path + '.') === 0) { // TODO(sorvell): ideally the effect function is on this prototype // so we don't have to call it like this. Polymer.Bind.annotationEffect.call(this, path, value, effect); } else if ((path.indexOf(effect.value + '.') === 0) && !effect.negate) { // locate the bound node var node = this._nodes[effect.index]; if (node && node.notifyPath) { var p = this._fixPath(effect.name , effect.value, path); node.notifyPath(p, value, true); } } }, complexObserverPathEffect: function(path, value, effect) { if (this._pathMatchesEffect(path, effect)) { Polymer.Bind.complexObserverEffect.call(this, path, value, effect); } }, computePathEffect: function(path, value, effect) { if (this._pathMatchesEffect(path, effect)) { Polymer.Bind.computeEffect.call(this, path, value, effect); } }, annotatedComputationPathEffect: function(path, value, effect) { if (this._pathMatchesEffect(path, effect)) { Polymer.Bind.annotatedComputationEffect.call(this, path, value, effect); } }, _pathMatchesEffect: function(path, effect) { var effectArg = effect.arg.name; return (effectArg == path) || (effectArg.indexOf(path + '.') === 0) || (effect.arg.wildcard && path.indexOf(effectArg) === 0); }, bindPaths: function(to, from) { this._boundPaths = this._boundPaths || {}; if (from) { this._boundPaths[to] = from; // this.setPathValue(to, this.getPathValue(from)); } else { this.unbindPath(to); // this.setPathValue(to, from); } }, unbindPaths: function(path) { if (this._boundPaths) { delete this._boundPaths[path]; } }, _notifyBoundPaths: function(path, value) { var from, to; for (var a in this._boundPaths) { var b = this._boundPaths[a]; if (path.indexOf(a + '.') == 0) { from = a; to = b; break; } if (path.indexOf(b + '.') == 0) { from = b; to = a; break; } } if (from && to) { var p = this._fixPath(to, from, path); this.notifyPath(p, value); } }, _fixPath: function(property, root, path) { return property + path.slice(root.length); }, _notifyPath: function(path, value) { var rootName = this._modelForPath(path); var dashCaseName = Polymer.CaseMap.camelToDashCase(rootName); var eventName = dashCaseName + this._EVENT_CHANGED; this.fire(eventName, { path: path, value: value }, {bubbles: false}); }, _modelForPath: function(path) { return path.split('.').shift(); }, _EVENT_CHANGED: '-changed', }); </script>