five-bells-visualization
Version:
Tool to visualize Five Bells payments
251 lines (230 loc) • 8.5 kB
HTML
<!--
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>