Speedometer: Update the vanilla JavaScript TodoMVC implem to a more recent version
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 12 May 2017 02:53:56 +0000 (02:53 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 12 May 2017 02:53:56 +0000 (02:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=171306

Patch by Addy Osmani <addyosmani@gmail.com> on 2017-05-11
Reviewed by Ryosuke Niwa.

Update vanilla JS TodoMVC implementation to the latest.

* Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower.json: Removed.
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/base.css: Removed.
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/base.js: Removed.
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/bg.png: Removed.
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/index.html:
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/app.js:
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/controller.js:
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/helpers.js:
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/model.js:
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/store.js:
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/template.js: Added.
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/view.js:
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-app-css/index.css: Added.
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-common/base.css: Added.
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-common/base.js: Added.
* Speedometer/resources/todomvc/vanilla-examples/vanillajs/package.json: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@216719 268f45cc-cd09-0410-ab3c-d52691b4dbfc

17 files changed:
PerformanceTests/ChangeLog
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower.json [deleted file]
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/base.css [deleted file]
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/base.js [deleted file]
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/bg.png [deleted file]
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/index.html
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/app.js
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/controller.js
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/helpers.js
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/model.js
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/store.js
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/template.js [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/view.js
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-app-css/index.css [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-common/base.css [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-common/base.js [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/package.json [new file with mode: 0644]

index 259f8c8..21e84ce 100644 (file)
@@ -1,5 +1,31 @@
 2017-05-11  Addy Osmani  <addyosmani@gmail.com>
 
+        Speedometer: Update the vanilla JavaScript TodoMVC implem to a more recent version
+        https://bugs.webkit.org/show_bug.cgi?id=171306
+
+        Reviewed by Ryosuke Niwa.
+
+        Update vanilla JS TodoMVC implementation to the latest.
+
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower.json: Removed.
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/base.css: Removed.
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/base.js: Removed.
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/bg.png: Removed.
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/index.html:
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/app.js:
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/controller.js:
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/helpers.js:
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/model.js:
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/store.js:
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/template.js: Added.
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/view.js:
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-app-css/index.css: Added.
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-common/base.css: Added.
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-common/base.js: Added.
+        * Speedometer/resources/todomvc/vanilla-examples/vanillajs/package.json: Added.
+
+2017-05-11  Addy Osmani  <addyosmani@gmail.com>
+
         Speedometer: Add an ES2015 TodoMVC implementation
         https://bugs.webkit.org/show_bug.cgi?id=171448
 
diff --git a/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower.json b/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower.json
deleted file mode 100644 (file)
index 08e3bce..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-    "name": "todomvc-vanillajs",
-    "version": "0.0.0",
-    "dependencies": {
-    "todomvc-common": "~0.1.4"
-    }
-}
diff --git a/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/base.css b/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/base.css
deleted file mode 100644 (file)
index 737cd9c..0000000
+++ /dev/null
@@ -1,554 +0,0 @@
-html,
-body {
-    margin: 0;
-    padding: 0;
-}
-
-button {
-    margin: 0;
-    padding: 0;
-    border: 0;
-    background: none;
-    font-size: 100%;
-    vertical-align: baseline;
-    font-family: inherit;
-    color: inherit;
-    -webkit-appearance: none;
-    /*-moz-appearance: none;*/
-    -ms-appearance: none;
-    -o-appearance: none;
-    appearance: none;
-}
-
-body {
-    font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
-    line-height: 1.4em;
-    background: #eaeaea url('bg.png');
-    color: #4d4d4d;
-    width: 550px;
-    margin: 0 auto;
-    -webkit-font-smoothing: antialiased;
-    -moz-font-smoothing: antialiased;
-    -ms-font-smoothing: antialiased;
-    -o-font-smoothing: antialiased;
-    font-smoothing: antialiased;
-}
-
-#todoapp {
-    background: #fff;
-    background: rgba(255, 255, 255, 0.9);
-    margin: 130px 0 40px 0;
-    border: 1px solid #ccc;
-    position: relative;
-    border-top-left-radius: 2px;
-    border-top-right-radius: 2px;
-    box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
-    0 25px 50px 0 rgba(0, 0, 0, 0.15);
-}
-
-#todoapp:before {
-    content: '';
-    border-left: 1px solid #f5d6d6;
-    border-right: 1px solid #f5d6d6;
-    width: 2px;
-    position: absolute;
-    top: 0;
-    left: 40px;
-    height: 100%;
-}
-
-#todoapp input::-webkit-input-placeholder {
-    font-style: italic;
-}
-
-#todoapp input::-moz-placeholder {
-    font-style: italic;
-    color: #a9a9a9;
-}
-
-#todoapp h1 {
-    position: absolute;
-    top: -120px;
-    width: 100%;
-    font-size: 70px;
-    font-weight: bold;
-    text-align: center;
-    color: #b3b3b3;
-    color: rgba(255, 255, 255, 0.3);
-    text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
-    -webkit-text-rendering: optimizeLegibility;
-    -moz-text-rendering: optimizeLegibility;
-    -ms-text-rendering: optimizeLegibility;
-    -o-text-rendering: optimizeLegibility;
-    text-rendering: optimizeLegibility;
-}
-
-#header {
-    padding-top: 15px;
-    border-radius: inherit;
-}
-
-#header:before {
-    content: '';
-    position: absolute;
-    top: 0;
-    right: 0;
-    left: 0;
-    height: 15px;
-    z-index: 2;
-    border-bottom: 1px solid #6c615c;
-    background: #8d7d77;
-    background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
-    background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
-    background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
-    background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
-    background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
-    background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
-    border-top-left-radius: 1px;
-    border-top-right-radius: 1px;
-}
-
-#new-todo,
-.edit {
-    position: relative;
-    margin: 0;
-    width: 100%;
-    font-size: 24px;
-    font-family: inherit;
-    line-height: 1.4em;
-    border: 0;
-    outline: none;
-    color: inherit;
-    padding: 6px;
-    border: 1px solid #999;
-    box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-    -webkit-box-sizing: border-box;
-    -moz-box-sizing: border-box;
-    -ms-box-sizing: border-box;
-    -o-box-sizing: border-box;
-    box-sizing: border-box;
-    -webkit-font-smoothing: antialiased;
-    -moz-font-smoothing: antialiased;
-    -ms-font-smoothing: antialiased;
-    -o-font-smoothing: antialiased;
-    font-smoothing: antialiased;
-}
-
-#new-todo {
-    padding: 16px 16px 16px 60px;
-    border: none;
-    background: rgba(0, 0, 0, 0.02);
-    z-index: 2;
-    box-shadow: none;
-}
-
-#main {
-    position: relative;
-    z-index: 2;
-    border-top: 1px dotted #adadad;
-}
-
-label[for='toggle-all'] {
-    display: none;
-}
-
-#toggle-all {
-    position: absolute;
-    top: -42px;
-    left: -4px;
-    width: 40px;
-    text-align: center;
-    border: none; /* Mobile Safari */
-}
-
-#toggle-all:before {
-    content: '»';
-    font-size: 28px;
-    color: #d9d9d9;
-    padding: 0 25px 7px;
-}
-
-#toggle-all:checked:before {
-    color: #737373;
-}
-
-#todo-list {
-    margin: 0;
-    padding: 0;
-    list-style: none;
-}
-
-#todo-list li {
-    position: relative;
-    font-size: 24px;
-    border-bottom: 1px dotted #ccc;
-}
-
-#todo-list li:last-child {
-    border-bottom: none;
-}
-
-#todo-list li.editing {
-    border-bottom: none;
-    padding: 0;
-}
-
-#todo-list li.editing .edit {
-    display: block;
-    width: 506px;
-    padding: 13px 17px 12px 17px;
-    margin: 0 0 0 43px;
-}
-
-#todo-list li.editing .view {
-    display: none;
-}
-
-#todo-list li .toggle {
-    text-align: center;
-    width: 40px;
-    /* auto, since non-WebKit browsers doesn't support input styling */
-    height: auto;
-    position: absolute;
-    top: 0;
-    bottom: 0;
-    margin: auto 0;
-    border: none; /* Mobile Safari */
-    -webkit-appearance: none;
-    /*-moz-appearance: none;*/
-    -ms-appearance: none;
-    -o-appearance: none;
-    appearance: none;
-}
-
-#todo-list li .toggle:after {
-    content: '✔';
-    line-height: 43px; /* 40 + a couple of pixels visual adjustment */
-    font-size: 20px;
-    color: #d9d9d9;
-    text-shadow: 0 -1px 0 #bfbfbf;
-}
-
-#todo-list li .toggle:checked:after {
-    color: #85ada7;
-    text-shadow: 0 1px 0 #669991;
-    bottom: 1px;
-    position: relative;
-}
-
-#todo-list li label {
-    word-break: break-word;
-    padding: 15px;
-    margin-left: 45px;
-    display: block;
-    line-height: 1.2;
-    -webkit-transition: color 0.4s;
-    -moz-transition: color 0.4s;
-    -ms-transition: color 0.4s;
-    -o-transition: color 0.4s;
-    transition: color 0.4s;
-}
-
-#todo-list li.completed label {
-    color: #a9a9a9;
-    text-decoration: line-through;
-}
-
-#todo-list li .destroy {
-    display: none;
-    position: absolute;
-    top: 0;
-    right: 10px;
-    bottom: 0;
-    width: 40px;
-    height: 40px;
-    margin: auto 0;
-    font-size: 22px;
-    color: #a88a8a;
-    -webkit-transition: all 0.2s;
-    -moz-transition: all 0.2s;
-    -ms-transition: all 0.2s;
-    -o-transition: all 0.2s;
-    transition: all 0.2s;
-}
-
-#todo-list li .destroy:hover {
-    text-shadow: 0 0 1px #000,
-     0 0 10px rgba(199, 107, 107, 0.8);
-    -webkit-transform: scale(1.3);
-    -moz-transform: scale(1.3);
-    -ms-transform: scale(1.3);
-    -o-transform: scale(1.3);
-    transform: scale(1.3);
-}
-
-#todo-list li .destroy:after {
-    content: '✖';
-}
-
-#todo-list li:hover .destroy {
-    display: block;
-}
-
-#todo-list li .edit {
-    display: none;
-}
-
-#todo-list li.editing:last-child {
-    margin-bottom: -1px;
-}
-
-#footer {
-    color: #777;
-    padding: 0 15px;
-    position: absolute;
-    right: 0;
-    bottom: -31px;
-    left: 0;
-    height: 20px;
-    z-index: 1;
-    text-align: center;
-}
-
-#footer:before {
-    content: '';
-    position: absolute;
-    right: 0;
-    bottom: 31px;
-    left: 0;
-    height: 50px;
-    z-index: -1;
-    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
-    0 6px 0 -3px rgba(255, 255, 255, 0.8),
-    0 7px 1px -3px rgba(0, 0, 0, 0.3),
-    0 43px 0 -6px rgba(255, 255, 255, 0.8),
-    0 44px 2px -6px rgba(0, 0, 0, 0.2);
-}
-
-#todo-count {
-    float: left;
-    text-align: left;
-}
-
-#filters {
-    margin: 0;
-    padding: 0;
-    list-style: none;
-    position: absolute;
-    right: 0;
-    left: 0;
-}
-
-#filters li {
-    display: inline;
-}
-
-#filters li a {
-    color: #83756f;
-    margin: 2px;
-    text-decoration: none;
-}
-
-#filters li a.selected {
-    font-weight: bold;
-}
-
-#clear-completed {
-    float: right;
-    position: relative;
-    line-height: 20px;
-    text-decoration: none;
-    background: rgba(0, 0, 0, 0.1);
-    font-size: 11px;
-    padding: 0 10px;
-    border-radius: 3px;
-    box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
-}
-
-#clear-completed:hover {
-    background: rgba(0, 0, 0, 0.15);
-    box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
-}
-
-#info {
-    margin: 65px auto 0;
-    color: #a6a6a6;
-    font-size: 12px;
-    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
-    text-align: center;
-}
-
-#info a {
-    color: inherit;
-}
-
-/*
-    Hack to remove background from Mobile Safari.
-    Can't use it globally since it destroys checkboxes in Firefox and Opera
-*/
-@media screen and (-webkit-min-device-pixel-ratio:0) {
-    #toggle-all,
-    #todo-list li .toggle {
-    background: none;
-    }
-
-    #todo-list li .toggle {
-    height: 40px;
-    }
-
-    #toggle-all {
-    top: -56px;
-    left: -15px;
-    width: 65px;
-    height: 41px;
-    -webkit-transform: rotate(90deg);
-    transform: rotate(90deg);
-    -webkit-appearance: none;
-    appearance: none;
-    }
-}
-
-.hidden{
-    display:none;
-}
-
-hr {
-    margin: 20px 0;
-    border: 0;
-    border-top: 1px dashed #C5C5C5;
-    border-bottom: 1px dashed #F7F7F7;
-}
-
-.learn a {
-    font-weight: normal;
-    text-decoration: none;
-    color: #b83f45;
-}
-
-.learn a:hover {
-    text-decoration: underline;
-    color: #787e7e;
-}
-
-.learn h3,
-.learn h4,
-.learn h5 {
-    margin: 10px 0;
-    font-weight: 500;
-    line-height: 1.2;
-    color: #000;
-}
-
-.learn h3 {
-    font-size: 24px;
-}
-
-.learn h4 {
-    font-size: 18px;
-}
-
-.learn h5 {
-    margin-bottom: 0;
-    font-size: 14px;
-}
-
-.learn ul {
-    padding: 0;
-    margin: 0 0 30px 25px;
-}
-
-.learn li {
-    line-height: 20px;
-}
-
-.learn p {
-    font-size: 15px;
-    font-weight: 300;
-    line-height: 1.3;
-    margin-top: 0;
-    margin-bottom: 0;
-}
-
-.quote {
-    border: none;
-    margin: 20px 0 60px 0;
-}
-
-.quote p {
-    font-style: italic;
-}
-
-.quote p:before {
-    content: '“';
-    font-size: 50px;
-    opacity: .15;
-    position: absolute;
-    top: -20px;
-    left: 3px;
-}
-
-.quote p:after {
-    content: '”';
-    font-size: 50px;
-    opacity: .15;
-    position: absolute;
-    bottom: -42px;
-    right: 3px;
-}
-
-.quote footer {
-    position: absolute;
-    bottom: -40px;
-    right: 0;
-}
-
-.quote footer img {
-    border-radius: 3px;
-}
-
-.quote footer a {
-    margin-left: 5px;
-    vertical-align: middle;
-}
-
-.speech-bubble {
-    position: relative;
-    padding: 10px;
-    background: rgba(0, 0, 0, .04);
-    border-radius: 5px;
-}
-
-.speech-bubble:after {
-    content: '';
-    position: absolute;
-    top: 100%;
-    right: 30px;
-    border: 13px solid transparent;
-    border-top-color: rgba(0, 0, 0, .04);
-}
-
-/**body*/.learn-bar > .learn {
-    position: absolute;
-    width: 272px;
-    top: 8px;
-    left: -300px;
-    padding: 10px;
-    border-radius: 5px;
-    background-color: rgba(255, 255, 255, .6);
-    transition-property: left;
-    transition-duration: 500ms;
-}
-
-@media (min-width: 899px) {
-    /**body*/.learn-bar {
-    width: auto;
-    margin: 0 0 0 300px;
-    }
-    /**body*/.learn-bar > .learn {
-    left: 8px;
-    }
-    /**body*/.learn-bar #todoapp {
-    width: 550px;
-    margin: 130px auto 40px auto;
-    }
-}
diff --git a/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/base.js b/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/base.js
deleted file mode 100644 (file)
index f9bf424..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-(function () {
-    'use strict';
-
-    // Underscore's Template Module
-    // Courtesy of underscorejs.org
-    var _ = (function (_) {
-    _.defaults = function (object) {
-    if (!object) {
-    return object;
-    }
-    for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
-    var iterable = arguments[argsIndex];
-    if (iterable) {
-    for (var key in iterable) {
-    if (object[key] == null) {
-    object[key] = iterable[key];
-    }
-    }
-    }
-    }
-    return object;
-    }
-
-    // By default, Underscore uses ERB-style template delimiters, change the
-    // following template settings to use alternative delimiters.
-    _.templateSettings = {
-    evaluate    : /<%([\s\S]+?)%>/g,
-    interpolate : /<%=([\s\S]+?)%>/g,
-    escape      : /<%-([\s\S]+?)%>/g
-    };
-
-    // When customizing `templateSettings`, if you don't want to define an
-    // interpolation, evaluation or escaping regex, we need one that is
-    // guaranteed not to match.
-    var noMatch = /(.)^/;
-
-    // Certain characters need to be escaped so that they can be put into a
-    // string literal.
-    var escapes = {
-    "'":      "'",
-    '\\':     '\\',
-    '\r':     'r',
-    '\n':     'n',
-    '\t':     't',
-    '\u2028': 'u2028',
-    '\u2029': 'u2029'
-    };
-
-    var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
-
-    // JavaScript micro-templating, similar to John Resig's implementation.
-    // Underscore templating handles arbitrary delimiters, preserves whitespace,
-    // and correctly escapes quotes within interpolated code.
-    _.template = function(text, data, settings) {
-    var render;
-    settings = _.defaults({}, settings, _.templateSettings);
-
-    // Combine delimiters into one regular expression via alternation.
-    var matcher = new RegExp([
-    (settings.escape || noMatch).source,
-    (settings.interpolate || noMatch).source,
-    (settings.evaluate || noMatch).source
-    ].join('|') + '|$', 'g');
-
-    // Compile the template source, escaping string literals appropriately.
-    var index = 0;
-    var source = "__p+='";
-    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
-    source += text.slice(index, offset)
-    .replace(escaper, function(match) { return '\\' + escapes[match]; });
-
-    if (escape) {
-    source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
-    }
-    if (interpolate) {
-    source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
-    }
-    if (evaluate) {
-    source += "';\n" + evaluate + "\n__p+='";
-    }
-    index = offset + match.length;
-    return match;
-    });
-    source += "';\n";
-
-    // If a variable is not specified, place data values in local scope.
-    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
-
-    source = "var __t,__p='',__j=Array.prototype.join," +
-    "print=function(){__p+=__j.call(arguments,'');};\n" +
-    source + "return __p;\n";
-
-    try {
-    render = new Function(settings.variable || 'obj', '_', source);
-    } catch (e) {
-    e.source = source;
-    throw e;
-    }
-
-    if (data) return render(data, _);
-    var template = function(data) {
-    return render.call(this, data, _);
-    };
-
-    // Provide the compiled function source as a convenience for precompilation.
-    template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
-
-    return template;
-    };
-
-    return _;
-    })({});
-
-    if (location.hostname === 'todomvc.com') {
-    window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script'));
-    }
-
-    function redirect() {
-    if (location.hostname === 'tastejs.github.io') {
-    location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com');
-    }
-    }
-
-    function findRoot() {
-    var base;
-
-    [/labs/, /\w*-examples/].forEach(function (href) {
-    var match = location.href.match(href);
-
-    if (!base && match) {
-    base = location.href.indexOf(match);
-    }
-    });
-
-    return location.href.substr(0, base);
-    }
-
-    function getFile(file, callback) {
-    if (!location.host) {
-    return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.');
-    }
-
-    var xhr = new XMLHttpRequest();
-
-    xhr.open('GET', findRoot() + file, true);
-    xhr.send();
-
-    xhr.onload = function () {
-    if (xhr.status === 200 && callback) {
-    callback(xhr.responseText);
-    }
-    };
-    }
-
-    function Learn(learnJSON, config) {
-    if (!(this instanceof Learn)) {
-    return new Learn(learnJSON, config);
-    }
-
-    var template, framework;
-
-    if (typeof learnJSON !== 'object') {
-    try {
-    learnJSON = JSON.parse(learnJSON);
-    } catch (e) {
-    return;
-    }
-    }
-
-    if (config) {
-    template = config.template;
-    framework = config.framework;
-    }
-
-    if (!template && learnJSON.templates) {
-    template = learnJSON.templates.todomvc;
-    }
-
-    if (!framework && document.querySelector('[data-framework]')) {
-    framework = document.querySelector('[data-framework]').getAttribute('data-framework');
-    }
-
-
-    if (template && learnJSON[framework]) {
-    this.frameworkJSON = learnJSON[framework];
-    this.template = template;
-
-    this.append();
-    }
-    }
-
-    Learn.prototype.append = function () {
-    var aside = document.createElement('aside');
-    aside.innerHTML = _.template(this.template, this.frameworkJSON);
-    aside.className = 'learn';
-
-    // Localize demo links
-    var demoLinks = aside.querySelectorAll('.demo-link');
-    Array.prototype.forEach.call(demoLinks, function (demoLink) {
-    demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href'));
-    });
-
-    document.body.className = (document.body.className + ' learn-bar').trim();
-    document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
-    };
-
-    redirect();
-    getFile('learn.json', Learn);
-})();
diff --git a/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/bg.png b/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/bg.png
deleted file mode 100644 (file)
index b2a7600..0000000
Binary files a/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/bower_components/todomvc-common/bg.png and /dev/null differ
index 477b9bf..6d944c4 100644 (file)
@@ -1,53 +1,51 @@
 <!doctype html>
 <html lang="en" data-framework="javascript">
     <head>
