├── .gitignore ├── CHANGELOG.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── build ├── angular-virtual-dom.js └── angular-virtual-dom.min.js ├── config ├── karma-angular-1.2.28.js ├── karma-angular-1.4.0-beta.3.js └── karma.js ├── examples └── hello │ ├── README.md │ ├── index.html │ ├── index.js │ └── package.json ├── files.js ├── lib ├── angular-1.2.28 │ ├── angular-mocks.js │ └── angular.js ├── angular-1.3.12 │ ├── angular-mocks.js │ └── angular.js ├── angular-1.4.0-beta.3 │ ├── angular-mocks.js │ └── angular.js ├── immutable.js ├── mori.js └── virtual-dom.js ├── package.json ├── release ├── angular-virtual-dom.js └── angular-virtual-dom.min.js ├── src ├── angular-virtual-dom.js ├── clone_tree.js ├── directive_normalize.js ├── get_attribute.js ├── link.js ├── v_if_directive.js ├── v_repeat_directive.js ├── v_root_directive.js └── virtualize.js └── test ├── clone_tree_spec.js ├── get_attribute_spec.js ├── link_spec.js ├── v_if_directive_spec.js ├── v_repeat_directive_spec.js ├── v_root_directive_spec.js └── virtualize_spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | public/app.js 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ### 0.1.1 (2015-02-14) 3 | 4 | * Fix problem with linking that mutated existing nodes without going through VNode constructor, causing the diff algorithm to get confused in some situations. 5 | 6 | 7 | ### 0.1.0 (2015-02-09) 8 | 9 | * Support directive priorities. Higher priority wins, same as in $compile. Ties are broken first by name, then by registration order. 10 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function (grunt) { 3 | 4 | require('load-grunt-tasks')(grunt); 5 | var files = require('./files').files; 6 | 7 | grunt.initConfig({ 8 | builddir: 'build', 9 | pkg: grunt.file.readJSON('package.json'), 10 | buildtag: '-dev-' + grunt.template.today('yyyy-mm-dd'), 11 | meta: { 12 | banner: '/**\n' + 13 | ' * <%= pkg.description %>\n' + 14 | ' * @version v<%= pkg.version %><%= buildtag %>\n' + 15 | ' * @link <%= pkg.homepage %>\n' + 16 | ' * @license MIT License, http://www.opensource.org/licenses/MIT\n' + 17 | ' *\n'+ 18 | ' * Bundles virtual-dom by Matt-Esch \n'+ 19 | ' * @link https://github.com/Matt-Esch/virtual-dom\n'+ 20 | ' * @license MIT License, http://www.opensource.org/licenses/MIT\n' + 21 | ' */' 22 | }, 23 | clean: [ '<%= builddir %>' ], 24 | concat: { 25 | options: { 26 | banner: '<%= meta.banner %>\n\n'+ 27 | '/* commonjs package manager support */\n'+ 28 | 'if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){\n'+ 29 | ' module.exports = \'teropa.virtualDom\';\n'+ 30 | '}\n\n'+ 31 | '(function (window, angular, undefined) {\n', 32 | footer: '})(window, window.angular);' 33 | }, 34 | build: { 35 | src: files.lib.concat(files.src), 36 | dest: '<%= builddir %>/<%= pkg.name %>.js' 37 | } 38 | }, 39 | uglify: { 40 | options: { 41 | banner: '<%= meta.banner %>\n' 42 | }, 43 | build: { 44 | files: { 45 | '<%= builddir %>/<%= pkg.name %>.min.js': ['', '<%= concat.build.dest %>'] 46 | } 47 | } 48 | }, 49 | release: { 50 | files: ['<%= pkg.name %>.js', '<%= pkg.name %>.min.js'], 51 | src: '<%= builddir %>', 52 | dest: 'release' 53 | }, 54 | jshint: { 55 | all: ['Gruntfile.js', 'src/*.js', '<%= builddir %>/<%= pkg.name %>.js'], 56 | options: { 57 | } 58 | }, 59 | watch: { 60 | files: ['src/*.js', 'test/**/*.js'], 61 | tasks: ['build', 'karma:background:run'] 62 | }, 63 | connect: { 64 | server: {}, 65 | sample: { 66 | options:{ 67 | port: 5555, 68 | keepalive: true 69 | } 70 | } 71 | }, 72 | karma: { 73 | options: { 74 | configFile: 'config/karma.js', 75 | singleRun: true, 76 | exclude: [], 77 | frameworks: ['jasmine'], 78 | reporters: 'dots', // 'dots' || 'progress' 79 | port: 8080, 80 | colors: true, 81 | autoWatch: false, 82 | autoWatchInterval: 0, 83 | browsers: [ grunt.option('browser') || 'PhantomJS' ] 84 | }, 85 | unit: { 86 | browsers: [ grunt.option('browser') || 'PhantomJS' ] 87 | }, 88 | debug: { 89 | singleRun: false, 90 | background: false, 91 | browsers: [ grunt.option('browser') || 'Chrome' ] 92 | }, 93 | past: { 94 | configFile: 'config/karma-angular-1.2.28.js' 95 | }, 96 | future: { 97 | configFile: 'config/karma-angular-1.4.0-beta.3.js' 98 | }, 99 | background: { 100 | background: true, 101 | browsers: [ grunt.option('browser') || 'PhantomJS' ] 102 | }, 103 | watch: { 104 | configFile: 'config/karma.js', 105 | singleRun: false, 106 | autoWatch: true, 107 | autoWatchInterval: 1 108 | } 109 | }, 110 | changelog: { 111 | options: { 112 | dest: 'CHANGELOG.md' 113 | } 114 | } 115 | }); 116 | 117 | grunt.registerTask('integrate', ['build', 'jshint', 'karma:unit', 'karma:past', 'karma:future']); 118 | grunt.registerTask('default', ['build', 'jshint', 'karma:unit']); 119 | grunt.registerTask('build', 'Perform a normal build', ['concat', 'uglify']); 120 | grunt.registerTask('dist', 'Perform a clean build', ['clean', 'build']); 121 | grunt.registerTask('release', 'Tag and perform a release', ['prepare-release', 'dist', 'perform-release']); 122 | grunt.registerTask('dev', 'Run dev server and watch for changes', ['build', 'connect:server', 'karma:background', 'watch']); 123 | 124 | grunt.registerTask('prepare-release', function () { 125 | var bower = grunt.file.readJSON('bower.json'), 126 | version = bower.version; 127 | if (version != grunt.config('pkg.version')) throw 'Version mismatch in bower.json'; 128 | 129 | promising(this, 130 | ensureCleanMaster().then(function () { 131 | return exec('git tag -l \'' + version + '\''); 132 | }).then(function (result) { 133 | if (result.stdout.trim() !== '') throw 'Tag \'' + version + '\' already exists'; 134 | grunt.config('buildtag', ''); 135 | grunt.config('builddir', 'release'); 136 | }) 137 | ); 138 | }); 139 | 140 | grunt.registerTask('perform-release', function () { 141 | grunt.task.requires([ 'prepare-release', 'dist' ]); 142 | 143 | var version = grunt.config('pkg.version'), releasedir = grunt.config('builddir'); 144 | promising(this, 145 | system('git add \'' + releasedir + '\'').then(function () { 146 | return system('git commit -m \'release ' + version + '\''); 147 | }).then(function () { 148 | return system('git tag \'' + version + '\''); 149 | }) 150 | ); 151 | }); 152 | 153 | 154 | // Helpers for custom tasks, mainly around promises / exec 155 | var exec = require('faithful-exec'), shjs = require('shelljs'); 156 | 157 | function system(cmd) { 158 | grunt.log.write('% ' + cmd + '\n'); 159 | return exec(cmd).then(function (result) { 160 | grunt.log.write(result.stderr + result.stdout); 161 | }, function (error) { 162 | grunt.log.write(error.stderr + '\n'); 163 | throw 'Failed to run \'' + cmd + '\''; 164 | }); 165 | } 166 | 167 | function promising(task, promise) { 168 | var done = task.async(); 169 | promise.then(function () { 170 | done(); 171 | }, function (error) { 172 | grunt.log.write(error + '\n'); 173 | done(false); 174 | }); 175 | } 176 | 177 | function ensureCleanMaster() { 178 | return exec('git symbolic-ref HEAD').then(function (result) { 179 | if (result.stdout.trim() !== 'refs/heads/master') throw 'Not on master branch, aborting'; 180 | return exec('git status --porcelain'); 181 | }).then(function (result) { 182 | if (result.stdout.trim() !== '') throw 'Working copy is dirty, aborting'; 183 | }); 184 | } 185 | }; 186 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015 Tero Parviainen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-virtual-dom 2 | 3 | [![npm version](https://badge.fury.io/js/angular-virtual-dom.svg)](http://badge.fury.io/js/angular-virtual-dom) 4 | [![Bower version](https://badge.fury.io/bo/angular-virtual-dom.svg)](http://badge.fury.io/bo/angular-virtual-dom) 5 | 6 | angular-virtual-dom is an experimental [Virtual DOM](https://github.com/Matt-Esch/virtual-dom) based AngularJS view renderer designed to be used with immutable data structures such as [immutable-js](https://github.com/facebook/immutable-js) and [mori](http://swannodette.github.io/mori/). 7 | 8 | angular-virtual-dom lets you use regular AngularJS templates and expressions to bind data to the DOM, but uses Virtual DOM diffing behind the scenes. 9 | 10 | angular-virtual-dom supports extensibility using directives - though only with directives that are Virtual DOM aware. That means angular-virtual-dom is not a drop-in substitute for the AngularJS directive compiler, and is meant to be used in limited contexts. 11 | 12 | angular-virtual-dom works with AngularJS versions 1.2 and newer. 13 | 14 | ## Usage 15 | 16 | ```` js 17 | angular.module('myModule', ['teropa.virtualDom']) 18 | .controller('MyCtrl', function($timeout) { 19 | this.myData = Immutable.fromJS({ 20 | cols: [ 21 | {name: 'One', cssClass: 'one', key: 'one'}, 22 | {name: 'Two', cssClass: 'two', key: 'two'} 23 | ], 24 | rows: [ 25 | {one: 'A1', two: 'B1'}, 26 | {one: 'A2', two: 'B2'} 27 | ] 28 | }); 29 | 30 | // A new version of the immutable data structure triggers 31 | // DOM diffing later. 32 | $timeout(function() { 33 | this.myData = this.myData.updateIn(['rows'], function(rows) { 34 | return rows.push(Immutable.Map({one: 'A3', two: 'B3'})); 35 | }); 36 | }.bind(this), 1000); 37 | }); 38 | ```` 39 | 40 | ```` html 41 |
42 | 43 | 44 | 48 | 49 | 50 | 52 | 55 | 56 | 57 |
46 | {{col.get('name')}} 47 |
53 | {{row.get(col.get('key'))}} 54 |
58 |
59 | ```` 60 | 61 | * `v-root` establishes a Virtual DOM tree. The `table` tag and all of its descendants will be rendered using [virtual-dom](https://github.com/Matt-Esch/virtual-dom), bypassing Angular's own DOM compiler. 62 | * Virtual DOM diffing and patching occurs when `myCtrl.myData` changes. *The whole Virtual DOM tree uses a single (reference) watch*, and only when it fires does the view re-render. The idea is to attach an immutable data structure on the scope, refer to it in `v-root`, and let Virtual DOM diffing take care of updates when new versions of the data structure are produced. 63 | * The expressions within the table are normal AngularJS expressions. However, they are *not being watched*, and are only re-evaluated when diffing is triggered by the containing `v-root`. 64 | * Directives bundled with angular-virtual-dom can be used within the Virtual DOM tree. Custom directives can also be created (see below). 65 | 66 | ## Installation 67 | 68 | ### With NPM / Browserify 69 | 70 | ``` sh 71 | npm install angular-virtual-dom 72 | ``` 73 | 74 | Require the module and include it in your AngularJS modules: 75 | 76 | ``` js 77 | require('angular-virtual-dom') 78 | 79 | angular.module('myModule', ['teropa.virtualDom']) 80 | ``` 81 | 82 | Or just: 83 | 84 | ``` js 85 | angular.module('myModule', [ 86 | require('angular-virtual-dom') 87 | ]) 88 | ``` 89 | 90 | ### With Bower 91 | 92 | The library is available as a Bower dependency: 93 | 94 | ``` sh 95 | bower install angular-virtual-dom --save 96 | ``` 97 | 98 | After installation, add one of the following to your loaded scripts: 99 | 100 | * `angular-virtual-dom/release/angular-virtual-dom.js` 101 | * `angular-virtual-dom/release/angular-virtual-dom.min.js` 102 | 103 | Finally, include the `teropa.virtualDom` module in your AngularJS modules: 104 | 105 | ``` js 106 | angular.module('myModule', ['teropa.virtualDom']) 107 | ``` 108 | 109 | ## API 110 | 111 | ### v-root 112 | 113 | Use the `v-root` directive in your Angular templates to establish a Virtual DOM. This will short-circuit Angular's normal DOM compilation and build the Virtual DOM template from the contained elements. 114 | 115 | The directive accepts an expression, and changes to that expression's value cause the Virtual DOM tree to be re-rendered: 116 | 117 | ``` html 118 |
119 | 123 |
124 | ``` 125 | 126 | ### Expressions 127 | 128 | Within a `v-root`, any AngularJS expressions are evaluated whenever DOM diffing occurs: 129 | 130 | ``` html 131 |
132 |

133 | {{anotherExpression}} 134 |

135 |
136 | ``` 137 | 138 | Typically, though not necessarily, the expressions will access data from the data structure referred to in `v-root`: 139 | 140 | ``` html 141 |
142 |

143 | {{baseData.headerText}} 144 |

145 |
146 | ``` 147 | 148 | ### Directives 149 | 150 | #### v-if 151 | 152 | Includes the node in the Virtual DOM only when the expression evaluates to a truthy value. Analogous with `ng-if`. 153 | 154 | ``` html 155 |
156 |

157 | {{baseData.headerText}} 158 |

159 |
160 | ``` 161 | 162 | #### v-repeat 163 | 164 | Includes a collection of nodes in the Virtual DOM, for each item in a collection. Analogous with `ng-repeat`. 165 | 166 | Supports at least the following types of collections: 167 | * [immutable-js](https://github.com/facebook/immutable-js) lists, maps, stacks, ordered maps, sets, and ordered sets. 168 | * [mori](http://swannodette.github.io/mori/) lists, seqs, vectors, maps, sets, sorted sets, and queues. 169 | * JavaScript arrays an objects. 170 | 171 | Should additionally support any ES6 iterable collections. 172 | 173 | Usage with sequential data structures: 174 | 175 | ``` html 176 | 181 | ``` 182 | 183 | Usage with associative data structures: 184 | 185 | ``` html 186 | 191 | ``` 192 | 193 | Additionally makes the special variables `$index`, `$even`, and `$odd` available within the template scope. 194 | 195 | ## Writing Custom Directives 196 | 197 | *Note:* The directive API should be considered highly unstable. 198 | 199 | Virtual DOM directives are registered as normal AngularJS directives, but must define a `linkVirtual` function in the directive definition object. This should be a *pure function* that take a Virtual DOM node as an argument, and returns a modified Virtual DOM node or collection thereof. 200 | 201 | The Virtual DOM nodes used by this library always hold a `$scope` attribute, referring to the current scope. A directive may create a new scope and attach it to the `$scope` attribute of the returned node. 202 | 203 | ## Usage with Mutable Data Structures 204 | 205 | While angular-virtual-dom is designed to be used with immutable data structures, it is not a hard requirement. Regular, mutable JavaScript data structures and objects work just as well. 206 | 207 | You will, however, need to manually trigger re-renders by reassigning `v-root` to a new value unless your code does so naturally. 208 | 209 | ## Contribution 210 | 211 | Use Github issues for requests. 212 | 213 | ## Author 214 | 215 | [Tero Parviainen](http://teropa.info) ([@teropa on Twitter](https://twitter.com/teropa)) 216 | 217 | Leans heavily on [virtual-dom by Matt-Esch](https://github.com/Matt-Esch/virtual-dom). 218 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-virtual-dom", 3 | "version": "0.1.1", 4 | "main": "./release/angular-virtual-dom.js", 5 | "dependencies": { 6 | "angular": ">= 1.2.0" 7 | }, 8 | "ignore": [ 9 | "**/.*", 10 | "node_modules", 11 | "bower_components", 12 | "component.json", 13 | "package.json", 14 | "lib", 15 | "config", 16 | "sample", 17 | "test", 18 | "tests", 19 | "ngdoc_assets", 20 | "Gruntfile.js", 21 | "files.js" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /build/angular-virtual-dom.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @version v0.0.2-dev-2015-02-09 4 | * @link https://github.com/teropa/angular-virtual-dom 5 | * @license MIT License, http://www.opensource.org/licenses/MIT 6 | * 7 | * Bundles virtual-dom by Matt-Esch 8 | * @link https://github.com/Matt-Esch/virtual-dom 9 | * @license MIT License, http://www.opensource.org/licenses/MIT 10 | */ 11 | "undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="teropa.virtualDom"),function(a,b,c){!function(b){var c;"undefined"!=typeof a?c=a:"undefined"!=typeof global?c=global:"undefined"!=typeof self&&(c=self),c.virtualDom=b()}(function(){return function b(a,c,d){function e(g,h){if(!c[g]){if(!a[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};a[g][0].call(k.exports,function(b){var c=a[g][1][b];return e(c?c:b)},k,k.exports,b,a,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g>>0:f>>>0;(h=e.exec(b))&&(i=h.index+h[0].length,!(i>m&&(k.push(b.slice(m,h.index)),!d&&h.length>1&&h[0].replace(g,function(){for(var b=1;b1&&h.index=f)));)e.lastIndex===h.index&&e.lastIndex++;return m===b.length?(j||!e.test(""))&&k.push(""):k.push(b.slice(m)),k.length>f?k.slice(0,f):k}}()},{}],6:[function(a,b){"use strict";function c(a){var b=a[f];return b||(b=a[f]={}),b}var d=a("individual/one-version"),e="7";d("ev-store",e);var f="__EV_STORE_KEY@"+e;b.exports=c},{"individual/one-version":8}],7:[function(b,c){(function(b){"use strict";function d(a,b){return a in e?e[a]:(e[a]=b,b)}var e="undefined"!=typeof a?a:"undefined"!=typeof b?b:{};c.exports=d}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof a?a:{})},{}],8:[function(a,b){"use strict";function c(a,b,c){var e="__INDIVIDUAL_ONE_VERSION_"+a,f=e+"_ENFORCE_SINGLETON",g=d(f,b);if(g!==b)throw new Error("Can only have one copy of "+a+".\nYou already have version "+g+" installed.\nThis means you cannot install version "+b);return d(e,c)}var d=a("./index.js");b.exports=c},{"./index.js":7}],9:[function(b,c){(function(d){var e="undefined"!=typeof d?d:"undefined"!=typeof a?a:{},f=b("min-document");if("undefined"!=typeof document)c.exports=document;else{var g=e["__GLOBAL_DOCUMENT_CACHE@4"];g||(g=e["__GLOBAL_DOCUMENT_CACHE@4"]=f),c.exports=g}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof a?a:{})},{"min-document":35}],10:[function(a,b){"use strict";b.exports=function(a){return"object"==typeof a&&null!==a}},{}],11:[function(a,b){function c(a){return"[object Array]"===e.call(a)}var d=Array.isArray,e=Object.prototype.toString;b.exports=d||c},{}],12:[function(a,b){var c=a("./vdom/patch.js");b.exports=c},{"./vdom/patch.js":17}],13:[function(a,b){function d(a,b,d){for(var g in b){var j=b[g];j===c?e(a,g,j,d):i(j)?(e(a,g,j,d),j.hook&&j.hook(a,g,d?d[g]:c)):h(j)?f(a,b,d,g,j):a[g]=j}}function e(a,b,c,d){if(d){var e=d[b];if(i(e))e.unhook&&e.unhook(a,b,c);else if("attributes"===b)for(var f in e)a.removeAttribute(f);else if("style"===b)for(var g in e)a.style[g]="";else a[b]="string"==typeof e?"":null}}function f(a,b,d,e,f){var i=d?d[e]:c;if("attributes"!==e){if(i&&h(i)&&g(i)!==g(f))return void(a[e]=f);h(a[e])||(a[e]={});var j="style"===e?"":c;for(var k in f){var l=f[k];a[e][k]=l===c?j:l}}else for(var m in f){var n=f[m];n===c?a.removeAttribute(m):a.setAttribute(m,n)}}function g(a){return Object.getPrototypeOf?Object.getPrototypeOf(a):a.__proto__?a.__proto__:a.constructor?a.constructor.prototype:void 0}var h=a("is-object"),i=a("../vnode/is-vhook.js");b.exports=d},{"../vnode/is-vhook.js":25,"is-object":10}],14:[function(a,b){function c(a,b){var j=b?b.document||d:d,k=b?b.warn:null;if(a=i(a).a,h(a))return a.init();if(g(a))return j.createTextNode(a.text);if(!f(a))return k&&k("Item is not a valid virtual dom node",a),null;var l=null===a.namespace?j.createElement(a.tagName):j.createElementNS(a.namespace,a.tagName),m=a.properties;e(l,m);for(var n=a.children,o=0;o=f;){if(d=(g+f)/2>>0,e=a[d],f===g)return e>=b&&c>=e;if(b>e)f=d+1;else{if(!(e>c))return!0;g=d-1}}return!1}function f(a,b){return a>b?1:-1}var g={};b.exports=c},{}],16:[function(a,b){function d(a,b,c){var d=a.type,j=a.vNode,n=a.patch;switch(d){case o.REMOVE:return e(b,j);case o.INSERT:return f(b,n,c);case o.VTEXT:return g(b,j,n,c);case o.WIDGET:return h(b,j,n,c);case o.VNODE:return i(b,j,n,c);case o.ORDER:return k(b,n),b;case o.PROPS:return m(b,n,j.properties),b;case o.THUNK:return l(b,c.patch(b,n,c));default:return b}}function e(a,b){var c=a.parentNode;return c&&c.removeChild(a),j(a,b),null}function f(a,b,c){var d=p(b,c);return a&&a.appendChild(d),a}function g(a,b,c,d){var e;if(3===a.nodeType)a.replaceData(0,a.length,c.text),e=a;else{var f=a.parentNode;e=p(c,d),f&&f.replaceChild(e,a)}return e}function h(a,b,c,d){var e,f=q(b,c);e=f?c.update(b,a)||a:p(c,d);var g=a.parentNode;return g&&e!==a&&g.replaceChild(e,a),f||j(a,b),e}function i(a,b,c,d){var e=a.parentNode,f=p(c,d);return e&&e.replaceChild(f,a),f}function j(a,b){"function"==typeof b.destroy&&n(b)&&b.destroy(a)}function k(a,b){var d,e=[],f=a.childNodes,g=f.length,h=b.reverse;for(d=0;g>d;d++)e.push(a.childNodes[d]);var i,j,k,l,m,n=0;for(d=0;g>d;){if(i=b[d],l=1,i!==c&&i!==d){for(;b[d+l]===i+l;)l++;for(h[d]>d+l&&n++,j=e[i],k=f[d+n]||null,m=0;j!==k&&m++i+l&&n--}d in b.removes&&n++,d+=l}}function l(a,b){return a&&b&&a!==b&&a.parentNode&&(console.log(a),a.parentNode.replaceChild(b,a)),b}var m=a("./apply-properties"),n=a("../vnode/is-widget.js"),o=a("../vnode/vpatch.js"),p=a("./create-element"),q=a("./update-widget");b.exports=d},{"../vnode/is-widget.js":28,"../vnode/vpatch.js":31,"./apply-properties":13,"./create-element":14,"./update-widget":18}],17:[function(a,b){function c(a,b){return d(a,b)}function d(a,b,c){var h=f(b);if(0===h.length)return a;var j=i(a,b.a,h),k=a.ownerDocument;c||(c={patch:d},k!==g&&(c.document=k));for(var l=0;lu;u++){var v=d[u];f(v)?(o+=v.count||0,!p&&v.hasWidgets&&(p=!0),!q&&v.hasThunks&&(q=!0),r||!v.hooks&&!v.descendantHooks||(r=!0)):!p&&g(v)?"function"==typeof v.destroy&&(p=!0):!q&&h(v)&&(q=!0)}this.count=n+o,this.hasWidgets=p,this.hasThunks=q,this.hooks=m,this.descendantHooks=r}var e=a("./version"),f=a("./is-vnode"),g=a("./is-widget"),h=a("./is-thunk"),i=a("./is-vhook");b.exports=d;var j={},k=[];d.prototype.version=e,d.prototype.type="VirtualNode"},{"./is-thunk":24,"./is-vhook":25,"./is-vnode":26,"./is-widget":28,"./version":29}],31:[function(a,b){function c(a,b,c){this.type=Number(a),this.vNode=b,this.patch=c}var d=a("./version");c.NONE=0,c.VTEXT=1,c.VNODE=2,c.WIDGET=3,c.PROPS=4,c.ORDER=5,c.INSERT=6,c.REMOVE=7,c.THUNK=8,b.exports=c,c.prototype.version=d,c.prototype.type="VirtualPatch"},{"./version":29}],32:[function(a,b){function c(a){this.text=String(a)}var d=a("./version");b.exports=c,c.prototype.version=d,c.prototype.type="VirtualText"},{"./version":29}],33:[function(a,b){function d(a,b){var h;for(var i in a){i in b||(h=h||{},h[i]=c);var j=a[i],k=b[i];if(j!==k)if(f(j)&&f(k))if(e(k)!==e(j))h=h||{},h[i]=k;else if(g(k))h=h||{},h[i]=k;else{var l=d(j,k);l&&(h=h||{},h[i]=l)}else h=h||{},h[i]=k}for(var m in b)m in a||(h=h||{},h[m]=b[m]);return h}function e(a){return Object.getPrototypeOf?Object.getPrototypeOf(a):a.__proto__?a.__proto__:a.constructor?a.constructor.prototype:void 0}var f=a("is-object"),g=a("../vnode/is-vhook");b.exports=d},{"../vnode/is-vhook":25,"is-object":10}],34:[function(a,b){function d(a,b){var c={a:a};return e(a,b,c,0),c}function e(a,b,c,d){if(a!==b){var e=c[d],h=!1;if(u(a)||u(b))i(a,b,c,d);else if(null==b)t(a)||(g(a,c,d),e=c[d]),e=o(e,new q(q.REMOVE,a,b));else if(r(b))if(r(a))if(a.tagName===b.tagName&&a.namespace===b.namespace&&a.key===b.key){var j=w(a.properties,b.properties);j&&(e=o(e,new q(q.PROPS,a,j))),e=f(a,b,c,e,d)}else e=o(e,new q(q.VNODE,a,b)),h=!0;else e=o(e,new q(q.VNODE,a,b)),h=!0;else s(b)?s(a)?a.text!==b.text&&(e=o(e,new q(q.VTEXT,a,b))):(e=o(e,new q(q.VTEXT,a,b)),h=!0):t(b)&&(t(a)||(h=!0),e=o(e,new q(q.WIDGET,a,b)));e&&(c[d]=e),h&&g(a,c,d)}}function f(a,b,c,d,f){for(var g=a.children,h=m(g,b.children),i=g.length,j=h.length,k=i>j?i:j,l=0;k>l;l++){var n=g[l],p=h[l];f+=1,n?e(n,p,c,f):p&&(d=o(d,new q(q.INSERT,null,p))),r(n)&&n.count&&(f+=n.count)}return h.moves&&(d=o(d,new q(q.ORDER,a,h.moves))),d}function g(a,b,c){k(a,b,c),h(a,b,c)}function h(a,b,c){if(t(a))"function"==typeof a.destroy&&(b[c]=o(b[c],new q(q.REMOVE,a,null)));else if(r(a)&&(a.hasWidgets||a.hasThunks))for(var d=a.children,e=d.length,f=0;e>f;f++){var g=d[f];c+=1,h(g,b,c),r(g)&&g.count&&(c+=g.count)}else u(a)&&i(a,null,b,c)}function i(a,b,c,e){var f=v(a,b),g=d(f.a,f.b);j(g)&&(c[e]=new q(q.THUNK,null,g))}function j(a){for(var b in a)if("a"!==b)return!0;return!1}function k(a,b,c){if(r(a)){if(a.hooks&&(b[c]=o(b[c],new q(q.PROPS,a,l(a.hooks)))),a.descendantHooks||a.hasThunks)for(var d=a.children,e=d.length,f=0;e>f;f++){var g=d[f];c+=1,k(g,b,c),r(g)&&g.count&&(c+=g.count)}}else u(a)&&i(a,null,b,c)}function l(a){var b={};for(var d in a)b[d]=c;return b}function m(a,b){var d=n(b);if(!d)return b;var e=n(a);if(!e)return b;var f={},g={};for(var h in d)f[d[h]]=e[h];for(var i in e)g[e[i]]=d[i];for(var j=a.length,k=b.length,l=j>k?j:k,m=[],o=0,p=0,q=0,r={},s=r.removes={},t=r.reverse={},u=!1;l>o;){var v=g[p];if(v!==c)m[p]=b[v],v!==q&&(r[v]=q,t[q]=v,u=!0),q++;else if(p in g)m[p]=c,s[p]=q++,u=!0;else{for(;f[o]!==c;)o++;if(l>o){var w=b[o];w&&(m[p]=w,o!==q&&(u=!0,r[o]=q,t[q]=o),q++),o++}}p++}return u&&(m.moves=r),m}function n(a){var b,d;for(b=0;bc;c++)d.next();return d.next().value}}function e(a,b){a.$index=b,a.$even=b%2===0,a.$odd=!a.$even}var f="undefined"!=typeof Symbol?Symbol.iterator:"@@iterator";return{restrict:"A",priority:1e3,linkVirtual:function(g){var h=b(g,"v-repeat"),i=h.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)\s*$/),j=i[1],k=i[2];i=j.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);var l=i[3]||i[1],m=i[2],n=a(k)(g.$scope);if(Array.isArray(n))return n.map(function(a,b){var d=c(g);return d.$scope=g.$scope.$new(),d.$scope[l]=a,e(d.$scope,b),d});if(n&&n[f]){for(var o=n[f](),p=[],q=0,r=o.next();!r.done;){var s=r.value,t=c(g);t.$scope=g.$scope.$new(),m?(t.$scope[m]=d(s,0),t.$scope[l]=d(s,1)):t.$scope[l]=s,e(t.$scope,q),p.push(t),q++,r=o.next()}return p}return"object"==typeof n&&null!==n?Object.keys(n).map(function(a,b){var d=c(g);return d.$scope=g.$scope.$new(),d.$scope[m]=a,d.$scope[l]=n[a],e(d.$scope,b),d}):[]}}}]),b.module("teropa.virtualDom.vRoot",["teropa.virtualDom.virtualize","teropa.virtualDom.link"]).directive("vRoot",["$injector","$interpolate","virtualizeDom","linkVDom",function(a,c,d,e){"use strict";return{compile:function(a){var c=a[0],f=d(c);return a.empty(),function(a,c,d){function g(){if(j){var b=e(f,a),c=virtualDom.diff(h,b);i=virtualDom.patch(i,c),h=b,j=!1}}var h=e(f,a),i=virtualDom.create(h);c.replaceWith(i);var j;a.$watch(d.vRoot,function(){j=!0,a.$$postDigest(g)}),a.$on("$destroy",function(){b.element(i).remove()})}}}}]),b.module("teropa.virtualDom",["teropa.virtualDom.getAttribute","teropa.virtualDom.cloneTree","teropa.virtualDom.virtualize","teropa.virtualDom.link","teropa.virtualDom.vIf","teropa.virtualDom.vRepeat","teropa.virtualDom.vRoot"])}(window,window.angular); -------------------------------------------------------------------------------- /config/karma-angular-1.2.28.js: -------------------------------------------------------------------------------- 1 | module.exports = function (karma) { 2 | 3 | var files = require('../files').files; 4 | 5 | karma.set({ 6 | basePath: '..', 7 | files: [].concat(files.angular('1.2.28'), files.lib, files.testLib, files.src, files.test), 8 | logLevel: karma.LOG_DEBUG, 9 | browsers: [ 'PhantomJS' ] 10 | }); 11 | 12 | }; 13 | -------------------------------------------------------------------------------- /config/karma-angular-1.4.0-beta.3.js: -------------------------------------------------------------------------------- 1 | module.exports = function (karma) { 2 | 3 | var files = require('../files').files; 4 | 5 | karma.set({ 6 | basePath: '..', 7 | files: [].concat(files.angular('1.4.0-beta.3'), files.lib, files.testLib, files.src, files.test), 8 | logLevel: karma.LOG_DEBUG, 9 | browsers: [ 'PhantomJS' ] 10 | }); 11 | 12 | }; 13 | -------------------------------------------------------------------------------- /config/karma.js: -------------------------------------------------------------------------------- 1 | module.exports = function (karma) { 2 | 3 | var files = require('../files').files; 4 | 5 | karma.set({ 6 | basePath: '..', 7 | files: [].concat(files.angular('1.3.12'), files.lib, files.testLib, files.src, files.test), 8 | logLevel: karma.LOG_DEBUG, 9 | browsers: [ 'PhantomJS' ] 10 | }); 11 | 12 | }; 13 | -------------------------------------------------------------------------------- /examples/hello/README.md: -------------------------------------------------------------------------------- 1 | To run this example, first run `npm install` and then open `index.html` in a browser. 2 | -------------------------------------------------------------------------------- /examples/hello/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 11 | 12 | 13 | 15 | 18 | 19 | 20 |
9 | {{col.get('name')}} 10 |
16 | {{row.get(col.get('key'))}} 17 |
21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/hello/index.js: -------------------------------------------------------------------------------- 1 | angular.module('myModule', ['teropa.virtualDom']) 2 | .controller('MyCtrl', function($timeout) { 3 | this.myData = Immutable.fromJS({ 4 | cols: [ 5 | {name: 'One', cssClass: 'one', key: 'one'}, 6 | {name: 'Two', cssClsas: 'two', key: 'two'} 7 | ], 8 | rows: [ 9 | {one: 'A1', two: 'B1'}, 10 | {one: 'A2', two: 'B2'} 11 | ] 12 | }); 13 | 14 | // A new version of the immutable data structure triggers 15 | // DOM diffing later. 16 | $timeout(function() { 17 | this.myData = this.myData.updateIn(['rows'], function(rows) { 18 | return rows.push(Immutable.Map({one: 'A3', two: 'B3'})); 19 | }); 20 | }.bind(this), 1000); 21 | }); 22 | -------------------------------------------------------------------------------- /examples/hello/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-virtual-dom-hello-example", 3 | "version": "0.0.1", 4 | "description": "The Hello World Example for angular-virtual-dom", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Tero Parviainen ", 10 | "licenses": { 11 | "type": "MIT", 12 | "url": "https://github.com/teropa/angular-virtual-dom/blob/master/LICENSE" 13 | }, 14 | "dependencies": { 15 | "angular": "^1.3.12", 16 | "angular-virtual-dom": "0.0.2", 17 | "immutable": "^3.6.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /files.js: -------------------------------------------------------------------------------- 1 | vdomFiles = { 2 | lib: [ 3 | 'lib/virtual-dom.js' 4 | ], 5 | src: [ 6 | 'src/get_attribute.js', 7 | 'src/directive_normalize.js', 8 | 'src/clone_tree.js', 9 | 'src/virtualize.js', 10 | 'src/link.js', 11 | 'src/v_if_directive.js', 12 | 'src/v_repeat_directive.js', 13 | 'src/v_root_directive.js', 14 | 'src/angular-virtual-dom.js', 15 | ], 16 | testLib: [ 17 | 'lib/immutable.js', 18 | 'lib/mori.js' 19 | ], 20 | test: [ 21 | 'test/*spec.js' 22 | ], 23 | angular: function(version) { 24 | return [ 25 | 'lib/angular-' + version + '/angular.js', 26 | 'lib/angular-' + version + '/angular-mocks.js' 27 | ]; 28 | } 29 | }; 30 | 31 | if (exports) { 32 | exports.files = vdomFiles; 33 | } 34 | -------------------------------------------------------------------------------- /lib/virtual-dom.js: -------------------------------------------------------------------------------- 1 | !function(e){var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.virtualDom=e()}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 41 | * Available under the MIT License 42 | * ECMAScript compliant, uniform cross-browser split method 43 | */ 44 | 45 | /** 46 | * Splits a string into an array of strings using a regex or string separator. Matches of the 47 | * separator are not included in the result array. However, if `separator` is a regex that contains 48 | * capturing groups, backreferences are spliced into the result each time `separator` is matched. 49 | * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably 50 | * cross-browser. 51 | * @param {String} str String to split. 52 | * @param {RegExp|String} separator Regex or string to use for separating the string. 53 | * @param {Number} [limit] Maximum number of items to include in the result array. 54 | * @returns {Array} Array of substrings. 55 | * @example 56 | * 57 | * // Basic use 58 | * split('a b c d', ' '); 59 | * // -> ['a', 'b', 'c', 'd'] 60 | * 61 | * // With limit 62 | * split('a b c d', ' ', 2); 63 | * // -> ['a', 'b'] 64 | * 65 | * // Backreferences in result array 66 | * split('..word1 word2..', /([a-z]+)(\d+)/i); 67 | * // -> ['..', 'word', '1', ' ', 'word', '2', '..'] 68 | */ 69 | module.exports = (function split(undef) { 70 | 71 | var nativeSplit = String.prototype.split, 72 | compliantExecNpcg = /()??/.exec("")[1] === undef, 73 | // NPCG: nonparticipating capturing group 74 | self; 75 | 76 | self = function(str, separator, limit) { 77 | // If `separator` is not a regex, use `nativeSplit` 78 | if (Object.prototype.toString.call(separator) !== "[object RegExp]") { 79 | return nativeSplit.call(str, separator, limit); 80 | } 81 | var output = [], 82 | flags = (separator.ignoreCase ? "i" : "") + (separator.multiline ? "m" : "") + (separator.extended ? "x" : "") + // Proposed for ES6 83 | (separator.sticky ? "y" : ""), 84 | // Firefox 3+ 85 | lastLastIndex = 0, 86 | // Make `global` and avoid `lastIndex` issues by working with a copy 87 | separator = new RegExp(separator.source, flags + "g"), 88 | separator2, match, lastIndex, lastLength; 89 | str += ""; // Type-convert 90 | if (!compliantExecNpcg) { 91 | // Doesn't need flags gy, but they don't hurt 92 | separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags); 93 | } 94 | /* Values for `limit`, per the spec: 95 | * If undefined: 4294967295 // Math.pow(2, 32) - 1 96 | * If 0, Infinity, or NaN: 0 97 | * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296; 98 | * If negative number: 4294967296 - Math.floor(Math.abs(limit)) 99 | * If other: Type-convert, then use the above rules 100 | */ 101 | limit = limit === undef ? -1 >>> 0 : // Math.pow(2, 32) - 1 102 | limit >>> 0; // ToUint32(limit) 103 | while (match = separator.exec(str)) { 104 | // `separator.lastIndex` is not reliable cross-browser 105 | lastIndex = match.index + match[0].length; 106 | if (lastIndex > lastLastIndex) { 107 | output.push(str.slice(lastLastIndex, match.index)); 108 | // Fix browsers whose `exec` methods don't consistently return `undefined` for 109 | // nonparticipating capturing groups 110 | if (!compliantExecNpcg && match.length > 1) { 111 | match[0].replace(separator2, function() { 112 | for (var i = 1; i < arguments.length - 2; i++) { 113 | if (arguments[i] === undef) { 114 | match[i] = undef; 115 | } 116 | } 117 | }); 118 | } 119 | if (match.length > 1 && match.index < str.length) { 120 | Array.prototype.push.apply(output, match.slice(1)); 121 | } 122 | lastLength = match[0].length; 123 | lastLastIndex = lastIndex; 124 | if (output.length >= limit) { 125 | break; 126 | } 127 | } 128 | if (separator.lastIndex === match.index) { 129 | separator.lastIndex++; // Avoid an infinite loop 130 | } 131 | } 132 | if (lastLastIndex === str.length) { 133 | if (lastLength || !separator.test("")) { 134 | output.push(""); 135 | } 136 | } else { 137 | output.push(str.slice(lastLastIndex)); 138 | } 139 | return output.length > limit ? output.slice(0, limit) : output; 140 | }; 141 | 142 | return self; 143 | })(); 144 | 145 | },{}],6:[function(_dereq_,module,exports){ 146 | 'use strict'; 147 | 148 | var OneVersionConstraint = _dereq_('individual/one-version'); 149 | 150 | var MY_VERSION = '7'; 151 | OneVersionConstraint('ev-store', MY_VERSION); 152 | 153 | var hashKey = '__EV_STORE_KEY@' + MY_VERSION; 154 | 155 | module.exports = EvStore; 156 | 157 | function EvStore(elem) { 158 | var hash = elem[hashKey]; 159 | 160 | if (!hash) { 161 | hash = elem[hashKey] = {}; 162 | } 163 | 164 | return hash; 165 | } 166 | 167 | },{"individual/one-version":8}],7:[function(_dereq_,module,exports){ 168 | (function (global){ 169 | 'use strict'; 170 | 171 | /*global window, global*/ 172 | 173 | var root = typeof window !== 'undefined' ? 174 | window : typeof global !== 'undefined' ? 175 | global : {}; 176 | 177 | module.exports = Individual; 178 | 179 | function Individual(key, value) { 180 | if (key in root) { 181 | return root[key]; 182 | } 183 | 184 | root[key] = value; 185 | 186 | return value; 187 | } 188 | 189 | }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 190 | },{}],8:[function(_dereq_,module,exports){ 191 | 'use strict'; 192 | 193 | var Individual = _dereq_('./index.js'); 194 | 195 | module.exports = OneVersion; 196 | 197 | function OneVersion(moduleName, version, defaultValue) { 198 | var key = '__INDIVIDUAL_ONE_VERSION_' + moduleName; 199 | var enforceKey = key + '_ENFORCE_SINGLETON'; 200 | 201 | var versionValue = Individual(enforceKey, version); 202 | 203 | if (versionValue !== version) { 204 | throw new Error('Can only have one copy of ' + 205 | moduleName + '.\n' + 206 | 'You already have version ' + versionValue + 207 | ' installed.\n' + 208 | 'This means you cannot install version ' + version); 209 | } 210 | 211 | return Individual(key, defaultValue); 212 | } 213 | 214 | },{"./index.js":7}],9:[function(_dereq_,module,exports){ 215 | (function (global){ 216 | var topLevel = typeof global !== 'undefined' ? global : 217 | typeof window !== 'undefined' ? window : {} 218 | var minDoc = _dereq_('min-document'); 219 | 220 | if (typeof document !== 'undefined') { 221 | module.exports = document; 222 | } else { 223 | var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4']; 224 | 225 | if (!doccy) { 226 | doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc; 227 | } 228 | 229 | module.exports = doccy; 230 | } 231 | 232 | }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 233 | },{"min-document":35}],10:[function(_dereq_,module,exports){ 234 | "use strict"; 235 | 236 | module.exports = function isObject(x) { 237 | return typeof x === "object" && x !== null; 238 | }; 239 | 240 | },{}],11:[function(_dereq_,module,exports){ 241 | var nativeIsArray = Array.isArray 242 | var toString = Object.prototype.toString 243 | 244 | module.exports = nativeIsArray || isArray 245 | 246 | function isArray(obj) { 247 | return toString.call(obj) === "[object Array]" 248 | } 249 | 250 | },{}],12:[function(_dereq_,module,exports){ 251 | var patch = _dereq_("./vdom/patch.js") 252 | 253 | module.exports = patch 254 | 255 | },{"./vdom/patch.js":17}],13:[function(_dereq_,module,exports){ 256 | var isObject = _dereq_("is-object") 257 | var isHook = _dereq_("../vnode/is-vhook.js") 258 | 259 | module.exports = applyProperties 260 | 261 | function applyProperties(node, props, previous) { 262 | for (var propName in props) { 263 | var propValue = props[propName] 264 | 265 | if (propValue === undefined) { 266 | removeProperty(node, propName, propValue, previous); 267 | } else if (isHook(propValue)) { 268 | removeProperty(node, propName, propValue, previous) 269 | if (propValue.hook) { 270 | propValue.hook(node, 271 | propName, 272 | previous ? previous[propName] : undefined) 273 | } 274 | } else { 275 | if (isObject(propValue)) { 276 | patchObject(node, props, previous, propName, propValue); 277 | } else { 278 | node[propName] = propValue 279 | } 280 | } 281 | } 282 | } 283 | 284 | function removeProperty(node, propName, propValue, previous) { 285 | if (previous) { 286 | var previousValue = previous[propName] 287 | 288 | if (!isHook(previousValue)) { 289 | if (propName === "attributes") { 290 | for (var attrName in previousValue) { 291 | node.removeAttribute(attrName) 292 | } 293 | } else if (propName === "style") { 294 | for (var i in previousValue) { 295 | node.style[i] = "" 296 | } 297 | } else if (typeof previousValue === "string") { 298 | node[propName] = "" 299 | } else { 300 | node[propName] = null 301 | } 302 | } else if (previousValue.unhook) { 303 | previousValue.unhook(node, propName, propValue) 304 | } 305 | } 306 | } 307 | 308 | function patchObject(node, props, previous, propName, propValue) { 309 | var previousValue = previous ? previous[propName] : undefined 310 | 311 | // Set attributes 312 | if (propName === "attributes") { 313 | for (var attrName in propValue) { 314 | var attrValue = propValue[attrName] 315 | 316 | if (attrValue === undefined) { 317 | node.removeAttribute(attrName) 318 | } else { 319 | node.setAttribute(attrName, attrValue) 320 | } 321 | } 322 | 323 | return 324 | } 325 | 326 | if(previousValue && isObject(previousValue) && 327 | getPrototype(previousValue) !== getPrototype(propValue)) { 328 | node[propName] = propValue 329 | return 330 | } 331 | 332 | if (!isObject(node[propName])) { 333 | node[propName] = {} 334 | } 335 | 336 | var replacer = propName === "style" ? "" : undefined 337 | 338 | for (var k in propValue) { 339 | var value = propValue[k] 340 | node[propName][k] = (value === undefined) ? replacer : value 341 | } 342 | } 343 | 344 | function getPrototype(value) { 345 | if (Object.getPrototypeOf) { 346 | return Object.getPrototypeOf(value) 347 | } else if (value.__proto__) { 348 | return value.__proto__ 349 | } else if (value.constructor) { 350 | return value.constructor.prototype 351 | } 352 | } 353 | 354 | },{"../vnode/is-vhook.js":25,"is-object":10}],14:[function(_dereq_,module,exports){ 355 | var document = _dereq_("global/document") 356 | 357 | var applyProperties = _dereq_("./apply-properties") 358 | 359 | var isVNode = _dereq_("../vnode/is-vnode.js") 360 | var isVText = _dereq_("../vnode/is-vtext.js") 361 | var isWidget = _dereq_("../vnode/is-widget.js") 362 | var handleThunk = _dereq_("../vnode/handle-thunk.js") 363 | 364 | module.exports = createElement 365 | 366 | function createElement(vnode, opts) { 367 | var doc = opts ? opts.document || document : document 368 | var warn = opts ? opts.warn : null 369 | 370 | vnode = handleThunk(vnode).a 371 | 372 | if (isWidget(vnode)) { 373 | return vnode.init() 374 | } else if (isVText(vnode)) { 375 | return doc.createTextNode(vnode.text) 376 | } else if (!isVNode(vnode)) { 377 | if (warn) { 378 | warn("Item is not a valid virtual dom node", vnode) 379 | } 380 | return null 381 | } 382 | 383 | var node = (vnode.namespace === null) ? 384 | doc.createElement(vnode.tagName) : 385 | doc.createElementNS(vnode.namespace, vnode.tagName) 386 | 387 | var props = vnode.properties 388 | applyProperties(node, props) 389 | 390 | var children = vnode.children 391 | 392 | for (var i = 0; i < children.length; i++) { 393 | var childNode = createElement(children[i], opts) 394 | if (childNode) { 395 | node.appendChild(childNode) 396 | } 397 | } 398 | 399 | return node 400 | } 401 | 402 | },{"../vnode/handle-thunk.js":23,"../vnode/is-vnode.js":26,"../vnode/is-vtext.js":27,"../vnode/is-widget.js":28,"./apply-properties":13,"global/document":9}],15:[function(_dereq_,module,exports){ 403 | // Maps a virtual DOM tree onto a real DOM tree in an efficient manner. 404 | // We don't want to read all of the DOM nodes in the tree so we use 405 | // the in-order tree indexing to eliminate recursion down certain branches. 406 | // We only recurse into a DOM node if we know that it contains a child of 407 | // interest. 408 | 409 | var noChild = {} 410 | 411 | module.exports = domIndex 412 | 413 | function domIndex(rootNode, tree, indices, nodes) { 414 | if (!indices || indices.length === 0) { 415 | return {} 416 | } else { 417 | indices.sort(ascending) 418 | return recurse(rootNode, tree, indices, nodes, 0) 419 | } 420 | } 421 | 422 | function recurse(rootNode, tree, indices, nodes, rootIndex) { 423 | nodes = nodes || {} 424 | 425 | 426 | if (rootNode) { 427 | if (indexInRange(indices, rootIndex, rootIndex)) { 428 | nodes[rootIndex] = rootNode 429 | } 430 | 431 | var vChildren = tree.children 432 | 433 | if (vChildren) { 434 | 435 | var childNodes = rootNode.childNodes 436 | 437 | for (var i = 0; i < tree.children.length; i++) { 438 | rootIndex += 1 439 | 440 | var vChild = vChildren[i] || noChild 441 | var nextIndex = rootIndex + (vChild.count || 0) 442 | 443 | // skip recursion down the tree if there are no nodes down here 444 | if (indexInRange(indices, rootIndex, nextIndex)) { 445 | recurse(childNodes[i], vChild, indices, nodes, rootIndex) 446 | } 447 | 448 | rootIndex = nextIndex 449 | } 450 | } 451 | } 452 | 453 | return nodes 454 | } 455 | 456 | // Binary search for an index in the interval [left, right] 457 | function indexInRange(indices, left, right) { 458 | if (indices.length === 0) { 459 | return false 460 | } 461 | 462 | var minIndex = 0 463 | var maxIndex = indices.length - 1 464 | var currentIndex 465 | var currentItem 466 | 467 | while (minIndex <= maxIndex) { 468 | currentIndex = ((maxIndex + minIndex) / 2) >> 0 469 | currentItem = indices[currentIndex] 470 | 471 | if (minIndex === maxIndex) { 472 | return currentItem >= left && currentItem <= right 473 | } else if (currentItem < left) { 474 | minIndex = currentIndex + 1 475 | } else if (currentItem > right) { 476 | maxIndex = currentIndex - 1 477 | } else { 478 | return true 479 | } 480 | } 481 | 482 | return false; 483 | } 484 | 485 | function ascending(a, b) { 486 | return a > b ? 1 : -1 487 | } 488 | 489 | },{}],16:[function(_dereq_,module,exports){ 490 | var applyProperties = _dereq_("./apply-properties") 491 | 492 | var isWidget = _dereq_("../vnode/is-widget.js") 493 | var VPatch = _dereq_("../vnode/vpatch.js") 494 | 495 | var render = _dereq_("./create-element") 496 | var updateWidget = _dereq_("./update-widget") 497 | 498 | module.exports = applyPatch 499 | 500 | function applyPatch(vpatch, domNode, renderOptions) { 501 | var type = vpatch.type 502 | var vNode = vpatch.vNode 503 | var patch = vpatch.patch 504 | 505 | switch (type) { 506 | case VPatch.REMOVE: 507 | return removeNode(domNode, vNode) 508 | case VPatch.INSERT: 509 | return insertNode(domNode, patch, renderOptions) 510 | case VPatch.VTEXT: 511 | return stringPatch(domNode, vNode, patch, renderOptions) 512 | case VPatch.WIDGET: 513 | return widgetPatch(domNode, vNode, patch, renderOptions) 514 | case VPatch.VNODE: 515 | return vNodePatch(domNode, vNode, patch, renderOptions) 516 | case VPatch.ORDER: 517 | reorderChildren(domNode, patch) 518 | return domNode 519 | case VPatch.PROPS: 520 | applyProperties(domNode, patch, vNode.properties) 521 | return domNode 522 | case VPatch.THUNK: 523 | return replaceRoot(domNode, 524 | renderOptions.patch(domNode, patch, renderOptions)) 525 | default: 526 | return domNode 527 | } 528 | } 529 | 530 | function removeNode(domNode, vNode) { 531 | var parentNode = domNode.parentNode 532 | 533 | if (parentNode) { 534 | parentNode.removeChild(domNode) 535 | } 536 | 537 | destroyWidget(domNode, vNode); 538 | 539 | return null 540 | } 541 | 542 | function insertNode(parentNode, vNode, renderOptions) { 543 | var newNode = render(vNode, renderOptions) 544 | 545 | if (parentNode) { 546 | parentNode.appendChild(newNode) 547 | } 548 | 549 | return parentNode 550 | } 551 | 552 | function stringPatch(domNode, leftVNode, vText, renderOptions) { 553 | var newNode 554 | 555 | if (domNode.nodeType === 3) { 556 | domNode.replaceData(0, domNode.length, vText.text) 557 | newNode = domNode 558 | } else { 559 | var parentNode = domNode.parentNode 560 | newNode = render(vText, renderOptions) 561 | 562 | if (parentNode) { 563 | parentNode.replaceChild(newNode, domNode) 564 | } 565 | } 566 | 567 | return newNode 568 | } 569 | 570 | function widgetPatch(domNode, leftVNode, widget, renderOptions) { 571 | var updating = updateWidget(leftVNode, widget) 572 | var newNode 573 | 574 | if (updating) { 575 | newNode = widget.update(leftVNode, domNode) || domNode 576 | } else { 577 | newNode = render(widget, renderOptions) 578 | } 579 | 580 | var parentNode = domNode.parentNode 581 | 582 | if (parentNode && newNode !== domNode) { 583 | parentNode.replaceChild(newNode, domNode) 584 | } 585 | 586 | if (!updating) { 587 | destroyWidget(domNode, leftVNode) 588 | } 589 | 590 | return newNode 591 | } 592 | 593 | function vNodePatch(domNode, leftVNode, vNode, renderOptions) { 594 | var parentNode = domNode.parentNode 595 | var newNode = render(vNode, renderOptions) 596 | 597 | if (parentNode) { 598 | parentNode.replaceChild(newNode, domNode) 599 | } 600 | 601 | return newNode 602 | } 603 | 604 | function destroyWidget(domNode, w) { 605 | if (typeof w.destroy === "function" && isWidget(w)) { 606 | w.destroy(domNode) 607 | } 608 | } 609 | 610 | function reorderChildren(domNode, bIndex) { 611 | var children = [] 612 | var childNodes = domNode.childNodes 613 | var len = childNodes.length 614 | var i 615 | var reverseIndex = bIndex.reverse 616 | 617 | for (i = 0; i < len; i++) { 618 | children.push(domNode.childNodes[i]) 619 | } 620 | 621 | var insertOffset = 0 622 | var move 623 | var node 624 | var insertNode 625 | var chainLength 626 | var insertedLength 627 | var nextSibling 628 | for (i = 0; i < len;) { 629 | move = bIndex[i] 630 | chainLength = 1 631 | if (move !== undefined && move !== i) { 632 | // try to bring forward as long of a chain as possible 633 | while (bIndex[i + chainLength] === move + chainLength) { 634 | chainLength++; 635 | } 636 | 637 | // the element currently at this index will be moved later so increase the insert offset 638 | if (reverseIndex[i] > i + chainLength) { 639 | insertOffset++ 640 | } 641 | 642 | node = children[move] 643 | insertNode = childNodes[i + insertOffset] || null 644 | insertedLength = 0 645 | while (node !== insertNode && insertedLength++ < chainLength) { 646 | domNode.insertBefore(node, insertNode); 647 | node = children[move + insertedLength]; 648 | } 649 | 650 | // the moved element came from the front of the array so reduce the insert offset 651 | if (move + chainLength < i) { 652 | insertOffset-- 653 | } 654 | } 655 | 656 | // element at this index is scheduled to be removed so increase insert offset 657 | if (i in bIndex.removes) { 658 | insertOffset++ 659 | } 660 | 661 | i += chainLength 662 | } 663 | } 664 | 665 | function replaceRoot(oldRoot, newRoot) { 666 | if (oldRoot && newRoot && oldRoot !== newRoot && oldRoot.parentNode) { 667 | console.log(oldRoot) 668 | oldRoot.parentNode.replaceChild(newRoot, oldRoot) 669 | } 670 | 671 | return newRoot; 672 | } 673 | 674 | },{"../vnode/is-widget.js":28,"../vnode/vpatch.js":31,"./apply-properties":13,"./create-element":14,"./update-widget":18}],17:[function(_dereq_,module,exports){ 675 | var document = _dereq_("global/document") 676 | var isArray = _dereq_("x-is-array") 677 | 678 | var domIndex = _dereq_("./dom-index") 679 | var patchOp = _dereq_("./patch-op") 680 | module.exports = patch 681 | 682 | function patch(rootNode, patches) { 683 | return patchRecursive(rootNode, patches) 684 | } 685 | 686 | function patchRecursive(rootNode, patches, renderOptions) { 687 | var indices = patchIndices(patches) 688 | 689 | if (indices.length === 0) { 690 | return rootNode 691 | } 692 | 693 | var index = domIndex(rootNode, patches.a, indices) 694 | var ownerDocument = rootNode.ownerDocument 695 | 696 | if (!renderOptions) { 697 | renderOptions = { patch: patchRecursive } 698 | if (ownerDocument !== document) { 699 | renderOptions.document = ownerDocument 700 | } 701 | } 702 | 703 | for (var i = 0; i < indices.length; i++) { 704 | var nodeIndex = indices[i] 705 | rootNode = applyPatch(rootNode, 706 | index[nodeIndex], 707 | patches[nodeIndex], 708 | renderOptions) 709 | } 710 | 711 | return rootNode 712 | } 713 | 714 | function applyPatch(rootNode, domNode, patchList, renderOptions) { 715 | if (!domNode) { 716 | return rootNode 717 | } 718 | 719 | var newNode 720 | 721 | if (isArray(patchList)) { 722 | for (var i = 0; i < patchList.length; i++) { 723 | newNode = patchOp(patchList[i], domNode, renderOptions) 724 | 725 | if (domNode === rootNode) { 726 | rootNode = newNode 727 | } 728 | } 729 | } else { 730 | newNode = patchOp(patchList, domNode, renderOptions) 731 | 732 | if (domNode === rootNode) { 733 | rootNode = newNode 734 | } 735 | } 736 | 737 | return rootNode 738 | } 739 | 740 | function patchIndices(patches) { 741 | var indices = [] 742 | 743 | for (var key in patches) { 744 | if (key !== "a") { 745 | indices.push(Number(key)) 746 | } 747 | } 748 | 749 | return indices 750 | } 751 | 752 | },{"./dom-index":15,"./patch-op":16,"global/document":9,"x-is-array":11}],18:[function(_dereq_,module,exports){ 753 | var isWidget = _dereq_("../vnode/is-widget.js") 754 | 755 | module.exports = updateWidget 756 | 757 | function updateWidget(a, b) { 758 | if (isWidget(a) && isWidget(b)) { 759 | if ("name" in a && "name" in b) { 760 | return a.id === b.id 761 | } else { 762 | return a.init === b.init 763 | } 764 | } 765 | 766 | return false 767 | } 768 | 769 | },{"../vnode/is-widget.js":28}],19:[function(_dereq_,module,exports){ 770 | 'use strict'; 771 | 772 | var EvStore = _dereq_('ev-store'); 773 | 774 | module.exports = EvHook; 775 | 776 | function EvHook(value) { 777 | if (!(this instanceof EvHook)) { 778 | return new EvHook(value); 779 | } 780 | 781 | this.value = value; 782 | } 783 | 784 | EvHook.prototype.hook = function (node, propertyName) { 785 | var es = EvStore(node); 786 | var propName = propertyName.substr(3); 787 | 788 | es[propName] = this.value; 789 | }; 790 | 791 | EvHook.prototype.unhook = function(node, propertyName) { 792 | var es = EvStore(node); 793 | var propName = propertyName.substr(3); 794 | 795 | es[propName] = undefined; 796 | }; 797 | 798 | },{"ev-store":6}],20:[function(_dereq_,module,exports){ 799 | 'use strict'; 800 | 801 | module.exports = SoftSetHook; 802 | 803 | function SoftSetHook(value) { 804 | if (!(this instanceof SoftSetHook)) { 805 | return new SoftSetHook(value); 806 | } 807 | 808 | this.value = value; 809 | } 810 | 811 | SoftSetHook.prototype.hook = function (node, propertyName) { 812 | if (node[propertyName] !== this.value) { 813 | node[propertyName] = this.value; 814 | } 815 | }; 816 | 817 | },{}],21:[function(_dereq_,module,exports){ 818 | 'use strict'; 819 | 820 | var isArray = _dereq_('x-is-array'); 821 | 822 | var VNode = _dereq_('../vnode/vnode.js'); 823 | var VText = _dereq_('../vnode/vtext.js'); 824 | var isVNode = _dereq_('../vnode/is-vnode'); 825 | var isVText = _dereq_('../vnode/is-vtext'); 826 | var isWidget = _dereq_('../vnode/is-widget'); 827 | var isHook = _dereq_('../vnode/is-vhook'); 828 | var isVThunk = _dereq_('../vnode/is-thunk'); 829 | 830 | var parseTag = _dereq_('./parse-tag.js'); 831 | var softSetHook = _dereq_('./hooks/soft-set-hook.js'); 832 | var evHook = _dereq_('./hooks/ev-hook.js'); 833 | 834 | module.exports = h; 835 | 836 | function h(tagName, properties, children) { 837 | var childNodes = []; 838 | var tag, props, key, namespace; 839 | 840 | if (!children && isChildren(properties)) { 841 | children = properties; 842 | props = {}; 843 | } 844 | 845 | props = props || properties || {}; 846 | tag = parseTag(tagName, props); 847 | 848 | // support keys 849 | if (props.hasOwnProperty('key')) { 850 | key = props.key; 851 | props.key = undefined; 852 | } 853 | 854 | // support namespace 855 | if (props.hasOwnProperty('namespace')) { 856 | namespace = props.namespace; 857 | props.namespace = undefined; 858 | } 859 | 860 | // fix cursor bug 861 | if (tag === 'INPUT' && 862 | !namespace && 863 | props.hasOwnProperty('value') && 864 | props.value !== undefined && 865 | !isHook(props.value) 866 | ) { 867 | props.value = softSetHook(props.value); 868 | } 869 | 870 | transformProperties(props); 871 | 872 | if (children !== undefined && children !== null) { 873 | addChild(children, childNodes, tag, props); 874 | } 875 | 876 | 877 | return new VNode(tag, props, childNodes, key, namespace); 878 | } 879 | 880 | function addChild(c, childNodes, tag, props) { 881 | if (typeof c === 'string') { 882 | childNodes.push(new VText(c)); 883 | } else if (isChild(c)) { 884 | childNodes.push(c); 885 | } else if (isArray(c)) { 886 | for (var i = 0; i < c.length; i++) { 887 | addChild(c[i], childNodes, tag, props); 888 | } 889 | } else if (c === null || c === undefined) { 890 | return; 891 | } else { 892 | throw UnexpectedVirtualElement({ 893 | foreignObject: c, 894 | parentVnode: { 895 | tagName: tag, 896 | properties: props 897 | } 898 | }); 899 | } 900 | } 901 | 902 | function transformProperties(props) { 903 | for (var propName in props) { 904 | if (props.hasOwnProperty(propName)) { 905 | var value = props[propName]; 906 | 907 | if (isHook(value)) { 908 | continue; 909 | } 910 | 911 | if (propName.substr(0, 3) === 'ev-') { 912 | // add ev-foo support 913 | props[propName] = evHook(value); 914 | } 915 | } 916 | } 917 | } 918 | 919 | function isChild(x) { 920 | return isVNode(x) || isVText(x) || isWidget(x) || isVThunk(x); 921 | } 922 | 923 | function isChildren(x) { 924 | return typeof x === 'string' || isArray(x) || isChild(x); 925 | } 926 | 927 | function UnexpectedVirtualElement(data) { 928 | var err = new Error(); 929 | 930 | err.type = 'virtual-hyperscript.unexpected.virtual-element'; 931 | err.message = 'Unexpected virtual child passed to h().\n' + 932 | 'Expected a VNode / Vthunk / VWidget / string but:\n' + 933 | 'got:\n' + 934 | errorString(data.foreignObject) + 935 | '.\n' + 936 | 'The parent vnode is:\n' + 937 | errorString(data.parentVnode) 938 | '\n' + 939 | 'Suggested fix: change your `h(..., [ ... ])` callsite.'; 940 | err.foreignObject = data.foreignObject; 941 | err.parentVnode = data.parentVnode; 942 | 943 | return err; 944 | } 945 | 946 | function errorString(obj) { 947 | try { 948 | return JSON.stringify(obj, null, ' '); 949 | } catch (e) { 950 | return String(obj); 951 | } 952 | } 953 | 954 | },{"../vnode/is-thunk":24,"../vnode/is-vhook":25,"../vnode/is-vnode":26,"../vnode/is-vtext":27,"../vnode/is-widget":28,"../vnode/vnode.js":30,"../vnode/vtext.js":32,"./hooks/ev-hook.js":19,"./hooks/soft-set-hook.js":20,"./parse-tag.js":22,"x-is-array":11}],22:[function(_dereq_,module,exports){ 955 | 'use strict'; 956 | 957 | var split = _dereq_('browser-split'); 958 | 959 | var classIdSplit = /([\.#]?[a-zA-Z0-9_:-]+)/; 960 | var notClassId = /^\.|#/; 961 | 962 | module.exports = parseTag; 963 | 964 | function parseTag(tag, props) { 965 | if (!tag) { 966 | return 'DIV'; 967 | } 968 | 969 | var noId = !(props.hasOwnProperty('id')); 970 | 971 | var tagParts = split(tag, classIdSplit); 972 | var tagName = null; 973 | 974 | if (notClassId.test(tagParts[1])) { 975 | tagName = 'DIV'; 976 | } 977 | 978 | var classes, part, type, i; 979 | 980 | for (i = 0; i < tagParts.length; i++) { 981 | part = tagParts[i]; 982 | 983 | if (!part) { 984 | continue; 985 | } 986 | 987 | type = part.charAt(0); 988 | 989 | if (!tagName) { 990 | tagName = part; 991 | } else if (type === '.') { 992 | classes = classes || []; 993 | classes.push(part.substring(1, part.length)); 994 | } else if (type === '#' && noId) { 995 | props.id = part.substring(1, part.length); 996 | } 997 | } 998 | 999 | if (classes) { 1000 | if (props.className) { 1001 | classes.push(props.className); 1002 | } 1003 | 1004 | props.className = classes.join(' '); 1005 | } 1006 | 1007 | return props.namespace ? tagName : tagName.toUpperCase(); 1008 | } 1009 | 1010 | },{"browser-split":5}],23:[function(_dereq_,module,exports){ 1011 | var isVNode = _dereq_("./is-vnode") 1012 | var isVText = _dereq_("./is-vtext") 1013 | var isWidget = _dereq_("./is-widget") 1014 | var isThunk = _dereq_("./is-thunk") 1015 | 1016 | module.exports = handleThunk 1017 | 1018 | function handleThunk(a, b) { 1019 | var renderedA = a 1020 | var renderedB = b 1021 | 1022 | if (isThunk(b)) { 1023 | renderedB = renderThunk(b, a) 1024 | } 1025 | 1026 | if (isThunk(a)) { 1027 | renderedA = renderThunk(a, null) 1028 | } 1029 | 1030 | return { 1031 | a: renderedA, 1032 | b: renderedB 1033 | } 1034 | } 1035 | 1036 | function renderThunk(thunk, previous) { 1037 | var renderedThunk = thunk.vnode 1038 | 1039 | if (!renderedThunk) { 1040 | renderedThunk = thunk.vnode = thunk.render(previous) 1041 | } 1042 | 1043 | if (!(isVNode(renderedThunk) || 1044 | isVText(renderedThunk) || 1045 | isWidget(renderedThunk))) { 1046 | throw new Error("thunk did not return a valid node"); 1047 | } 1048 | 1049 | return renderedThunk 1050 | } 1051 | 1052 | },{"./is-thunk":24,"./is-vnode":26,"./is-vtext":27,"./is-widget":28}],24:[function(_dereq_,module,exports){ 1053 | module.exports = isThunk 1054 | 1055 | function isThunk(t) { 1056 | return t && t.type === "Thunk" 1057 | } 1058 | 1059 | },{}],25:[function(_dereq_,module,exports){ 1060 | module.exports = isHook 1061 | 1062 | function isHook(hook) { 1063 | return hook && 1064 | (typeof hook.hook === "function" && !hook.hasOwnProperty("hook") || 1065 | typeof hook.unhook === "function" && !hook.hasOwnProperty("unhook")) 1066 | } 1067 | 1068 | },{}],26:[function(_dereq_,module,exports){ 1069 | var version = _dereq_("./version") 1070 | 1071 | module.exports = isVirtualNode 1072 | 1073 | function isVirtualNode(x) { 1074 | return x && x.type === "VirtualNode" && x.version === version 1075 | } 1076 | 1077 | },{"./version":29}],27:[function(_dereq_,module,exports){ 1078 | var version = _dereq_("./version") 1079 | 1080 | module.exports = isVirtualText 1081 | 1082 | function isVirtualText(x) { 1083 | return x && x.type === "VirtualText" && x.version === version 1084 | } 1085 | 1086 | },{"./version":29}],28:[function(_dereq_,module,exports){ 1087 | module.exports = isWidget 1088 | 1089 | function isWidget(w) { 1090 | return w && w.type === "Widget" 1091 | } 1092 | 1093 | },{}],29:[function(_dereq_,module,exports){ 1094 | module.exports = "1" 1095 | 1096 | },{}],30:[function(_dereq_,module,exports){ 1097 | var version = _dereq_("./version") 1098 | var isVNode = _dereq_("./is-vnode") 1099 | var isWidget = _dereq_("./is-widget") 1100 | var isThunk = _dereq_("./is-thunk") 1101 | var isVHook = _dereq_("./is-vhook") 1102 | 1103 | module.exports = VirtualNode 1104 | 1105 | var noProperties = {} 1106 | var noChildren = [] 1107 | 1108 | function VirtualNode(tagName, properties, children, key, namespace) { 1109 | this.tagName = tagName 1110 | this.properties = properties || noProperties 1111 | this.children = children || noChildren 1112 | this.key = key != null ? String(key) : undefined 1113 | this.namespace = (typeof namespace === "string") ? namespace : null 1114 | 1115 | var count = (children && children.length) || 0 1116 | var descendants = 0 1117 | var hasWidgets = false 1118 | var hasThunks = false 1119 | var descendantHooks = false 1120 | var hooks 1121 | 1122 | for (var propName in properties) { 1123 | if (properties.hasOwnProperty(propName)) { 1124 | var property = properties[propName] 1125 | if (isVHook(property) && property.unhook) { 1126 | if (!hooks) { 1127 | hooks = {} 1128 | } 1129 | 1130 | hooks[propName] = property 1131 | } 1132 | } 1133 | } 1134 | 1135 | for (var i = 0; i < count; i++) { 1136 | var child = children[i] 1137 | if (isVNode(child)) { 1138 | descendants += child.count || 0 1139 | 1140 | if (!hasWidgets && child.hasWidgets) { 1141 | hasWidgets = true 1142 | } 1143 | 1144 | if (!hasThunks && child.hasThunks) { 1145 | hasThunks = true 1146 | } 1147 | 1148 | if (!descendantHooks && (child.hooks || child.descendantHooks)) { 1149 | descendantHooks = true 1150 | } 1151 | } else if (!hasWidgets && isWidget(child)) { 1152 | if (typeof child.destroy === "function") { 1153 | hasWidgets = true 1154 | } 1155 | } else if (!hasThunks && isThunk(child)) { 1156 | hasThunks = true; 1157 | } 1158 | } 1159 | 1160 | this.count = count + descendants 1161 | this.hasWidgets = hasWidgets 1162 | this.hasThunks = hasThunks 1163 | this.hooks = hooks 1164 | this.descendantHooks = descendantHooks 1165 | } 1166 | 1167 | VirtualNode.prototype.version = version 1168 | VirtualNode.prototype.type = "VirtualNode" 1169 | 1170 | },{"./is-thunk":24,"./is-vhook":25,"./is-vnode":26,"./is-widget":28,"./version":29}],31:[function(_dereq_,module,exports){ 1171 | var version = _dereq_("./version") 1172 | 1173 | VirtualPatch.NONE = 0 1174 | VirtualPatch.VTEXT = 1 1175 | VirtualPatch.VNODE = 2 1176 | VirtualPatch.WIDGET = 3 1177 | VirtualPatch.PROPS = 4 1178 | VirtualPatch.ORDER = 5 1179 | VirtualPatch.INSERT = 6 1180 | VirtualPatch.REMOVE = 7 1181 | VirtualPatch.THUNK = 8 1182 | 1183 | module.exports = VirtualPatch 1184 | 1185 | function VirtualPatch(type, vNode, patch) { 1186 | this.type = Number(type) 1187 | this.vNode = vNode 1188 | this.patch = patch 1189 | } 1190 | 1191 | VirtualPatch.prototype.version = version 1192 | VirtualPatch.prototype.type = "VirtualPatch" 1193 | 1194 | },{"./version":29}],32:[function(_dereq_,module,exports){ 1195 | var version = _dereq_("./version") 1196 | 1197 | module.exports = VirtualText 1198 | 1199 | function VirtualText(text) { 1200 | this.text = String(text) 1201 | } 1202 | 1203 | VirtualText.prototype.version = version 1204 | VirtualText.prototype.type = "VirtualText" 1205 | 1206 | },{"./version":29}],33:[function(_dereq_,module,exports){ 1207 | var isObject = _dereq_("is-object") 1208 | var isHook = _dereq_("../vnode/is-vhook") 1209 | 1210 | module.exports = diffProps 1211 | 1212 | function diffProps(a, b) { 1213 | var diff 1214 | 1215 | for (var aKey in a) { 1216 | if (!(aKey in b)) { 1217 | diff = diff || {} 1218 | diff[aKey] = undefined 1219 | } 1220 | 1221 | var aValue = a[aKey] 1222 | var bValue = b[aKey] 1223 | 1224 | if (aValue === bValue) { 1225 | continue 1226 | } else if (isObject(aValue) && isObject(bValue)) { 1227 | if (getPrototype(bValue) !== getPrototype(aValue)) { 1228 | diff = diff || {} 1229 | diff[aKey] = bValue 1230 | } else if (isHook(bValue)) { 1231 | diff = diff || {} 1232 | diff[aKey] = bValue 1233 | } else { 1234 | var objectDiff = diffProps(aValue, bValue) 1235 | if (objectDiff) { 1236 | diff = diff || {} 1237 | diff[aKey] = objectDiff 1238 | } 1239 | } 1240 | } else { 1241 | diff = diff || {} 1242 | diff[aKey] = bValue 1243 | } 1244 | } 1245 | 1246 | for (var bKey in b) { 1247 | if (!(bKey in a)) { 1248 | diff = diff || {} 1249 | diff[bKey] = b[bKey] 1250 | } 1251 | } 1252 | 1253 | return diff 1254 | } 1255 | 1256 | function getPrototype(value) { 1257 | if (Object.getPrototypeOf) { 1258 | return Object.getPrototypeOf(value) 1259 | } else if (value.__proto__) { 1260 | return value.__proto__ 1261 | } else if (value.constructor) { 1262 | return value.constructor.prototype 1263 | } 1264 | } 1265 | 1266 | },{"../vnode/is-vhook":25,"is-object":10}],34:[function(_dereq_,module,exports){ 1267 | var isArray = _dereq_("x-is-array") 1268 | 1269 | var VPatch = _dereq_("../vnode/vpatch") 1270 | var isVNode = _dereq_("../vnode/is-vnode") 1271 | var isVText = _dereq_("../vnode/is-vtext") 1272 | var isWidget = _dereq_("../vnode/is-widget") 1273 | var isThunk = _dereq_("../vnode/is-thunk") 1274 | var handleThunk = _dereq_("../vnode/handle-thunk") 1275 | 1276 | var diffProps = _dereq_("./diff-props") 1277 | 1278 | module.exports = diff 1279 | 1280 | function diff(a, b) { 1281 | var patch = { a: a } 1282 | walk(a, b, patch, 0) 1283 | return patch 1284 | } 1285 | 1286 | function walk(a, b, patch, index) { 1287 | if (a === b) { 1288 | return 1289 | } 1290 | 1291 | var apply = patch[index] 1292 | var applyClear = false 1293 | 1294 | if (isThunk(a) || isThunk(b)) { 1295 | thunks(a, b, patch, index) 1296 | } else if (b == null) { 1297 | 1298 | // If a is a widget we will add a remove patch for it 1299 | // Otherwise any child widgets/hooks must be destroyed. 1300 | // This prevents adding two remove patches for a widget. 1301 | if (!isWidget(a)) { 1302 | clearState(a, patch, index) 1303 | apply = patch[index] 1304 | } 1305 | 1306 | apply = appendPatch(apply, new VPatch(VPatch.REMOVE, a, b)) 1307 | } else if (isVNode(b)) { 1308 | if (isVNode(a)) { 1309 | if (a.tagName === b.tagName && 1310 | a.namespace === b.namespace && 1311 | a.key === b.key) { 1312 | var propsPatch = diffProps(a.properties, b.properties) 1313 | if (propsPatch) { 1314 | apply = appendPatch(apply, 1315 | new VPatch(VPatch.PROPS, a, propsPatch)) 1316 | } 1317 | apply = diffChildren(a, b, patch, apply, index) 1318 | } else { 1319 | apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b)) 1320 | applyClear = true 1321 | } 1322 | } else { 1323 | apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b)) 1324 | applyClear = true 1325 | } 1326 | } else if (isVText(b)) { 1327 | if (!isVText(a)) { 1328 | apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b)) 1329 | applyClear = true 1330 | } else if (a.text !== b.text) { 1331 | apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b)) 1332 | } 1333 | } else if (isWidget(b)) { 1334 | if (!isWidget(a)) { 1335 | applyClear = true; 1336 | } 1337 | 1338 | apply = appendPatch(apply, new VPatch(VPatch.WIDGET, a, b)) 1339 | } 1340 | 1341 | if (apply) { 1342 | patch[index] = apply 1343 | } 1344 | 1345 | if (applyClear) { 1346 | clearState(a, patch, index) 1347 | } 1348 | } 1349 | 1350 | function diffChildren(a, b, patch, apply, index) { 1351 | var aChildren = a.children 1352 | var bChildren = reorder(aChildren, b.children) 1353 | 1354 | var aLen = aChildren.length 1355 | var bLen = bChildren.length 1356 | var len = aLen > bLen ? aLen : bLen 1357 | 1358 | for (var i = 0; i < len; i++) { 1359 | var leftNode = aChildren[i] 1360 | var rightNode = bChildren[i] 1361 | index += 1 1362 | 1363 | if (!leftNode) { 1364 | if (rightNode) { 1365 | // Excess nodes in b need to be added 1366 | apply = appendPatch(apply, 1367 | new VPatch(VPatch.INSERT, null, rightNode)) 1368 | } 1369 | } else { 1370 | walk(leftNode, rightNode, patch, index) 1371 | } 1372 | 1373 | if (isVNode(leftNode) && leftNode.count) { 1374 | index += leftNode.count 1375 | } 1376 | } 1377 | 1378 | if (bChildren.moves) { 1379 | // Reorder nodes last 1380 | apply = appendPatch(apply, new VPatch(VPatch.ORDER, a, bChildren.moves)) 1381 | } 1382 | 1383 | return apply 1384 | } 1385 | 1386 | function clearState(vNode, patch, index) { 1387 | // TODO: Make this a single walk, not two 1388 | unhook(vNode, patch, index) 1389 | destroyWidgets(vNode, patch, index) 1390 | } 1391 | 1392 | // Patch records for all destroyed widgets must be added because we need 1393 | // a DOM node reference for the destroy function 1394 | function destroyWidgets(vNode, patch, index) { 1395 | if (isWidget(vNode)) { 1396 | if (typeof vNode.destroy === "function") { 1397 | patch[index] = appendPatch( 1398 | patch[index], 1399 | new VPatch(VPatch.REMOVE, vNode, null) 1400 | ) 1401 | } 1402 | } else if (isVNode(vNode) && (vNode.hasWidgets || vNode.hasThunks)) { 1403 | var children = vNode.children 1404 | var len = children.length 1405 | for (var i = 0; i < len; i++) { 1406 | var child = children[i] 1407 | index += 1 1408 | 1409 | destroyWidgets(child, patch, index) 1410 | 1411 | if (isVNode(child) && child.count) { 1412 | index += child.count 1413 | } 1414 | } 1415 | } else if (isThunk(vNode)) { 1416 | thunks(vNode, null, patch, index) 1417 | } 1418 | } 1419 | 1420 | // Create a sub-patch for thunks 1421 | function thunks(a, b, patch, index) { 1422 | var nodes = handleThunk(a, b); 1423 | var thunkPatch = diff(nodes.a, nodes.b) 1424 | if (hasPatches(thunkPatch)) { 1425 | patch[index] = new VPatch(VPatch.THUNK, null, thunkPatch) 1426 | } 1427 | } 1428 | 1429 | function hasPatches(patch) { 1430 | for (var index in patch) { 1431 | if (index !== "a") { 1432 | return true; 1433 | } 1434 | } 1435 | 1436 | return false; 1437 | } 1438 | 1439 | // Execute hooks when two nodes are identical 1440 | function unhook(vNode, patch, index) { 1441 | if (isVNode(vNode)) { 1442 | if (vNode.hooks) { 1443 | patch[index] = appendPatch( 1444 | patch[index], 1445 | new VPatch( 1446 | VPatch.PROPS, 1447 | vNode, 1448 | undefinedKeys(vNode.hooks) 1449 | ) 1450 | ) 1451 | } 1452 | 1453 | if (vNode.descendantHooks || vNode.hasThunks) { 1454 | var children = vNode.children 1455 | var len = children.length 1456 | for (var i = 0; i < len; i++) { 1457 | var child = children[i] 1458 | index += 1 1459 | 1460 | unhook(child, patch, index) 1461 | 1462 | if (isVNode(child) && child.count) { 1463 | index += child.count 1464 | } 1465 | } 1466 | } 1467 | } else if (isThunk(vNode)) { 1468 | thunks(vNode, null, patch, index) 1469 | } 1470 | } 1471 | 1472 | function undefinedKeys(obj) { 1473 | var result = {} 1474 | 1475 | for (var key in obj) { 1476 | result[key] = undefined 1477 | } 1478 | 1479 | return result 1480 | } 1481 | 1482 | // List diff, naive left to right reordering 1483 | function reorder(aChildren, bChildren) { 1484 | 1485 | var bKeys = keyIndex(bChildren) 1486 | 1487 | if (!bKeys) { 1488 | return bChildren 1489 | } 1490 | 1491 | var aKeys = keyIndex(aChildren) 1492 | 1493 | if (!aKeys) { 1494 | return bChildren 1495 | } 1496 | 1497 | var bMatch = {}, aMatch = {} 1498 | 1499 | for (var aKey in bKeys) { 1500 | bMatch[bKeys[aKey]] = aKeys[aKey] 1501 | } 1502 | 1503 | for (var bKey in aKeys) { 1504 | aMatch[aKeys[bKey]] = bKeys[bKey] 1505 | } 1506 | 1507 | var aLen = aChildren.length 1508 | var bLen = bChildren.length 1509 | var len = aLen > bLen ? aLen : bLen 1510 | var shuffle = [] 1511 | var freeIndex = 0 1512 | var i = 0 1513 | var moveIndex = 0 1514 | var moves = {} 1515 | var removes = moves.removes = {} 1516 | var reverse = moves.reverse = {} 1517 | var hasMoves = false 1518 | 1519 | while (freeIndex < len) { 1520 | var move = aMatch[i] 1521 | if (move !== undefined) { 1522 | shuffle[i] = bChildren[move] 1523 | if (move !== moveIndex) { 1524 | moves[move] = moveIndex 1525 | reverse[moveIndex] = move 1526 | hasMoves = true 1527 | } 1528 | moveIndex++ 1529 | } else if (i in aMatch) { 1530 | shuffle[i] = undefined 1531 | removes[i] = moveIndex++ 1532 | hasMoves = true 1533 | } else { 1534 | while (bMatch[freeIndex] !== undefined) { 1535 | freeIndex++ 1536 | } 1537 | 1538 | if (freeIndex < len) { 1539 | var freeChild = bChildren[freeIndex] 1540 | if (freeChild) { 1541 | shuffle[i] = freeChild 1542 | if (freeIndex !== moveIndex) { 1543 | hasMoves = true 1544 | moves[freeIndex] = moveIndex 1545 | reverse[moveIndex] = freeIndex 1546 | } 1547 | moveIndex++ 1548 | } 1549 | freeIndex++ 1550 | } 1551 | } 1552 | i++ 1553 | } 1554 | 1555 | if (hasMoves) { 1556 | shuffle.moves = moves 1557 | } 1558 | 1559 | return shuffle 1560 | } 1561 | 1562 | function keyIndex(children) { 1563 | var i, keys 1564 | 1565 | for (i = 0; i < children.length; i++) { 1566 | var child = children[i] 1567 | 1568 | if (child.key !== undefined) { 1569 | keys = keys || {} 1570 | keys[child.key] = i 1571 | } 1572 | } 1573 | 1574 | return keys 1575 | } 1576 | 1577 | function appendPatch(apply, patch) { 1578 | if (apply) { 1579 | if (isArray(apply)) { 1580 | apply.push(patch) 1581 | } else { 1582 | apply = [apply, patch] 1583 | } 1584 | 1585 | return apply 1586 | } else { 1587 | return patch 1588 | } 1589 | } 1590 | 1591 | },{"../vnode/handle-thunk":23,"../vnode/is-thunk":24,"../vnode/is-vnode":26,"../vnode/is-vtext":27,"../vnode/is-widget":28,"../vnode/vpatch":31,"./diff-props":33,"x-is-array":11}],35:[function(_dereq_,module,exports){ 1592 | 1593 | },{}]},{},[4])(4) 1594 | }); 1595 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-virtual-dom", 3 | "version": "0.1.1", 4 | "homepage": "https://github.com/teropa/angular-virtual-dom", 5 | "author": "Tero Parviainen ", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/teropa/angular-virtual-dom.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/teropa/angular-virtual-dom/issues" 12 | }, 13 | "licenses": { 14 | "type": "MIT", 15 | "url": "https://github.com/teropa/angular-virtual-dom/blob/master/LICENSE" 16 | }, 17 | "dependencies": { 18 | }, 19 | "devDependencies": { 20 | "faithful-exec": "^0.1.0", 21 | "grunt": "^0.4.5", 22 | "grunt-contrib-clean": "^0.6.0", 23 | "grunt-contrib-concat": "^0.5.0", 24 | "grunt-contrib-connect": "^0.9.0", 25 | "grunt-contrib-jshint": "^0.11.0", 26 | "grunt-contrib-uglify": "^0.7.0", 27 | "grunt-contrib-watch": "^0.6.1", 28 | "grunt-conventional-changelog": "^1.1.0", 29 | "grunt-karma": "^0.10.1", 30 | "immutable": "=3.6.2", 31 | "jasmine-core": "^2.2.0", 32 | "karma": "^0.12.31", 33 | "karma-chrome-launcher": "^0.1.7", 34 | "karma-firefox-launcher": "^0.1.4", 35 | "karma-jasmine": "^0.3.5", 36 | "karma-phantomjs-launcher": "^0.1.4", 37 | "load-grunt-tasks": "^3.1.0", 38 | "shelljs": "^0.3.0" 39 | }, 40 | "scripts": { 41 | "test": "grunt karma:unit" 42 | }, 43 | "main": "release/angular-virtual-dom.js" 44 | } 45 | -------------------------------------------------------------------------------- /release/angular-virtual-dom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @version v0.1.1 4 | * @link https://github.com/teropa/angular-virtual-dom 5 | * @license MIT License, http://www.opensource.org/licenses/MIT 6 | * 7 | * Bundles virtual-dom by Matt-Esch 8 | * @link https://github.com/Matt-Esch/virtual-dom 9 | * @license MIT License, http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* commonjs package manager support */ 13 | if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){ 14 | module.exports = 'teropa.virtualDom'; 15 | } 16 | 17 | (function (window, angular, undefined) { 18 | !function(e){var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.virtualDom=e()}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 58 | * Available under the MIT License 59 | * ECMAScript compliant, uniform cross-browser split method 60 | */ 61 | 62 | /** 63 | * Splits a string into an array of strings using a regex or string separator. Matches of the 64 | * separator are not included in the result array. However, if `separator` is a regex that contains 65 | * capturing groups, backreferences are spliced into the result each time `separator` is matched. 66 | * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably 67 | * cross-browser. 68 | * @param {String} str String to split. 69 | * @param {RegExp|String} separator Regex or string to use for separating the string. 70 | * @param {Number} [limit] Maximum number of items to include in the result array. 71 | * @returns {Array} Array of substrings. 72 | * @example 73 | * 74 | * // Basic use 75 | * split('a b c d', ' '); 76 | * // -> ['a', 'b', 'c', 'd'] 77 | * 78 | * // With limit 79 | * split('a b c d', ' ', 2); 80 | * // -> ['a', 'b'] 81 | * 82 | * // Backreferences in result array 83 | * split('..word1 word2..', /([a-z]+)(\d+)/i); 84 | * // -> ['..', 'word', '1', ' ', 'word', '2', '..'] 85 | */ 86 | module.exports = (function split(undef) { 87 | 88 | var nativeSplit = String.prototype.split, 89 | compliantExecNpcg = /()??/.exec("")[1] === undef, 90 | // NPCG: nonparticipating capturing group 91 | self; 92 | 93 | self = function(str, separator, limit) { 94 | // If `separator` is not a regex, use `nativeSplit` 95 | if (Object.prototype.toString.call(separator) !== "[object RegExp]") { 96 | return nativeSplit.call(str, separator, limit); 97 | } 98 | var output = [], 99 | flags = (separator.ignoreCase ? "i" : "") + (separator.multiline ? "m" : "") + (separator.extended ? "x" : "") + // Proposed for ES6 100 | (separator.sticky ? "y" : ""), 101 | // Firefox 3+ 102 | lastLastIndex = 0, 103 | // Make `global` and avoid `lastIndex` issues by working with a copy 104 | separator = new RegExp(separator.source, flags + "g"), 105 | separator2, match, lastIndex, lastLength; 106 | str += ""; // Type-convert 107 | if (!compliantExecNpcg) { 108 | // Doesn't need flags gy, but they don't hurt 109 | separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags); 110 | } 111 | /* Values for `limit`, per the spec: 112 | * If undefined: 4294967295 // Math.pow(2, 32) - 1 113 | * If 0, Infinity, or NaN: 0 114 | * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296; 115 | * If negative number: 4294967296 - Math.floor(Math.abs(limit)) 116 | * If other: Type-convert, then use the above rules 117 | */ 118 | limit = limit === undef ? -1 >>> 0 : // Math.pow(2, 32) - 1 119 | limit >>> 0; // ToUint32(limit) 120 | while (match = separator.exec(str)) { 121 | // `separator.lastIndex` is not reliable cross-browser 122 | lastIndex = match.index + match[0].length; 123 | if (lastIndex > lastLastIndex) { 124 | output.push(str.slice(lastLastIndex, match.index)); 125 | // Fix browsers whose `exec` methods don't consistently return `undefined` for 126 | // nonparticipating capturing groups 127 | if (!compliantExecNpcg && match.length > 1) { 128 | match[0].replace(separator2, function() { 129 | for (var i = 1; i < arguments.length - 2; i++) { 130 | if (arguments[i] === undef) { 131 | match[i] = undef; 132 | } 133 | } 134 | }); 135 | } 136 | if (match.length > 1 && match.index < str.length) { 137 | Array.prototype.push.apply(output, match.slice(1)); 138 | } 139 | lastLength = match[0].length; 140 | lastLastIndex = lastIndex; 141 | if (output.length >= limit) { 142 | break; 143 | } 144 | } 145 | if (separator.lastIndex === match.index) { 146 | separator.lastIndex++; // Avoid an infinite loop 147 | } 148 | } 149 | if (lastLastIndex === str.length) { 150 | if (lastLength || !separator.test("")) { 151 | output.push(""); 152 | } 153 | } else { 154 | output.push(str.slice(lastLastIndex)); 155 | } 156 | return output.length > limit ? output.slice(0, limit) : output; 157 | }; 158 | 159 | return self; 160 | })(); 161 | 162 | },{}],6:[function(_dereq_,module,exports){ 163 | 'use strict'; 164 | 165 | var OneVersionConstraint = _dereq_('individual/one-version'); 166 | 167 | var MY_VERSION = '7'; 168 | OneVersionConstraint('ev-store', MY_VERSION); 169 | 170 | var hashKey = '__EV_STORE_KEY@' + MY_VERSION; 171 | 172 | module.exports = EvStore; 173 | 174 | function EvStore(elem) { 175 | var hash = elem[hashKey]; 176 | 177 | if (!hash) { 178 | hash = elem[hashKey] = {}; 179 | } 180 | 181 | return hash; 182 | } 183 | 184 | },{"individual/one-version":8}],7:[function(_dereq_,module,exports){ 185 | (function (global){ 186 | 'use strict'; 187 | 188 | /*global window, global*/ 189 | 190 | var root = typeof window !== 'undefined' ? 191 | window : typeof global !== 'undefined' ? 192 | global : {}; 193 | 194 | module.exports = Individual; 195 | 196 | function Individual(key, value) { 197 | if (key in root) { 198 | return root[key]; 199 | } 200 | 201 | root[key] = value; 202 | 203 | return value; 204 | } 205 | 206 | }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 207 | },{}],8:[function(_dereq_,module,exports){ 208 | 'use strict'; 209 | 210 | var Individual = _dereq_('./index.js'); 211 | 212 | module.exports = OneVersion; 213 | 214 | function OneVersion(moduleName, version, defaultValue) { 215 | var key = '__INDIVIDUAL_ONE_VERSION_' + moduleName; 216 | var enforceKey = key + '_ENFORCE_SINGLETON'; 217 | 218 | var versionValue = Individual(enforceKey, version); 219 | 220 | if (versionValue !== version) { 221 | throw new Error('Can only have one copy of ' + 222 | moduleName + '.\n' + 223 | 'You already have version ' + versionValue + 224 | ' installed.\n' + 225 | 'This means you cannot install version ' + version); 226 | } 227 | 228 | return Individual(key, defaultValue); 229 | } 230 | 231 | },{"./index.js":7}],9:[function(_dereq_,module,exports){ 232 | (function (global){ 233 | var topLevel = typeof global !== 'undefined' ? global : 234 | typeof window !== 'undefined' ? window : {} 235 | var minDoc = _dereq_('min-document'); 236 | 237 | if (typeof document !== 'undefined') { 238 | module.exports = document; 239 | } else { 240 | var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4']; 241 | 242 | if (!doccy) { 243 | doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc; 244 | } 245 | 246 | module.exports = doccy; 247 | } 248 | 249 | }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 250 | },{"min-document":35}],10:[function(_dereq_,module,exports){ 251 | "use strict"; 252 | 253 | module.exports = function isObject(x) { 254 | return typeof x === "object" && x !== null; 255 | }; 256 | 257 | },{}],11:[function(_dereq_,module,exports){ 258 | var nativeIsArray = Array.isArray 259 | var toString = Object.prototype.toString 260 | 261 | module.exports = nativeIsArray || isArray 262 | 263 | function isArray(obj) { 264 | return toString.call(obj) === "[object Array]" 265 | } 266 | 267 | },{}],12:[function(_dereq_,module,exports){ 268 | var patch = _dereq_("./vdom/patch.js") 269 | 270 | module.exports = patch 271 | 272 | },{"./vdom/patch.js":17}],13:[function(_dereq_,module,exports){ 273 | var isObject = _dereq_("is-object") 274 | var isHook = _dereq_("../vnode/is-vhook.js") 275 | 276 | module.exports = applyProperties 277 | 278 | function applyProperties(node, props, previous) { 279 | for (var propName in props) { 280 | var propValue = props[propName] 281 | 282 | if (propValue === undefined) { 283 | removeProperty(node, propName, propValue, previous); 284 | } else if (isHook(propValue)) { 285 | removeProperty(node, propName, propValue, previous) 286 | if (propValue.hook) { 287 | propValue.hook(node, 288 | propName, 289 | previous ? previous[propName] : undefined) 290 | } 291 | } else { 292 | if (isObject(propValue)) { 293 | patchObject(node, props, previous, propName, propValue); 294 | } else { 295 | node[propName] = propValue 296 | } 297 | } 298 | } 299 | } 300 | 301 | function removeProperty(node, propName, propValue, previous) { 302 | if (previous) { 303 | var previousValue = previous[propName] 304 | 305 | if (!isHook(previousValue)) { 306 | if (propName === "attributes") { 307 | for (var attrName in previousValue) { 308 | node.removeAttribute(attrName) 309 | } 310 | } else if (propName === "style") { 311 | for (var i in previousValue) { 312 | node.style[i] = "" 313 | } 314 | } else if (typeof previousValue === "string") { 315 | node[propName] = "" 316 | } else { 317 | node[propName] = null 318 | } 319 | } else if (previousValue.unhook) { 320 | previousValue.unhook(node, propName, propValue) 321 | } 322 | } 323 | } 324 | 325 | function patchObject(node, props, previous, propName, propValue) { 326 | var previousValue = previous ? previous[propName] : undefined 327 | 328 | // Set attributes 329 | if (propName === "attributes") { 330 | for (var attrName in propValue) { 331 | var attrValue = propValue[attrName] 332 | 333 | if (attrValue === undefined) { 334 | node.removeAttribute(attrName) 335 | } else { 336 | node.setAttribute(attrName, attrValue) 337 | } 338 | } 339 | 340 | return 341 | } 342 | 343 | if(previousValue && isObject(previousValue) && 344 | getPrototype(previousValue) !== getPrototype(propValue)) { 345 | node[propName] = propValue 346 | return 347 | } 348 | 349 | if (!isObject(node[propName])) { 350 | node[propName] = {} 351 | } 352 | 353 | var replacer = propName === "style" ? "" : undefined 354 | 355 | for (var k in propValue) { 356 | var value = propValue[k] 357 | node[propName][k] = (value === undefined) ? replacer : value 358 | } 359 | } 360 | 361 | function getPrototype(value) { 362 | if (Object.getPrototypeOf) { 363 | return Object.getPrototypeOf(value) 364 | } else if (value.__proto__) { 365 | return value.__proto__ 366 | } else if (value.constructor) { 367 | return value.constructor.prototype 368 | } 369 | } 370 | 371 | },{"../vnode/is-vhook.js":25,"is-object":10}],14:[function(_dereq_,module,exports){ 372 | var document = _dereq_("global/document") 373 | 374 | var applyProperties = _dereq_("./apply-properties") 375 | 376 | var isVNode = _dereq_("../vnode/is-vnode.js") 377 | var isVText = _dereq_("../vnode/is-vtext.js") 378 | var isWidget = _dereq_("../vnode/is-widget.js") 379 | var handleThunk = _dereq_("../vnode/handle-thunk.js") 380 | 381 | module.exports = createElement 382 | 383 | function createElement(vnode, opts) { 384 | var doc = opts ? opts.document || document : document 385 | var warn = opts ? opts.warn : null 386 | 387 | vnode = handleThunk(vnode).a 388 | 389 | if (isWidget(vnode)) { 390 | return vnode.init() 391 | } else if (isVText(vnode)) { 392 | return doc.createTextNode(vnode.text) 393 | } else if (!isVNode(vnode)) { 394 | if (warn) { 395 | warn("Item is not a valid virtual dom node", vnode) 396 | } 397 | return null 398 | } 399 | 400 | var node = (vnode.namespace === null) ? 401 | doc.createElement(vnode.tagName) : 402 | doc.createElementNS(vnode.namespace, vnode.tagName) 403 | 404 | var props = vnode.properties 405 | applyProperties(node, props) 406 | 407 | var children = vnode.children 408 | 409 | for (var i = 0; i < children.length; i++) { 410 | var childNode = createElement(children[i], opts) 411 | if (childNode) { 412 | node.appendChild(childNode) 413 | } 414 | } 415 | 416 | return node 417 | } 418 | 419 | },{"../vnode/handle-thunk.js":23,"../vnode/is-vnode.js":26,"../vnode/is-vtext.js":27,"../vnode/is-widget.js":28,"./apply-properties":13,"global/document":9}],15:[function(_dereq_,module,exports){ 420 | // Maps a virtual DOM tree onto a real DOM tree in an efficient manner. 421 | // We don't want to read all of the DOM nodes in the tree so we use 422 | // the in-order tree indexing to eliminate recursion down certain branches. 423 | // We only recurse into a DOM node if we know that it contains a child of 424 | // interest. 425 | 426 | var noChild = {} 427 | 428 | module.exports = domIndex 429 | 430 | function domIndex(rootNode, tree, indices, nodes) { 431 | if (!indices || indices.length === 0) { 432 | return {} 433 | } else { 434 | indices.sort(ascending) 435 | return recurse(rootNode, tree, indices, nodes, 0) 436 | } 437 | } 438 | 439 | function recurse(rootNode, tree, indices, nodes, rootIndex) { 440 | nodes = nodes || {} 441 | 442 | 443 | if (rootNode) { 444 | if (indexInRange(indices, rootIndex, rootIndex)) { 445 | nodes[rootIndex] = rootNode 446 | } 447 | 448 | var vChildren = tree.children 449 | 450 | if (vChildren) { 451 | 452 | var childNodes = rootNode.childNodes 453 | 454 | for (var i = 0; i < tree.children.length; i++) { 455 | rootIndex += 1 456 | 457 | var vChild = vChildren[i] || noChild 458 | var nextIndex = rootIndex + (vChild.count || 0) 459 | 460 | // skip recursion down the tree if there are no nodes down here 461 | if (indexInRange(indices, rootIndex, nextIndex)) { 462 | recurse(childNodes[i], vChild, indices, nodes, rootIndex) 463 | } 464 | 465 | rootIndex = nextIndex 466 | } 467 | } 468 | } 469 | 470 | return nodes 471 | } 472 | 473 | // Binary search for an index in the interval [left, right] 474 | function indexInRange(indices, left, right) { 475 | if (indices.length === 0) { 476 | return false 477 | } 478 | 479 | var minIndex = 0 480 | var maxIndex = indices.length - 1 481 | var currentIndex 482 | var currentItem 483 | 484 | while (minIndex <= maxIndex) { 485 | currentIndex = ((maxIndex + minIndex) / 2) >> 0 486 | currentItem = indices[currentIndex] 487 | 488 | if (minIndex === maxIndex) { 489 | return currentItem >= left && currentItem <= right 490 | } else if (currentItem < left) { 491 | minIndex = currentIndex + 1 492 | } else if (currentItem > right) { 493 | maxIndex = currentIndex - 1 494 | } else { 495 | return true 496 | } 497 | } 498 | 499 | return false; 500 | } 501 | 502 | function ascending(a, b) { 503 | return a > b ? 1 : -1 504 | } 505 | 506 | },{}],16:[function(_dereq_,module,exports){ 507 | var applyProperties = _dereq_("./apply-properties") 508 | 509 | var isWidget = _dereq_("../vnode/is-widget.js") 510 | var VPatch = _dereq_("../vnode/vpatch.js") 511 | 512 | var render = _dereq_("./create-element") 513 | var updateWidget = _dereq_("./update-widget") 514 | 515 | module.exports = applyPatch 516 | 517 | function applyPatch(vpatch, domNode, renderOptions) { 518 | var type = vpatch.type 519 | var vNode = vpatch.vNode 520 | var patch = vpatch.patch 521 | 522 | switch (type) { 523 | case VPatch.REMOVE: 524 | return removeNode(domNode, vNode) 525 | case VPatch.INSERT: 526 | return insertNode(domNode, patch, renderOptions) 527 | case VPatch.VTEXT: 528 | return stringPatch(domNode, vNode, patch, renderOptions) 529 | case VPatch.WIDGET: 530 | return widgetPatch(domNode, vNode, patch, renderOptions) 531 | case VPatch.VNODE: 532 | return vNodePatch(domNode, vNode, patch, renderOptions) 533 | case VPatch.ORDER: 534 | reorderChildren(domNode, patch) 535 | return domNode 536 | case VPatch.PROPS: 537 | applyProperties(domNode, patch, vNode.properties) 538 | return domNode 539 | case VPatch.THUNK: 540 | return replaceRoot(domNode, 541 | renderOptions.patch(domNode, patch, renderOptions)) 542 | default: 543 | return domNode 544 | } 545 | } 546 | 547 | function removeNode(domNode, vNode) { 548 | var parentNode = domNode.parentNode 549 | 550 | if (parentNode) { 551 | parentNode.removeChild(domNode) 552 | } 553 | 554 | destroyWidget(domNode, vNode); 555 | 556 | return null 557 | } 558 | 559 | function insertNode(parentNode, vNode, renderOptions) { 560 | var newNode = render(vNode, renderOptions) 561 | 562 | if (parentNode) { 563 | parentNode.appendChild(newNode) 564 | } 565 | 566 | return parentNode 567 | } 568 | 569 | function stringPatch(domNode, leftVNode, vText, renderOptions) { 570 | var newNode 571 | 572 | if (domNode.nodeType === 3) { 573 | domNode.replaceData(0, domNode.length, vText.text) 574 | newNode = domNode 575 | } else { 576 | var parentNode = domNode.parentNode 577 | newNode = render(vText, renderOptions) 578 | 579 | if (parentNode) { 580 | parentNode.replaceChild(newNode, domNode) 581 | } 582 | } 583 | 584 | return newNode 585 | } 586 | 587 | function widgetPatch(domNode, leftVNode, widget, renderOptions) { 588 | var updating = updateWidget(leftVNode, widget) 589 | var newNode 590 | 591 | if (updating) { 592 | newNode = widget.update(leftVNode, domNode) || domNode 593 | } else { 594 | newNode = render(widget, renderOptions) 595 | } 596 | 597 | var parentNode = domNode.parentNode 598 | 599 | if (parentNode && newNode !== domNode) { 600 | parentNode.replaceChild(newNode, domNode) 601 | } 602 | 603 | if (!updating) { 604 | destroyWidget(domNode, leftVNode) 605 | } 606 | 607 | return newNode 608 | } 609 | 610 | function vNodePatch(domNode, leftVNode, vNode, renderOptions) { 611 | var parentNode = domNode.parentNode 612 | var newNode = render(vNode, renderOptions) 613 | 614 | if (parentNode) { 615 | parentNode.replaceChild(newNode, domNode) 616 | } 617 | 618 | return newNode 619 | } 620 | 621 | function destroyWidget(domNode, w) { 622 | if (typeof w.destroy === "function" && isWidget(w)) { 623 | w.destroy(domNode) 624 | } 625 | } 626 | 627 | function reorderChildren(domNode, bIndex) { 628 | var children = [] 629 | var childNodes = domNode.childNodes 630 | var len = childNodes.length 631 | var i 632 | var reverseIndex = bIndex.reverse 633 | 634 | for (i = 0; i < len; i++) { 635 | children.push(domNode.childNodes[i]) 636 | } 637 | 638 | var insertOffset = 0 639 | var move 640 | var node 641 | var insertNode 642 | var chainLength 643 | var insertedLength 644 | var nextSibling 645 | for (i = 0; i < len;) { 646 | move = bIndex[i] 647 | chainLength = 1 648 | if (move !== undefined && move !== i) { 649 | // try to bring forward as long of a chain as possible 650 | while (bIndex[i + chainLength] === move + chainLength) { 651 | chainLength++; 652 | } 653 | 654 | // the element currently at this index will be moved later so increase the insert offset 655 | if (reverseIndex[i] > i + chainLength) { 656 | insertOffset++ 657 | } 658 | 659 | node = children[move] 660 | insertNode = childNodes[i + insertOffset] || null 661 | insertedLength = 0 662 | while (node !== insertNode && insertedLength++ < chainLength) { 663 | domNode.insertBefore(node, insertNode); 664 | node = children[move + insertedLength]; 665 | } 666 | 667 | // the moved element came from the front of the array so reduce the insert offset 668 | if (move + chainLength < i) { 669 | insertOffset-- 670 | } 671 | } 672 | 673 | // element at this index is scheduled to be removed so increase insert offset 674 | if (i in bIndex.removes) { 675 | insertOffset++ 676 | } 677 | 678 | i += chainLength 679 | } 680 | } 681 | 682 | function replaceRoot(oldRoot, newRoot) { 683 | if (oldRoot && newRoot && oldRoot !== newRoot && oldRoot.parentNode) { 684 | console.log(oldRoot) 685 | oldRoot.parentNode.replaceChild(newRoot, oldRoot) 686 | } 687 | 688 | return newRoot; 689 | } 690 | 691 | },{"../vnode/is-widget.js":28,"../vnode/vpatch.js":31,"./apply-properties":13,"./create-element":14,"./update-widget":18}],17:[function(_dereq_,module,exports){ 692 | var document = _dereq_("global/document") 693 | var isArray = _dereq_("x-is-array") 694 | 695 | var domIndex = _dereq_("./dom-index") 696 | var patchOp = _dereq_("./patch-op") 697 | module.exports = patch 698 | 699 | function patch(rootNode, patches) { 700 | return patchRecursive(rootNode, patches) 701 | } 702 | 703 | function patchRecursive(rootNode, patches, renderOptions) { 704 | var indices = patchIndices(patches) 705 | 706 | if (indices.length === 0) { 707 | return rootNode 708 | } 709 | 710 | var index = domIndex(rootNode, patches.a, indices) 711 | var ownerDocument = rootNode.ownerDocument 712 | 713 | if (!renderOptions) { 714 | renderOptions = { patch: patchRecursive } 715 | if (ownerDocument !== document) { 716 | renderOptions.document = ownerDocument 717 | } 718 | } 719 | 720 | for (var i = 0; i < indices.length; i++) { 721 | var nodeIndex = indices[i] 722 | rootNode = applyPatch(rootNode, 723 | index[nodeIndex], 724 | patches[nodeIndex], 725 | renderOptions) 726 | } 727 | 728 | return rootNode 729 | } 730 | 731 | function applyPatch(rootNode, domNode, patchList, renderOptions) { 732 | if (!domNode) { 733 | return rootNode 734 | } 735 | 736 | var newNode 737 | 738 | if (isArray(patchList)) { 739 | for (var i = 0; i < patchList.length; i++) { 740 | newNode = patchOp(patchList[i], domNode, renderOptions) 741 | 742 | if (domNode === rootNode) { 743 | rootNode = newNode 744 | } 745 | } 746 | } else { 747 | newNode = patchOp(patchList, domNode, renderOptions) 748 | 749 | if (domNode === rootNode) { 750 | rootNode = newNode 751 | } 752 | } 753 | 754 | return rootNode 755 | } 756 | 757 | function patchIndices(patches) { 758 | var indices = [] 759 | 760 | for (var key in patches) { 761 | if (key !== "a") { 762 | indices.push(Number(key)) 763 | } 764 | } 765 | 766 | return indices 767 | } 768 | 769 | },{"./dom-index":15,"./patch-op":16,"global/document":9,"x-is-array":11}],18:[function(_dereq_,module,exports){ 770 | var isWidget = _dereq_("../vnode/is-widget.js") 771 | 772 | module.exports = updateWidget 773 | 774 | function updateWidget(a, b) { 775 | if (isWidget(a) && isWidget(b)) { 776 | if ("name" in a && "name" in b) { 777 | return a.id === b.id 778 | } else { 779 | return a.init === b.init 780 | } 781 | } 782 | 783 | return false 784 | } 785 | 786 | },{"../vnode/is-widget.js":28}],19:[function(_dereq_,module,exports){ 787 | 'use strict'; 788 | 789 | var EvStore = _dereq_('ev-store'); 790 | 791 | module.exports = EvHook; 792 | 793 | function EvHook(value) { 794 | if (!(this instanceof EvHook)) { 795 | return new EvHook(value); 796 | } 797 | 798 | this.value = value; 799 | } 800 | 801 | EvHook.prototype.hook = function (node, propertyName) { 802 | var es = EvStore(node); 803 | var propName = propertyName.substr(3); 804 | 805 | es[propName] = this.value; 806 | }; 807 | 808 | EvHook.prototype.unhook = function(node, propertyName) { 809 | var es = EvStore(node); 810 | var propName = propertyName.substr(3); 811 | 812 | es[propName] = undefined; 813 | }; 814 | 815 | },{"ev-store":6}],20:[function(_dereq_,module,exports){ 816 | 'use strict'; 817 | 818 | module.exports = SoftSetHook; 819 | 820 | function SoftSetHook(value) { 821 | if (!(this instanceof SoftSetHook)) { 822 | return new SoftSetHook(value); 823 | } 824 | 825 | this.value = value; 826 | } 827 | 828 | SoftSetHook.prototype.hook = function (node, propertyName) { 829 | if (node[propertyName] !== this.value) { 830 | node[propertyName] = this.value; 831 | } 832 | }; 833 | 834 | },{}],21:[function(_dereq_,module,exports){ 835 | 'use strict'; 836 | 837 | var isArray = _dereq_('x-is-array'); 838 | 839 | var VNode = _dereq_('../vnode/vnode.js'); 840 | var VText = _dereq_('../vnode/vtext.js'); 841 | var isVNode = _dereq_('../vnode/is-vnode'); 842 | var isVText = _dereq_('../vnode/is-vtext'); 843 | var isWidget = _dereq_('../vnode/is-widget'); 844 | var isHook = _dereq_('../vnode/is-vhook'); 845 | var isVThunk = _dereq_('../vnode/is-thunk'); 846 | 847 | var parseTag = _dereq_('./parse-tag.js'); 848 | var softSetHook = _dereq_('./hooks/soft-set-hook.js'); 849 | var evHook = _dereq_('./hooks/ev-hook.js'); 850 | 851 | module.exports = h; 852 | 853 | function h(tagName, properties, children) { 854 | var childNodes = []; 855 | var tag, props, key, namespace; 856 | 857 | if (!children && isChildren(properties)) { 858 | children = properties; 859 | props = {}; 860 | } 861 | 862 | props = props || properties || {}; 863 | tag = parseTag(tagName, props); 864 | 865 | // support keys 866 | if (props.hasOwnProperty('key')) { 867 | key = props.key; 868 | props.key = undefined; 869 | } 870 | 871 | // support namespace 872 | if (props.hasOwnProperty('namespace')) { 873 | namespace = props.namespace; 874 | props.namespace = undefined; 875 | } 876 | 877 | // fix cursor bug 878 | if (tag === 'INPUT' && 879 | !namespace && 880 | props.hasOwnProperty('value') && 881 | props.value !== undefined && 882 | !isHook(props.value) 883 | ) { 884 | props.value = softSetHook(props.value); 885 | } 886 | 887 | transformProperties(props); 888 | 889 | if (children !== undefined && children !== null) { 890 | addChild(children, childNodes, tag, props); 891 | } 892 | 893 | 894 | return new VNode(tag, props, childNodes, key, namespace); 895 | } 896 | 897 | function addChild(c, childNodes, tag, props) { 898 | if (typeof c === 'string') { 899 | childNodes.push(new VText(c)); 900 | } else if (isChild(c)) { 901 | childNodes.push(c); 902 | } else if (isArray(c)) { 903 | for (var i = 0; i < c.length; i++) { 904 | addChild(c[i], childNodes, tag, props); 905 | } 906 | } else if (c === null || c === undefined) { 907 | return; 908 | } else { 909 | throw UnexpectedVirtualElement({ 910 | foreignObject: c, 911 | parentVnode: { 912 | tagName: tag, 913 | properties: props 914 | } 915 | }); 916 | } 917 | } 918 | 919 | function transformProperties(props) { 920 | for (var propName in props) { 921 | if (props.hasOwnProperty(propName)) { 922 | var value = props[propName]; 923 | 924 | if (isHook(value)) { 925 | continue; 926 | } 927 | 928 | if (propName.substr(0, 3) === 'ev-') { 929 | // add ev-foo support 930 | props[propName] = evHook(value); 931 | } 932 | } 933 | } 934 | } 935 | 936 | function isChild(x) { 937 | return isVNode(x) || isVText(x) || isWidget(x) || isVThunk(x); 938 | } 939 | 940 | function isChildren(x) { 941 | return typeof x === 'string' || isArray(x) || isChild(x); 942 | } 943 | 944 | function UnexpectedVirtualElement(data) { 945 | var err = new Error(); 946 | 947 | err.type = 'virtual-hyperscript.unexpected.virtual-element'; 948 | err.message = 'Unexpected virtual child passed to h().\n' + 949 | 'Expected a VNode / Vthunk / VWidget / string but:\n' + 950 | 'got:\n' + 951 | errorString(data.foreignObject) + 952 | '.\n' + 953 | 'The parent vnode is:\n' + 954 | errorString(data.parentVnode) 955 | '\n' + 956 | 'Suggested fix: change your `h(..., [ ... ])` callsite.'; 957 | err.foreignObject = data.foreignObject; 958 | err.parentVnode = data.parentVnode; 959 | 960 | return err; 961 | } 962 | 963 | function errorString(obj) { 964 | try { 965 | return JSON.stringify(obj, null, ' '); 966 | } catch (e) { 967 | return String(obj); 968 | } 969 | } 970 | 971 | },{"../vnode/is-thunk":24,"../vnode/is-vhook":25,"../vnode/is-vnode":26,"../vnode/is-vtext":27,"../vnode/is-widget":28,"../vnode/vnode.js":30,"../vnode/vtext.js":32,"./hooks/ev-hook.js":19,"./hooks/soft-set-hook.js":20,"./parse-tag.js":22,"x-is-array":11}],22:[function(_dereq_,module,exports){ 972 | 'use strict'; 973 | 974 | var split = _dereq_('browser-split'); 975 | 976 | var classIdSplit = /([\.#]?[a-zA-Z0-9_:-]+)/; 977 | var notClassId = /^\.|#/; 978 | 979 | module.exports = parseTag; 980 | 981 | function parseTag(tag, props) { 982 | if (!tag) { 983 | return 'DIV'; 984 | } 985 | 986 | var noId = !(props.hasOwnProperty('id')); 987 | 988 | var tagParts = split(tag, classIdSplit); 989 | var tagName = null; 990 | 991 | if (notClassId.test(tagParts[1])) { 992 | tagName = 'DIV'; 993 | } 994 | 995 | var classes, part, type, i; 996 | 997 | for (i = 0; i < tagParts.length; i++) { 998 | part = tagParts[i]; 999 | 1000 | if (!part) { 1001 | continue; 1002 | } 1003 | 1004 | type = part.charAt(0); 1005 | 1006 | if (!tagName) { 1007 | tagName = part; 1008 | } else if (type === '.') { 1009 | classes = classes || []; 1010 | classes.push(part.substring(1, part.length)); 1011 | } else if (type === '#' && noId) { 1012 | props.id = part.substring(1, part.length); 1013 | } 1014 | } 1015 | 1016 | if (classes) { 1017 | if (props.className) { 1018 | classes.push(props.className); 1019 | } 1020 | 1021 | props.className = classes.join(' '); 1022 | } 1023 | 1024 | return props.namespace ? tagName : tagName.toUpperCase(); 1025 | } 1026 | 1027 | },{"browser-split":5}],23:[function(_dereq_,module,exports){ 1028 | var isVNode = _dereq_("./is-vnode") 1029 | var isVText = _dereq_("./is-vtext") 1030 | var isWidget = _dereq_("./is-widget") 1031 | var isThunk = _dereq_("./is-thunk") 1032 | 1033 | module.exports = handleThunk 1034 | 1035 | function handleThunk(a, b) { 1036 | var renderedA = a 1037 | var renderedB = b 1038 | 1039 | if (isThunk(b)) { 1040 | renderedB = renderThunk(b, a) 1041 | } 1042 | 1043 | if (isThunk(a)) { 1044 | renderedA = renderThunk(a, null) 1045 | } 1046 | 1047 | return { 1048 | a: renderedA, 1049 | b: renderedB 1050 | } 1051 | } 1052 | 1053 | function renderThunk(thunk, previous) { 1054 | var renderedThunk = thunk.vnode 1055 | 1056 | if (!renderedThunk) { 1057 | renderedThunk = thunk.vnode = thunk.render(previous) 1058 | } 1059 | 1060 | if (!(isVNode(renderedThunk) || 1061 | isVText(renderedThunk) || 1062 | isWidget(renderedThunk))) { 1063 | throw new Error("thunk did not return a valid node"); 1064 | } 1065 | 1066 | return renderedThunk 1067 | } 1068 | 1069 | },{"./is-thunk":24,"./is-vnode":26,"./is-vtext":27,"./is-widget":28}],24:[function(_dereq_,module,exports){ 1070 | module.exports = isThunk 1071 | 1072 | function isThunk(t) { 1073 | return t && t.type === "Thunk" 1074 | } 1075 | 1076 | },{}],25:[function(_dereq_,module,exports){ 1077 | module.exports = isHook 1078 | 1079 | function isHook(hook) { 1080 | return hook && 1081 | (typeof hook.hook === "function" && !hook.hasOwnProperty("hook") || 1082 | typeof hook.unhook === "function" && !hook.hasOwnProperty("unhook")) 1083 | } 1084 | 1085 | },{}],26:[function(_dereq_,module,exports){ 1086 | var version = _dereq_("./version") 1087 | 1088 | module.exports = isVirtualNode 1089 | 1090 | function isVirtualNode(x) { 1091 | return x && x.type === "VirtualNode" && x.version === version 1092 | } 1093 | 1094 | },{"./version":29}],27:[function(_dereq_,module,exports){ 1095 | var version = _dereq_("./version") 1096 | 1097 | module.exports = isVirtualText 1098 | 1099 | function isVirtualText(x) { 1100 | return x && x.type === "VirtualText" && x.version === version 1101 | } 1102 | 1103 | },{"./version":29}],28:[function(_dereq_,module,exports){ 1104 | module.exports = isWidget 1105 | 1106 | function isWidget(w) { 1107 | return w && w.type === "Widget" 1108 | } 1109 | 1110 | },{}],29:[function(_dereq_,module,exports){ 1111 | module.exports = "1" 1112 | 1113 | },{}],30:[function(_dereq_,module,exports){ 1114 | var version = _dereq_("./version") 1115 | var isVNode = _dereq_("./is-vnode") 1116 | var isWidget = _dereq_("./is-widget") 1117 | var isThunk = _dereq_("./is-thunk") 1118 | var isVHook = _dereq_("./is-vhook") 1119 | 1120 | module.exports = VirtualNode 1121 | 1122 | var noProperties = {} 1123 | var noChildren = [] 1124 | 1125 | function VirtualNode(tagName, properties, children, key, namespace) { 1126 | this.tagName = tagName 1127 | this.properties = properties || noProperties 1128 | this.children = children || noChildren 1129 | this.key = key != null ? String(key) : undefined 1130 | this.namespace = (typeof namespace === "string") ? namespace : null 1131 | 1132 | var count = (children && children.length) || 0 1133 | var descendants = 0 1134 | var hasWidgets = false 1135 | var hasThunks = false 1136 | var descendantHooks = false 1137 | var hooks 1138 | 1139 | for (var propName in properties) { 1140 | if (properties.hasOwnProperty(propName)) { 1141 | var property = properties[propName] 1142 | if (isVHook(property) && property.unhook) { 1143 | if (!hooks) { 1144 | hooks = {} 1145 | } 1146 | 1147 | hooks[propName] = property 1148 | } 1149 | } 1150 | } 1151 | 1152 | for (var i = 0; i < count; i++) { 1153 | var child = children[i] 1154 | if (isVNode(child)) { 1155 | descendants += child.count || 0 1156 | 1157 | if (!hasWidgets && child.hasWidgets) { 1158 | hasWidgets = true 1159 | } 1160 | 1161 | if (!hasThunks && child.hasThunks) { 1162 | hasThunks = true 1163 | } 1164 | 1165 | if (!descendantHooks && (child.hooks || child.descendantHooks)) { 1166 | descendantHooks = true 1167 | } 1168 | } else if (!hasWidgets && isWidget(child)) { 1169 | if (typeof child.destroy === "function") { 1170 | hasWidgets = true 1171 | } 1172 | } else if (!hasThunks && isThunk(child)) { 1173 | hasThunks = true; 1174 | } 1175 | } 1176 | 1177 | this.count = count + descendants 1178 | this.hasWidgets = hasWidgets 1179 | this.hasThunks = hasThunks 1180 | this.hooks = hooks 1181 | this.descendantHooks = descendantHooks 1182 | } 1183 | 1184 | VirtualNode.prototype.version = version 1185 | VirtualNode.prototype.type = "VirtualNode" 1186 | 1187 | },{"./is-thunk":24,"./is-vhook":25,"./is-vnode":26,"./is-widget":28,"./version":29}],31:[function(_dereq_,module,exports){ 1188 | var version = _dereq_("./version") 1189 | 1190 | VirtualPatch.NONE = 0 1191 | VirtualPatch.VTEXT = 1 1192 | VirtualPatch.VNODE = 2 1193 | VirtualPatch.WIDGET = 3 1194 | VirtualPatch.PROPS = 4 1195 | VirtualPatch.ORDER = 5 1196 | VirtualPatch.INSERT = 6 1197 | VirtualPatch.REMOVE = 7 1198 | VirtualPatch.THUNK = 8 1199 | 1200 | module.exports = VirtualPatch 1201 | 1202 | function VirtualPatch(type, vNode, patch) { 1203 | this.type = Number(type) 1204 | this.vNode = vNode 1205 | this.patch = patch 1206 | } 1207 | 1208 | VirtualPatch.prototype.version = version 1209 | VirtualPatch.prototype.type = "VirtualPatch" 1210 | 1211 | },{"./version":29}],32:[function(_dereq_,module,exports){ 1212 | var version = _dereq_("./version") 1213 | 1214 | module.exports = VirtualText 1215 | 1216 | function VirtualText(text) { 1217 | this.text = String(text) 1218 | } 1219 | 1220 | VirtualText.prototype.version = version 1221 | VirtualText.prototype.type = "VirtualText" 1222 | 1223 | },{"./version":29}],33:[function(_dereq_,module,exports){ 1224 | var isObject = _dereq_("is-object") 1225 | var isHook = _dereq_("../vnode/is-vhook") 1226 | 1227 | module.exports = diffProps 1228 | 1229 | function diffProps(a, b) { 1230 | var diff 1231 | 1232 | for (var aKey in a) { 1233 | if (!(aKey in b)) { 1234 | diff = diff || {} 1235 | diff[aKey] = undefined 1236 | } 1237 | 1238 | var aValue = a[aKey] 1239 | var bValue = b[aKey] 1240 | 1241 | if (aValue === bValue) { 1242 | continue 1243 | } else if (isObject(aValue) && isObject(bValue)) { 1244 | if (getPrototype(bValue) !== getPrototype(aValue)) { 1245 | diff = diff || {} 1246 | diff[aKey] = bValue 1247 | } else if (isHook(bValue)) { 1248 | diff = diff || {} 1249 | diff[aKey] = bValue 1250 | } else { 1251 | var objectDiff = diffProps(aValue, bValue) 1252 | if (objectDiff) { 1253 | diff = diff || {} 1254 | diff[aKey] = objectDiff 1255 | } 1256 | } 1257 | } else { 1258 | diff = diff || {} 1259 | diff[aKey] = bValue 1260 | } 1261 | } 1262 | 1263 | for (var bKey in b) { 1264 | if (!(bKey in a)) { 1265 | diff = diff || {} 1266 | diff[bKey] = b[bKey] 1267 | } 1268 | } 1269 | 1270 | return diff 1271 | } 1272 | 1273 | function getPrototype(value) { 1274 | if (Object.getPrototypeOf) { 1275 | return Object.getPrototypeOf(value) 1276 | } else if (value.__proto__) { 1277 | return value.__proto__ 1278 | } else if (value.constructor) { 1279 | return value.constructor.prototype 1280 | } 1281 | } 1282 | 1283 | },{"../vnode/is-vhook":25,"is-object":10}],34:[function(_dereq_,module,exports){ 1284 | var isArray = _dereq_("x-is-array") 1285 | 1286 | var VPatch = _dereq_("../vnode/vpatch") 1287 | var isVNode = _dereq_("../vnode/is-vnode") 1288 | var isVText = _dereq_("../vnode/is-vtext") 1289 | var isWidget = _dereq_("../vnode/is-widget") 1290 | var isThunk = _dereq_("../vnode/is-thunk") 1291 | var handleThunk = _dereq_("../vnode/handle-thunk") 1292 | 1293 | var diffProps = _dereq_("./diff-props") 1294 | 1295 | module.exports = diff 1296 | 1297 | function diff(a, b) { 1298 | var patch = { a: a } 1299 | walk(a, b, patch, 0) 1300 | return patch 1301 | } 1302 | 1303 | function walk(a, b, patch, index) { 1304 | if (a === b) { 1305 | return 1306 | } 1307 | 1308 | var apply = patch[index] 1309 | var applyClear = false 1310 | 1311 | if (isThunk(a) || isThunk(b)) { 1312 | thunks(a, b, patch, index) 1313 | } else if (b == null) { 1314 | 1315 | // If a is a widget we will add a remove patch for it 1316 | // Otherwise any child widgets/hooks must be destroyed. 1317 | // This prevents adding two remove patches for a widget. 1318 | if (!isWidget(a)) { 1319 | clearState(a, patch, index) 1320 | apply = patch[index] 1321 | } 1322 | 1323 | apply = appendPatch(apply, new VPatch(VPatch.REMOVE, a, b)) 1324 | } else if (isVNode(b)) { 1325 | if (isVNode(a)) { 1326 | if (a.tagName === b.tagName && 1327 | a.namespace === b.namespace && 1328 | a.key === b.key) { 1329 | var propsPatch = diffProps(a.properties, b.properties) 1330 | if (propsPatch) { 1331 | apply = appendPatch(apply, 1332 | new VPatch(VPatch.PROPS, a, propsPatch)) 1333 | } 1334 | apply = diffChildren(a, b, patch, apply, index) 1335 | } else { 1336 | apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b)) 1337 | applyClear = true 1338 | } 1339 | } else { 1340 | apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b)) 1341 | applyClear = true 1342 | } 1343 | } else if (isVText(b)) { 1344 | if (!isVText(a)) { 1345 | apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b)) 1346 | applyClear = true 1347 | } else if (a.text !== b.text) { 1348 | apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b)) 1349 | } 1350 | } else if (isWidget(b)) { 1351 | if (!isWidget(a)) { 1352 | applyClear = true; 1353 | } 1354 | 1355 | apply = appendPatch(apply, new VPatch(VPatch.WIDGET, a, b)) 1356 | } 1357 | 1358 | if (apply) { 1359 | patch[index] = apply 1360 | } 1361 | 1362 | if (applyClear) { 1363 | clearState(a, patch, index) 1364 | } 1365 | } 1366 | 1367 | function diffChildren(a, b, patch, apply, index) { 1368 | var aChildren = a.children 1369 | var bChildren = reorder(aChildren, b.children) 1370 | 1371 | var aLen = aChildren.length 1372 | var bLen = bChildren.length 1373 | var len = aLen > bLen ? aLen : bLen 1374 | 1375 | for (var i = 0; i < len; i++) { 1376 | var leftNode = aChildren[i] 1377 | var rightNode = bChildren[i] 1378 | index += 1 1379 | 1380 | if (!leftNode) { 1381 | if (rightNode) { 1382 | // Excess nodes in b need to be added 1383 | apply = appendPatch(apply, 1384 | new VPatch(VPatch.INSERT, null, rightNode)) 1385 | } 1386 | } else { 1387 | walk(leftNode, rightNode, patch, index) 1388 | } 1389 | 1390 | if (isVNode(leftNode) && leftNode.count) { 1391 | index += leftNode.count 1392 | } 1393 | } 1394 | 1395 | if (bChildren.moves) { 1396 | // Reorder nodes last 1397 | apply = appendPatch(apply, new VPatch(VPatch.ORDER, a, bChildren.moves)) 1398 | } 1399 | 1400 | return apply 1401 | } 1402 | 1403 | function clearState(vNode, patch, index) { 1404 | // TODO: Make this a single walk, not two 1405 | unhook(vNode, patch, index) 1406 | destroyWidgets(vNode, patch, index) 1407 | } 1408 | 1409 | // Patch records for all destroyed widgets must be added because we need 1410 | // a DOM node reference for the destroy function 1411 | function destroyWidgets(vNode, patch, index) { 1412 | if (isWidget(vNode)) { 1413 | if (typeof vNode.destroy === "function") { 1414 | patch[index] = appendPatch( 1415 | patch[index], 1416 | new VPatch(VPatch.REMOVE, vNode, null) 1417 | ) 1418 | } 1419 | } else if (isVNode(vNode) && (vNode.hasWidgets || vNode.hasThunks)) { 1420 | var children = vNode.children 1421 | var len = children.length 1422 | for (var i = 0; i < len; i++) { 1423 | var child = children[i] 1424 | index += 1 1425 | 1426 | destroyWidgets(child, patch, index) 1427 | 1428 | if (isVNode(child) && child.count) { 1429 | index += child.count 1430 | } 1431 | } 1432 | } else if (isThunk(vNode)) { 1433 | thunks(vNode, null, patch, index) 1434 | } 1435 | } 1436 | 1437 | // Create a sub-patch for thunks 1438 | function thunks(a, b, patch, index) { 1439 | var nodes = handleThunk(a, b); 1440 | var thunkPatch = diff(nodes.a, nodes.b) 1441 | if (hasPatches(thunkPatch)) { 1442 | patch[index] = new VPatch(VPatch.THUNK, null, thunkPatch) 1443 | } 1444 | } 1445 | 1446 | function hasPatches(patch) { 1447 | for (var index in patch) { 1448 | if (index !== "a") { 1449 | return true; 1450 | } 1451 | } 1452 | 1453 | return false; 1454 | } 1455 | 1456 | // Execute hooks when two nodes are identical 1457 | function unhook(vNode, patch, index) { 1458 | if (isVNode(vNode)) { 1459 | if (vNode.hooks) { 1460 | patch[index] = appendPatch( 1461 | patch[index], 1462 | new VPatch( 1463 | VPatch.PROPS, 1464 | vNode, 1465 | undefinedKeys(vNode.hooks) 1466 | ) 1467 | ) 1468 | } 1469 | 1470 | if (vNode.descendantHooks || vNode.hasThunks) { 1471 | var children = vNode.children 1472 | var len = children.length 1473 | for (var i = 0; i < len; i++) { 1474 | var child = children[i] 1475 | index += 1 1476 | 1477 | unhook(child, patch, index) 1478 | 1479 | if (isVNode(child) && child.count) { 1480 | index += child.count 1481 | } 1482 | } 1483 | } 1484 | } else if (isThunk(vNode)) { 1485 | thunks(vNode, null, patch, index) 1486 | } 1487 | } 1488 | 1489 | function undefinedKeys(obj) { 1490 | var result = {} 1491 | 1492 | for (var key in obj) { 1493 | result[key] = undefined 1494 | } 1495 | 1496 | return result 1497 | } 1498 | 1499 | // List diff, naive left to right reordering 1500 | function reorder(aChildren, bChildren) { 1501 | 1502 | var bKeys = keyIndex(bChildren) 1503 | 1504 | if (!bKeys) { 1505 | return bChildren 1506 | } 1507 | 1508 | var aKeys = keyIndex(aChildren) 1509 | 1510 | if (!aKeys) { 1511 | return bChildren 1512 | } 1513 | 1514 | var bMatch = {}, aMatch = {} 1515 | 1516 | for (var aKey in bKeys) { 1517 | bMatch[bKeys[aKey]] = aKeys[aKey] 1518 | } 1519 | 1520 | for (var bKey in aKeys) { 1521 | aMatch[aKeys[bKey]] = bKeys[bKey] 1522 | } 1523 | 1524 | var aLen = aChildren.length 1525 | var bLen = bChildren.length 1526 | var len = aLen > bLen ? aLen : bLen 1527 | var shuffle = [] 1528 | var freeIndex = 0 1529 | var i = 0 1530 | var moveIndex = 0 1531 | var moves = {} 1532 | var removes = moves.removes = {} 1533 | var reverse = moves.reverse = {} 1534 | var hasMoves = false 1535 | 1536 | while (freeIndex < len) { 1537 | var move = aMatch[i] 1538 | if (move !== undefined) { 1539 | shuffle[i] = bChildren[move] 1540 | if (move !== moveIndex) { 1541 | moves[move] = moveIndex 1542 | reverse[moveIndex] = move 1543 | hasMoves = true 1544 | } 1545 | moveIndex++ 1546 | } else if (i in aMatch) { 1547 | shuffle[i] = undefined 1548 | removes[i] = moveIndex++ 1549 | hasMoves = true 1550 | } else { 1551 | while (bMatch[freeIndex] !== undefined) { 1552 | freeIndex++ 1553 | } 1554 | 1555 | if (freeIndex < len) { 1556 | var freeChild = bChildren[freeIndex] 1557 | if (freeChild) { 1558 | shuffle[i] = freeChild 1559 | if (freeIndex !== moveIndex) { 1560 | hasMoves = true 1561 | moves[freeIndex] = moveIndex 1562 | reverse[moveIndex] = freeIndex 1563 | } 1564 | moveIndex++ 1565 | } 1566 | freeIndex++ 1567 | } 1568 | } 1569 | i++ 1570 | } 1571 | 1572 | if (hasMoves) { 1573 | shuffle.moves = moves 1574 | } 1575 | 1576 | return shuffle 1577 | } 1578 | 1579 | function keyIndex(children) { 1580 | var i, keys 1581 | 1582 | for (i = 0; i < children.length; i++) { 1583 | var child = children[i] 1584 | 1585 | if (child.key !== undefined) { 1586 | keys = keys || {} 1587 | keys[child.key] = i 1588 | } 1589 | } 1590 | 1591 | return keys 1592 | } 1593 | 1594 | function appendPatch(apply, patch) { 1595 | if (apply) { 1596 | if (isArray(apply)) { 1597 | apply.push(patch) 1598 | } else { 1599 | apply = [apply, patch] 1600 | } 1601 | 1602 | return apply 1603 | } else { 1604 | return patch 1605 | } 1606 | } 1607 | 1608 | },{"../vnode/handle-thunk":23,"../vnode/is-thunk":24,"../vnode/is-vnode":26,"../vnode/is-vtext":27,"../vnode/is-widget":28,"../vnode/vpatch":31,"./diff-props":33,"x-is-array":11}],35:[function(_dereq_,module,exports){ 1609 | 1610 | },{}]},{},[4])(4) 1611 | }); 1612 | 1613 | angular.module('teropa.virtualDom.getAttribute', []) 1614 | .factory('getVDomAttribute', function() { 1615 | 'use strict'; 1616 | return function getVDomAttribute(node, name) { 1617 | if (node.properties && node.properties.attributes) { 1618 | return node.properties.attributes[name]; 1619 | } 1620 | }; 1621 | }); 1622 | 1623 | angular.module('teropa.virtualDom.directiveNormalize', []) 1624 | .factory('directiveNormalize', function() { 1625 | var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 1626 | var MOZ_HACK_REGEXP = /^moz([A-Z])/; 1627 | var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; 1628 | 1629 | function camelCase(name) { 1630 | return name. 1631 | replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 1632 | return offset ? letter.toUpperCase() : letter; 1633 | }). 1634 | replace(MOZ_HACK_REGEXP, 'Moz$1'); 1635 | } 1636 | 1637 | return function directiveNormalize(name) { 1638 | return camelCase(name.replace(PREFIX_REGEXP, '')); 1639 | } 1640 | }); 1641 | 1642 | angular.module('teropa.virtualDom.cloneTree', []) 1643 | .factory('cloneVDomTree', function() { 1644 | 'use strict'; 1645 | return function cloneTree(tree) { 1646 | if (virtualDom.isVNode(tree)) { 1647 | return new virtualDom.VNode( 1648 | tree.tagName, 1649 | angular.copy(tree.properties), 1650 | tree.children.map(cloneTree) 1651 | ); 1652 | } else if (virtualDom.isVText(tree)) { 1653 | return new virtualDom.VText(tree.text); 1654 | } 1655 | }; 1656 | }); 1657 | 1658 | angular.module('teropa.virtualDom.virtualize', []) 1659 | .factory('virtualizeDom', function() { 1660 | 'use strict'; 1661 | 1662 | function virtualizeTextNode(node) { 1663 | return new virtualDom.VText(node.nodeValue); 1664 | } 1665 | 1666 | function virtualizeProperties(node) { 1667 | var attrs = {}; 1668 | Array.prototype.forEach.call(node.attributes, function(attr) { 1669 | attrs[attr.name] = attr.value; 1670 | }); 1671 | return {attributes: attrs}; 1672 | } 1673 | 1674 | function virtualizeChildren(node) { 1675 | var children = []; 1676 | Array.prototype.forEach.call(node.childNodes, function(childNode) { 1677 | var childTree = virtualizeTree(childNode); 1678 | if (childTree) { 1679 | children.push(childTree); 1680 | } 1681 | }); 1682 | return children; 1683 | } 1684 | 1685 | function virtualizeElementNode(node) { 1686 | return new virtualDom.VNode( 1687 | node.tagName.toLowerCase(), 1688 | virtualizeProperties(node), 1689 | virtualizeChildren(node) 1690 | ); 1691 | } 1692 | 1693 | function virtualizeTree(node) { 1694 | if (node.nodeType === Node.TEXT_NODE) { 1695 | return virtualizeTextNode(node); 1696 | } else if (node.nodeType === Node.ELEMENT_NODE) { 1697 | return virtualizeElementNode(node); 1698 | } 1699 | } 1700 | 1701 | return virtualizeTree; 1702 | 1703 | }); 1704 | 1705 | angular.module('teropa.virtualDom.link', ['teropa.virtualDom.cloneTree', 'teropa.virtualDom.directiveNormalize']) 1706 | .factory('linkVDom', ['$injector', '$interpolate', 'directiveNormalize', 'cloneVDomTree', function($injector, $interpolate, directiveNormalize, cloneVDomTree) { 1707 | 'use strict'; 1708 | 1709 | function byPriority(a, b) { 1710 | var diff = b.priority - a.priority; 1711 | if (diff !== 0) { 1712 | return diff; 1713 | } 1714 | if (a.name !== b.name) { 1715 | return (a.name < b.name) ? -1 : 1; 1716 | } 1717 | return a.index - b.index; 1718 | } 1719 | 1720 | function getDirectives(node) { 1721 | var dirs = []; 1722 | if (node.properties && node.properties.attributes) { 1723 | Object.keys(node.properties.attributes).forEach(function(attrName) { 1724 | var dName = directiveNormalize(attrName) + 'Directive'; 1725 | if ($injector.has(dName)) { 1726 | dirs.push.apply(dirs, $injector.get(dName)); 1727 | } 1728 | }); 1729 | } 1730 | return dirs.sort(byPriority); 1731 | } 1732 | 1733 | function linkVisit(node, scope) { 1734 | node.$scope = scope; 1735 | var linkedNodes; 1736 | if (virtualDom.isVNode(node)) { 1737 | var directives = getDirectives(node); 1738 | linkedNodes = directives.reduce(function(nodes, directive) { 1739 | var nextNodes = []; 1740 | nodes.forEach(function(node) { 1741 | var linked = node; 1742 | if (directive.linkVirtual) { 1743 | linked = directive.linkVirtual(node); 1744 | } 1745 | if (Array.isArray(linked)) { 1746 | nextNodes.push.apply(nextNodes, linked); 1747 | } else if (!linked) { 1748 | nextNodes.push(node); 1749 | } else { 1750 | nextNodes.push(linked); 1751 | } 1752 | }); 1753 | return nextNodes; 1754 | }, [node]); 1755 | 1756 | linkedNodes.forEach(function(node) { 1757 | if (node.properties && node.properties.attributes) { 1758 | Object.keys(node.properties.attributes).forEach(function(attrName) { 1759 | var interpolateFn = $interpolate(node.properties.attributes[attrName]); 1760 | if (interpolateFn) { 1761 | node.properties.attributes[attrName] = interpolateFn(node.$scope); 1762 | } 1763 | }); 1764 | } 1765 | 1766 | var linkedChildren = []; 1767 | node.children.forEach(function(childNode) { 1768 | linkedChildren.push.apply(linkedChildren, linkVisit(childNode, node.$scope)); 1769 | }); 1770 | node.children = linkedChildren; 1771 | }); 1772 | } else { 1773 | node.text = $interpolate(node.text)(node.$scope); 1774 | linkedNodes = [node]; 1775 | } 1776 | return linkedNodes; 1777 | } 1778 | 1779 | return function linkVDom(tree, scope) { 1780 | var clone = cloneVDomTree(tree); 1781 | return linkVisit(clone, scope)[0]; 1782 | }; 1783 | 1784 | }]); 1785 | 1786 | angular.module('teropa.virtualDom.vIf', ['teropa.virtualDom.getAttribute']) 1787 | .directive('vIf', ['$parse', 'getVDomAttribute', function($parse, getVDomAttribute) { 1788 | 'use strict'; 1789 | return { 1790 | restrict: 'A', 1791 | priority: 600, 1792 | linkVirtual: function(node) { 1793 | var expr = $parse(getVDomAttribute(node, 'v-if')); 1794 | if (expr(node.$scope)) { 1795 | return node; 1796 | } else { 1797 | return []; 1798 | } 1799 | } 1800 | }; 1801 | }]); 1802 | 1803 | angular.module('teropa.virtualDom.vRepeat', ['teropa.virtualDom.getAttribute', 'teropa.virtualDom.cloneTree']) 1804 | .directive('vRepeat', ['$parse', 'getVDomAttribute', 'cloneVDomTree', function($parse, getVDomAttribute, cloneVDomTree) { 1805 | 'use strict'; 1806 | 1807 | var iteratorSymbol = (typeof Symbol !== 'undefined') ? Symbol.iterator : "@@iterator"; 1808 | 1809 | function nth(v, n) { 1810 | if (Array.isArray(v)) { 1811 | return v[n]; 1812 | } else if (v[iteratorSymbol]) { 1813 | var iterator = v[iteratorSymbol](); 1814 | var i; 1815 | for (i=0 ; i 8 | * @link https://github.com/Matt-Esch/virtual-dom 9 | * @license MIT License, http://www.opensource.org/licenses/MIT 10 | */ 11 | "undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="teropa.virtualDom"),function(a,b,c){!function(b){var c;"undefined"!=typeof a?c=a:"undefined"!=typeof global?c=global:"undefined"!=typeof self&&(c=self),c.virtualDom=b()}(function(){return function b(a,c,d){function e(g,h){if(!c[g]){if(!a[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};a[g][0].call(k.exports,function(b){var c=a[g][1][b];return e(c?c:b)},k,k.exports,b,a,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g>>0:f>>>0;(h=e.exec(b))&&(i=h.index+h[0].length,!(i>m&&(k.push(b.slice(m,h.index)),!d&&h.length>1&&h[0].replace(g,function(){for(var b=1;b1&&h.index=f)));)e.lastIndex===h.index&&e.lastIndex++;return m===b.length?(j||!e.test(""))&&k.push(""):k.push(b.slice(m)),k.length>f?k.slice(0,f):k}}()},{}],6:[function(a,b){"use strict";function c(a){var b=a[f];return b||(b=a[f]={}),b}var d=a("individual/one-version"),e="7";d("ev-store",e);var f="__EV_STORE_KEY@"+e;b.exports=c},{"individual/one-version":8}],7:[function(b,c){(function(b){"use strict";function d(a,b){return a in e?e[a]:(e[a]=b,b)}var e="undefined"!=typeof a?a:"undefined"!=typeof b?b:{};c.exports=d}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof a?a:{})},{}],8:[function(a,b){"use strict";function c(a,b,c){var e="__INDIVIDUAL_ONE_VERSION_"+a,f=e+"_ENFORCE_SINGLETON",g=d(f,b);if(g!==b)throw new Error("Can only have one copy of "+a+".\nYou already have version "+g+" installed.\nThis means you cannot install version "+b);return d(e,c)}var d=a("./index.js");b.exports=c},{"./index.js":7}],9:[function(b,c){(function(d){var e="undefined"!=typeof d?d:"undefined"!=typeof a?a:{},f=b("min-document");if("undefined"!=typeof document)c.exports=document;else{var g=e["__GLOBAL_DOCUMENT_CACHE@4"];g||(g=e["__GLOBAL_DOCUMENT_CACHE@4"]=f),c.exports=g}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof a?a:{})},{"min-document":35}],10:[function(a,b){"use strict";b.exports=function(a){return"object"==typeof a&&null!==a}},{}],11:[function(a,b){function c(a){return"[object Array]"===e.call(a)}var d=Array.isArray,e=Object.prototype.toString;b.exports=d||c},{}],12:[function(a,b){var c=a("./vdom/patch.js");b.exports=c},{"./vdom/patch.js":17}],13:[function(a,b){function d(a,b,d){for(var g in b){var j=b[g];j===c?e(a,g,j,d):i(j)?(e(a,g,j,d),j.hook&&j.hook(a,g,d?d[g]:c)):h(j)?f(a,b,d,g,j):a[g]=j}}function e(a,b,c,d){if(d){var e=d[b];if(i(e))e.unhook&&e.unhook(a,b,c);else if("attributes"===b)for(var f in e)a.removeAttribute(f);else if("style"===b)for(var g in e)a.style[g]="";else a[b]="string"==typeof e?"":null}}function f(a,b,d,e,f){var i=d?d[e]:c;if("attributes"!==e){if(i&&h(i)&&g(i)!==g(f))return void(a[e]=f);h(a[e])||(a[e]={});var j="style"===e?"":c;for(var k in f){var l=f[k];a[e][k]=l===c?j:l}}else for(var m in f){var n=f[m];n===c?a.removeAttribute(m):a.setAttribute(m,n)}}function g(a){return Object.getPrototypeOf?Object.getPrototypeOf(a):a.__proto__?a.__proto__:a.constructor?a.constructor.prototype:void 0}var h=a("is-object"),i=a("../vnode/is-vhook.js");b.exports=d},{"../vnode/is-vhook.js":25,"is-object":10}],14:[function(a,b){function c(a,b){var j=b?b.document||d:d,k=b?b.warn:null;if(a=i(a).a,h(a))return a.init();if(g(a))return j.createTextNode(a.text);if(!f(a))return k&&k("Item is not a valid virtual dom node",a),null;var l=null===a.namespace?j.createElement(a.tagName):j.createElementNS(a.namespace,a.tagName),m=a.properties;e(l,m);for(var n=a.children,o=0;o=f;){if(d=(g+f)/2>>0,e=a[d],f===g)return e>=b&&c>=e;if(b>e)f=d+1;else{if(!(e>c))return!0;g=d-1}}return!1}function f(a,b){return a>b?1:-1}var g={};b.exports=c},{}],16:[function(a,b){function d(a,b,c){var d=a.type,j=a.vNode,n=a.patch;switch(d){case o.REMOVE:return e(b,j);case o.INSERT:return f(b,n,c);case o.VTEXT:return g(b,j,n,c);case o.WIDGET:return h(b,j,n,c);case o.VNODE:return i(b,j,n,c);case o.ORDER:return k(b,n),b;case o.PROPS:return m(b,n,j.properties),b;case o.THUNK:return l(b,c.patch(b,n,c));default:return b}}function e(a,b){var c=a.parentNode;return c&&c.removeChild(a),j(a,b),null}function f(a,b,c){var d=p(b,c);return a&&a.appendChild(d),a}function g(a,b,c,d){var e;if(3===a.nodeType)a.replaceData(0,a.length,c.text),e=a;else{var f=a.parentNode;e=p(c,d),f&&f.replaceChild(e,a)}return e}function h(a,b,c,d){var e,f=q(b,c);e=f?c.update(b,a)||a:p(c,d);var g=a.parentNode;return g&&e!==a&&g.replaceChild(e,a),f||j(a,b),e}function i(a,b,c,d){var e=a.parentNode,f=p(c,d);return e&&e.replaceChild(f,a),f}function j(a,b){"function"==typeof b.destroy&&n(b)&&b.destroy(a)}function k(a,b){var d,e=[],f=a.childNodes,g=f.length,h=b.reverse;for(d=0;g>d;d++)e.push(a.childNodes[d]);var i,j,k,l,m,n=0;for(d=0;g>d;){if(i=b[d],l=1,i!==c&&i!==d){for(;b[d+l]===i+l;)l++;for(h[d]>d+l&&n++,j=e[i],k=f[d+n]||null,m=0;j!==k&&m++i+l&&n--}d in b.removes&&n++,d+=l}}function l(a,b){return a&&b&&a!==b&&a.parentNode&&(console.log(a),a.parentNode.replaceChild(b,a)),b}var m=a("./apply-properties"),n=a("../vnode/is-widget.js"),o=a("../vnode/vpatch.js"),p=a("./create-element"),q=a("./update-widget");b.exports=d},{"../vnode/is-widget.js":28,"../vnode/vpatch.js":31,"./apply-properties":13,"./create-element":14,"./update-widget":18}],17:[function(a,b){function c(a,b){return d(a,b)}function d(a,b,c){var h=f(b);if(0===h.length)return a;var j=i(a,b.a,h),k=a.ownerDocument;c||(c={patch:d},k!==g&&(c.document=k));for(var l=0;lu;u++){var v=d[u];f(v)?(o+=v.count||0,!p&&v.hasWidgets&&(p=!0),!q&&v.hasThunks&&(q=!0),r||!v.hooks&&!v.descendantHooks||(r=!0)):!p&&g(v)?"function"==typeof v.destroy&&(p=!0):!q&&h(v)&&(q=!0)}this.count=n+o,this.hasWidgets=p,this.hasThunks=q,this.hooks=m,this.descendantHooks=r}var e=a("./version"),f=a("./is-vnode"),g=a("./is-widget"),h=a("./is-thunk"),i=a("./is-vhook");b.exports=d;var j={},k=[];d.prototype.version=e,d.prototype.type="VirtualNode"},{"./is-thunk":24,"./is-vhook":25,"./is-vnode":26,"./is-widget":28,"./version":29}],31:[function(a,b){function c(a,b,c){this.type=Number(a),this.vNode=b,this.patch=c}var d=a("./version");c.NONE=0,c.VTEXT=1,c.VNODE=2,c.WIDGET=3,c.PROPS=4,c.ORDER=5,c.INSERT=6,c.REMOVE=7,c.THUNK=8,b.exports=c,c.prototype.version=d,c.prototype.type="VirtualPatch"},{"./version":29}],32:[function(a,b){function c(a){this.text=String(a)}var d=a("./version");b.exports=c,c.prototype.version=d,c.prototype.type="VirtualText"},{"./version":29}],33:[function(a,b){function d(a,b){var h;for(var i in a){i in b||(h=h||{},h[i]=c);var j=a[i],k=b[i];if(j!==k)if(f(j)&&f(k))if(e(k)!==e(j))h=h||{},h[i]=k;else if(g(k))h=h||{},h[i]=k;else{var l=d(j,k);l&&(h=h||{},h[i]=l)}else h=h||{},h[i]=k}for(var m in b)m in a||(h=h||{},h[m]=b[m]);return h}function e(a){return Object.getPrototypeOf?Object.getPrototypeOf(a):a.__proto__?a.__proto__:a.constructor?a.constructor.prototype:void 0}var f=a("is-object"),g=a("../vnode/is-vhook");b.exports=d},{"../vnode/is-vhook":25,"is-object":10}],34:[function(a,b){function d(a,b){var c={a:a};return e(a,b,c,0),c}function e(a,b,c,d){if(a!==b){var e=c[d],h=!1;if(u(a)||u(b))i(a,b,c,d);else if(null==b)t(a)||(g(a,c,d),e=c[d]),e=o(e,new q(q.REMOVE,a,b));else if(r(b))if(r(a))if(a.tagName===b.tagName&&a.namespace===b.namespace&&a.key===b.key){var j=w(a.properties,b.properties);j&&(e=o(e,new q(q.PROPS,a,j))),e=f(a,b,c,e,d)}else e=o(e,new q(q.VNODE,a,b)),h=!0;else e=o(e,new q(q.VNODE,a,b)),h=!0;else s(b)?s(a)?a.text!==b.text&&(e=o(e,new q(q.VTEXT,a,b))):(e=o(e,new q(q.VTEXT,a,b)),h=!0):t(b)&&(t(a)||(h=!0),e=o(e,new q(q.WIDGET,a,b)));e&&(c[d]=e),h&&g(a,c,d)}}function f(a,b,c,d,f){for(var g=a.children,h=m(g,b.children),i=g.length,j=h.length,k=i>j?i:j,l=0;k>l;l++){var n=g[l],p=h[l];f+=1,n?e(n,p,c,f):p&&(d=o(d,new q(q.INSERT,null,p))),r(n)&&n.count&&(f+=n.count)}return h.moves&&(d=o(d,new q(q.ORDER,a,h.moves))),d}function g(a,b,c){k(a,b,c),h(a,b,c)}function h(a,b,c){if(t(a))"function"==typeof a.destroy&&(b[c]=o(b[c],new q(q.REMOVE,a,null)));else if(r(a)&&(a.hasWidgets||a.hasThunks))for(var d=a.children,e=d.length,f=0;e>f;f++){var g=d[f];c+=1,h(g,b,c),r(g)&&g.count&&(c+=g.count)}else u(a)&&i(a,null,b,c)}function i(a,b,c,e){var f=v(a,b),g=d(f.a,f.b);j(g)&&(c[e]=new q(q.THUNK,null,g))}function j(a){for(var b in a)if("a"!==b)return!0;return!1}function k(a,b,c){if(r(a)){if(a.hooks&&(b[c]=o(b[c],new q(q.PROPS,a,l(a.hooks)))),a.descendantHooks||a.hasThunks)for(var d=a.children,e=d.length,f=0;e>f;f++){var g=d[f];c+=1,k(g,b,c),r(g)&&g.count&&(c+=g.count)}}else u(a)&&i(a,null,b,c)}function l(a){var b={};for(var d in a)b[d]=c;return b}function m(a,b){var d=n(b);if(!d)return b;var e=n(a);if(!e)return b;var f={},g={};for(var h in d)f[d[h]]=e[h];for(var i in e)g[e[i]]=d[i];for(var j=a.length,k=b.length,l=j>k?j:k,m=[],o=0,p=0,q=0,r={},s=r.removes={},t=r.reverse={},u=!1;l>o;){var v=g[p];if(v!==c)m[p]=b[v],v!==q&&(r[v]=q,t[q]=v,u=!0),q++;else if(p in g)m[p]=c,s[p]=q++,u=!0;else{for(;f[o]!==c;)o++;if(l>o){var w=b[o];w&&(m[p]=w,o!==q&&(u=!0,r[o]=q,t[q]=o),q++),o++}}p++}return u&&(m.moves=r),m}function n(a){var b,d;for(b=0;bc;c++)d.next();return d.next().value}}function e(a,b){a.$index=b,a.$even=b%2===0,a.$odd=!a.$even}var f="undefined"!=typeof Symbol?Symbol.iterator:"@@iterator";return{restrict:"A",priority:1e3,linkVirtual:function(g){var h=b(g,"v-repeat"),i=h.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)\s*$/),j=i[1],k=i[2];i=j.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);var l=i[3]||i[1],m=i[2],n=a(k)(g.$scope);if(Array.isArray(n))return n.map(function(a,b){var d=c(g);return d.$scope=g.$scope.$new(),d.$scope[l]=a,e(d.$scope,b),d});if(n&&n[f]){for(var o=n[f](),p=[],q=0,r=o.next();!r.done;){var s=r.value,t=c(g);t.$scope=g.$scope.$new(),m?(t.$scope[m]=d(s,0),t.$scope[l]=d(s,1)):t.$scope[l]=s,e(t.$scope,q),p.push(t),q++,r=o.next()}return p}return"object"==typeof n&&null!==n?Object.keys(n).map(function(a,b){var d=c(g);return d.$scope=g.$scope.$new(),d.$scope[m]=a,d.$scope[l]=n[a],e(d.$scope,b),d}):[]}}}]),b.module("teropa.virtualDom.vRoot",["teropa.virtualDom.virtualize","teropa.virtualDom.link"]).directive("vRoot",["$injector","$interpolate","virtualizeDom","linkVDom",function(a,c,d,e){"use strict";return{compile:function(a){var c=a[0],f=d(c);return a.empty(),function(a,c,d){function g(){if(j){var b=e(f,a),c=virtualDom.diff(h,b);i=virtualDom.patch(i,c),h=b,j=!1}}var h=e(f,a),i=virtualDom.create(h);c.replaceWith(i);var j;a.$watch(d.vRoot,function(){j=!0,a.$$postDigest(g)}),a.$on("$destroy",function(){b.element(i).remove()})}}}}]),b.module("teropa.virtualDom",["teropa.virtualDom.getAttribute","teropa.virtualDom.cloneTree","teropa.virtualDom.virtualize","teropa.virtualDom.link","teropa.virtualDom.vIf","teropa.virtualDom.vRepeat","teropa.virtualDom.vRoot"])}(window,window.angular); -------------------------------------------------------------------------------- /src/angular-virtual-dom.js: -------------------------------------------------------------------------------- 1 | angular.module('teropa.virtualDom', [ 2 | 'teropa.virtualDom.getAttribute', 3 | 'teropa.virtualDom.cloneTree', 4 | 'teropa.virtualDom.virtualize', 5 | 'teropa.virtualDom.link', 6 | 'teropa.virtualDom.vIf', 7 | 'teropa.virtualDom.vRepeat', 8 | 'teropa.virtualDom.vRoot' 9 | ]); 10 | -------------------------------------------------------------------------------- /src/clone_tree.js: -------------------------------------------------------------------------------- 1 | angular.module('teropa.virtualDom.cloneTree', []) 2 | .factory('cloneVDomTree', function() { 3 | 'use strict'; 4 | return function cloneTree(tree) { 5 | if (virtualDom.isVNode(tree)) { 6 | return new virtualDom.VNode( 7 | tree.tagName, 8 | angular.copy(tree.properties), 9 | tree.children.map(cloneTree) 10 | ); 11 | } else if (virtualDom.isVText(tree)) { 12 | return new virtualDom.VText(tree.text); 13 | } 14 | }; 15 | }); 16 | -------------------------------------------------------------------------------- /src/directive_normalize.js: -------------------------------------------------------------------------------- 1 | angular.module('teropa.virtualDom.directiveNormalize', []) 2 | .factory('directiveNormalize', function() { 3 | var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 4 | var MOZ_HACK_REGEXP = /^moz([A-Z])/; 5 | var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; 6 | 7 | function camelCase(name) { 8 | return name. 9 | replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 10 | return offset ? letter.toUpperCase() : letter; 11 | }). 12 | replace(MOZ_HACK_REGEXP, 'Moz$1'); 13 | } 14 | 15 | return function directiveNormalize(name) { 16 | return camelCase(name.replace(PREFIX_REGEXP, '')); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /src/get_attribute.js: -------------------------------------------------------------------------------- 1 | angular.module('teropa.virtualDom.getAttribute', []) 2 | .factory('getVDomAttribute', function() { 3 | 'use strict'; 4 | return function getVDomAttribute(node, name) { 5 | if (node.properties && node.properties.attributes) { 6 | return node.properties.attributes[name]; 7 | } 8 | }; 9 | }); 10 | -------------------------------------------------------------------------------- /src/link.js: -------------------------------------------------------------------------------- 1 | angular.module('teropa.virtualDom.link', ['teropa.virtualDom.cloneTree', 'teropa.virtualDom.directiveNormalize']) 2 | .factory('linkVDom', ['$injector', '$interpolate', 'directiveNormalize', 'cloneVDomTree', function($injector, $interpolate, directiveNormalize, cloneVDomTree) { 3 | 'use strict'; 4 | 5 | function byPriority(a, b) { 6 | var diff = b.priority - a.priority; 7 | if (diff !== 0) { 8 | return diff; 9 | } 10 | if (a.name !== b.name) { 11 | return (a.name < b.name) ? -1 : 1; 12 | } 13 | return a.index - b.index; 14 | } 15 | 16 | function getDirectives(node) { 17 | var dirs = []; 18 | if (node.properties && node.properties.attributes) { 19 | Object.keys(node.properties.attributes).forEach(function(attrName) { 20 | var dName = directiveNormalize(attrName) + 'Directive'; 21 | if ($injector.has(dName)) { 22 | dirs.push.apply(dirs, $injector.get(dName)); 23 | } 24 | }); 25 | } 26 | return dirs.sort(byPriority); 27 | } 28 | 29 | function linkVisit(node, scope) { 30 | node.$scope = scope; 31 | var linkedNodes; 32 | if (virtualDom.isVNode(node)) { 33 | var directives = getDirectives(node); 34 | linkedNodes = directives.reduce(function(nodes, directive) { 35 | var nextNodes = []; 36 | nodes.forEach(function(node) { 37 | var linked = node; 38 | if (directive.linkVirtual) { 39 | linked = directive.linkVirtual(node); 40 | } 41 | if (Array.isArray(linked)) { 42 | nextNodes.push.apply(nextNodes, linked); 43 | } else if (!linked) { 44 | nextNodes.push(node); 45 | } else { 46 | nextNodes.push(linked); 47 | } 48 | }); 49 | return nextNodes; 50 | }, [node]); 51 | 52 | linkedNodes.forEach(function(node) { 53 | if (node.properties && node.properties.attributes) { 54 | Object.keys(node.properties.attributes).forEach(function(attrName) { 55 | var interpolateFn = $interpolate(node.properties.attributes[attrName]); 56 | if (interpolateFn) { 57 | node.properties.attributes[attrName] = interpolateFn(node.$scope); 58 | } 59 | }); 60 | } 61 | 62 | var linkedChildren = []; 63 | node.children.forEach(function(childNode) { 64 | linkedChildren.push.apply(linkedChildren, linkVisit(childNode, node.$scope)); 65 | }); 66 | node.children = linkedChildren; 67 | }); 68 | } else { 69 | node.text = $interpolate(node.text)(node.$scope); 70 | linkedNodes = [node]; 71 | } 72 | return linkedNodes; 73 | } 74 | 75 | return function linkVDom(tree, scope) { 76 | var clone = cloneVDomTree(tree); 77 | return linkVisit(clone, scope)[0]; 78 | }; 79 | 80 | }]); 81 | -------------------------------------------------------------------------------- /src/v_if_directive.js: -------------------------------------------------------------------------------- 1 | angular.module('teropa.virtualDom.vIf', ['teropa.virtualDom.getAttribute']) 2 | .directive('vIf', ['$parse', 'getVDomAttribute', function($parse, getVDomAttribute) { 3 | 'use strict'; 4 | return { 5 | restrict: 'A', 6 | priority: 600, 7 | linkVirtual: function(node) { 8 | var expr = $parse(getVDomAttribute(node, 'v-if')); 9 | if (expr(node.$scope)) { 10 | return node; 11 | } else { 12 | return []; 13 | } 14 | } 15 | }; 16 | }]); 17 | -------------------------------------------------------------------------------- /src/v_repeat_directive.js: -------------------------------------------------------------------------------- 1 | angular.module('teropa.virtualDom.vRepeat', ['teropa.virtualDom.getAttribute', 'teropa.virtualDom.cloneTree']) 2 | .directive('vRepeat', ['$parse', 'getVDomAttribute', 'cloneVDomTree', function($parse, getVDomAttribute, cloneVDomTree) { 3 | 'use strict'; 4 | 5 | var iteratorSymbol = (typeof Symbol !== 'undefined') ? Symbol.iterator : "@@iterator"; 6 | 7 | function nth(v, n) { 8 | if (Array.isArray(v)) { 9 | return v[n]; 10 | } else if (v[iteratorSymbol]) { 11 | var iterator = v[iteratorSymbol](); 12 | var i; 13 | for (i=0 ; iStuff'); 12 | $compile(element); 13 | expect(element.children().length).toBe(0); 14 | }); 15 | 16 | it('renders element at link time', function() { 17 | var element = angular.element('
Stuff
'); 18 | $compile(element)($rootScope); 19 | expect(element[0].firstChild.childNodes.length).toBe(1); 20 | expect(element[0].firstChild.firstChild.nodeValue).toBe('Stuff'); 21 | }); 22 | 23 | it('uses given scope for linking', function() { 24 | var element = angular.element('
{{msg}}
'); 25 | $rootScope.msg = 'Hello'; 26 | $compile(element)($rootScope); 27 | expect(element[0].firstChild.firstChild.nodeValue).toBe('Hello'); 28 | }); 29 | 30 | it('diffs the dom when root expression changes', function() { 31 | var element = angular.element('
{{msg}}
'); 32 | $rootScope.msg = 'Hello'; 33 | $compile(element)($rootScope); 34 | $rootScope.$digest(); 35 | expect(element[0].firstChild.firstChild.nodeValue).toBe('Hello'); 36 | 37 | $rootScope.msg = 'World'; 38 | $rootScope.$digest(); 39 | expect(element[0].firstChild.firstChild.nodeValue).toBe('World'); 40 | }); 41 | 42 | it('does not diff the dom when root expression mutates', function() { 43 | var element = angular.element('
{{stuff[0]}}
'); 44 | $rootScope.stuff = ['Hello']; 45 | $compile(element)($rootScope); 46 | $rootScope.$digest(); 47 | expect(element[0].firstChild.firstChild.nodeValue).toBe('Hello'); 48 | 49 | $rootScope.stuff[0] = 'World'; 50 | $rootScope.$digest(); 51 | expect(element[0].firstChild.firstChild.nodeValue).toBe('Hello'); 52 | }); 53 | 54 | it('does not diff the dom when inner expressions change', function() { 55 | var element = angular.element('
{{msg}}
'); 56 | $rootScope.msg = 'Hello'; 57 | $compile(element)($rootScope); 58 | $rootScope.$digest(); 59 | 60 | $rootScope.msg = 'World'; 61 | $rootScope.$digest(); 62 | expect(element[0].firstChild.firstChild.nodeValue).toBe('Hello'); 63 | }); 64 | 65 | it('cleans up after itself', function() { 66 | var scope = $rootScope.$new(); 67 | var element = angular.element('
{{msg}}
'); 68 | scope.msg = 'Hello'; 69 | $compile(element)(scope); 70 | scope.$digest(); 71 | expect(element[0].firstChild.firstChild.nodeValue).toBe('Hello'); 72 | 73 | scope.msg = 'World'; 74 | scope.$destroy(); 75 | scope.$digest(); 76 | expect(element[0].childNodes.length).toBe(0); 77 | }); 78 | 79 | }); 80 | -------------------------------------------------------------------------------- /test/virtualize_spec.js: -------------------------------------------------------------------------------- 1 | describe('teropa.virtualDom.virtualize', function() { 2 | 3 | var virtualizeDom; 4 | beforeEach(module('teropa.virtualDom.virtualize')); 5 | beforeEach(inject(function(_virtualizeDom_) { 6 | virtualizeDom = _virtualizeDom_; 7 | })); 8 | 9 | it('virtualizes a single node', function() { 10 | var dom = angular.element('
'); 11 | var result = virtualizeDom(dom[0]); 12 | expect(result).toBeDefined(); 13 | expect(virtualDom.isVNode(result)).toBe(true); 14 | expect(result.tagName).toBe('div'); 15 | }); 16 | 17 | it('virtualizes attributes', function() { 18 | var dom = angular.element('
'); 19 | var result = virtualizeDom(dom[0]); 20 | expect(result.properties.attributes.class).toBe('test'); 21 | }); 22 | 23 | it('virtualizes valueless attributes', function() { 24 | var dom = angular.element('
'); 25 | var result = virtualizeDom(dom[0]); 26 | expect(result.properties.attributes.readonly).toBe(''); 27 | }); 28 | 29 | it('virtualizes a node with nested text nodes', function() { 30 | var dom = angular.element('

Hello

'); 31 | var result = virtualizeDom(dom[0]); 32 | expect(result.children.length).toBe(1); 33 | expect(virtualDom.isVText(result.children[0])).toBe(true); 34 | expect(result.children[0].text).toBe('Hello'); 35 | }); 36 | 37 | it('virtualizes a node with nested nodes', function() { 38 | var dom = angular.element('

HelloSomething

'); 39 | var result = virtualizeDom(dom[0]); 40 | expect(result.children.length).toBe(2); 41 | expect(virtualDom.isVNode(result.children[0])).toBe(true); 42 | expect(virtualDom.isVNode(result.children[1])).toBe(true); 43 | }); 44 | 45 | it('strips comments', function() { 46 | var dom = angular.element('

Hello again

'); 47 | var result = virtualizeDom(dom[0]); 48 | expect(result.children.length).toBe(2); 49 | expect(virtualDom.isVText(result.children[0])).toBe(true); 50 | expect(result.children[0].text).toBe('Hello '); 51 | expect(virtualDom.isVText(result.children[1])).toBe(true); 52 | expect(result.children[1].text).toBe(' again'); 53 | }); 54 | 55 | }); 56 | --------------------------------------------------------------------------------