33fa4d5eba
this is part of addressing: 13554, 14157, 13359 R=sigmund@google.com Review URL: https://codereview.chromium.org//53743002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@29630 260f80e4-7a28-3924-810f-c04153c831b5
178 lines
5.8 KiB
Dart
178 lines
5.8 KiB
Dart
// 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/node_binding.dart';
|
|
|
|
export 'src/binding_delegate.dart';
|
|
export 'src/node_binding.dart' show NodeBinding;
|
|
|
|
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.
|
|
|
|
/**
|
|
* 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);
|
|
|
|
/**
|
|
* 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 &&
|
|
(n.localName == 'template' || _isAttributeTemplate(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');
|