-    <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <title>VanillaJS • TodoMVC</title>
-    <link rel="stylesheet" href="bower_components/todomvc-common/base.css">
+        <meta charset="utf-8">
+        <title>VanillaJS • TodoMVC</title>
+        <link rel="stylesheet" href="node_modules/todomvc-common/base.css">
+        <link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
     </head>
     <body>
-    <section id="todoapp">
-    <header id="header">
-    <h1>todos</h1>
-    <input id="new-todo" placeholder="What needs to be done?" autofocus>
-    </header>
-    <section id="main">
-    <input id="toggle-all" type="checkbox">
-    <label for="toggle-all">Mark all as complete</label>
-    <ul id="todo-list"></ul>
-    </section>
-    <footer id="footer">
-    <span id="todo-count"></span>
-    <ul id="filters">
-    <li>
-    <a href="#/">All</a>
-    </li>
-    <li>
-    <a href="#/active">Active</a>
-    </li>
-    <li>
-    <a href="#/completed">Completed</a>
-    </li>
-    </ul>
-    <button id="clear-completed">Clear completed</button>
-    </footer>
-    </section>
-    <footer id="info">
-    <p>Double-click to edit a todo</p>
-    <p>Created by <a href="http://twitter.com/oscargodson">Oscar Godson</a></p>
-    <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
-    </footer>
-    <script src="bower_components/todomvc-common/base.js"></script>
-    <script>
-    // Bootstrap app data
-    window.app = {};
-    </script>
-    <script src="js/helpers.js"></script>
-    <script src="js/store.js"></script>
-    <script src="js/model.js"></script>
-    <script src="js/view.js"></script>
-    <script src="js/controller.js"></script>
-    <script src="js/app.js"></script>
+        <section class="todoapp">
+            <header class="header">
+                <h1>todos</h1>
+                <input class="new-todo" placeholder="What needs to be done?" autofocus>
+            </header>
+            <section class="main">
+                <input class="toggle-all" type="checkbox">
+                <label for="toggle-all">Mark all as complete</label>
+                <ul class="todo-list"></ul>
+            </section>
+            <footer class="footer">
+                <span class="todo-count"></span>
+                <ul class="filters">
+                    <li>
+                        <a href="#/" class="selected">All</a>
+                    </li>
+                    <li>
+                        <a href="#/active">Active</a>
+                    </li>
+                    <li>
+                        <a href="#/completed">Completed</a>
+                    </li>
+                </ul>
+                <button class="clear-completed">Clear completed</button>
+            </footer>
+        </section>
+        <footer class="info">
+            <p>Double-click to edit a todo</p>
+            <p>Speedometer version by <a href="http://twitter.com/addyosmani">Addy Osmani</p>
+            <p>Parts by <a href="http://twitter.com/oscargodson">Oscar Godson</a></p>
+            <p>Refactored by <a href="https://github.com/cburgmer">Christoph Burgmer</a></p>
+            <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
+        </footer>
+        <script src="js/helpers.js"></script>
+        <script src="js/store.js"></script>
+        <script src="js/model.js"></script>
+        <script src="js/template.js"></script>
+        <script src="js/view.js"></script>
+        <script src="js/controller.js"></script>
+        <script src="js/app.js"></script>
     </body>
 </html>
