blob: 97daeabad53b7e1dffa7ae95c3eebf90c1d848d5 [file] [log] [blame]
// 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.
/**
* This library provides access to the Polymer project's
* [Data Binding](http://www.polymer-project.org/docs/polymer/databinding.html)
* Find more information at the
* [Polymer.dart homepage](https://www.dartlang.org/polymer-dart/).
*
* Extends the capabilities of the HTML Template Element by enabling it to
* create, manage, and remove instances of content bound to data defined in
* Dart.
*
* Node.bind() is a new method added to all DOM nodes which instructs them to
* bind the named property to the data provided. These allows applications to
* create a data model in Dart or JavaScript that DOM reacts to.
*/
library template_binding;
import 'dart:async';
import 'dart:collection';
import 'dart:html';
import 'dart:svg' show SvgSvgElement;
import 'package:observe/observe.dart';
import 'src/binding_delegate.dart';
import 'src/mustache_tokens.dart';
export 'src/binding_delegate.dart';
part 'src/element.dart';
part 'src/input_bindings.dart';
part 'src/input_element.dart';
part 'src/instance_binding_map.dart';
part 'src/node.dart';
part 'src/select_element.dart';
part 'src/template.dart';
part 'src/template_iterator.dart';
part 'src/text.dart';
part 'src/text_area_element.dart';
// TODO(jmesserly): ideally we would split TemplateBinding and Node.bind into
// two packages, but this is not easy when we are faking extension methods.
// Since TemplateElement needs to override Node.bind, it seems like the
// Node.bind layer must have some innate knowledge of TemplateBinding.
// NOTE: I've heard NodeBind might become an internal API, which is all the more
// reason to have it in this package.
/**
* Provides access to the data binding APIs for the [node]. For example:
*
* templateBind(node).model = new MyModel();
*
* This is equivalent to [nodeBind], but provides access to
* [TemplateBindExtension] APIs. [node] should be a [TemplateElement], or
* equivalent semantic template such as:
*
* <table template repeat="{{row in rows}}">
* <tr template repeat="{{item in row}}">
* <td>{{item}}</td>
* </tr>
* </table>
*/
TemplateBindExtension templateBind(Element node) => nodeBind(node);
/**
* Like [templateBind], but intended to be used only within a custom element
* that implements [TemplateBindExtension]. This method can be used to simulate
* a super call. For example:
*
* class CoolTemplate extends TemplateElement
* implements TemplateBindExtension {
*
* createInstance(model, delegate) {
* // do something cool...
* // otherwise, fall back to superclass
* return templateBindFallback(this).createInstance(model, delegate);
* }
* ...
* }
*/
TemplateBindExtension templateBindFallback(Element node) =>
nodeBindFallback(node);
/**
* Provides access to the data binding APIs for the [node]. For example:
*
* nodeBind(node).bind('checked', model, 'path.to.some.value');
*/
NodeBindExtension nodeBind(Node node) {
return node is NodeBindExtension ? node : nodeBindFallback(node);
}
/**
* Like [nodeBind], but intended to be used only within a custom element that
* implements [NodeBindExtension]. This method can be used to simulate a super
* call. For example:
*
* class FancyButton extends ButtonElement implements NodeBindExtension {
* bind(name, model, path) {
* if (name == 'fancy-prop') ... // do fancy binding
* // otherwise, fall back to superclass
* return nodeBindFallback(this).bind(name, model, path);
* }
* ...
* }
*/
NodeBindExtension nodeBindFallback(Node node) {
var extension = _expando[node];
if (extension != null) return extension;
// TODO(jmesserly): switch on localName?
if (node is InputElement) {
extension = new _InputElementExtension(node);
} else if (node is SelectElement) {
extension = new _SelectElementExtension(node);
} else if (node is TextAreaElement) {
extension = new _TextAreaElementExtension(node);
} else if (node is Element) {
if (isSemanticTemplate(node)) {
extension = new TemplateBindExtension._(node);
} else {
extension = new _ElementExtension(node);
}
} else if (node is Text) {
extension = new _TextExtension(node);
} else {
extension = new NodeBindExtension._(node);
}
_expando[node] = extension;
return extension;
}
bool _isAttributeTemplate(Element n) => n.attributes.containsKey('template') &&
_SEMANTIC_TEMPLATE_TAGS.containsKey(n.localName);
bool _isSvgTemplate(Element el) => el.tagName == 'template' &&
el.namespaceUri == 'http://www.w3.org/2000/svg';
bool _isHtmlTemplate(Element el) => el.tagName == 'TEMPLATE' &&
el.namespaceUri == 'http://www.w3.org/1999/xhtml';
/**
* Returns true if this node is semantically a template.
*
* A node is a template if [tagName] is TEMPLATE, or the node has the
* 'template' attribute and this tag supports attribute form for backwards
* compatibility with existing HTML parsers. The nodes that can use attribute
* form are table elments (THEAD, TBODY, TFOOT, TH, TR, TD, CAPTION, COLGROUP
* and COL), OPTION, and OPTGROUP.
*/
bool isSemanticTemplate(Node n) => n is Element &&
(_isHtmlTemplate(n) || _isAttributeTemplate(n) || _isSvgTemplate(n));
// TODO(jmesserly): const set would be better
const _SEMANTIC_TEMPLATE_TAGS = const {
'caption': null,
'col': null,
'colgroup': null,
'option': null,
'optgroup': null,
'tbody': null,
'td': null,
'tfoot': null,
'th': null,
'thead': null,
'tr': null,
};
// TODO(jmesserly): investigate if expandos give us enough performance.
// The expando for storing our MDV extensions.
//
// In general, we need state associated with the nodes. Rather than having a
// bunch of individual expandos, we keep one per node.
//
// Aside from the potentially helping performance, it also keeps things simpler
// if we decide to integrate MDV into the DOM later, and means less code needs
// to worry about expandos.
final Expando _expando = new Expando('template_binding');