polymer_expressions 0.11.2

polymer_expressions

Polymer expressions are an expressive syntax that can be used in HTML templates with Dart.

Templates are one feature of Polymer.dart, which is a set of comprehensive UI and utility components for building web applications. This package is automatically included with the Polymer package because Polymer expressions are the default expression syntax in Polymer Dart apps. The [Polymer.dart homepage][home_page] contains a list of features, project status, installation instructions, tips for upgrading from Web UI, and links to other documentation.

Overview

Polymer expressions allow you to write complex binding expressions, with property access, function invocation, list/map indexing, and two-way filtering like:

    {{ person.title + " " + person.getFullName() | upppercase }}

Model-Driven Views (MDV)

MDV allows you to define templates directly in HTML that are rendered by the browser into the DOM. Templates are bound to a data model, and changes to the data are automatically reflected in the DOM, and changes in HTML inputs are assigned back into the model. The template and model are bound together via binding expressions that are evaluated against the model. These binding expressions are placed in double-curly-braces, or "mustaches".

Example:

    <template>
      <p>Hello {{ person.name }}</p>
    </template>

MDV includes a very basic binding syntax which only allows a series of dot-separate property names.

Custom binding syntaxes with binding delegate

While MDV's built-in syntax is very basic, it does allow custom syntaxes called "binding delegates" to be installed and used. A binding delegate can interpret the contents of mustaches however it likes. PolymerExpressions is such a binding delegate.

Example:

    <template bind>
      <p>Hello {{ person.title + " " + person.getFullName() | uppercase }}</p>
    </template>

Usage

Installing from Pub

Add the following to your pubspec.yaml file:

    dependencies:
      polymer_expressions: any

Hint: check https://pub.dartlang.org/packages/polymer_expressions for the latest version number.

Then import polymer_expressions.dart:

import 'package:polymer_expressions/polymer_expressions.dart';

Registering a binding delegate

Polymer Expressions are now the default syntax for <polymer-element> custom elements.

You do not need to manually register the bindingDelegate if your bindings are inside a custom element. However, if you want to use polymer_expressions outside a custom element, read on:

Binding delegates must be installed on a template before they can be used. For example, set the bindingDelegate property of your template elements to an instance of PolymerExpressions. The templates will then use the PolymerExpressions instance to interpret binding expressions.

    import 'dart:html';
    import 'package:polymer_expressions/polymer_expressions.dart';

    main() {
      var template = query('#my_template');
      template.bindingDelegate = new PolymerExpressions();
    }

Registering top-level variables

Before a top-level variable can be used, it must be registered. The PolymerExpressions constructor takes a map of named values to use as variables.

    main() {
      var globals = {
        'uppercase': (String v) => v.toUpperCase(),
        'app_id': 'my_app_123',
      };
      var template = query('#my_template');
      template.bindingDelegate = new PolymerExpressions(globals: globals);
    }

Features

The model and scope

Polymer Expressions allow binding to more than just the model assigned to a template instance. Top-level variables can be defined so that you can use filters, global variables and constants, functions, etc. These variables and the model are held together in a container called a Scope. Scopes can be nested, which happens when template tags are nested.

Two-way bindings

Bindings can be used to modify the data model based on events in the DOM. The most common case is to bind an &lt;input&gt; element's value field to a model property and have the property update when the input changes. For this to work, the binding expression must be "assignable". Only a subset of expressions are assignable. Assignable expressions cannot contain function calls, operators, and any index operator must have a literal argument. Assignable expressions can contain filter operators as long as all the filters are two-way transformers.

Some restrictions may be relaxed further as allowed.

Assignable Expressions:

  • foo
  • foo.bar
  • items[0].description
  • people['john'].name
  • product.cost | convertCurrency('ZWD') where convertCurrency evaluates to a Tranformer object.

Non-Assignable Expressions:

  • a + 1
  • !c
  • foo()
  • person.lastName | uppercase where uppercase is a filter function.

Null-Safety

Expressions are generally null-safe. If an intermediate expression yields null the entire expression will return null, rather than throwing an exception. Property access, method invocation and operators are null-safe. Passing null to a function that doesn't handle null will not be null safe.

Streams

Polymer Expressions have experimental support for binding to streams, and when new values are passed to the stream, the template updates. The feature is not fully implemented yet.

See the examples in /example/streams for more details.

Syntax

Property access

Properties on the model and in the scope are looked up via simple property names, like foo. Property names are looked up first in the top-level variables, next in the model, then recursively in parent scopes. Properties on objects can be access with dot notation like foo.bar.

The keyword this always refers to the model if there is one, otherwise this is null. If you have model properties and top-level variables with the same name, you can use this to refer to the model property.

Literals

Polymer Expressions support number, boolean, string, and map literals. Strings can use either single or double quotes.

  • Numbers: 1, 1.0
  • Booleans: true, false
  • Strings: 'abc', "xyz"
  • Maps: { 'a': 1, 'b': 2 }

List literals are planned, see issue 9

Functions and methods

