589c718ac0
BUG= http://dartbug.com/20809 The following files were not migrated, they used to be in platform.js and are like polyfills, but are adding non-html spec functionality used by polymer: src/declaration/mdv.js src/declaration/polymer.js src/declaration/queue.js src/declaration/styles.js src/lib/dom.js src/lib/lang.js src/lib/loader.js src/lib/styleloader.js src/lib/url.js src/system/compat.js src/system/microtask.js src/system/module.js src/system/patches-mdv.js src/system/unresolved.js The following tests were not migrated. For the most part they are just testing the features added by the files above: test/html/loader-deduplicate.html test/html/mdv-shadow.html test/html/mdv-tests/polymer-expressions-tests.js test/html/styling/recursive-style-import.html test/html/url.html test/js/bindMDV.js test/js/oop.js test/html/styling/rules/* test/js/styling.js test/js/register.js test/js/utils.js test/html/template-repeat-wrappers.html R=sigmund@google.com Review URL: https://codereview.chromium.org//558673004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@40120 260f80e4-7a28-3924-810f-c04153c831b5
344 lines
10 KiB
JavaScript
344 lines
10 KiB
JavaScript
// 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);
|