index 288e59b..301cf9f 100644 (file)
@@ -1,4 +1,4 @@
-/*global Store, Model, View, Controller, $$ */
+/*global app, $on */
 (function () {
     'use strict';
 
@@ -8,67 +8,18 @@
      * @param {string} name The name of your new to do list.
      */
     function Todo(name) {
-    this.storage = new app.Store(name);
-    this.model = new app.Model(this.storage);
-    this.view = new app.View();
-    this.controller = new app.Controller(this.model, this.view);
+        this.storage = new app.Store(name);
+        this.model = new app.Model(this.storage);
+        this.template = new app.Template();
+        this.view = new app.View(this.template);
+        this.controller = new app.Controller(this.model, this.view);
     }
 
     var todo = new Todo('todos-vanillajs');
 
-    window.todo = todo; // Benchmark modification; Expose todo so that we can call methods on it.
-
-    /**
-     * Finds the model ID of the clicked DOM element
-     *
-     * @param {object} target The starting point in the DOM for it to try to find
-     * the ID of the model.
-     */
-    function lookupId(target) {
-    var lookup = target;
-
-    while (lookup.nodeName !== 'LI') {
-    lookup = lookup.parentNode;
+    function setView() {
+        todo.controller.setView(document.location.hash);
     }
-
-    return lookup.dataset.id;
-    }
-
-    // When the enter key is pressed fire the addItem method.
-    $$('#new-todo').addEventListener('keypress', function (e) {
-    todo.controller.addItem(e);
-    });
-
-    // A delegation event. Will check what item was clicked whenever you click on any
-    // part of a list item.
-    $$('#todo-list').addEventListener('click', function (e) {
-    var target = e.target;
-
-    // If you click a destroy button
-    if (target.className.indexOf('destroy') > -1) {
-    todo.controller.removeItem(lookupId(target));
-    }
-
-    // If you click the checkmark
-    if (target.className.indexOf('toggle') > -1) {
-    todo.controller.toggleComplete(lookupId(target), target);
-    }
-
-    });
-
-    $$('#todo-list').addEventListener('dblclick', function (e) {
-    var target = e.target;
-
-    if (target.nodeName === 'LABEL') {
-    todo.controller.editItem(lookupId(target), target);
-    }
-    });
-
-    $$('#toggle-all').addEventListener('click', function (e) {
-    todo.controller.toggleAll(e);
-    });
-
-    $$('#clear-completed').addEventListener('click', function () {
-    todo.controller.removeCompletedItems();
-    });
+    $on(window, 'load', setView);
+    $on(window, 'hashchange', setView);
 })();
index e3a0d6c..0fbc62a 100644 (file)
@@ -1,4 +1,3 @@
-/*global $$, $ */
 (function (window) {
     'use strict';
 
      * Takes a model and view and acts as the controller between them
      *
      * @constructor
-     * @param {object} model The model constructor
-     * @param {object} view The view constructor
+     * @param {object} model The model instance
+     * @param {object} view The view instance
      */
     function Controller(model, view) {
-    this.model = model;
-    this.view = view;
-
-    this.ENTER_KEY = 13;
-    this.ESCAPE_KEY = 27;
-
-    this.$main = $$('#main');
-    this.$toggleAll = $$('#toggle-all');
-    this.$todoList = $$('#todo-list');
-    this.$todoItemCounter = $$('#todo-count');
-    this.$clearCompleted = $$('#clear-completed');
-    this.$footer = $$('#footer');
-
-    window.addEventListener('load', function () {
-    this._updateFilterState();
-    }.bind(this));
-
-    window.addEventListener('hashchange', function () {
-    this._updateFilterState();
-    }.bind(this));
+        var self = this;
+        self.model = model;
+        self.view = view;
+
+        self.view.bind('newTodo', function (title) {
+            self.addItem(title);
+        });
+
+        self.view.bind('itemEdit', function (item) {
+            self.editItem(item.id);
+        });
+
+        self.view.bind('itemEditDone', function (item) {
+            self.editItemSave(item.id, item.title);
+        });
+
+        self.view.bind('itemEditCancel', function (item) {
+            self.editItemCancel(item.id);
+        });
+
+        self.view.bind('itemRemove', function (item) {
+            self.removeItem(item.id);
+        });
+
+        self.view.bind('itemToggle', function (item) {
+            self.toggleComplete(item.id, item.completed);
+        });
+
+        self.view.bind('removeCompleted', function () {
+            self.removeCompletedItems();
+        });
+
+        self.view.bind('toggleAll', function (status) {
+            self.toggleAll(status.completed);
+        });
     }
 
     /**
+     * Loads and initialises the view
+     *
+     * @param {string} '' | 'active' | 'completed'
+     */
+    Controller.prototype.setView = function (locationHash) {
+        var route = locationHash.split('/')[1];
+        var page = route || '';
+        this._updateFilterState(page);
+    };
+
+    /**
      * An event to fire on load. Will get all items and display them in the
      * todo-list
      */
     Controller.prototype.showAll = function () {
-    this.model.read(function (data) {
-    this.$todoList.innerHTML = this.view.show(data);
-    }.bind(this));
+        var self = this;
+        self.model.read(function (data) {
+            self.view.render('showEntries', data);
+        });
     };
 
     /**
      * Renders all active tasks
      */
     Controller.prototype.showActive = function () {
-    this.model.read({ completed: 0 }, function (data) {
-    this.$todoList.innerHTML = this.view.show(data);
-    }.bind(this));
+        var self = this;
+        self.model.read({ completed: false }, function (data) {
+            self.view.render('showEntries', data);
+        });
     };
 
     /**
      * Renders all completed tasks
      */
     Controller.prototype.showCompleted = function () {
-    this.model.read({ completed: 1 }, function (data) {
-    this.$todoList.innerHTML = this.view.show(data);
-    }.bind(this));
+        var self = this;
+        self.model.read({ completed: true }, function (data) {
+            self.view.render('showEntries', data);
+        });
     };
 
     /**
      * An event to fire whenever you want to add an item. Simply pass in the event
      * object and it'll handle the DOM insertion and saving of the new item.
-     *
-     * @param {object} e The event object
      */
-    Controller.prototype.addItem = function (e) {
-    var input = $$('#new-todo');
-    var title = title || '';
+    Controller.prototype.addItem = function (title) {
+        var self = this;
 
-    if (e.keyCode === this.ENTER_KEY) {
-    if (e.target.value.trim() === '') {
-    return;
-    }
-
-    this.model.create(e.target.value, function (data) {
-    input.value = '';
-    this._filter(true);
-    }.bind(this));
-    }
+        if (title.trim() === '') {
+            return;
+        }
 
+        self.model.create(title, function () {
+            self.view.render('clearNewTodo');
+            self._filter(true);
+        });
     };
 
-    /**
-     * Hides the label text and creates an input to edit the title of the item.
-     * When you hit enter or blur out of the input it saves it and updates the UI
-     * with the new name.
-     *
-     * @param {number} id The id of the item to edit
-     * @param {object} label The label you want to edit the text of
+    /*
+     * Triggers the item editing mode.
      */
-    Controller.prototype.editItem = function (id, label) {
-    var li =  label;
-
-    // This finds the <label>'s parent <li>
-    while (li.nodeName !== 'LI') {
-    li = li.parentNode;
-    }
-
-    var onSaveHandler = function () {
-    var value = input.value.trim();
-    var discarding = input.dataset.discard;
-
-    if (value.length && !discarding) {
-    this.model.update(id, { title: input.value });
-
-    // Instead of re-rendering the whole view just update
-    // this piece of it
-    label.innerHTML = value;
-    } else if (value.length === 0) {
-    // No value was entered in the input. We'll remove the todo item.
-    this.removeItem(id);
-    }
-
-    // Remove the input since we no longer need it
-    // Less DOM means faster rendering
-    li.removeChild(input);
-
-    // Remove the editing class
-    li.className = li.className.replace('editing', '');
-    }.bind(this);
-
-    // Append the editing class
-    li.className = li.className + ' editing';
-
-    var input = document.createElement('input');
-    input.className = 'edit';
-
-    // Get the innerHTML of the label instead of requesting the data from the
-    // ORM. If this were a real DB this would save a lot of time and would avoid
-    // a spinner gif.
-    input.value = label.innerHTML;
-
-    li.appendChild(input);
-
-    input.addEventListener('blur', onSaveHandler);
-
-    input.addEventListener('keypress', function (e) {
-    if (e.keyCode === this.ENTER_KEY) {
-    // Remove the cursor from the input when you hit enter just like if it
-    // were a real form
-    input.blur();
-    }
+    Controller.prototype.editItem = function (id) {
+        var self = this;
+        self.model.read(id, function (data) {
+            self.view.render('editItem', {id: id, title: data[0].title});
+        });
+    };
 
-    if (e.keyCode === this.ESCAPE_KEY) {
-    // Discard the changes
-    input.dataset.discard = true;
-    input.blur();
-    }
-    }.bind(this));
+    /*
+     * Finishes the item editing mode successfully.
+     */
+    Controller.prototype.editItemSave = function (id, title) {
+        var self = this;
+        title = title.trim();
+
+        if (title.length !== 0) {
+            self.model.update(id, {title: title}, function () {
+                self.view.render('editItemDone', {id: id, title: title});
+            });
+        } else {
+            self.removeItem(id);
+        }
+    };
 
-    input.focus();
+    /*
+     * Cancels the item editing mode.
+     */
+    Controller.prototype.editItemCancel = function (id) {
+        var self = this;
+        self.model.read(id, function (data) {
+            self.view.render('editItemDone', {id: id, title: data[0].title});
+        });
     };
 
     /**
      * storage
      */
     Controller.prototype.removeItem = function (id) {
-    this.model.remove(id, function () {
-    this.$todoList.removeChild($$('[data-id="' + id + '"]'));
-    }.bind(this));
+        var self = this;
+        self.model.remove(id, function () {
+            self.view.render('removeItem', id);
+        });
 
-    this._filter();
+        self._filter();
     };
 
     /**
      * Will remove all completed items from the DOM and storage.
      */
     Controller.prototype.removeCompletedItems = function () {
-    this.model.read({ completed: 1 }, function (data) {
-    data.forEach(function (item) {
-    this.removeItem(item.id);
-    }.bind(this));
-    }.bind(this));
-
-    this._filter();
+        var self = this;
+        self.model.read({ completed: true }, function (data) {
+            data.forEach(function (item) {
+                self.removeItem(item.id);
+            });
+        });
+
+        self._filter();
     };
 
     /**
      *                          or not
      * @param {boolean|undefined} silent Prevent re-filtering the todo items
      */
-    Controller.prototype.toggleComplete = function (id, checkbox, silent) {
-    var completed = checkbox.checked ? 1 : 0;
-
-    this.model.update(id, { completed: completed }, function () {
-    var listItem = $$('[data-id="' + id + '"]');
-
-    if (!listItem) {
-    return;
-    }
-
-    listItem.className = completed ? 'completed' : '';
-
-    // In case it was toggled from an event and not by clicking the checkbox
-    listItem.querySelector('input').checked = completed;
-    });
-
-    if (!silent) {
-    this._filter();
-    }
+    Controller.prototype.toggleComplete = function (id, completed, silent) {
+        var self = this;
+        self.model.update(id, { completed: completed }, function () {
+            self.view.render('elementComplete', {
+                id: id,
+                completed: completed
+            });
+        });
+
+        if (!silent) {
+            self._filter();
+        }
     };
 
     /**
-     * Will toggle ALL checkboxe's on/off state and completeness of models.
+     * Will toggle ALL checkboxes' on/off state and completeness of models.
      * Just pass in the event object.
-     *
-     * @param {object} e The event object
      */
-    Controller.prototype.toggleAll = function (e) {
-    var completed = e.target.checked ? 1 : 0;
-    var query = 0;
-
-    if (completed === 0) {
-    query = 1;
-    }
-
-    this.model.read({ completed: query }, function (data) {
-    data.forEach(function (item) {
-    this.toggleComplete(item.id, e.target, true);
-    }.bind(this));
-    }.bind(this));
-
-    this._filter();
+    Controller.prototype.toggleAll = function (completed) {
+        var self = this;
+        self.model.read({ completed: !completed }, function (data) {
+            data.forEach(function (item) {
+                self.toggleComplete(item.id, completed, true);
+            });
+        });
+
+        self._filter();
     };
 
     /**
      * number of todos.
      */
     Controller.prototype._updateCount = function () {
-    var todos = this.model.getCount();
-
-    this.$todoItemCounter.innerHTML = this.view.itemCounter(todos.active);
-
-    this.$clearCompleted.innerHTML = this.view.clearCompletedButton(todos.completed);
-    this.$clearCompleted.style.display = todos.completed > 0 ? 'block' : 'none';
-
-    this.$toggleAll.checked = todos.completed === todos.total;
-
-    this._toggleFrame(todos);
-    };
-
-    /**
-     * The main body and footer elements should not be visible when there are no
-     * todos left.
-     *
-     * @param {object} todos Contains a count of all todos, and their statuses.
-     */
-    Controller.prototype._toggleFrame = function (todos) {
-    var frameDisplay = this.$main.style.display;
-    var frameVisible = frameDisplay === 'block' || frameDisplay === '';
-
-    if (todos.total === 0 && frameVisible) {
-    this.$main.style.display = 'none';
-    this.$footer.style.display = 'none';
-    }
-
-    if (todos.total > 0 && !frameVisible) {
-    this.$main.style.display = 'block';
-    this.$footer.style.display = 'block';
-    }
+        var self = this;
+        self.model.getCount(function (todos) {
+            self.view.render('updateElementCount', todos.active);
+            self.view.render('clearCompletedButton', {
+                completed: todos.completed,
+                visible: todos.completed > 0
+            });
+
+            self.view.render('toggleAll', {checked: todos.completed === todos.total});
+            self.view.render('contentBlockVisibility', {visible: todos.total > 0});
+        });
     };
 
     /**
      * @param {boolean|undefined} force  forces a re-painting of todo items.
      */
     Controller.prototype._filter = function (force) {
-    var activeRoute = this._activeRoute.charAt(0).toUpperCase() + this._activeRoute.substr(1);
+        var activeRoute = this._activeRoute.charAt(0).toUpperCase() + this._activeRoute.substr(1);
 
-    // Update the elements on the page, which change with each completed todo
-    this._updateCount();
+        // Update the elements on the page, which change with each completed todo
+        this._updateCount();
 
-    // If the last active route isn't "All", or we're switching routes, we
-    // re-create the todo item elements, calling:
-    //   this.show[All|Active|Completed]();
-    if (force || this._lastActiveRoute !== 'All' || this._lastActiveRoute !== activeRoute) {
-    this['show' + activeRoute]();
-    }
+        // If the last active route isn't "All", or we're switching routes, we
+        // re-create the todo item elements, calling:
+        //   this.show[All|Active|Completed]();
+        if (force || this._lastActiveRoute !== 'All' || this._lastActiveRoute !== activeRoute) {
+            this['show' + activeRoute]();
+        }
 
-    this._lastActiveRoute = activeRoute;
+        this._lastActiveRoute = activeRoute;
     };
 
     /**
      * Simply updates the filter nav's selected states
      */
-    Controller.prototype._updateFilterState = function () {
-    var currentPage = this._getCurrentPage() || '';
-
-    // Store a reference to the active route, allowing us to re-filter todo
-    // items as they are marked complete or incomplete.
-    this._activeRoute = currentPage;
-
-    if (currentPage === '') {
-    this._activeRoute = 'All';
-    }
+    Controller.prototype._updateFilterState = function (currentPage) {
+        // Store a reference to the active route, allowing us to re-filter todo
+        // items as they are marked complete or incomplete.
+        this._activeRoute = currentPage;
 
-    this._filter();
+        if (currentPage === '') {
+            this._activeRoute = 'All';
+        }
 
-    // Remove all other selected states. We loop through all of them in case the
-    // UI gets in a funky state with two selected.
-    $('#filters .selected').each(function (item) {
-    item.className = '';
-    });
+        this._filter();
 
-    $$('#filters [href="#/' + currentPage + '"]').className = 'selected';
-    };
-
-    /**
-     * A getter for getting the current page
-     */
-    Controller.prototype._getCurrentPage = function () {
-    return document.location.hash.split('/')[1];
+        this.view.render('setFilter', currentPage);
     };
 
     // Export to window
+    window.app = window.app || {};
     window.app.Controller = Controller;
 })(window);
index 58a0e9f..2af956f 100644 (file)
@@ -1,17 +1,52 @@
+/*global NodeList */
 (function (window) {
     'use strict';
 
-    // Cache the querySelector/All for easier and faster reuse
-    window.$ = document.querySelectorAll.bind(document);
-    window.$$ = document.querySelector.bind(document);
-
-    // Allow for looping on Objects by chaining:
-    // $('.foo').each(function () {})
-    Object.prototype.each = function (callback) {
-    for (var x in this) {
-    if (this.hasOwnProperty(x)) {
-    callback.call(this, this[x]);
-    }
-    }
+    // Get element(s) by CSS selector:
+    window.qs = function (selector, scope) {
+        return (scope || document).querySelector(selector);
     };
+    window.qsa = function (selector, scope) {
+        return (scope || document).querySelectorAll(selector);
+    };
+
+    // addEventListener wrapper:
+    window.$on = function (target, type, callback, useCapture) {
+        target.addEventListener(type, callback, !!useCapture);
+    };
+
+    // Attach a handler to event for all elements that match the selector,
+    // now or in the future, based on a root element
+    window.$delegate = function (target, selector, type, handler) {
+        function dispatchEvent(event) {
+            var targetElement = event.target;
+            var potentialElements = window.qsa(selector, target);
+            var hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0;
+
+            if (hasMatch) {
+                handler.call(targetElement, event);
+            }
+        }
+
+        // https://developer.mozilla.org/en-US/docs/Web/Events/blur
+        var useCapture = type === 'blur' || type === 'focus';
+
+        window.$on(target, type, dispatchEvent, useCapture);
+    };
+
+    // Find the element's parent with the given tag name:
+    // $parent(qs('a'), 'div');
+    window.$parent = function (element, tagName) {
+        if (!element.parentNode) {
+            return;
+        }
+        if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase()) {
+            return element.parentNode;
+        }
+        return window.$parent(element.parentNode, tagName);
+    };
+
+    // Allow for looping on nodes by chaining:
+    // qsa('.foo').forEach(function () {})
+    NodeList.prototype.forEach = Array.prototype.forEach;
 })(window);
index e014674..d8a7319 100644 (file)
@@ -8,7 +8,7 @@
      * @param {object} storage A reference to the client side storage class
      */
     function Model(storage) {
-    this.storage = storage;
+        this.storage = storage;
     }
 
     /**
      * @param {function} [callback] The callback to fire after the model is created
      */
     Model.prototype.create = function (title, callback) {
-    title = title || '';
-    callback = callback || function () {};
+        title = title || '';
+        callback = callback || function () {};
 
-    var newItem = {
-    title: title.trim(),
-    completed: 0
-    };
+        var newItem = {
+            title: title.trim(),
+            completed: false
+        };
 
-    this.storage.save(newItem, callback);
+        this.storage.save(newItem, callback);
     };
 
     /**
      * model.read({ foo: 'bar', hello: 'world' });
      */
     Model.prototype.read = function (query, callback) {
-    var queryType = typeof query;
-    callback = callback || function () {};
+        var queryType = typeof query;
+        callback = callback || function () {};
 
-    if (queryType === 'function') {
-    callback = query;
-    return this.storage.findAll(callback);
-    } else if (queryType === 'string' || queryType === 'number') {
-    this.storage.find({ id: query }, callback);
-    } else {
-    this.storage.find(query, callback);
-    }
+        if (queryType === 'function') {
+            callback = query;
+            return this.storage.findAll(callback);
+        } else if (queryType === 'string' || queryType === 'number') {
+            query = parseInt(query, 10);
+            this.storage.find({ id: query }, callback);
+        } else {
+            this.storage.find(query, callback);
+        }
     };
 
     /**
@@ -67,7 +68,7 @@
      * @param {function} callback The callback to fire when the update is complete.
      */
     Model.prototype.update = function (id, data, callback) {
-    this.storage.save(id, data, callback);
+        this.storage.save(data, callback, id);
     };
 
     /**
@@ -77,7 +78,7 @@
      * @param {function} callback The callback to fire when the removal is complete.
      */
     Model.prototype.remove = function (id, callback) {
-    this.storage.remove(id, callback);
+        this.storage.remove(id, callback);
     };
 
     /**
      * @param {function} callback The callback to fire when the storage is wiped.
      */
     Model.prototype.removeAll = function (callback) {
-    this.storage.drop(callback);
+        this.storage.drop(callback);
     };
 
     /**
      * Returns a count of all todos
      */
-    Model.prototype.getCount = function () {
-    var todos = {
-    active: 0,
-    completed: 0,
-    total: 0
-    };
-
-    this.storage.findAll(function (data) {
-    data.each(function (todo) {
-    if (todo.completed === 1) {
-    todos.completed++;
-    } else {
-    todos.active++;
-    }
+    Model.prototype.getCount = function (callback) {
+        var todos = {
+            active: 0,
+            completed: 0,
+            total: 0
+        };
 
-    todos.total++;
-    });
-    });
+        this.storage.findAll(function (data) {
+            data.forEach(function (todo) {
+                if (todo.completed) {
+                    todos.completed++;
+                } else {
+                    todos.active++;
+                }
 
-    return todos;
+                todos.total++;
+            });
+            callback(todos);
+        });
     };
 
     // Export to window
+    window.app = window.app || {};
     window.app.Model = Model;
 })(window);
index 9a7da0d..fe682c8 100644 (file)
@@ -2,8 +2,7 @@
 (function (window) {
     'use strict';
 
-    var localStorage = {}; // Benchmark modification: Avoid disk access.
-
+    var MemoryStorage = {};
     /**
      * Creates a new client side storage object and will create an empty
      * collection if no collection already exists.
      * real life you probably would be making AJAX calls
      */
     function Store(name, callback) {
-    var data;
-    var dbName;
-
-    callback = callback || function () {};
+        callback = callback || function () {};
 
-    dbName = this._dbName = name;
+        this._dbName = name;
 
-    if (!localStorage[dbName]) {
-    data = {
-    todos: []
-    };
+        if (!MemoryStorage[name]) {
+            var data = {
+                todos: []
+            };
 
-    localStorage[dbName] = JSON.stringify(data);
-    }
+            MemoryStorage[name] = JSON.stringify(data);
+        }
 
-    callback.call(this, JSON.parse(localStorage[dbName]));
+        callback.call(this, JSON.parse(MemoryStorage[name]));
     }
 
     /**
      * });
      */
     Store.prototype.find = function (query, callback) {
-    if (!callback) {
-    return;
-    }
-
-    var todos = JSON.parse(localStorage[this._dbName]).todos;
-
-    callback.call(this, todos.filter(function (todo) {
-    for (var q in query) {
-    return query[q] === todo[q];
-    }
-    }));
+        if (!callback) {
+            return;
+        }
+
+        var todos = JSON.parse(MemoryStorage[this._dbName]).todos;
+
+        callback.call(this, todos.filter(function (todo) {
+            for (var q in query) {
+                if (query[q] !== todo[q]) {
+                    return false;
+                }
+            }
+            return true;
+        }));
     };
 
     /**
      * @param {function} callback The callback to fire upon retrieving data
      */
     Store.prototype.findAll = function (callback) {
-    callback = callback || function () {};
-    callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
+        callback = callback || function () {};
+        callback.call(this, JSON.parse(MemoryStorage[this._dbName]).todos);
     };
 
     /**
      * Will save the given data to the DB. If no item exists it will create a new
      * item, otherwise it'll simply update an existing item's properties
      *
-     * @param {number} id An optional param to enter an ID of an item to update
-     * @param {object} data The data to save back into the DB
+     * @param {object} updateData The data to save back into the DB
      * @param {function} callback The callback to fire after saving
+     * @param {number} id An optional param to enter an ID of an item to update
      */
-    Store.prototype.save = function (id, updateData, callback) {
-    var data = JSON.parse(localStorage[this._dbName]);
-    var todos = data.todos;
-
-    callback = callback || function () {};
-
-    // If an ID was actually given, find the item and update each property
-    if (typeof id !== 'object') {
-    for (var i = 0; i < todos.length; i++) {
-    if (todos[i].id == id) {
-    for (var x in updateData) {
-    todos[i][x] = updateData[x];
-    }
-    }
-    }
-
-    localStorage[this._dbName] = JSON.stringify(data);
-    callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
-    } else {
-    callback = updateData;
-
-    updateData = id;
-
-    // Generate an ID
-    updateData.id = new Date().getTime();
-
-    todos.push(updateData);
-    localStorage[this._dbName] = JSON.stringify(data);
-    callback.call(this, [updateData]);
-    }
+    Store.prototype.save = function (updateData, callback, id) {
+        var data = JSON.parse(MemoryStorage[this._dbName]);
+        var todos = data.todos;
+
+        callback = callback || function () {};
+
+        // If an ID was actually given, find the item and update each property
+        if (id) {
+            for (var i = 0; i < todos.length; i++) {
+                if (todos[i].id === id) {
+                    for (var key in updateData) {
+                        todos[i][key] = updateData[key];
+                    }
+                    break;
+                }
+            }
+
+            MemoryStorage[this._dbName] = JSON.stringify(data);
+            callback.call(this, todos);
+        } else {
+            // Generate an ID
+            updateData.id = new Date().getTime();
+
+            todos.push(updateData);
+            MemoryStorage[this._dbName] = JSON.stringify(data);
+            callback.call(this, [updateData]);
+        }
     };
 
     /**
      * @param {function} callback The callback to fire after saving
      */
     Store.prototype.remove = function (id, callback) {
-    var data = JSON.parse(localStorage[this._dbName]);
-    var todos = data.todos;
-
-    for (var i = 0; i < todos.length; i++) {
-    if (todos[i].id == id) {
-    todos.splice(i, 1);
-    break;
-    }
-    }
-
-    localStorage[this._dbName] = JSON.stringify(data);
-    callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
+        var data = JSON.parse(MemoryStorage[this._dbName]);
+        var todos = data.todos;
+
+        for (var i = 0; i < todos.length; i++) {
+            if (todos[i].id == id) {
+                todos.splice(i, 1);
+                break;
+            }
+        }
+
+        MemoryStorage[this._dbName] = JSON.stringify(data);
+        callback.call(this, todos);
     };
 
     /**
      * @param {function} callback The callback to fire after dropping the data
      */
     Store.prototype.drop = function (callback) {
-    localStorage[this._dbName] = JSON.stringify({todos: []});
-    callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
+        var data = {todos: []};
+        MemoryStorage[this._dbName] = JSON.stringify(data);
+        callback.call(this, data.todos);
     };
 
     // Export to window
+    window.app = window.app || {};
     window.app.Store = Store;
 })(window);
diff --git a/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/template.js b/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/js/template.js
new file mode 100644 (file)
index 0000000..43724c6
--- /dev/null
@@ -0,0 +1,114 @@
+/*jshint laxbreak:true */
+(function (window) {
+    'use strict';
+
+    var htmlEscapes = {
+        '&': '&amp;',
+        '<': '&lt;',
+        '>': '&gt;',
+        '"': '&quot;',
+        '\'': '&#x27;',
+        '`': '&#x60;'
+    };
+
+    var escapeHtmlChar = function (chr) {
+        return htmlEscapes[chr];
+    };
+
+    var reUnescapedHtml = /[&<>"'`]/g;
+    var reHasUnescapedHtml = new RegExp(reUnescapedHtml.source);
+
+    var escape = function (string) {
+        return (string && reHasUnescapedHtml.test(string))
+            ? string.replace(reUnescapedHtml, escapeHtmlChar)
+            : string;
+    };
+
+    /**
+     * Sets up defaults for all the Template methods such as a default template
+     *
+     * @constructor
+     */
+    function Template() {
+        this.defaultTemplate
+        =    '<li data-id="{{id}}" class="{{completed}}">'
+        +        '<div class="view">'
+        +            '<input class="toggle" type="checkbox" {{checked}}>'
+        +            '<label>{{title}}</label>'
+        +            '<button class="destroy"></button>'
+        +        '</div>'
+        +    '</li>';
+    }
+
+    /**
+     * Creates an <li> HTML string and returns it for placement in your app.
+     *
+     * NOTE: In real life you should be using a templating engine such as Mustache
+     * or Handlebars, however, this is a vanilla JS example.
+     *
+     * @param {object} data The object containing keys you want to find in the
+     *                      template to replace.
+     * @returns {string} HTML String of an <li> element
+     *
+     * @example
+     * view.show({
+     *    id: 1,
+     *    title: "Hello World",
+     *    completed: 0,
+     * });
+     */
+    Template.prototype.show = function (data) {
+        var i, l;
+        var view = '';
+
+        for (i = 0, l = data.length; i < l; i++) {
+            var template = this.defaultTemplate;
+            var completed = '';
+            var checked = '';
+
+            if (data[i].completed) {
+                completed = 'completed';
+                checked = 'checked';
+            }
+
+            template = template.replace('{{id}}', data[i].id);
+            template = template.replace('{{title}}', escape(data[i].title));
+            template = template.replace('{{completed}}', completed);
+            template = template.replace('{{checked}}', checked);
+
+            view = view + template;
+        }
+
+        return view;
+    };
+
+    /**
+     * Displays a counter of how many to dos are left to complete
+     *
+     * @param {number} activeTodos The number of active todos.
+     * @returns {string} String containing the count
+     */
+    Template.prototype.itemCounter = function (activeTodos) {
+        var plural = activeTodos === 1 ? '' : 's';
+
+        return '<strong>' + activeTodos + '</strong> item' + plural + ' left';
+    };
+
+    /**
+     * Updates the text within the "Clear completed" button
+     *
+     * @param  {[type]} completedTodos The number of completed todos.
+     * @returns {string} String containing the count
+     */
+    Template.prototype.clearCompletedButton = function (completedTodos) {
+        if (completedTodos > 0) {
+            return 'Clear completed';
+        } else {
+            return '';
+        }
+    };
+
+    // Export to window
+    window.app = window.app || {};
+    window.app.Template = Template;
+})(window);
index d82b098..68de830 100644 (file)
-/*jshint laxbreak:true */
+/*global qs, qsa, $on, $parent, $delegate */
+
 (function (window) {
     'use strict';
 
     /**
-     * Sets up defaults for all the View methods such as a default template
-     *
-     * @constructor
-     */
-    function View() {
-    this.defaultTemplate
-    =    '<li data-id="{{id}}" class="{{completed}}">'
-    +        '<div class="view">'
-    +            '<input class="toggle" type="checkbox" {{checked}}>'
-    +            '<label>{{title}}</label>'
-    +            '<button class="destroy"></button>'
-    +        '</div>'
-    +    '</li>';
-    }
+         * View that abstracts away the browser's DOM completely.
+         * It has two simple entry points:
+         *
+         *   - bind(eventName, handler)
+         *     Takes a todo application event and registers the handler
+         *   - render(command, parameterObject)
+         *     Renders the given command with the options
+         */
+    function View(template) {
+        this.template = template;
 
-    /**
-     * Creates an <li> HTML string and returns it for placement in your app.
-     *
-     * NOTE: In real life you should be using a templating engine such as Mustache
-     * or Handlebars, however, this is a vanilla JS example.
-     *
-     * @param {object} data The object containing keys you want to find in the
-     *                      template to replace.
-     * @returns {string} HTML String of an <li> element
-     *
-     * @example
-     * view.show({
-     *    id: 1,
-     *    title: "Hello World",
-     *    completed: 0,
-     * });
-     */
-    View.prototype.show = function (data) {
-    var i, l;
-    var view = '';
-
-    for (i = 0, l = data.length; i < l; i++) {
-    var template = this.defaultTemplate;
-    var completed = '';
-    var checked = '';
-
-    if (data[i].completed === 1) {
-    completed = 'completed';
-    checked = 'checked';
+        this.ENTER_KEY = 13;
+        this.ESCAPE_KEY = 27;
+
+        this.$todoList = qs('.todo-list');
+        this.$todoItemCounter = qs('.todo-count');
+        this.$clearCompleted = qs('.clear-completed');
+        this.$main = qs('.main');
+        this.$footer = qs('.footer');
+        this.$toggleAll = qs('.toggle-all');
+        this.$newTodo = qs('.new-todo');
     }
 
-    template = template.replace('{{id}}', data[i].id);
-    template = template.replace('{{title}}', data[i].title);
-    template = template.replace('{{completed}}', completed);
-    template = template.replace('{{checked}}', checked);
+    View.prototype._removeItem = function (id) {
+        var elem = qs('[data-id="' + id + '"]');
 
-    view = view + template;
-    }
+        if (elem) {
+            this.$todoList.removeChild(elem);
+        }
+    };
 
-    return view;
+    View.prototype._clearCompletedButton = function (completedCount, visible) {
+        this.$clearCompleted.innerHTML = this.template.clearCompletedButton(completedCount);
+        this.$clearCompleted.style.display = visible ? 'block' : 'none';
     };
 
-    /**
-     * Displays a counter of how many to dos are left to complete
-     *
-     * @param {number} activeTodos The number of active todos.
-     * @returns {string} String containing the count
-     */
-    View.prototype.itemCounter = function (activeTodos) {
-    var plural = activeTodos === 1 ? '' : 's';
-
-    return '<strong>' + activeTodos + '</strong> item' + plural + ' left';
+    View.prototype._setFilter = function (currentPage) {
+        qs('.filters .selected').className = '';
+        qs('.filters [href="#/' + currentPage + '"]').className = 'selected';
     };
 
-    /**
-     * Updates the text within the "Clear completed" button
-     *
-     * @param  {[type]} completedTodos The number of completed todos.
-     * @returns {string} String containing the count
-     */
-    View.prototype.clearCompletedButton = function (completedTodos) {
-    if (completedTodos > 0) {
-    return 'Clear completed (' + completedTodos + ')';
-    } else {
-    return '';
-    }
+    View.prototype._elementComplete = function (id, completed) {
+        var listItem = qs('[data-id="' + id + '"]');
+
+        if (!listItem) {
+            return;
+        }
+
+        listItem.className = completed ? 'completed' : '';
+
+        // In case it was toggled from an event and not by clicking the checkbox
+        qs('input', listItem).checked = completed;
+    };
+
+    View.prototype._editItem = function (id, title) {
+        var listItem = qs('[data-id="' + id + '"]');
+
+        if (!listItem) {
+            return;
+        }
+
+        listItem.className = listItem.className + ' editing';
+
+        var input = document.createElement('input');
+        input.className = 'edit';
+
+        listItem.appendChild(input);
+        input.focus();
+        input.value = title;
+    };
+
+    View.prototype._editItemDone = function (id, title) {
+        var listItem = qs('[data-id="' + id + '"]');
+
+        if (!listItem) {
+            return;
+        }
+
+        var input = qs('input.edit', listItem);
+        listItem.removeChild(input);
+
+        listItem.className = listItem.className.replace('editing', '');
+
+        qsa('label', listItem).forEach(function (label) {
+            label.textContent = title;
+        });
+    };
+
+    View.prototype.render = function (viewCmd, parameter) {
+        var self = this;
+        var viewCommands = {
+            showEntries: function () {
+                self.$todoList.innerHTML = self.template.show(parameter);
+            },
+            removeItem: function () {
+                self._removeItem(parameter);
+            },
+            updateElementCount: function () {
+                self.$todoItemCounter.innerHTML = self.template.itemCounter(parameter);
+            },
+            clearCompletedButton: function () {
+                self._clearCompletedButton(parameter.completed, parameter.visible);
+            },
+            contentBlockVisibility: function () {
+                self.$main.style.display = self.$footer.style.display = parameter.visible ? 'block' : 'none';
+            },
+            toggleAll: function () {
+                self.$toggleAll.checked = parameter.checked;
+            },
+            setFilter: function () {
+                self._setFilter(parameter);
+            },
+            clearNewTodo: function () {
+                self.$newTodo.value = '';
+            },
+            elementComplete: function () {
+                self._elementComplete(parameter.id, parameter.completed);
+            },
+            editItem: function () {
+                self._editItem(parameter.id, parameter.title);
+            },
+            editItemDone: function () {
+                self._editItemDone(parameter.id, parameter.title);
+            }
+        };
+
+        viewCommands[viewCmd]();
+    };
+
+    View.prototype._itemId = function (element) {
+        var li = $parent(element, 'li');
+        return parseInt(li.dataset.id, 10);
+    };
+
+    View.prototype._bindItemEditDone = function (handler) {
+        var self = this;
+        $delegate(self.$todoList, 'li .edit', 'blur', function () {
+            if (!this.dataset.iscanceled) {
+                handler({
+                    id: self._itemId(this),
+                    title: this.value
+                });
+            }
+        });
+
+        $delegate(self.$todoList, 'li .edit', 'keypress', function (event) {
+            if (event.keyCode === self.ENTER_KEY) {
+                // Remove the cursor from the input when you hit enter just like if it
+                // were a real form
+                this.blur();
+            }
+        });
+    };
+
+    View.prototype._bindItemEditCancel = function (handler) {
+        var self = this;
+        $delegate(self.$todoList, 'li .edit', 'keyup', function (event) {
+            if (event.keyCode === self.ESCAPE_KEY) {
+                this.dataset.iscanceled = true;
+                this.blur();
+
+                handler({id: self._itemId(this)});
+            }
+        });
+    };
+
+    View.prototype.bind = function (event, handler) {
+        var self = this;
+        if (event === 'newTodo') {
+            $on(self.$newTodo, 'change', function () {
+                handler(self.$newTodo.value);
+            });
+
+        } else if (event === 'removeCompleted') {
+            $on(self.$clearCompleted, 'click', function () {
+                handler();
+            });
+
+        } else if (event === 'toggleAll') {
+            $on(self.$toggleAll, 'click', function () {
+                handler({completed: this.checked});
+            });
+
+        } else if (event === 'itemEdit') {
+            $delegate(self.$todoList, 'li label', 'dblclick', function () {
+                handler({id: self._itemId(this)});
+            });
+
+        } else if (event === 'itemRemove') {
+            $delegate(self.$todoList, '.destroy', 'click', function () {
+                handler({id: self._itemId(this)});
+            });
+
+        } else if (event === 'itemToggle') {
+            $delegate(self.$todoList, '.toggle', 'click', function () {
+                handler({
+                    id: self._itemId(this),
+                    completed: this.checked
+                });
+            });
+
+        } else if (event === 'itemEditDone') {
+            self._bindItemEditDone(handler);
+
+        } else if (event === 'itemEditCancel') {
+            self._bindItemEditCancel(handler);
+        }
     };
 
     // Export to window
+    window.app = window.app || {};
     window.app.View = View;
-})(window);
+}(window));
diff --git a/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-app-css/index.css b/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-app-css/index.css
new file mode 100644 (file)
index 0000000..22ab9e2
--- /dev/null
@@ -0,0 +1,378 @@
+html,
+body {
+    margin: 0;
+    padding: 0;
+}
+
+button {
+    margin: 0;
+    padding: 0;
+    border: 0;
+    background: none;
+    font-size: 100%;
+    vertical-align: baseline;
+    font-family: inherit;
+    font-weight: inherit;
+    color: inherit;
+    -webkit-appearance: none;
+    appearance: none;
+    -webkit-font-smoothing: antialiased;
+    -moz-font-smoothing: antialiased;
+    font-smoothing: antialiased;
+}
+
+body {
+    font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
+    line-height: 1.4em;
+    background: #f5f5f5;
+    color: #4d4d4d;
+    min-width: 230px;
+    max-width: 550px;
+    margin: 0 auto;
+    -webkit-font-smoothing: antialiased;
+    -moz-font-smoothing: antialiased;
+    font-smoothing: antialiased;
+    font-weight: 300;
+}
+
+button,
+input[type="checkbox"] {
+    outline: none;
+}
+
+.hidden {
+    display: none;
+}
+
+.todoapp {
+    background: #fff;
+    margin: 130px 0 40px 0;
+    position: relative;
+    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
+                0 25px 50px 0 rgba(0, 0, 0, 0.1);
+}
+
+.todoapp input::-webkit-input-placeholder {
+    font-style: italic;
+    font-weight: 300;
+    color: #e6e6e6;
+}
+
+.todoapp input::-moz-placeholder {
+    font-style: italic;
+    font-weight: 300;
+    color: #e6e6e6;
+}
+
+.todoapp input::input-placeholder {
+    font-style: italic;
+    font-weight: 300;
+    color: #e6e6e6;
+}
+
+.todoapp h1 {
+    position: absolute;
+    top: -155px;
+    width: 100%;
+    font-size: 100px;
+    font-weight: 100;
+    text-align: center;
+    color: rgba(175, 47, 47, 0.15);
+    -webkit-text-rendering: optimizeLegibility;
+    -moz-text-rendering: optimizeLegibility;
+    text-rendering: optimizeLegibility;
+}
+
+.new-todo,
+.edit {
+    position: relative;
+    margin: 0;
+    width: 100%;
+    font-size: 24px;
+    font-family: inherit;
+    font-weight: inherit;
+    line-height: 1.4em;
+    border: 0;
+    outline: none;
+    color: inherit;
+    padding: 6px;
+    border: 1px solid #999;
+    box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
+    box-sizing: border-box;
+    -webkit-font-smoothing: antialiased;
+    -moz-font-smoothing: antialiased;
+    font-smoothing: antialiased;
+}
+
+.new-todo {
+    padding: 16px 16px 16px 60px;
+    border: none;
+    background: rgba(0, 0, 0, 0.003);
+    box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
+}
+
+.main {
+    position: relative;
+    z-index: 2;
+    border-top: 1px solid #e6e6e6;
+}
+
+label[for='toggle-all'] {
+    display: none;
+}
+
+.toggle-all {
+    position: absolute;
+    top: -55px;
+    left: -12px;
+    width: 60px;
+    height: 34px;
+    text-align: center;
+    border: none; /* Mobile Safari */
+}
+
+.toggle-all:before {
+    content: '❯';
+    font-size: 22px;
+    color: #e6e6e6;
+    padding: 10px 27px 10px 27px;
+}
+
+.toggle-all:checked:before {
+    color: #737373;
+}
+
+.todo-list {
+    margin: 0;
+    padding: 0;
+    list-style: none;
+}
+
+.todo-list li {
+    position: relative;
+    font-size: 24px;
+    border-bottom: 1px solid #ededed;
+}
+
+.todo-list li:last-child {
+    border-bottom: none;
+}
+
+.todo-list li.editing {
+    border-bottom: none;
+    padding: 0;
+}
+
+.todo-list li.editing .edit {
+    display: block;
+    width: 506px;
+    padding: 13px 17px 12px 17px;
+    margin: 0 0 0 43px;
+}
+
+.todo-list li.editing .view {
+    display: none;
+}
+
+.todo-list li .toggle {
+    text-align: center;
+    width: 40px;
+    /* auto, since non-WebKit browsers doesn't support input styling */
+    height: auto;
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    margin: auto 0;
+    border: none; /* Mobile Safari */
+    -webkit-appearance: none;
+    appearance: none;
+}
+
+.todo-list li .toggle:after {
+    content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
+}
+
+.todo-list li .toggle:checked:after {
+    content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
+}
+
+.todo-list li label {
+    white-space: pre-line;
+    word-break: break-all;
+    padding: 15px 60px 15px 15px;
+    margin-left: 45px;
+    display: block;
+    line-height: 1.2;
+    transition: color 0.4s;
+}
+
+.todo-list li.completed label {
+    color: #d9d9d9;
+    text-decoration: line-through;
+}
+
+.todo-list li .destroy {
+    display: none;
+    position: absolute;
+    top: 0;
+    right: 10px;
+    bottom: 0;
+    width: 40px;
+    height: 40px;
+    margin: auto 0;
+    font-size: 30px;
+    color: #cc9a9a;
+    margin-bottom: 11px;
+    transition: color 0.2s ease-out;
+}
+
+.todo-list li .destroy:hover {
+    color: #af5b5e;
+}
+
+.todo-list li .destroy:after {
+    content: '×';
+}
+
+.todo-list li:hover .destroy {
+    display: block;
+}
+
+.todo-list li .edit {
+    display: none;
+}
+
+.todo-list li.editing:last-child {
+    margin-bottom: -1px;
+}
+
+.footer {
+    color: #777;
+    padding: 10px 15px;
+    height: 20px;
+    text-align: center;
+    border-top: 1px solid #e6e6e6;
+}
+
+.footer:before {
+    content: '';
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    height: 50px;
+    overflow: hidden;
+    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
+                0 8px 0 -3px #f6f6f6,
+                0 9px 1px -3px rgba(0, 0, 0, 0.2),
+                0 16px 0 -6px #f6f6f6,
+                0 17px 2px -6px rgba(0, 0, 0, 0.2);
+}
+
+.todo-count {
+    float: left;
+    text-align: left;
+}
+
+.todo-count strong {
+    font-weight: 300;
+}
+
+.filters {
+    margin: 0;
+    padding: 0;
+    list-style: none;
+    position: absolute;
+    right: 0;
+    left: 0;
+}
+
+.filters li {
+    display: inline;
+}
+
+.filters li a {
+    color: inherit;
+    margin: 3px;
+    padding: 3px 7px;
+    text-decoration: none;
+    border: 1px solid transparent;
+    border-radius: 3px;
+}
+
+.filters li a.selected,
+.filters li a:hover {
+    border-color: rgba(175, 47, 47, 0.1);
+}
+
+.filters li a.selected {
+    border-color: rgba(175, 47, 47, 0.2);
+}
+
+.clear-completed,
+html .clear-completed:active {
+    float: right;
+    position: relative;
+    line-height: 20px;
+    text-decoration: none;
+    cursor: pointer;
+    position: relative;
+}
+
+.clear-completed:hover {
+    text-decoration: underline;
+}
+
+.info {
+    margin: 65px auto 0;
+    color: #bfbfbf;
+    font-size: 10px;
+    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+    text-align: center;
+}
+
+.info p {
+    line-height: 1;
+}
+
+.info a {
+    color: inherit;
+    text-decoration: none;
+    font-weight: 400;
+}
+
+.info a:hover {
+    text-decoration: underline;
+}
+
+/*
+    Hack to remove background from Mobile Safari.
+    Can't use it globally since it destroys checkboxes in Firefox
+*/
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+    .toggle-all,
+    .todo-list li .toggle {
+        background: none;
+    }
+
+    .todo-list li .toggle {
+        height: 40px;
+    }
+
+    .toggle-all {
+        -webkit-transform: rotate(90deg);
+        transform: rotate(90deg);
+        -webkit-appearance: none;
+        appearance: none;
+    }
+}
+
+@media (max-width: 430px) {
+    .footer {
+        height: 50px;
+    }
+
+    .filters {
+        bottom: 10px;
+    }
+}
diff --git a/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-common/base.css b/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-common/base.css
new file mode 100644 (file)
index 0000000..4d25d3c
--- /dev/null
@@ -0,0 +1,141 @@
+hr {
+    margin: 20px 0;
+    border: 0;
+    border-top: 1px dashed #c5c5c5;
+    border-bottom: 1px dashed #f7f7f7;
+}
+
+.learn a {
+    font-weight: normal;
+    text-decoration: none;
+    color: #b83f45;
+}
+
+.learn a:hover {
+    text-decoration: underline;
+    color: #787e7e;
+}
+
+.learn h3,
+.learn h4,
+.learn h5 {
+    margin: 10px 0;
+    font-weight: 500;
+    line-height: 1.2;
+    color: #000;
+}
+
+.learn h3 {
+    font-size: 24px;
+}
+
+.learn h4 {
+    font-size: 18px;
+}
+
+.learn h5 {
+    margin-bottom: 0;
+    font-size: 14px;
+}
+
+.learn ul {
+    padding: 0;
+    margin: 0 0 30px 25px;
+}
+
+.learn li {
+    line-height: 20px;
+}
+
+.learn p {
+    font-size: 15px;
+    font-weight: 300;
+    line-height: 1.3;
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+#issue-count {
+    display: none;
+}
+
+.quote {
+    border: none;
+    margin: 20px 0 60px 0;
+}
+
+.quote p {
+    font-style: italic;
+}
+
+.quote p:before {
+    content: '“';
+    font-size: 50px;
+    opacity: .15;
+    position: absolute;
+    top: -20px;
+    left: 3px;
+}
+
+.quote p:after {
+    content: '”';
+    font-size: 50px;
+    opacity: .15;
+    position: absolute;
+    bottom: -42px;
+    right: 3px;
+}
+
+.quote footer {
+    position: absolute;
+    bottom: -40px;
+    right: 0;
+}
+
+.quote footer img {
+    border-radius: 3px;
+}
+
+.quote footer a {
+    margin-left: 5px;
+    vertical-align: middle;
+}
+
+.speech-bubble {
+    position: relative;
+    padding: 10px;
+    background: rgba(0, 0, 0, .04);
+    border-radius: 5px;
+}
+
+.speech-bubble:after {
+    content: '';
+    position: absolute;
+    top: 100%;
+    right: 30px;
+    border: 13px solid transparent;
+    border-top-color: rgba(0, 0, 0, .04);
+}
+
+.learn-bar > .learn {
+    position: absolute;
+    width: 272px;
+    top: 8px;
+    left: -300px;
+    padding: 10px;
+    border-radius: 5px;
+    background-color: rgba(255, 255, 255, .6);
+    transition-property: left;
+    transition-duration: 500ms;
+}
+
+@media (min-width: 899px) {
+    .learn-bar {
+        width: auto;
+        padding-left: 300px;
+    }
+
+    .learn-bar > .learn {
+        left: 8px;
+    }
+}
diff --git a/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-common/base.js b/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/node_modules/todomvc-common/base.js
new file mode 100644 (file)
index 0000000..81e006b
--- /dev/null
@@ -0,0 +1,249 @@
+/* global _ */
+(function () {
+    'use strict';
+
+    /* jshint ignore:start */
+    // Underscore's Template Module
+    // Courtesy of underscorejs.org
+    var _ = (function (_) {
+        _.defaults = function (object) {
+            if (!object) {
+                return object;
+            }
+            for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
+                var iterable = arguments[argsIndex];
+                if (iterable) {
+                    for (var key in iterable) {
+                        if (object[key] == null) {
+                            object[key] = iterable[key];
+                        }
+                    }
+                }
+            }
+            return object;
+        }
+
+        // By default, Underscore uses ERB-style template delimiters, change the
+        // following template settings to use alternative delimiters.
+        _.templateSettings = {
+            evaluate    : /<%([\s\S]+?)%>/g,
+            interpolate : /<%=([\s\S]+?)%>/g,
+            escape      : /<%-([\s\S]+?)%>/g
+        };
+
+        // When customizing `templateSettings`, if you don't want to define an
+        // interpolation, evaluation or escaping regex, we need one that is
+        // guaranteed not to match.
+        var noMatch = /(.)^/;
+
+        // Certain characters need to be escaped so that they can be put into a
+        // string literal.
+        var escapes = {
+            "'":      "'",
+            '\\':     '\\',
+            '\r':     'r',
+            '\n':     'n',
+            '\t':     't',
+            '\u2028': 'u2028',
+            '\u2029': 'u2029'
+        };
+
+        var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
+
+        // JavaScript micro-templating, similar to John Resig's implementation.
+        // Underscore templating handles arbitrary delimiters, preserves whitespace,
+        // and correctly escapes quotes within interpolated code.
+        _.template = function(text, data, settings) {
+            var render;
+            settings = _.defaults({}, settings, _.templateSettings);
+
+            // Combine delimiters into one regular expression via alternation.
+            var matcher = new RegExp([
+                (settings.escape || noMatch).source,
+                (settings.interpolate || noMatch).source,
+                (settings.evaluate || noMatch).source
+            ].join('|') + '|$', 'g');
+
+            // Compile the template source, escaping string literals appropriately.
+            var index = 0;
+            var source = "__p+='";
+            text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+                source += text.slice(index, offset)
+                    .replace(escaper, function(match) { return '\\' + escapes[match]; });
+
+                if (escape) {
+                    source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+                }
+                if (interpolate) {
+                    source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+                }
+                if (evaluate) {
+                    source += "';\n" + evaluate + "\n__p+='";
+                }
+                index = offset + match.length;
+                return match;
+            });
+            source += "';\n";
+
+            // If a variable is not specified, place data values in local scope.
+            if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+            source = "var __t,__p='',__j=Array.prototype.join," +
+                "print=function(){__p+=__j.call(arguments,'');};\n" +
+                source + "return __p;\n";
+
+            try {
+                render = new Function(settings.variable || 'obj', '_', source);
+            } catch (e) {
+                e.source = source;
+                throw e;
+            }
+
+            if (data) return render(data, _);
+            var template = function(data) {
+                return render.call(this, data, _);
+            };
+
+            // Provide the compiled function source as a convenience for precompilation.
+            template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
+
+            return template;
+        };
+
+        return _;
+    })({});
+
+    if (location.hostname === 'todomvc.com') {
+        (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+        (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+        m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+        })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+        ga('create', 'UA-31081062-1', 'auto');
+        ga('send', 'pageview');
+    }
+    /* jshint ignore:end */
+
+    function redirect() {
+        if (location.hostname === 'tastejs.github.io') {
+            location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com');
+        }
+    }
+
+    function findRoot() {
+        var base = location.href.indexOf('examples/');
+        return location.href.substr(0, base);
+    }
+
+    function getFile(file, callback) {
+        if (!location.host) {
+            return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.');
+        }
+
+        var xhr = new XMLHttpRequest();
+
+        xhr.open('GET', findRoot() + file, true);
+        xhr.send();
+
+        xhr.onload = function () {
+            if (xhr.status === 200 && callback) {
+                callback(xhr.responseText);
+            }
+        };
+    }
+
+    function Learn(learnJSON, config) {
+        if (!(this instanceof Learn)) {
+            return new Learn(learnJSON, config);
+        }
+
+        var template, framework;
+
+        if (typeof learnJSON !== 'object') {
+            try {
+                learnJSON = JSON.parse(learnJSON);
+            } catch (e) {
+                return;
+            }
+        }
+
+        if (config) {
+            template = config.template;
+            framework = config.framework;
+        }
+
+        if (!template && learnJSON.templates) {
+            template = learnJSON.templates.todomvc;
+        }
+
+        if (!framework && document.querySelector('[data-framework]')) {
+            framework = document.querySelector('[data-framework]').dataset.framework;
+        }
+
+        this.template = template;
+
+        if (learnJSON.backend) {
+            this.frameworkJSON = learnJSON.backend;
+            this.frameworkJSON.issueLabel = framework;
+            this.append({
+                backend: true
+            });
+        } else if (learnJSON[framework]) {
+            this.frameworkJSON = learnJSON[framework];
+            this.frameworkJSON.issueLabel = framework;
+            this.append();
+        }
+
+        this.fetchIssueCount();
+    }
+
+    Learn.prototype.append = function (opts) {
+        var aside = document.createElement('aside');
+        aside.innerHTML = _.template(this.template, this.frameworkJSON);
+        aside.className = 'learn';
+
+        if (opts && opts.backend) {
+            // Remove demo link
+            var sourceLinks = aside.querySelector('.source-links');
+            var heading = sourceLinks.firstElementChild;
+            var sourceLink = sourceLinks.lastElementChild;
+            // Correct link path
+            var href = sourceLink.getAttribute('href');
+            sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http')));
+            sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML;
+        } else {
+            // Localize demo links
+            var demoLinks = aside.querySelectorAll('.demo-link');
+            Array.prototype.forEach.call(demoLinks, function (demoLink) {
+                if (demoLink.getAttribute('href').substr(0, 4) !== 'http') {
+                    demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href'));
+                }
+            });
+        }
+
+        document.body.className = (document.body.className + ' learn-bar').trim();
+        document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
+    };
+
+    Learn.prototype.fetchIssueCount = function () {
+        var issueLink = document.getElementById('issue-count-link');
+        if (issueLink) {
+            var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos');
+            var xhr = new XMLHttpRequest();
+            xhr.open('GET', url, true);
+            xhr.onload = function (e) {
+                var parsedResponse = JSON.parse(e.target.responseText);
+                if (parsedResponse instanceof Array) {
+                    var count = parsedResponse.length;
+                    if (count !== 0) {
+                        issueLink.innerHTML = 'This app has ' + count + ' open issues';
+                        document.getElementById('issue-count').style.display = 'inline';
+                    }
+                }
+            };
+            xhr.send();
+        }
+    };
+
+    redirect();
+    getFile('learn.json', Learn);
+})();
diff --git a/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/package.json b/PerformanceTests/Speedometer/resources/todomvc/vanilla-examples/vanillajs/package.json
new file mode 100644 (file)
index 0000000..58c5a80
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "private": true,
+  "dependencies": {
+    "todomvc-common": "^1.0.1",
+    "todomvc-app-css": "^2.0.1"
+  },
+  "devDependencies": {
+    "jasmine-core": "^2.0.0"
+  }
+}