| Index: packages/polymer_expressions/lib/polymer_expressions.dart
|
| diff --git a/packages/polymer_expressions/lib/polymer_expressions.dart b/packages/polymer_expressions/lib/polymer_expressions.dart
|
| deleted file mode 100644
|
| index 332e3a1ae9c2b8e8fa63b20e455718b44926847e..0000000000000000000000000000000000000000
|
| --- a/packages/polymer_expressions/lib/polymer_expressions.dart
|
| +++ /dev/null
|
| @@ -1,397 +0,0 @@
|
| -// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
| -// for details. All rights reserved. Use of this source code is governed by a
|
| -// BSD-style license that can be found in the LICENSE file.
|
| -
|
| -/**
|
| - * A binding delegate used with Polymer elements that
|
| - * allows for complex binding expressions, including
|
| - * property access, function invocation,
|
| - * list/map indexing, and two-way filtering.
|
| - *
|
| - * When you install polymer.dart,
|
| - * polymer_expressions is automatically installed as well.
|
| - *
|
| - * Polymer expressions are part of the Polymer.dart project.
|
| - * Refer to the
|
| - * [Polymer.dart](http://www.dartlang.org/polymer-dart/)
|
| - * homepage for example code, project status, and
|
| - * information about how to get started using Polymer.dart in your apps.
|
| - *
|
| - * ## Other resources
|
| - *
|
| - * The
|
| - * [Polymer expressions](http://pub.dartlang.org/packages/polymer_expressions)
|
| - * pub repository contains detailed documentation about using polymer
|
| - * expressions.
|
| - */
|
| -
|
| -library polymer_expressions;
|
| -
|
| -import 'dart:async';
|
| -import 'dart:html';
|
| -
|
| -import 'package:observe/observe.dart';
|
| -import 'package:template_binding/template_binding.dart';
|
| -
|
| -import 'eval.dart';
|
| -import 'expression.dart';
|
| -import 'parser.dart';
|
| -import 'src/globals.dart';
|
| -
|
| -Object _classAttributeConverter(v) =>
|
| - (v is Map) ? v.keys.where((k) => v[k] == true).join(' ') :
|
| - (v is Iterable) ? v.join(' ') :
|
| - v;
|
| -
|
| -Object _styleAttributeConverter(v) =>
|
| - (v is Map) ? v.keys.map((k) => '$k: ${v[k]}').join(';') :
|
| - (v is Iterable) ? v.join(';') :
|
| - v;
|
| -
|
| -class PolymerExpressions extends BindingDelegate {
|
| - /** The default [globals] to use for Polymer expressions. */
|
| - static const Map DEFAULT_GLOBALS = const { 'enumerate': enumerate };
|
| -
|
| - final ScopeFactory _scopeFactory;
|
| - final Map<String, Object> globals;
|
| -
|
| - // allows access to scopes created for template instances
|
| - final Expando<Scope> _scopes = new Expando<Scope>();
|
| - // allows access to scope identifiers (for "in" and "as")
|
| - final Expando<String> _scopeIdents = new Expando<String>();
|
| -
|
| - /**
|
| - * Creates a new binding delegate for Polymer expressions, with the provided
|
| - * variables used as [globals]. If no globals are supplied, a copy of the
|
| - * [DEFAULT_GLOBALS] will be used.
|
| - */
|
| - PolymerExpressions({Map<String, Object> globals,
|
| - ScopeFactory scopeFactory: const ScopeFactory()})
|
| - : globals = globals == null ?
|
| - new Map<String, Object>.from(DEFAULT_GLOBALS) : globals,
|
| - _scopeFactory = scopeFactory;
|
| -
|
| - @override
|
| - PrepareBindingFunction prepareBinding(String path, name, Node boundNode) {
|
| - if (path == null) return null;
|
| - var expr = new Parser(path).parse();
|
| -
|
| - if (isSemanticTemplate(boundNode) && (name == 'bind' || name == 'repeat')) {
|
| - if (expr is HasIdentifier) {
|
| - var identifier = expr.identifier;
|
| - var bindExpr = expr.expr;
|
| - return (model, Node node, bool oneTime) {
|
| - _scopeIdents[node] = identifier;
|
| - // model may not be a Scope if it was assigned directly via
|
| - // template.model = x; In that case, prepareInstanceModel will
|
| - // be called _after_ prepareBinding and will lookup this scope from
|
| - // _scopes
|
| - var scope = _scopes[node] = (model is Scope)
|
| - ? model
|
| - : _scopeFactory.modelScope(model: model, variables: globals);
|
| - return new _Binding(bindExpr, scope);
|
| - };
|
| - } else {
|
| - return (model, Node node, bool oneTime) {
|
| - var scope = _scopes[node] = (model is Scope)
|
| - ? model
|
| - : _scopeFactory.modelScope(model: model, variables: globals);
|
| - if (oneTime) {
|
| - return _Binding._oneTime(expr, scope);
|
| - }
|
| - return new _Binding(expr, scope);
|
| - };
|
| - }
|
| - }
|
| -
|
| - // For regular bindings, not bindings on a template, the model is always
|
| - // a Scope created by prepareInstanceModel
|
| - _Converter converter = null;
|
| - if (boundNode is Element && name == 'class') {
|
| - converter = _classAttributeConverter;
|
| - } else if (boundNode is Element && name == 'style') {
|
| - converter = _styleAttributeConverter;
|
| - }
|
| -
|
| - return (model, Node node, bool oneTime) {
|
| - var scope = _getScopeForModel(node, model);
|
| - if (oneTime) {
|
| - return _Binding._oneTime(expr, scope, converter);
|
| - }
|
| - return new _Binding(expr, scope, converter);
|
| - };
|
| - }
|
| -
|
| - prepareInstanceModel(Element template) {
|
| - var ident = _scopeIdents[template];
|
| -
|
| - if (ident == null) {
|
| - return (model) {
|
| - var existingScope = _scopes[template];
|
| - // TODO (justinfagnani): make template binding always call
|
| - // prepareInstanceModel first and get rid of this check
|
| - if (existingScope != null) {
|
| - // If there's an existing scope, we created it in prepareBinding
|
| - // If it has the same model, then we can reuse it, otherwise it's
|
| - // a repeat with no identifier and we create new scope to occlude
|
| - // the outer one
|
| - if (model == existingScope.model) return existingScope;
|
| - return _scopeFactory.modelScope(model: model, variables: globals);
|
| - } else {
|
| - return _getScopeForModel(template, model);
|
| - }
|
| - };
|
| - }
|
| -
|
| - return (model) {
|
| - var existingScope = _scopes[template];
|
| - if (existingScope != null) {
|
| - // This only happens when a model has been assigned programatically
|
| - // and prepareBinding is called _before_ prepareInstanceModel.
|
| - // The scope assigned in prepareBinding wraps the model and is the
|
| - // scope of the expression. That should be the parent of the templates
|
| - // scope in the case of bind/as or repeat/in bindings.
|
| - return _scopeFactory.childScope(existingScope, ident, model);
|
| - } else {
|
| - // If there's not an existing scope then we have a bind/as or
|
| - // repeat/in binding enclosed in an outer scope, so we use that as
|
| - // the parent
|
| - var parentScope = _getParentScope(template);
|
| - return _scopeFactory.childScope(parentScope, ident, model);
|
| - }
|
| - };
|
| - }
|
| -
|
| - /**
|
| - * Gets an existing scope for use as a parent, but does not create a new one.
|
| - */
|
| - Scope _getParentScope(Node node) {
|
| - var parent = node.parentNode;
|
| - if (parent == null) return null;
|
| -
|
| - if (isSemanticTemplate(node)) {
|
| - var templateExtension = templateBind(node);
|
| - var templateInstance = templateExtension.templateInstance;
|
| - var model = templateInstance == null
|
| - ? templateExtension.model
|
| - : templateInstance.model;
|
| - if (model is Scope) {
|
| - return model;
|
| - } else {
|
| - // A template with a bind binding might have a non-Scope model
|
| - return _scopes[node];
|
| - }
|
| - }
|
| - if (parent != null) return _getParentScope(parent);
|
| - return null;
|
| - }
|
| -
|
| - /**
|
| - * Returns the Scope to be used to evaluate expressions in the template
|
| - * containing [node]. Since all expressions in the same template evaluate
|
| - * against the same model, [model] is passed in and checked against the
|
| - * template model to make sure they agree.
|
| - *
|
| - * For nested templates, we might have a binding on the nested template that
|
| - * should be evaluated in the context of the parent template. All scopes are
|
| - * retreived from an ancestor of [node], since node may be establishing a new
|
| - * Scope.
|
| - */
|
| - Scope _getScopeForModel(Node node, model) {
|
| - // This only happens in bindings_test because it calls prepareBinding()
|
| - // directly. Fix the test and throw if node is null?
|
| - if (node == null) {
|
| - return _scopeFactory.modelScope(model: model, variables: globals);
|
| - }
|
| -
|
| - var id = node is Element ? node.id : '';
|
| - if (model is Scope) {
|
| - return model;
|
| - }
|
| - if (_scopes[node] != null) {
|
| - var scope = _scopes[node];
|
| - assert(scope.model == model);
|
| - return _scopes[node];
|
| - } else if (node.parentNode != null) {
|
| - return _getContainingScope(node.parentNode, model);
|
| - } else {
|
| - // here we should be at a top-level template, so there's no parent to
|
| - // look for a Scope on.
|
| - if (!isSemanticTemplate(node)) {
|
| - throw "expected a template instead of $node";
|
| - }
|
| - return _getContainingScope(node, model);
|
| - }
|
| - }
|
| -
|
| - Scope _getContainingScope(Node node, model) {
|
| - if (isSemanticTemplate(node)) {
|
| - var templateExtension = templateBind(node);
|
| - var templateInstance = templateExtension.templateInstance;
|
| - var templateModel = templateInstance == null
|
| - ? templateExtension.model
|
| - : templateInstance.model;
|
| - assert(templateModel == model);
|
| - var scope = _scopes[node];
|
| - assert(scope != null);
|
| - assert(scope.model == model);
|
| - return scope;
|
| - } else if (node.parent == null) {
|
| - var scope = _scopes[node];
|
| - if (scope != null) {
|
| - assert(scope.model == model);
|
| - } else {
|
| - // only happens in bindings_test
|
| - scope = _scopeFactory.modelScope(model: model, variables: globals);
|
| - }
|
| - return scope;
|
| - } else {
|
| - return _getContainingScope(node.parentNode, model);
|
| - }
|
| - }
|
| -
|
| - /// Parse the expression string and return an expression tree.
|
| - static Expression getExpression(String exprString) =>
|
| - new Parser(exprString).parse();
|
| -
|
| - /// Determines the value of evaluating [expr] on the given [model] and returns
|
| - /// either its value or a binding for it. If [oneTime] is true, it direclty
|
| - /// returns the value. Otherwise, when [oneTime] is false, it returns a
|
| - /// [Bindable] that besides evaluating the expression, it will also react to
|
| - /// observable changes from the model and update the value accordingly.
|
| - static getBinding(Expression expr, model, {Map<String, Object> globals,
|
| - oneTime: false}) {
|
| - if (globals == null) globals = new Map.from(DEFAULT_GLOBALS);
|
| - var scope = model is Scope ? model
|
| - : new Scope(model: model, variables: globals);
|
| - return oneTime ? _Binding._oneTime(expr, scope)
|
| - : new _Binding(expr, scope);
|
| - }
|
| -}
|
| -
|
| -typedef Object _Converter(Object);
|
| -
|
| -class _Binding extends Bindable {
|
| - final Scope _scope;
|
| - final _Converter _converter;
|
| - final Expression _expr;
|
| -
|
| - Function _callback;
|
| - StreamSubscription _sub;
|
| - ExpressionObserver _observer;
|
| - var _value;
|
| -
|
| - _Binding(this._expr, this._scope, [this._converter]);
|
| -
|
| - static Object _oneTime(Expression expr, Scope scope, [_Converter converter]) {
|
| - try {
|
| - var value = eval(expr, scope);
|
| - return (converter == null) ? value : converter(value);
|
| - } catch (e, s) {
|
| - new Completer().completeError(
|
| - "Error evaluating expression '$expr': $e", s);
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - bool _convertAndCheck(newValue, {bool skipChanges: false}) {
|
| - var oldValue = _value;
|
| - _value = _converter == null ? newValue : _converter(newValue);
|
| -
|
| - if (!skipChanges && _callback != null && oldValue != _value) {
|
| - _callback(_value);
|
| - return true;
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - get value {
|
| - // if there's a callback, then _value has been set, if not we need to
|
| - // force an evaluation
|
| - if (_callback != null) {
|
| - _check(skipChanges: true);
|
| - return _value;
|
| - }
|
| - return _Binding._oneTime(_expr, _scope, _converter);
|
| - }
|
| -
|
| - set value(v) {
|
| - try {
|
| - assign(_expr, v, _scope, checkAssignability: false);
|
| - } catch (e, s) {
|
| - new Completer().completeError(
|
| - "Error evaluating expression '$_expr': $e", s);
|
| - }
|
| - }
|
| -
|
| - Object open(callback(value)) {
|
| - if (_callback != null) throw new StateError('already open');
|
| -
|
| - _callback = callback;
|
| - _observer = observe(_expr, _scope);
|
| - _sub = _observer.onUpdate.listen(_convertAndCheck)..onError((e, s) {
|
| - new Completer().completeError(
|
| - "Error evaluating expression '$_observer': $e", s);
|
| - });
|
| -
|
| - _check(skipChanges: true);
|
| - return _value;
|
| - }
|
| -
|
| - bool _check({bool skipChanges: false}) {
|
| - try {
|
| - update(_observer, _scope, skipChanges: skipChanges);
|
| - return _convertAndCheck(_observer.currentValue, skipChanges: skipChanges);
|
| - } catch (e, s) {
|
| - new Completer().completeError(
|
| - "Error evaluating expression '$_observer': $e", s);
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - void close() {
|
| - if (_callback == null) return;
|
| -
|
| - _sub.cancel();
|
| - _sub = null;
|
| - _callback = null;
|
| -
|
| - new Closer().visit(_observer);
|
| - _observer = null;
|
| - }
|
| -
|
| -
|
| - // TODO(jmesserly): the following code is copy+pasted from path_observer.dart
|
| - // What seems to be going on is: polymer_expressions.dart has its own _Binding
|
| - // unlike polymer-expressions.js, which builds on CompoundObserver.
|
| - // This can lead to subtle bugs and should be reconciled. I'm not sure how it
|
| - // should go, but CompoundObserver does have some nice optimizations around
|
| - // ObservedSet which are lacking here. And reuse is nice.
|
| - void deliver() {
|
| - if (_callback != null) _dirtyCheck();
|
| - }
|
| -
|
| - bool _dirtyCheck() {
|
| - var cycles = 0;
|
| - while (cycles < _MAX_DIRTY_CHECK_CYCLES && _check()) {
|
| - cycles++;
|
| - }
|
| - return cycles > 0;
|
| - }
|
| -
|
| - static const int _MAX_DIRTY_CHECK_CYCLES = 1000;
|
| -}
|
| -
|
| -_identity(x) => x;
|
| -
|
| -/**
|
| - * Factory function used for testing.
|
| - */
|
| -class ScopeFactory {
|
| - const ScopeFactory();
|
| - modelScope({Object model, Map<String, Object> variables}) =>
|
| - new Scope(model: model, variables: variables);
|
| -
|
| - childScope(Scope parent, String name, Object value) =>
|
| - parent.childScope(name, value);
|
| -}
|
|
|