| Index: sky/framework/sky-element/sky-binder.sky
|
| diff --git a/sky/framework/sky-element/sky-binder.sky b/sky/framework/sky-element/sky-binder.sky
|
| deleted file mode 100644
|
| index bfef1ab86162231ac2ec85065e80eb031a9d4b5e..0000000000000000000000000000000000000000
|
| --- a/sky/framework/sky-element/sky-binder.sky
|
| +++ /dev/null
|
| @@ -1,498 +0,0 @@
|
| -<!--
|
| -// Copyright 2014 The Chromium Authors. All rights reserved.
|
| -// Use of this source code is governed by a BSD-style license that can be
|
| -// found in the LICENSE file.
|
| --->
|
| -<import src="observe.sky" as="observe" />
|
| -<import src="element-registry.sky" as="registry" />
|
| -
|
| -<script>
|
| -var stagingDocument = new Document();
|
| -
|
| -class TemplateInstance {
|
| - constructor() {
|
| - this.bindings = [];
|
| - this.terminator = null;
|
| - this.fragment = stagingDocument.createDocumentFragment();
|
| - Object.preventExtensions(this);
|
| - }
|
| - close() {
|
| - var bindings = this.bindings;
|
| - for (var i = 0; i < bindings.length; i++) {
|
| - bindings[i].close();
|
| - }
|
| - }
|
| -}
|
| -
|
| -var emptyInstance = new TemplateInstance();
|
| -var directiveCache = new WeakMap();
|
| -
|
| -function createInstance(template, model) {
|
| - var content = template.content;
|
| - if (!content.firstChild)
|
| - return emptyInstance;
|
| -
|
| - var directives = directiveCache.get(content);
|
| - if (!directives) {
|
| - directives = new NodeDirectives(content);
|
| - directiveCache.set(content, directives);
|
| - }
|
| -
|
| - var instance = new TemplateInstance();
|
| -
|
| - var length = directives.children.length;
|
| - for (var i = 0; i < length; ++i) {
|
| - var clone = directives.children[i].createBoundClone(instance.fragment,
|
| - model, instance.bindings);
|
| -
|
| - // The terminator of the instance is the clone of the last child of the
|
| - // content. If the last child is an active template, it may produce
|
| - // instances as a result of production, so simply collecting the last
|
| - // child of the instance after it has finished producing may be wrong.
|
| - if (i == length - 1)
|
| - instance.terminator = clone;
|
| - }
|
| -
|
| - return instance;
|
| -}
|
| -
|
| -function sanitizeValue(value) {
|
| - return value == null ? '' : value;
|
| -}
|
| -
|
| -function updateText(node, value) {
|
| - node.data = sanitizeValue(value);
|
| -}
|
| -
|
| -function updateAttribute(element, name, value) {
|
| - element.setAttribute(name, sanitizeValue(value));
|
| -}
|
| -
|
| -class BindingExpression {
|
| - constructor(prefix, path) {
|
| - this.prefix = prefix;
|
| - this.path = observe.Path.get(path);
|
| - Object.preventExtensions(this);
|
| - }
|
| -}
|
| -
|
| -class PropertyDirective {
|
| - constructor(name) {
|
| - this.name = name;
|
| - this.expressions = [];
|
| - this.suffix = "";
|
| - Object.preventExtensions(this);
|
| - }
|
| - createObserver(model) {
|
| - var expressions = this.expressions;
|
| - var suffix = this.suffix;
|
| -
|
| - if (expressions.length == 1 && expressions[0].prefix == "" && suffix == "")
|
| - return new observe.PathObserver(model, expressions[0].path);
|
| -
|
| - var observer = new observe.CompoundObserver();
|
| -
|
| - for (var i = 0; i < expressions.length; ++i)
|
| - observer.addPath(model, expressions[i].path);
|
| -
|
| - return new observe.ObserverTransform(observer, function(values) {
|
| - var buffer = "";
|
| - for (var i = 0; i < values.length; ++i) {
|
| - buffer += expressions[i].prefix;
|
| - buffer += values[i];
|
| - }
|
| - buffer += suffix;
|
| - return buffer;
|
| - });
|
| - }
|
| - bindProperty(node, model) {
|
| - var name = this.name;
|
| - var observable = this.createObserver(model);
|
| - if (node instanceof Text) {
|
| - updateText(node, observable.open(function(value) {
|
| - return updateText(node, value);
|
| - }));
|
| - } else if (name == 'style' || name == 'class') {
|
| - updateAttribute(node, name, observable.open(function(value) {
|
| - updateAttribute(node, name, value);
|
| - }));
|
| - } else {
|
| - node[name] = observable.open(function(value) {
|
| - node[name] = value;
|
| - });
|
| - }
|
| - if (typeof node.addPropertyBinding == 'function')
|
| - node.addPropertyBinding(this.name, observable);
|
| - return observable;
|
| - }
|
| -}
|
| -
|
| -function parsePropertyDirective(value, property) {
|
| - if (!value || !value.length)
|
| - return;
|
| -
|
| - var result;
|
| - var offset = 0;
|
| - var firstIndex = 0;
|
| - var lastIndex = 0;
|
| -
|
| - while (offset < value.length) {
|
| - firstIndex = value.indexOf('{{', offset);
|
| - if (firstIndex == -1)
|
| - break;
|
| - lastIndex = value.indexOf('}}', firstIndex + 2);
|
| - if (lastIndex == -1)
|
| - lastIndex = value.length;
|
| - var prefix = value.substring(offset, firstIndex);
|
| - var path = value.substring(firstIndex + 2, lastIndex);
|
| - offset = lastIndex + 2;
|
| - if (!result)
|
| - result = new PropertyDirective(property);
|
| - result.expressions.push(new BindingExpression(prefix, path));
|
| - }
|
| -
|
| - if (result && offset < value.length)
|
| - result.suffix = value.substring(offset);
|
| -
|
| - return result;
|
| -}
|
| -
|
| -function parseAttributeDirectives(element, directives) {
|
| - var attributes = element.getAttributes();
|
| - var tagName = element.tagName;
|
| -
|
| - for (var i = 0; i < attributes.length; i++) {
|
| - var attr = attributes[i];
|
| - var name = attr.name;
|
| - var value = attr.value;
|
| -
|
| - if (name.startsWith('on-')) {
|
| - directives.eventHandlers.push(name.substring(3));
|
| - continue;
|
| - }
|
| -
|
| - if (!registry.checkAttribute(tagName, name)) {
|
| - console.error('Element "'+ tagName +
|
| - '" has unknown attribute "' + name + '".');
|
| - }
|
| -
|
| - var property = parsePropertyDirective(value, name);
|
| - if (property)
|
| - directives.properties.push(property);
|
| - }
|
| -}
|
| -
|
| -function createCloneSource(element, properties) {
|
| - if (!properties.length)
|
| - return element;
|
| -
|
| - // Leave attributes alone on template so you can see the if/repeat statements
|
| - // in the inspector.
|
| - if (element instanceof HTMLTemplateElement)
|
| - return element;
|
| -
|
| - var result = element.cloneNode(false);
|
| -
|
| - for (var i = 0; i < properties.length; ++i) {
|
| - result.removeAttribute(properties[i].name);
|
| - }
|
| -
|
| - return result;
|
| -}
|
| -
|
| -function eventHandlerCallback(event) {
|
| - var element = event.currentTarget;
|
| - var method = element.getAttribute('on-' + event.type);
|
| - var scope = element.ownerScope;
|
| - var host = scope.host;
|
| - var handler = host && host[method];
|
| - if (handler instanceof Function)
|
| - return handler.call(host, event);
|
| -}
|
| -
|
| -class NodeDirectives {
|
| - constructor(node) {
|
| - this.eventHandlers = [];
|
| - this.children = [];
|
| - this.properties = [];
|
| - this.node = node;
|
| - this.cloneSourceNode = node;
|
| - Object.preventExtensions(this);
|
| -
|
| - if (node instanceof Element) {
|
| - parseAttributeDirectives(node, this);
|
| - this.cloneSourceNode = createCloneSource(node, this.properties);
|
| - } else if (node instanceof Text) {
|
| - var property = parsePropertyDirective(node.data, 'textContent');
|
| - if (property)
|
| - this.properties.push(property);
|
| - }
|
| -
|
| - for (var child = node.firstChild; child; child = child.nextSibling) {
|
| - this.children.push(new NodeDirectives(child));
|
| - }
|
| - }
|
| - findProperty(name) {
|
| - for (var i = 0; i < this.properties.length; ++i) {
|
| - if (this.properties[i].name === name)
|
| - return this.properties[i];
|
| - }
|
| - return null;
|
| - }
|
| - createBoundClone(parent, model, bindings) {
|
| - // TODO(esprehn): In sky instead of needing to use a staging docuemnt per
|
| - // custom element registry we're going to need to use the current module's
|
| - // registry.
|
| - var clone = stagingDocument.importNode(this.cloneSourceNode, false);
|
| -
|
| - for (var i = 0; i < this.eventHandlers.length; ++i) {
|
| - clone.addEventListener(this.eventHandlers[i], eventHandlerCallback);
|
| - }
|
| -
|
| - for (var i = 0; i < this.properties.length; ++i) {
|
| - bindings.push(this.properties[i].bindProperty(clone, model));
|
| - }
|
| -
|
| - parent.appendChild(clone);
|
| -
|
| - for (var i = 0; i < this.children.length; ++i) {
|
| - this.children[i].createBoundClone(clone, model, bindings);
|
| - }
|
| -
|
| - if (clone instanceof HTMLTemplateElement) {
|
| - var iterator = new TemplateIterator(clone);
|
| - iterator.updateDependencies(this, model);
|
| - bindings.push(iterator);
|
| - }
|
| -
|
| - return clone;
|
| - }
|
| -}
|
| -
|
| -var iterators = new WeakMap();
|
| -
|
| -class TemplateIterator {
|
| - constructor(element) {
|
| - this.closed = false;
|
| - this.template = element;
|
| - this.contentTemplate = null;
|
| - this.instances = [];
|
| - this.hasRepeat = false;
|
| - this.ifObserver = null;
|
| - this.valueObserver = null;
|
| - this.iteratedValue = [];
|
| - this.presentValue = null;
|
| - this.arrayObserver = null;
|
| - Object.preventExtensions(this);
|
| - iterators.set(element, this);
|
| - }
|
| -
|
| - updateDependencies(directives, model) {
|
| - this.contentTemplate = directives.node;
|
| -
|
| - var ifValue = true;
|
| - var ifProperty = directives.findProperty('if');
|
| - if (ifProperty) {
|
| - this.ifObserver = ifProperty.createObserver(model);
|
| - ifValue = this.ifObserver.open(this.updateIfValue, this);
|
| - }
|
| -
|
| - var repeatProperty = directives.findProperty('repeat');
|
| - if (repeatProperty) {
|
| - this.hasRepeat = true;
|
| - this.valueObserver = repeatProperty.createObserver(model);
|
| - } else {
|
| - var path = observe.Path.get("");
|
| - this.valueObserver = new observe.PathObserver(model, path);
|
| - }
|
| -
|
| - var value = this.valueObserver.open(this.updateIteratedValue, this);
|
| - this.updateValue(ifValue ? value : null);
|
| - }
|
| -
|
| - getUpdatedValue() {
|
| - return this.valueObserver.discardChanges();
|
| - }
|
| -
|
| - updateIfValue(ifValue) {
|
| - if (!ifValue) {
|
| - this.valueChanged();
|
| - return;
|
| - }
|
| -
|
| - this.updateValue(this.getUpdatedValue());
|
| - }
|
| -
|
| - updateIteratedValue(value) {
|
| - if (this.ifObserver) {
|
| - var ifValue = this.ifObserver.discardChanges();
|
| - if (!ifValue) {
|
| - this.valueChanged();
|
| - return;
|
| - }
|
| - }
|
| -
|
| - this.updateValue(value);
|
| - }
|
| -
|
| - updateValue(value) {
|
| - if (!this.hasRepeat)
|
| - value = [value];
|
| - var observe = this.hasRepeat && Array.isArray(value);
|
| - this.valueChanged(value, observe);
|
| - }
|
| -
|
| - valueChanged(value, observeValue) {
|
| - if (!Array.isArray(value))
|
| - value = [];
|
| -
|
| - if (value === this.iteratedValue)
|
| - return;
|
| -
|
| - this.unobserve();
|
| - this.presentValue = value;
|
| - if (observeValue) {
|
| - this.arrayObserver = new observe.ArrayObserver(this.presentValue);
|
| - this.arrayObserver.open(this.handleSplices, this);
|
| - }
|
| -
|
| - this.handleSplices(observe.ArrayObserver.calculateSplices(this.presentValue,
|
| - this.iteratedValue));
|
| - }
|
| -
|
| - getLastInstanceNode(index) {
|
| - if (index == -1)
|
| - return this.template;
|
| - var instance = this.instances[index];
|
| - var terminator = instance.terminator;
|
| - if (!terminator)
|
| - return this.getLastInstanceNode(index - 1);
|
| -
|
| - if (!(terminator instanceof Element) || this.template === terminator) {
|
| - return terminator;
|
| - }
|
| -
|
| - var subtemplateIterator = iterators.get(terminator);
|
| - if (!subtemplateIterator)
|
| - return terminator;
|
| -
|
| - return subtemplateIterator.getLastTemplateNode();
|
| - }
|
| -
|
| - getLastTemplateNode() {
|
| - return this.getLastInstanceNode(this.instances.length - 1);
|
| - }
|
| -
|
| - insertInstanceAt(index, instance) {
|
| - var previousInstanceLast = this.getLastInstanceNode(index - 1);
|
| - var parent = this.template.parentNode;
|
| - this.instances.splice(index, 0, instance);
|
| - parent.insertBefore(instance.fragment, previousInstanceLast.nextSibling);
|
| - }
|
| -
|
| - extractInstanceAt(index) {
|
| - var previousInstanceLast = this.getLastInstanceNode(index - 1);
|
| - var lastNode = this.getLastInstanceNode(index);
|
| - var parent = this.template.parentNode;
|
| - var instance = this.instances.splice(index, 1)[0];
|
| -
|
| - while (lastNode !== previousInstanceLast) {
|
| - var node = previousInstanceLast.nextSibling;
|
| - if (node == lastNode)
|
| - lastNode = previousInstanceLast;
|
| -
|
| - instance.fragment.appendChild(parent.removeChild(node));
|
| - }
|
| -
|
| - return instance;
|
| - }
|
| -
|
| - handleSplices(splices) {
|
| - if (this.closed || !splices.length)
|
| - return;
|
| -
|
| - var template = this.template;
|
| -
|
| - if (!template.parentNode) {
|
| - this.close();
|
| - return;
|
| - }
|
| -
|
| - observe.ArrayObserver.applySplices(this.iteratedValue, this.presentValue,
|
| - splices);
|
| -
|
| - // Instance Removals
|
| - var instanceCache = new Map;
|
| - var removeDelta = 0;
|
| - for (var i = 0; i < splices.length; i++) {
|
| - var splice = splices[i];
|
| - var removed = splice.removed;
|
| - for (var j = 0; j < removed.length; j++) {
|
| - var model = removed[j];
|
| - var instance = this.extractInstanceAt(splice.index + removeDelta);
|
| - if (instance !== emptyInstance) {
|
| - instanceCache.set(model, instance);
|
| - }
|
| - }
|
| -
|
| - removeDelta -= splice.addedCount;
|
| - }
|
| -
|
| - // Instance Insertions
|
| - for (var i = 0; i < splices.length; i++) {
|
| - var splice = splices[i];
|
| - var addIndex = splice.index;
|
| - for (; addIndex < splice.index + splice.addedCount; addIndex++) {
|
| - var model = this.iteratedValue[addIndex];
|
| - var instance = instanceCache.get(model);
|
| - if (instance) {
|
| - instanceCache.delete(model);
|
| - } else {
|
| - if (model === undefined || model === null) {
|
| - instance = emptyInstance;
|
| - } else {
|
| - instance = createInstance(this.contentTemplate, model);
|
| - }
|
| - }
|
| -
|
| - this.insertInstanceAt(addIndex, instance);
|
| - }
|
| - }
|
| -
|
| - instanceCache.forEach(function(instance) {
|
| - instance.close();
|
| - });
|
| - }
|
| -
|
| - unobserve() {
|
| - if (!this.arrayObserver)
|
| - return;
|
| -
|
| - this.arrayObserver.close();
|
| - this.arrayObserver = null;
|
| - }
|
| -
|
| - close() {
|
| - if (this.closed)
|
| - return;
|
| - this.unobserve();
|
| - for (var i = 0; i < this.instances.length; i++) {
|
| - this.instances[i].close();
|
| - }
|
| -
|
| - this.instances.length = 0;
|
| -
|
| - if (this.ifObserver)
|
| - this.ifObserver.close();
|
| - if (this.valueObserver)
|
| - this.valueObserver.close();
|
| -
|
| - iterators.delete(this.template);
|
| - this.closed = true;
|
| - }
|
| -}
|
| -
|
| -module.exports = {
|
| - createInstance: createInstance,
|
| -};
|
| -</script>
|
|
|