If a property is a function in the scope, a method on the model, or a method on an object, it can be invoked with standard function syntax. Functions and Methods can take arguments. Named arguments are not supported. Arguments can be literals or variables.

Examples:

  • Top-level function: myFunction()
  • Top-level function with arguments: myFunction(a, b, 42)
  • Model method: aMethod()
  • Method on nested-property: a.b.anotherMethod()

Operators

Polymer Expressions supports the following binary and unary operators:

  • Arithmetic operators: +, -, *, /, %, unary + and -
  • Comparison operators: ==, !=, <=, <, >, >=
  • Boolean operators: &&, ||, unary !

Expressions do not support bitwise operators such as &, |, << and >>, or increment/decrement operators (++ and --)

List and Map indexing

List and Map like objects can be accessed via the index operator: []

Examples:

  • items[2]
  • people['john']

Unlike JavaScript, list and map contents are not generally available via property access. That is, the previous examples are not equivalent to items.2 and people.john. This ensures that access to properties and methods on Lists and Maps is preserved.

Filters and transformers

A filter is a function that transforms a value into another, used via the pipe syntax: value | filter Any function that takes exactly one argument can be used as a filter.

Example:

If person.name is "John", and a top-level function named uppercase has been registered, then person.name | uppercase will have the value "JOHN".

The pipe syntax is used rather than a regular function call so that we can support two-way bindings through transformers. A transformer is a filter that has an inverse function. Transformers must extend or implement the Transformer class, which has forward() and reverse() methods.

Repeating templates

A template can be repeated by using the "repeat" attribute with a binding. The binding can either evaluate to an Iterable, in which case the template is instantiated for each item in the iterable and the model of the instance is set to the item, or the binding can be a "in" iterator expression, in which case a new variable is added to each scope.

The following examples produce the same output.

Evaluate to an iterable:

    <template repeat="{{ items }}">
      <div>{{ }}</div>
    </template>

"in" expression:

    <template repeat="{{ item in items }}">
      <div>{{ item }}</div>
    </template>

Status

The syntax implemented is experimental and subject to change, in fact, it will change soon. The goal is to be compatible with Polymer's binding syntax. We will announce breaking changes on the web-ui@dartlang.org mailing list.

Please file issues on Dart project page for any bugs you find or for feature requests. Make a note that it applies to "package:polymer_expressions"

You can discuss Polymer Expressions on the web-ui@dartlang.org mailing list.

changelog

This file contains highlights of what changes on each version of the polymer_expressions package.

Pub version 0.11.0

  • Remove faulty assert that threw when an iterable field was updated.

Pub version 0.11.0

  • Scopes created by templates are created less often and nested properly. The previous version had a bug with respect to the names visible in <template releat> tags without an "in" expression, and <template bind> tags. In those templates, names for the outer templates should not be visible. This may result in some breakages in templates that relied on the buggy behavior.
  • <template bind> now supports "as" expressions.
  • Removed warnings when attempting to assign a value to a property on null object, or assign a value to a non-assignable expression. Polymer binding initialization sometimes assigns to expressions, so this should reduce unecessary warnings.
  • Added the % (modulo), === (identical) and !== (not identical) operators.
  • Fast-path for eval(). eval() no longer observes expressions or creates a tree of observers.
  • PolymerExpressions bindings clean up expression observers when closed, fixing a potential memory leak.
  • Better parse errors. Unknown operators are reported, and all exceptions are instances of ParseException so that they can be caught independently of exceptions generated by calling user code.

Pub version 0.10.0

  • package:polymer_expressions no longer declares @MirrosUsed. The package uses mirrors at development time, but assumes frameworks like polymer will generate code that replaces the use of mirrors. If you use this directly, you might need to do code generation as well, or add the @MirrorsUsed declaration. This can be done either explicitly or by importing the old settings from 'package:observe/mirrors_used.dart' (which include @reflectable and @observable by default).

  • Errors that occur within bindings are now thrown asycnhronously. We used to trap some errors and report them in a Logger, and we would let other errors halt the rendering process. Now all errors are caught, but they are reported asynchornously so they are visible even when logging is not set up.

  • Fixed several bugs, including:

    • propagating list changes (18749).
    • precedence of ternary operators (17805).
    • two-way bindings (18410 and 18792).

1. Depend on it

Add this to your package's pubspec.yaml file:

dependencies:
  polymer_expressions: ">=0.11.2 <0.12.0"

If your package is an application package you should use any as the version constraint.

2. Install it

If you're using the Dart Editor, choose:

Menu > Tools > Pub Install

Or if you want to install from the command line, run:

$ pub install

3. Import it

Now in your Dart code, you can use:

import 'package:polymer_expressions/polymer_expressions.dart';

About

An expressive custom binding syntax for HTML templates

Author

Email web-ui-dev@dartlang.org Polymer.dart Authors

Homepage

www.dartlang.org/polymer-dart/

Documentation

www.dartdocs.org/documentation/polymer_e...

Uploader

dgrove@google.com
sigmund@google.com
jmesserly@google.com
justinfagnani@google.com

Share