Index: runtime/bin/vmservice/observatory/deployed/web/packages/template_binding/js/node_bind.js |
diff --git a/runtime/bin/vmservice/observatory/deployed/web/packages/template_binding/js/node_bind.js b/runtime/bin/vmservice/observatory/deployed/web/packages/template_binding/js/node_bind.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..270c557b185ea8a36c5cabb97affeb865322e483 |
--- /dev/null |
+++ b/runtime/bin/vmservice/observatory/deployed/web/packages/template_binding/js/node_bind.js |
@@ -0,0 +1,343 @@ |
+// 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 |
+ |
+(function(global) { |
+ 'use strict'; |
+ |
+ var filter = Array.prototype.filter.call.bind(Array.prototype.filter); |
+ |
+ function getTreeScope(node) { |
+ while (node.parentNode) { |
+ node = node.parentNode; |
+ } |
+ |
+ return typeof node.getElementById === 'function' ? node : null; |
+ } |
+ |
+ Node.prototype.bind = function(name, observable) { |
+ console.error('Unhandled binding to Node: ', this, name, observable); |
+ }; |
+ |
+ Node.prototype.bindFinished = function() {}; |
+ |
+ function updateBindings(node, name, binding) { |
+ var bindings = node.bindings_; |
+ if (!bindings) |
+ bindings = node.bindings_ = {}; |
+ |
+ if (bindings[name]) |
+ binding[name].close(); |
+ |
+ return bindings[name] = binding; |
+ } |
+ |
+ function returnBinding(node, name, binding) { |
+ return binding; |
+ } |
+ |
+ function sanitizeValue(value) { |
+ return value == null ? '' : value; |
+ } |
+ |
+ function updateText(node, value) { |
+ node.data = sanitizeValue(value); |
+ } |
+ |
+ function textBinding(node) { |
+ return function(value) { |
+ return updateText(node, value); |
+ }; |
+ } |
+ |
+ var maybeUpdateBindings = returnBinding; |
+ |
+ Object.defineProperty(Platform, 'enableBindingsReflection', { |
+ get: function() { |
+ return maybeUpdateBindings === updateBindings; |
+ }, |
+ set: function(enable) { |
+ maybeUpdateBindings = enable ? updateBindings : returnBinding; |
+ return enable; |
+ }, |
+ configurable: true |
+ }); |
+ |
+ Text.prototype.bind = function(name, value, oneTime) { |
+ if (name !== 'textContent') |
+ return Node.prototype.bind.call(this, name, value, oneTime); |
+ |
+ if (oneTime) |
+ return updateText(this, value); |
+ |
+ var observable = value; |
+ updateText(this, observable.open(textBinding(this))); |
+ return maybeUpdateBindings(this, name, observable); |
+ } |
+ |
+ function updateAttribute(el, name, conditional, value) { |
+ if (conditional) { |
+ if (value) |
+ el.setAttribute(name, ''); |
+ else |
+ el.removeAttribute(name); |
+ return; |
+ } |
+ |
+ el.setAttribute(name, sanitizeValue(value)); |
+ } |
+ |
+ function attributeBinding(el, name, conditional) { |
+ return function(value) { |
+ updateAttribute(el, name, conditional, value); |
+ }; |
+ } |
+ |
+ Element.prototype.bind = function(name, value, oneTime) { |
+ var conditional = name[name.length - 1] == '?'; |
+ if (conditional) { |
+ this.removeAttribute(name); |
+ name = name.slice(0, -1); |
+ } |
+ |
+ if (oneTime) |
+ return updateAttribute(this, name, conditional, value); |
+ |
+ |
+ var observable = value; |
+ updateAttribute(this, name, conditional, |
+ observable.open(attributeBinding(this, name, conditional))); |
+ |
+ return maybeUpdateBindings(this, name, observable); |
+ }; |
+ |
+ var checkboxEventType; |
+ (function() { |
+ // Attempt to feature-detect which event (change or click) is fired first |
+ // for checkboxes. |
+ var div = document.createElement('div'); |
+ var checkbox = div.appendChild(document.createElement('input')); |
+ checkbox.setAttribute('type', 'checkbox'); |
+ var first; |
+ var count = 0; |
+ checkbox.addEventListener('click', function(e) { |
+ count++; |
+ first = first || 'click'; |
+ }); |
+ checkbox.addEventListener('change', function() { |
+ count++; |
+ first = first || 'change'; |
+ }); |
+ |
+ var event = document.createEvent('MouseEvent'); |
+ event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, |
+ false, false, false, 0, null); |
+ checkbox.dispatchEvent(event); |
+ // WebKit/Blink don't fire the change event if the element is outside the |
+ // document, so assume 'change' for that case. |
+ checkboxEventType = count == 1 ? 'change' : first; |
+ })(); |
+ |
+ function getEventForInputType(element) { |
+ switch (element.type) { |
+ case 'checkbox': |
+ return checkboxEventType; |
+ case 'radio': |
+ case 'select-multiple': |
+ case 'select-one': |
+ return 'change'; |
+ case 'range': |
+ if (/Trident|MSIE/.test(navigator.userAgent)) |
+ return 'change'; |
+ default: |
+ return 'input'; |
+ } |
+ } |
+ |
+ function updateInput(input, property, value, santizeFn) { |
+ input[property] = (santizeFn || sanitizeValue)(value); |
+ } |
+ |
+ function inputBinding(input, property, santizeFn) { |
+ return function(value) { |
+ return updateInput(input, property, value, santizeFn); |
+ } |
+ } |
+ |
+ function noop() {} |
+ |
+ function bindInputEvent(input, property, observable, postEventFn) { |
+ var eventType = getEventForInputType(input); |
+ |
+ function eventHandler() { |
+ observable.setValue(input[property]); |
+ observable.discardChanges(); |
+ (postEventFn || noop)(input); |
+ Platform.performMicrotaskCheckpoint(); |
+ } |
+ input.addEventListener(eventType, eventHandler); |
+ |
+ return { |
+ close: function() { |
+ input.removeEventListener(eventType, eventHandler); |
+ observable.close(); |
+ }, |
+ |
+ observable_: observable |
+ } |
+ } |
+ |
+ function booleanSanitize(value) { |
+ return Boolean(value); |
+ } |
+ |
+ // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. |
+ // Returns an array containing all radio buttons other than |element| that |
+ // have the same |name|, either in the form that |element| belongs to or, |
+ // if no form, in the document tree to which |element| belongs. |
+ // |
+ // This implementation is based upon the HTML spec definition of a |
+ // "radio button group": |
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.html#radio-button-group |
+ // |
+ function getAssociatedRadioButtons(element) { |
+ if (element.form) { |
+ return filter(element.form.elements, function(el) { |
+ return el != element && |
+ el.tagName == 'INPUT' && |
+ el.type == 'radio' && |
+ el.name == element.name; |
+ }); |
+ } else { |
+ var treeScope = getTreeScope(element); |
+ if (!treeScope) |
+ return []; |
+ var radios = treeScope.querySelectorAll( |
+ 'input[type="radio"][name="' + element.name + '"]'); |
+ return filter(radios, function(el) { |
+ return el != element && !el.form; |
+ }); |
+ } |
+ } |
+ |
+ function checkedPostEvent(input) { |
+ // Only the radio button that is getting checked gets an event. We |
+ // therefore find all the associated radio buttons and update their |
+ // check binding manually. |
+ if (input.tagName === 'INPUT' && |
+ input.type === 'radio') { |
+ getAssociatedRadioButtons(input).forEach(function(radio) { |
+ var checkedBinding = radio.bindings_.checked; |
+ if (checkedBinding) { |
+ // Set the value directly to avoid an infinite call stack. |
+ checkedBinding.observable_.setValue(false); |
+ } |
+ }); |
+ } |
+ } |
+ |
+ HTMLInputElement.prototype.bind = function(name, value, oneTime) { |
+ if (name !== 'value' && name !== 'checked') |
+ return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
+ |
+ this.removeAttribute(name); |
+ var sanitizeFn = name == 'checked' ? booleanSanitize : sanitizeValue; |
+ var postEventFn = name == 'checked' ? checkedPostEvent : noop; |
+ |
+ if (oneTime) |
+ return updateInput(this, name, value, sanitizeFn); |
+ |
+ |
+ var observable = value; |
+ var binding = bindInputEvent(this, name, observable, postEventFn); |
+ updateInput(this, name, |
+ observable.open(inputBinding(this, name, sanitizeFn)), |
+ sanitizeFn); |
+ |
+ // Checkboxes may need to update bindings of other checkboxes. |
+ return updateBindings(this, name, binding); |
+ } |
+ |
+ HTMLTextAreaElement.prototype.bind = function(name, value, oneTime) { |
+ if (name !== 'value') |
+ return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
+ |
+ this.removeAttribute('value'); |
+ |
+ if (oneTime) |
+ return updateInput(this, 'value', value); |
+ |
+ var observable = value; |
+ var binding = bindInputEvent(this, 'value', observable); |
+ updateInput(this, 'value', |
+ observable.open(inputBinding(this, 'value', sanitizeValue))); |
+ return maybeUpdateBindings(this, name, binding); |
+ } |
+ |
+ function updateOption(option, value) { |
+ var parentNode = option.parentNode;; |
+ var select; |
+ var selectBinding; |
+ var oldValue; |
+ if (parentNode instanceof HTMLSelectElement && |
+ parentNode.bindings_ && |
+ parentNode.bindings_.value) { |
+ select = parentNode; |
+ selectBinding = select.bindings_.value; |
+ oldValue = select.value; |
+ } |
+ |
+ option.value = sanitizeValue(value); |
+ |
+ if (select && select.value != oldValue) { |
+ selectBinding.observable_.setValue(select.value); |
+ selectBinding.observable_.discardChanges(); |
+ Platform.performMicrotaskCheckpoint(); |
+ } |
+ } |
+ |
+ function optionBinding(option) { |
+ return function(value) { |
+ updateOption(option, value); |
+ } |
+ } |
+ |
+ HTMLOptionElement.prototype.bind = function(name, value, oneTime) { |
+ if (name !== 'value') |
+ return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
+ |
+ this.removeAttribute('value'); |
+ |
+ if (oneTime) |
+ return updateOption(this, value); |
+ |
+ var observable = value; |
+ var binding = bindInputEvent(this, 'value', observable); |
+ updateOption(this, observable.open(optionBinding(this))); |
+ return maybeUpdateBindings(this, name, binding); |
+ } |
+ |
+ HTMLSelectElement.prototype.bind = function(name, value, oneTime) { |
+ if (name === 'selectedindex') |
+ name = 'selectedIndex'; |
+ |
+ if (name !== 'selectedIndex' && name !== 'value') |
+ return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
+ |
+ this.removeAttribute(name); |
+ |
+ if (oneTime) |
+ return updateInput(this, name, value); |
+ |
+ var observable = value; |
+ var binding = bindInputEvent(this, name, observable); |
+ updateInput(this, name, |
+ observable.open(inputBinding(this, name))); |
+ |
+ // Option update events may need to access select bindings. |
+ return updateBindings(this, name, binding); |
+ } |
+})(this); |