diff --git a/.gitmodules b/.gitmodules
index bdb4604..75d0b96 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,7 @@
[submodule "third_party/ChangeSummary"]
path = third_party/ChangeSummary
url = https://github.com/rafaelw/ChangeSummary.git
+ branch = master
[submodule "third_party/mocha"]
path = third_party/mocha
url = https://github.com/visionmedia/mocha.git
diff --git a/README.md b/README.md
index e32f2fc..1e44f04 100644
--- a/README.md
+++ b/README.md
@@ -137,6 +137,7 @@ MDV is designed to as two primitives which could eventually become standardized
MDV is mainly concerned with being robust and efficient in interacting with application data and keeping the DOM in sync , but more advanced behaviors can be accomplished via one or both of the following:
* [A Custom Syntax API](https://github.com/Polymer/mdv/blob/master/docs/syntax_api.md)
+* [Expression Syntax](https://github.com/Polymer/mdv/blob/master/docs/expression_syntax.md)
### Advanced Topics
diff --git a/benchmark/index.html b/benchmark/index.html
new file mode 100644
index 0000000..d5080b5
--- /dev/null
+++ b/benchmark/index.html
@@ -0,0 +1,223 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/benchmark/mdv_benchmark.js b/benchmark/mdv_benchmark.js
new file mode 100644
index 0000000..a1cad23
--- /dev/null
+++ b/benchmark/mdv_benchmark.js
@@ -0,0 +1,229 @@
+// Copyright 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+(function(global) {
+ 'use strict';
+
+ var createObject = ('__proto__' in {}) ?
+ function(obj) { return obj; } :
+ function(obj) {
+ var proto = obj.__proto__;
+ if (!proto)
+ return obj;
+ var newObject = Object.create(proto);
+ Object.getOwnPropertyNames(obj).forEach(function(name) {
+ Object.defineProperty(newObject, name,
+ Object.getOwnPropertyDescriptor(obj, name));
+ });
+ return newObject;
+ };
+
+ var attribNames = [
+ 'foo',
+ 'bar',
+ 'baz',
+ 'bat',
+ 'boo',
+ 'cat',
+ 'dog',
+ 'fog',
+ 'hat',
+ 'pig'
+ ]
+
+ var propNames = [
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i',
+ 'j'
+ ];
+
+ function MDVBenchmark(testDiv, width, depth, decoration,
+ instanceCount) {
+ Benchmark.call(this);
+ this.testDiv = testDiv;
+ this.width = width;
+ this.depth = depth;
+ this.decoration = decoration;
+
+ this.valueCounter = 1;
+ this.ping = this.objectArray(instanceCount);
+ this.pong = this.objectArray(instanceCount);
+ this.flip = true;
+ }
+
+ MDVBenchmark.prototype = createObject({
+ __proto__: Benchmark.prototype,
+
+ dataObject: function() {
+ var obj = {};
+ propNames.forEach(function(prop) {
+ obj[prop] = 'value' + (this.valueCounter++);
+ }, this);
+ return obj;
+ },
+
+ objectArray: function(count) {
+ var array = [];
+
+ for (var i = 0; i < count; i++)
+ array.push(this.dataObject());
+
+ return array;
+ },
+
+ nextBindingText: function() {
+ if (this.bindingCounter++ > this.bindingCount)
+ return 'I am Text!';
+
+ if (this.propNameCounter >= propNames.length)
+ this.propNameCounter = 0;
+
+ return '{{ ' + propNames[this.propNameCounter++] + ' }}';
+ },
+
+ decorate: function(element) {
+ if (!this.decoration)
+ return;
+
+ if (element.nodeType === Node.TEXT_NODE) {
+ element.textContent = this.nextBindingText();
+ return;
+ }
+
+ for (var i = 0; i < this.decoration; i++) {
+ element.setAttribute(attribNames[i], this.nextBindingText());
+ }
+ },
+
+ buildFragment: function(parent, width, depth) {
+ if (!depth)
+ return;
+
+ var text = parent.appendChild(document.createTextNode('I am text!'));
+ this.decorate(text);
+
+ for (var i = 0; i < width; i++) {
+ var div = parent.appendChild(document.createElement('div'));
+ this.buildFragment(div, width, depth - 1);
+ this.decorate(div);
+ }
+ },
+
+ setupTest: function(density) {
+ // |decoration| attributes on each element in each depth
+ var bindingCount = this.decoration *
+ (Math.pow(this.width, this.depth) - 1) * this.width;
+ // if |decoration| >= 1, one binding for each text node at each depth.
+ if (this.decoration > 0)
+ bindingCount += Math.pow(this.width, this.depth) - 1;
+
+ this.bindingCount = Math.round(bindingCount * density);
+ this.bindingCounter = 0;
+ this.propNameCounter = 0;
+ this.fragment = document.createDocumentFragment();
+ this.buildFragment(this.fragment, this.width, this.depth, this.decoration,
+ density);
+ },
+
+ teardownTest: function(density) {
+ this.fragment = undefined;
+ },
+
+ setupMDVVariant: function() {
+ if (testDiv.childNodes.length > 1)
+ alert('Failed to cleanup last test');
+
+ testDiv.innerHTML = '';
+ this.template = testDiv.appendChild(document.createElement('template'));
+ HTMLTemplateElement.decorate(this.template);
+ this.template.content.appendChild(this.fragment.cloneNode(true));
+ this.template.setAttribute('repeat', '');
+ },
+
+ runMDV: function() {
+ this.template.model = this.flip ? this.ping : this.pong;
+ this.flip = !this.flip;
+ },
+
+ teardownMDVVariant: function() {
+ this.template.model = undefined;
+ },
+
+ setupHandlebarsVariant: function() {
+ testDiv.innerHTML = '';
+ var div = document.createElement('div');
+ div.appendChild(this.fragment.cloneNode(true));
+ this.handlebarsTemplate = '{{#each this}}' + div.innerHTML + '{{/each}}';
+ this.compiledTemplate = Handlebars.compile(this.handlebarsTemplate);
+ },
+
+ runHandlebars: function() {
+ testDiv.innerHTML = '';
+ testDiv.innerHTML = this.compiledTemplate(this.flip ?
+ this.ping : this.pong);
+ if (!testDiv.querySelectorAll('div').length)
+ console.error('Foo');
+ this.flip = !this.flip;
+ },
+
+ teardownHandlebarsVariant: function() {
+ testDiv.innerHTML = '';
+ },
+
+ setupVariant: function(testType) {
+ switch (testType) {
+ case 'MDV':
+ this.setupMDVVariant();
+ break;
+ case 'Handlebars':
+ this.setupHandlebarsVariant();
+ break;
+ }
+ },
+
+ run: function(testType) {
+ switch (testType) {
+ case 'MDV':
+ this.runMDV();
+ break;
+ case 'Handlebars':
+ this.runHandlebars();
+ break;
+ }
+ },
+
+ teardownVariant: function(testType) {
+ switch (testType) {
+ case 'MDV':
+ this.teardownMDVVariant();
+ break;
+ case 'Handlebars':
+ this.teardownHandlebarsVariant();
+ break;
+ }
+ },
+
+ destroy: function() {}
+ });
+
+ global.MDVBenchmark = MDVBenchmark;
+
+})(this);
\ No newline at end of file
diff --git a/build.json b/build.json
new file mode 100644
index 0000000..1fc19df
--- /dev/null
+++ b/build.json
@@ -0,0 +1,13 @@
+[
+ "third_party/ChangeSummary/change_summary.js",
+ "src/compat.js",
+ "src/sidetable.js",
+ "src/model.js",
+ "src/script_value_binding.js",
+ "src/text_replacements_binding.js",
+ "src/element_attribute_bindings.js",
+ "src/element_bindings.js",
+ "src/input_bindings.js",
+ "src/template_element.js",
+ "src/delegates.js"
+]
diff --git a/conf/karma.conf.js b/conf/karma.conf.js
index 2c8e2c2..6cbb7b4 100644
--- a/conf/karma.conf.js
+++ b/conf/karma.conf.js
@@ -1,89 +1,85 @@
-// Sample Karma configuration file, that contain pretty much all the available options
-// It's used for running client tests on Travis (http://travis-ci.org/#!/karma-runner/karma)
-// Most of the options can be overriden by cli arguments (see karma --help)
-//
-// For all available config options and default values, see:
-// https://github.com/karma-runner/karma/blob/stable/lib/config.js#L54
-
-
-// base path, that will be used to resolve files and exclude
-basePath = '../';
-
-// list of files / patterns to load in the browser
-files = [
- 'node_modules/chai/chai.js',
- 'conf/mocha.conf.js',
- 'tests/setup.js',
- 'mdv.js',
- 'tests/*.js',
- {pattern: 'src/*.css', included: false},
- {pattern: 'src/*.js', included: false},
- {pattern: 'util/*.js', included: false},
- {pattern: 'third_party/**/*.js', included: false}
-];
-
-// list of files to exclude
-exclude = [];
-
-frameworks = ['mocha'];
-
-// use dots reporter, as travis terminal does not support escaping sequences
-// possible values: 'dots', 'progress', 'junit', 'teamcity'
-// CLI --reporters progress
-reporters = ['progress'];
-
-// web server port
-// CLI --port 9876
-port = 9876;
-
-// cli runner port
-// CLI --runner-port 9100
-runnerPort = 9100;
-
-// enable / disable colors in the output (reporters and logs)
-// CLI --colors --no-colors
-colors = true;
-
-// level of logging
-// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
-// CLI --log-level debug
-logLevel = LOG_INFO;
-
-// enable / disable watching file and executing tests whenever any file changes
-// CLI --auto-watch --no-auto-watch
-autoWatch = true;
-
-// Start these browsers, currently available:
-// - Chrome
-// - ChromeCanary
-// - Firefox
-// - Opera
-// - Safari (only Mac)
-// - PhantomJS
-// - IE (only Windows)
-// CLI --browsers Chrome,Firefox,Safari
-browsers = ['ChromeCanary'];
-
-// If browser does not capture in given timeout [ms], kill it
-// CLI --capture-timeout 5000
-captureTimeout = 50000;
-
-// Auto run tests on start (when browsers are captured) and exit
-// CLI --single-run --no-single-run
-singleRun = true;
-
-// report which specs are slower than 500ms
-// CLI --report-slower-than 500
-reportSlowerThan = 500;
-
-// compile coffee scripts
-preprocessors = {
+module.exports = function(karma) {
+ karma.configure({
+ // base path, that will be used to resolve files and exclude
+ basePath: '../',
+
+ // list of files / patterns to load in the browser
+ files: [
+ 'node_modules/chai/chai.js',
+ 'conf/mocha.conf.js',
+ 'tests/setup.js',
+ 'mdv.js',
+ 'tests/*.js',
+ {pattern: 'src/*.css', included: false},
+ {pattern: 'src/*.js', included: false},
+ {pattern: 'util/*.js', included: false},
+ {pattern: 'third_party/**/*.js', included: false}
+ ],
+
+ // list of files to exclude
+ exclude: [],
+
+ frameworks: ['mocha'],
+
+ // use dots reporter, as travis terminal does not support escaping sequences
+ // possible values: 'dots', 'progress', 'junit', 'teamcity'
+ // CLI --reporters progress
+ reporters: ['progress'],
+
+ // web server port
+ // CLI --port 9876
+ port: 9876,
+
+ // cli runner port
+ // CLI --runner-port 9100
+ runnerPort: 9100,
+
+ // enable / disable colors in the output (reporters and logs)
+ // CLI --colors --no-colors
+ colors: true,
+
+ // level of logging
+ // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
+ // CLI --log-level debug
+ logLevel: karma.LOG_INFO,
+
+ // enable / disable watching file and executing tests whenever any file changes
+ // CLI --auto-watch --no-auto-watch
+ autoWatch: true,
+
+ // Start these browsers, currently available:
+ // - Chrome
+ // - ChromeCanary
+ // - Firefox
+ // - Opera
+ // - Safari (only Mac)
+ // - PhantomJS
+ // - IE (only Windows)
+ // CLI --browsers Chrome,Firefox,Safari
+ browsers: ['ChromeCanary'],
+
+ // If browser does not capture in given timeout [ms], kill it
+ // CLI --capture-timeout 5000
+ captureTimeout: 50000,
+
+ // Auto run tests on start (when browsers are captured) and exit
+ // CLI --single-run --no-single-run
+ singleRun: true,
+
+ // report which specs are slower than 500ms
+ // CLI --report-slower-than 500
+ reportSlowerThan: 500,
+
+ // compile coffee scripts
+ preprocessors: {
+ },
+
+ plugins: [
+ 'karma-mocha',
+ 'karma-chrome-launcher',
+ 'karma-firefox-launcher',
+ 'karma-script-launcher',
+ 'karma-crbot-reporter'
+ ]
+ });
};
-
-plugins = [
- 'karma-mocha',
- 'karma-chrome-launcher',
- 'karma-firefox-launcher',
- 'karma-script-launcher',
- 'karma-crbot-reporter'
-]
diff --git a/examples/how_to/array_reduction.html b/examples/how_to/array_reduction.html
index dfb1b20..9d3473f 100644
--- a/examples/how_to/array_reduction.html
+++ b/examples/how_to/array_reduction.html
@@ -1,43 +1,58 @@
-
-
-
Reduction
+
+
+
+
+
+
+
+
Reduction
-
+
-
+ document.getElementById('reduction').model = model;
+
+ // Needed to detect model changes if Object.observe
+ // is not available in the JS VM.
+ Platform.performMicrotaskCheckpoint();
+ });
+
+
+
\ No newline at end of file
diff --git a/examples/how_to/bind_to_attributes.html b/examples/how_to/bind_to_attributes.html
index b49e302..33d8b39 100644
--- a/examples/how_to/bind_to_attributes.html
+++ b/examples/how_to/bind_to_attributes.html
@@ -1,31 +1,41 @@
-
+
+
+
+
+
+
+
Bind To Attributes
-
Bind To Attributes
+
+
+
The style attribute of this list item is bound
+
+
-
-
-
The style attribute of this list item is bound
-
-
+
-
+
+ Platform.performMicrotaskCheckpoint();
+ });
+ });
+
+
+
\ No newline at end of file
diff --git a/examples/how_to/bind_to_input_elements.html b/examples/how_to/bind_to_input_elements.html
index 4b3d8fd..bd10f14 100644
--- a/examples/how_to/bind_to_input_elements.html
+++ b/examples/how_to/bind_to_input_elements.html
@@ -1,42 +1,54 @@
-
-
-
Bind to Input Elements
-
-
-
Text Input
-
-
The amount(value: {{ amount }}) property is bound to both of these text input elements:
-
-
-
-
-
Checkbox
-
-
The toggle(value: {{ toggle }}) property is bound to both of these check boxes
-
-
-
-
-
Radio
-
-
radio1(value: {{ radio1 }}), radio2(value: {{ radio2 }}), and radio3(value: {{ radio3 }}) are bound to these radio buttons
-
-
-
-
-
+
+
+
+
+
+
+
Bind to Input Elements
+
+
+
Text Input
+
+
The amount(value: {{ amount }}) property is bound to both of these text input elements:
+
+
+
+
+
Checkbox
+
+
The toggle(value: {{ toggle }}) property is bound to both of these check boxes
+
+
+
+
+
Radio
+
+
radio1(value: {{ radio1 }}), radio2(value: {{ radio2 }}), and radio3(value: {{ radio3 }}) are bound to these radio buttons
The hidden attribute of this span is bound conditionally.
+
-
-
-
The hidden attribute of this span is bound conditionally.
-
+
+ // Needed to detect model changes if Object.observe
+ // is not available in the JS VM.
+ Platform.performMicrotaskCheckpoint();
+ });
+
+
+
\ No newline at end of file
diff --git a/examples/how_to/conditional_template.html b/examples/how_to/conditional_template.html
index 328220e..3047c32 100644
--- a/examples/how_to/conditional_template.html
+++ b/examples/how_to/conditional_template.html
@@ -1,19 +1,31 @@
-
+
+
+
+
+
+
+
Conditional Template
-
Conditional Template
+
+
+ Show?:
+
+ Yay! I'm shown
+
+
+
-
-
- Show?:
-
- Yay! I'm shown
-
-
-
+
+ // Needed to detect model changes if Object.observe
+ // is not available in the JS VM.
+ Platform.performMicrotaskCheckpoint();
+ });
+
+
+
\ No newline at end of file
diff --git a/examples/how_to/custom_syntax.html b/examples/how_to/custom_syntax.html
index 4377838..9c283d5 100644
--- a/examples/how_to/custom_syntax.html
+++ b/examples/how_to/custom_syntax.html
@@ -1,33 +1,46 @@
-
+
+
+
+
+
+
+
Custom Syntax
-
Custom Syntax
+
+
+ Amount: , Twice Amount: {{ 2x: value }}
+
+
-
-
- Amount: , Twice Amount: {{ 2x: value }}
-
-
+
+ // Needed to detect model changes if Object.observe
+ // is not available in the JS VM.
+ Platform.performMicrotaskCheckpoint();
+ });
+
+
+
\ No newline at end of file
diff --git a/examples/how_to/nested_templates.html b/examples/how_to/nested_templates.html
index 26345a4..a4adc31 100644
--- a/examples/how_to/nested_templates.html
+++ b/examples/how_to/nested_templates.html
@@ -1,34 +1,46 @@
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/how_to/template_ref.html b/examples/how_to/template_ref.html
index de844a5..1fb3e56 100644
--- a/examples/how_to/template_ref.html
+++ b/examples/how_to/template_ref.html
@@ -1,26 +1,38 @@
-
+
+
+
+
+
+
+
Re-using templates
-
Re-using templates
+
+
{{ name }}
+
-
-
{{ name }}
-
+
Usage one:
+ User:
-
Usage one:
-User:
+
Usage two:
+ More users:
+
+
+
+
+
-
Usage two:
-More users:
-
-
-
-
-
+
+ // Needed to detect model changes if Object.observe
+ // is not available in the JS VM.
+ Platform.performMicrotaskCheckpoint();
+ });
+
+
+
\ No newline at end of file
diff --git a/examples/twitter.html b/examples/twitter.html
deleted file mode 100644
index 211614e..0000000
--- a/examples/twitter.html
+++ /dev/null
@@ -1,205 +0,0 @@
-
-
-
-
-Twitter Demo
-
-
-
-
-
-
-
-