├── .gitignore ├── README.md ├── index.js ├── light.js ├── package.json ├── test ├── dist │ ├── test-light.js │ └── test.js ├── test-light.js ├── test.html └── test.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .c9/ 3 | *.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # html2idom 2 | 3 | Convert a string of HTML into an [Incremental DOM](https://github.com/google/incremental-dom) render. 4 | 5 | HTML parsing done by [htmlparser2](https://github.com/fb55/htmlparser2), which is a very fast and accurate HTML parser. However, it comes at a cost up front as it adds around 40kb (min and gzipped) to your code. 6 | 7 | If that dependency is too large,then there is a light version (`require("html2idom/light")`) using [html-parse-stringy](https://github.com/HenrikJoreteg/html-parse-stringify), which is much smaller in size, but slower when parsing HTML. If you are only dealing with small sets of HTML, then this option might be better. 8 | 9 | 10 | ## Installation 11 | 12 | ``` 13 | npm install html2idom 14 | ``` 15 | 16 | ## Usage 17 | 18 | ``` 19 | var patchHTML = require("html2idom").patchHTML; 20 | 21 | // get you view's el 22 | var el = document.getElementById("view"); 23 | var html = "

Hello, World

"; 24 | 25 | // apply the HTML to the el via incremental dom 26 | patchHTML(el, HTML); 27 | ``` 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Parser = require("htmlparser2").Parser; 2 | var IncrementalDOM = require("incremental-dom"); 3 | 4 | var elementOpen = IncrementalDOM.elementOpen; 5 | var elementClose = IncrementalDOM.elementClose; 6 | var text = IncrementalDOM.text; 7 | var patch = IncrementalDOM.patch; 8 | 9 | /** Parser with callbacks that invoke Incremental DOM */ 10 | var parser = new Parser({ 11 | onopentag: function (name, attrs) { 12 | var argsArray = [name, null, null]; 13 | 14 | // convert attribs object into a flat array 15 | for (var attr in attrs) { 16 | argsArray.push(attr, attrs[attr]); 17 | } 18 | 19 | elementOpen.apply(null, argsArray); 20 | }, 21 | ontext: text, 22 | onclosetag: elementClose 23 | }, {decodeEntities: true}); 24 | 25 | /** 26 | * build IDOM for ast node 27 | * @private 28 | * @param {Object} node - An AST node to render 29 | */ 30 | function renderToIDom(html) { 31 | parser.write(html); 32 | parser.end(); 33 | }; 34 | 35 | /** 36 | * apply the HTML to an element via Incremental DOM's `patch` 37 | * @param {Element} el - The element to apply the patch to 38 | * @param {String} html - A string of HTML 39 | */ 40 | function patchHTML(el, html) { 41 | patch(el, function() { 42 | return renderToIDom(html); 43 | }); 44 | } 45 | 46 | module.exports = { 47 | renderToIDom: renderToIDom, 48 | patchHTML: patchHTML 49 | } -------------------------------------------------------------------------------- /light.js: -------------------------------------------------------------------------------- 1 | var parse = require("html-parse-stringify").parse; 2 | var IncrementalDOM = require("incremental-dom"); 3 | 4 | var elementOpen = IncrementalDOM.elementOpen; 5 | var elementClose = IncrementalDOM.elementClose; 6 | var elementVoid = IncrementalDOM.elementVoid; 7 | var text = IncrementalDOM.text; 8 | var patch = IncrementalDOM.patch; 9 | 10 | /** 11 | * build IDOM for ast node 12 | * @private 13 | * @param {Object} node - An AST node to render 14 | */ 15 | function renderAstNode(node) { 16 | if (node.type == "text") { 17 | text(node.content); 18 | } 19 | 20 | if (node.type == "tag") { 21 | var argsArray = [node.name, null, null]; 22 | 23 | // convert attribs object into a flat array 24 | for (var attr in node.attrs) { 25 | argsArray.push(attr); 26 | argsArray.push(node.attrs[attr]); 27 | } 28 | 29 | if (node.voidElement) { 30 | elementVoid.apply(null, argsArray); 31 | } else { 32 | elementOpen.apply(null, argsArray); 33 | 34 | for (var i = 0, len = node.children.length; i < len; i++) { 35 | renderAstNode(node.children[i]); 36 | } 37 | 38 | elementClose(node.name); 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * render function for IDOM that takes a string of HTML 45 | * @param {String} html - The string of HTML to render 46 | */ 47 | function renderToIDom(html) { 48 | var ast = parse(html); 49 | 50 | if (Array.isArray(ast)) { 51 | ast.forEach(renderAstNode); 52 | } else { 53 | renderAstNode(ast); 54 | } 55 | }; 56 | 57 | /** 58 | * apply the HTML to an element via Incremental DOM's `patch` 59 | * @param {Element} el - The element to apply the patch to 60 | * @param {String} html - A string of HTML 61 | */ 62 | function patchHTML(el, html) { 63 | patch(el, function() { 64 | return renderToIDom(html); 65 | }); 66 | } 67 | 68 | module.exports = { 69 | renderToIDom: renderToIDom, 70 | patchHTML: patchHTML 71 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html2idom", 3 | "version": "0.1.0", 4 | "description": "parse a string of html into incremental dom", 5 | "main": "index.js", 6 | "scripts": { 7 | "build-tests": "browserify test/test.js -o test/dist/test.js && browserify test/test-light.js -o test/dist/test-light.js" 8 | }, 9 | "author": "eric.ponto@gmail.com", 10 | "license": "ISC", 11 | "directories": { 12 | "test": "test" 13 | }, 14 | "dependencies": { 15 | "html-parse-stringify": "^1.0.2", 16 | "htmlparser2": "^3.9.2", 17 | "incremental-dom": "^0.5.1" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/ericponto/html2IDOM.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/ericponto/html2IDOM/issues" 25 | }, 26 | "homepage": "https://github.com/ericponto/html2IDOM" 27 | } 28 | -------------------------------------------------------------------------------- /test/dist/test-light.js: -------------------------------------------------------------------------------- 1 | (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 1) { 47 | for (var i = 1; i < arguments.length; i++) { 48 | args[i - 1] = arguments[i]; 49 | } 50 | } 51 | queue.push(new Item(fun, args)); 52 | if (queue.length === 1 && !draining) { 53 | setTimeout(drainQueue, 0); 54 | } 55 | }; 56 | 57 | // v8 likes predictible objects 58 | function Item(fun, array) { 59 | this.fun = fun; 60 | this.array = array; 61 | } 62 | Item.prototype.run = function () { 63 | this.fun.apply(null, this.array); 64 | }; 65 | process.title = 'browser'; 66 | process.browser = true; 67 | process.env = {}; 68 | process.argv = []; 69 | process.version = ''; // empty string to avoid regexp issues 70 | process.versions = {}; 71 | 72 | function noop() {} 73 | 74 | process.on = noop; 75 | process.addListener = noop; 76 | process.once = noop; 77 | process.off = noop; 78 | process.removeListener = noop; 79 | process.removeAllListeners = noop; 80 | process.emit = noop; 81 | 82 | process.binding = function (name) { 83 | throw new Error('process.binding is not supported'); 84 | }; 85 | 86 | // TODO(shtylman) 87 | process.cwd = function () { return '/' }; 88 | process.chdir = function (dir) { 89 | throw new Error('process.chdir is not supported'); 90 | }; 91 | process.umask = function() { return 0; }; 92 | 93 | },{}],2:[function(require,module,exports){ 94 | var parse = require("html-parse-stringify").parse; 95 | var IncrementalDOM = require("incremental-dom"); 96 | 97 | var elementOpen = IncrementalDOM.elementOpen; 98 | var elementClose = IncrementalDOM.elementClose; 99 | var elementVoid = IncrementalDOM.elementVoid; 100 | var text = IncrementalDOM.text; 101 | var patch = IncrementalDOM.patch; 102 | 103 | /** 104 | * build IDOM for ast node 105 | * @private 106 | * @param {Object} node - An AST node to render 107 | */ 108 | function renderAstNode(node) { 109 | if (node.type == "text") { 110 | text(node.content); 111 | } 112 | 113 | if (node.type == "tag") { 114 | var argsArray = [node.name, null, null]; 115 | 116 | // convert attribs object into a flat array 117 | for (var attr in node.attrs) { 118 | argsArray.push(attr); 119 | argsArray.push(node.attrs[attr]); 120 | } 121 | 122 | if (node.voidElement) { 123 | elementVoid.apply(null, argsArray); 124 | } else { 125 | elementOpen.apply(null, argsArray); 126 | 127 | for (var i = 0, len = node.children.length; i < len; i++) { 128 | renderAstNode(node.children[i]); 129 | } 130 | 131 | elementClose(node.name); 132 | } 133 | } 134 | } 135 | 136 | /** 137 | * render function for IDOM that takes a string of HTML 138 | * @param {String} html - The string of HTML to render 139 | */ 140 | function renderToIDom(html) { 141 | var ast = parse(html); 142 | 143 | if (Array.isArray(ast)) { 144 | ast.forEach(renderAstNode); 145 | } else { 146 | renderAstNode(ast); 147 | } 148 | }; 149 | 150 | /** 151 | * apply the HTML to an element via Incremental DOM's `patch` 152 | * @param {Element} el - The element to apply the patch to 153 | * @param {String} html - A string of HTML 154 | */ 155 | function patchHTML(el, html) { 156 | patch(el, function() { 157 | return renderToIDom(html); 158 | }); 159 | } 160 | 161 | module.exports = { 162 | renderToIDom: renderToIDom, 163 | patchHTML: patchHTML 164 | } 165 | },{"html-parse-stringify":3,"incremental-dom":7}],3:[function(require,module,exports){ 166 | module.exports = { 167 | parse: require('./lib/parse'), 168 | stringify: require('./lib/stringify') 169 | }; 170 | 171 | },{"./lib/parse":5,"./lib/stringify":6}],4:[function(require,module,exports){ 172 | var attrRE = /([\w-]+)|['"]{1}([^'"]*)['"]{1}/g; 173 | 174 | // create optimized lookup object for 175 | // void elements as listed here: 176 | // http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements 177 | var lookup = (Object.create) ? Object.create(null) : {}; 178 | lookup.area = true; 179 | lookup.base = true; 180 | lookup.br = true; 181 | lookup.col = true; 182 | lookup.embed = true; 183 | lookup.hr = true; 184 | lookup.img = true; 185 | lookup.input = true; 186 | lookup.keygen = true; 187 | lookup.link = true; 188 | lookup.menuitem = true; 189 | lookup.meta = true; 190 | lookup.param = true; 191 | lookup.source = true; 192 | lookup.track = true; 193 | lookup.wbr = true; 194 | 195 | module.exports = function (tag) { 196 | var i = 0; 197 | var key; 198 | var res = { 199 | type: 'tag', 200 | name: '', 201 | voidElement: false, 202 | attrs: {}, 203 | children: [] 204 | }; 205 | 206 | tag.replace(attrRE, function (match) { 207 | if (i % 2) { 208 | key = match; 209 | } else { 210 | if (i === 0) { 211 | if (lookup[match] || tag.charAt(tag.length - 2) === '/') { 212 | res.voidElement = true; 213 | } 214 | res.name = match; 215 | } else { 216 | res.attrs[key] = match.replace(/['"]/g, ''); 217 | } 218 | } 219 | i++; 220 | }); 221 | 222 | return res; 223 | }; 224 | 225 | },{}],5:[function(require,module,exports){ 226 | /*jshint -W030 */ 227 | var tagRE = /<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g; 228 | var parseTag = require('./parse-tag'); 229 | // re-used obj for quick lookups of components 230 | var empty = Object.create ? Object.create(null) : {}; 231 | 232 | module.exports = function parse(html, options) { 233 | options || (options = {}); 234 | options.components || (options.components = empty); 235 | var result = []; 236 | var current; 237 | var level = -1; 238 | var arr = []; 239 | var byTag = {}; 240 | var inComponent = false; 241 | 242 | html.replace(tagRE, function (tag, index) { 243 | if (inComponent) { 244 | if (tag !== ('')) { 245 | return; 246 | } else { 247 | inComponent = false; 248 | } 249 | } 250 | var isOpen = tag.charAt(1) !== '/'; 251 | var start = index + tag.length; 252 | var nextChar = html.charAt(start); 253 | var parent; 254 | 255 | if (isOpen) { 256 | level++; 257 | 258 | current = parseTag(tag); 259 | if (current.type === 'tag' && options.components[current.name]) { 260 | current.type = 'component'; 261 | inComponent = true; 262 | } 263 | 264 | if (!current.voidElement && !inComponent && nextChar && nextChar !== '<') { 265 | current.children.push({ 266 | type: 'text', 267 | content: html.slice(start, html.indexOf('<', start)) 268 | }); 269 | } 270 | 271 | byTag[current.tagName] = current; 272 | 273 | // if we're at root, push new base node 274 | if (level === 0) { 275 | result.push(current); 276 | } 277 | 278 | parent = arr[level - 1]; 279 | 280 | if (parent) { 281 | parent.children.push(current); 282 | } 283 | 284 | arr[level] = current; 285 | } 286 | 287 | if (!isOpen || current.voidElement) { 288 | level--; 289 | if (!inComponent && nextChar !== '<' && nextChar) { 290 | // trailing text node 291 | arr[level].children.push({ 292 | type: 'text', 293 | content: html.slice(start, html.indexOf('<', start)) 294 | }); 295 | } 296 | } 297 | }); 298 | 299 | return result; 300 | }; 301 | 302 | },{"./parse-tag":4}],6:[function(require,module,exports){ 303 | function attrString(attrs) { 304 | var buff = []; 305 | for (var key in attrs) { 306 | buff.push(key + '="' + attrs[key] + '"'); 307 | } 308 | if (!buff.length) { 309 | return ''; 310 | } 311 | return ' ' + buff.join(' '); 312 | } 313 | 314 | function stringify(buff, doc) { 315 | switch (doc.type) { 316 | case 'text': 317 | return buff + doc.content; 318 | case 'tag': 319 | buff += '<' + doc.name + (doc.attrs ? attrString(doc.attrs) : '') + (doc.voidElement ? '/>' : '>'); 320 | if (doc.voidElement) { 321 | return buff; 322 | } 323 | return buff + doc.children.reduce(stringify, '') + ''; 324 | } 325 | } 326 | 327 | module.exports = function (doc) { 328 | return doc.reduce(function (token, rootEl) { 329 | return token + stringify('', rootEl); 330 | }, ''); 331 | }; 332 | 333 | },{}],7:[function(require,module,exports){ 334 | /** 335 | * @license 336 | * Copyright 2015 The Incremental DOM Authors. All Rights Reserved. 337 | * 338 | * Licensed under the Apache License, Version 2.0 (the "License"); 339 | * you may not use this file except in compliance with the License. 340 | * You may obtain a copy of the License at 341 | * 342 | * http://www.apache.org/licenses/LICENSE-2.0 343 | * 344 | * Unless required by applicable law or agreed to in writing, software 345 | * distributed under the License is distributed on an "AS-IS" BASIS, 346 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 347 | * See the License for the specific language governing permissions and 348 | * limitations under the License. 349 | */ 350 | 351 | var patch = require('./src/patch').patch; 352 | var elements = require('./src/virtual_elements'); 353 | 354 | module.exports = { 355 | patch: patch, 356 | elementVoid: elements.elementVoid, 357 | elementOpenStart: elements.elementOpenStart, 358 | elementOpenEnd: elements.elementOpenEnd, 359 | elementOpen: elements.elementOpen, 360 | elementClose: elements.elementClose, 361 | text: elements.text, 362 | attr: elements.attr 363 | }; 364 | 365 | 366 | },{"./src/patch":12,"./src/virtual_elements":15}],8:[function(require,module,exports){ 367 | /** 368 | * Copyright 2015 The Incremental DOM Authors. All Rights Reserved. 369 | * 370 | * Licensed under the Apache License, Version 2.0 (the "License"); 371 | * you may not use this file except in compliance with the License. 372 | * You may obtain a copy of the License at 373 | * 374 | * http://www.apache.org/licenses/LICENSE-2.0 375 | * 376 | * Unless required by applicable law or agreed to in writing, software 377 | * distributed under the License is distributed on an "AS-IS" BASIS, 378 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 379 | * See the License for the specific language governing permissions and 380 | * limitations under the License. 381 | */ 382 | 383 | var nodes = require('./nodes'), 384 | createNode = nodes.createNode, 385 | getKey = nodes.getKey, 386 | getNodeName = nodes.getNodeName, 387 | getChild = nodes.getChild, 388 | registerChild = nodes.registerChild; 389 | var markVisited = require('./traversal').markVisited; 390 | var getWalker = require('./walker').getWalker; 391 | 392 | 393 | /** 394 | * Checks whether or not a given node matches the specified nodeName and key. 395 | * 396 | * @param {?Node} node An HTML node, typically an HTMLElement or Text. 397 | * @param {?string} nodeName The nodeName for this node. 398 | * @param {?string} key An optional key that identifies a node. 399 | * @return {boolean} True if the node matches, false otherwise. 400 | */ 401 | var matches = function(node, nodeName, key) { 402 | return node && 403 | key === getKey(node) && 404 | nodeName === getNodeName(node); 405 | }; 406 | 407 | 408 | /** 409 | * Aligns the virtual Element definition with the actual DOM, moving the 410 | * corresponding DOM node to the correct location or creating it if necessary. 411 | * @param {?string} nodeName For an Element, this should be a valid tag string. 412 | * For a Text, this should be #text. 413 | * @param {?string} key The key used to identify this element. 414 | * @param {?Array<*>|string} statics For an Element, this should be an array of 415 | * name-value pairs. For a Text, this should be the text content of the 416 | * node. 417 | * @return {!Node} The matching node. 418 | */ 419 | var alignWithDOM = function(nodeName, key, statics) { 420 | var walker = getWalker(); 421 | var currentNode = walker.currentNode; 422 | var parent = walker.getCurrentParent(); 423 | var matchingNode; 424 | 425 | // Check to see if we have a node to reuse 426 | if (matches(currentNode, nodeName, key)) { 427 | matchingNode = currentNode; 428 | } else { 429 | var existingNode = key && getChild(parent, key); 430 | 431 | // Check to see if the node has moved within the parent or if a new one 432 | // should be created 433 | if (existingNode) { 434 | matchingNode = existingNode; 435 | } else { 436 | matchingNode = createNode(walker.doc, nodeName, key, statics); 437 | registerChild(parent, key, matchingNode); 438 | } 439 | 440 | parent.insertBefore(matchingNode, currentNode); 441 | walker.currentNode = matchingNode; 442 | } 443 | 444 | markVisited(parent, matchingNode); 445 | 446 | return matchingNode; 447 | }; 448 | 449 | 450 | /** */ 451 | module.exports = { 452 | alignWithDOM: alignWithDOM 453 | }; 454 | 455 | 456 | },{"./nodes":11,"./traversal":13,"./walker":16}],9:[function(require,module,exports){ 457 | /** 458 | * Copyright 2015 The Incremental DOM Authors. All Rights Reserved. 459 | * 460 | * Licensed under the Apache License, Version 2.0 (the "License"); 461 | * you may not use this file except in compliance with the License. 462 | * You may obtain a copy of the License at 463 | * 464 | * http://www.apache.org/licenses/LICENSE-2.0 465 | * 466 | * Unless required by applicable law or agreed to in writing, software 467 | * distributed under the License is distributed on an "AS-IS" BASIS, 468 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 469 | * See the License for the specific language governing permissions and 470 | * limitations under the License. 471 | */ 472 | 473 | var getData = require('./node_data').getData; 474 | 475 | 476 | /** 477 | * Applies an attribute or property to a given Element. If the value is a object 478 | * or a function (which includes null), it is set as a property on the Element. 479 | * Otherwise, the value is set as an attribute. 480 | * @param {!Element} el 481 | * @param {string} name The attribute's name. 482 | * @param {*} value The attribute's value. If the value is a string, it is set 483 | * as an HTML attribute, otherwise, it is set on node. 484 | */ 485 | var applyAttr = function(el, name, value) { 486 | var data = getData(el); 487 | var attrs = data.attrs; 488 | 489 | if (attrs[name] === value) { 490 | return; 491 | } 492 | 493 | var type = typeof value; 494 | 495 | if (value === undefined) { 496 | el.removeAttribute(name); 497 | } else if (type === 'object' || type === 'function') { 498 | el[name] = value; 499 | } else { 500 | el.setAttribute(name, value); 501 | } 502 | 503 | attrs[name] = value; 504 | }; 505 | 506 | 507 | /** 508 | * Applies a style to an Element. No vendor prefix expansion is done for 509 | * property names/values. 510 | * @param {!Element} el 511 | * @param {string|Object} style The style to set. Either a string 512 | * of css or an object containing property-value pairs. 513 | */ 514 | var applyStyle = function(el, style) { 515 | if (typeof style === 'string' || style instanceof String) { 516 | el.style.cssText = style; 517 | } else { 518 | el.style.cssText = ''; 519 | 520 | for (var prop in style) { 521 | el.style[prop] = style[prop]; 522 | } 523 | } 524 | }; 525 | 526 | 527 | /** 528 | * Updates a single attribute on an Element. For some types (e.g. id or class), 529 | * the value is applied directly to the Element using the corresponding accessor 530 | * function. 531 | * @param {!Element} el 532 | * @param {string} name The attribute's name. 533 | * @param {*} value The attribute's value. If the value is a string, it is set 534 | * as an HTML attribute, otherwise, it is set on node. 535 | */ 536 | var updateAttribute = function(el, name, value) { 537 | switch (name) { 538 | case 'id': 539 | el.id = value; 540 | break; 541 | case 'class': 542 | el.className = value; 543 | break; 544 | case 'tabindex': 545 | el.tabIndex = value; 546 | break; 547 | case 'style': 548 | applyStyle(el, value); 549 | break; 550 | default: 551 | applyAttr(el, name, value); 552 | break; 553 | } 554 | }; 555 | 556 | 557 | /** */ 558 | module.exports = { 559 | updateAttribute: updateAttribute 560 | }; 561 | 562 | 563 | },{"./node_data":10}],10:[function(require,module,exports){ 564 | /** 565 | * Copyright 2015 The Incremental DOM Authors. All Rights Reserved. 566 | * 567 | * Licensed under the Apache License, Version 2.0 (the "License"); 568 | * you may not use this file except in compliance with the License. 569 | * You may obtain a copy of the License at 570 | * 571 | * http://www.apache.org/licenses/LICENSE-2.0 572 | * 573 | * Unless required by applicable law or agreed to in writing, software 574 | * distributed under the License is distributed on an "AS-IS" BASIS, 575 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 576 | * See the License for the specific language governing permissions and 577 | * limitations under the License. 578 | */ 579 | 580 | 581 | /** 582 | * Keeps track of information needed to perform diffs for a given DOM node. 583 | * @param {?string} nodeName 584 | * @param {?string} key 585 | * @constructor 586 | */ 587 | function NodeData(nodeName, key) { 588 | /** 589 | * The attributes and their values. 590 | * @const 591 | */ 592 | this.attrs = {}; 593 | 594 | /** 595 | * An array of attribute name/value pairs, used for quickly diffing the 596 | * incomming attributes to see if the DOM node's attributes need to be 597 | * updated. 598 | * @const {Array<*>} 599 | */ 600 | this.attrsArr = []; 601 | 602 | /** 603 | * The incoming attributes for this Node, before they are updated. 604 | * @const {!Object} 605 | */ 606 | this.newAttrs = {}; 607 | 608 | /** 609 | * The key used to identify this node, used to preserve DOM nodes when they 610 | * move within their parent. 611 | * @const 612 | */ 613 | this.key = key; 614 | 615 | /** 616 | * Keeps track of children within this node by their key. 617 | * {?Object} 618 | */ 619 | this.keyMap = null; 620 | 621 | /** 622 | * The last child to have been visited within the current pass. 623 | * {?Node} 624 | */ 625 | this.lastVisitedChild = null; 626 | 627 | /** 628 | * The node name for this node. 629 | * @const 630 | */ 631 | this.nodeName = nodeName; 632 | 633 | /** 634 | * @const {string} 635 | */ 636 | this.text = null; 637 | } 638 | 639 | 640 | /** 641 | * Initializes a NodeData object for a Node. 642 | * 643 | * @param {!Node} node The node to initialze data for. 644 | * @param {string} nodeName The node name of node. 645 | * @param {?string} key The key that identifies the node. 646 | * @return {!NodeData} The newly initialized data object 647 | */ 648 | var initData = function(node, nodeName, key) { 649 | var data = new NodeData(nodeName, key); 650 | node['__incrementalDOMData'] = data; 651 | return data; 652 | }; 653 | 654 | 655 | /** 656 | * Retrieves the NodeData object for a Node, creating it if necessary. 657 | * 658 | * @param {!Node} node The node to retrieve the data for. 659 | * @return {NodeData} The NodeData for this Node. 660 | */ 661 | var getData = function(node) { 662 | var data = node['__incrementalDOMData']; 663 | 664 | if (!data) { 665 | var nodeName = node.nodeName.toLowerCase(); 666 | var key = null; 667 | 668 | if (node instanceof Element) { 669 | key = node.getAttribute('key'); 670 | } 671 | 672 | data = initData(node, nodeName, key); 673 | } 674 | 675 | return data; 676 | }; 677 | 678 | 679 | /** */ 680 | module.exports = { 681 | getData: getData, 682 | initData: initData 683 | }; 684 | 685 | 686 | },{}],11:[function(require,module,exports){ 687 | /** 688 | * Copyright 2015 The Incremental DOM Authors. All Rights Reserved. 689 | * 690 | * Licensed under the Apache License, Version 2.0 (the "License"); 691 | * you may not use this file except in compliance with the License. 692 | * You may obtain a copy of the License at 693 | * 694 | * http://www.apache.org/licenses/LICENSE-2.0 695 | * 696 | * Unless required by applicable law or agreed to in writing, software 697 | * distributed under the License is distributed on an "AS-IS" BASIS, 698 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 699 | * See the License for the specific language governing permissions and 700 | * limitations under the License. 701 | */ 702 | 703 | var updateAttribute = require('./attributes').updateAttribute; 704 | var nodeData = require('./node_data'), 705 | getData = nodeData.getData, 706 | initData = nodeData.initData; 707 | 708 | 709 | /** 710 | * Creates an Element. 711 | * @param {!Document} doc The document with which to create the Element. 712 | * @param {string} tag The tag for the Element. 713 | * @param {?string} key A key to identify the Element. 714 | * @param {?Array<*>} statics An array of attribute name/value pairs of 715 | * the static attributes for the Element. 716 | * @return {!Element} 717 | */ 718 | var createElement = function(doc, tag, key, statics) { 719 | var el = doc.createElement(tag); 720 | initData(el, tag, key); 721 | 722 | if (statics) { 723 | for (var i = 0; i < statics.length; i += 2) { 724 | updateAttribute(el, statics[i], statics[i + 1]); 725 | } 726 | } 727 | 728 | return el; 729 | }; 730 | 731 | /** 732 | * Creates a Text. 733 | * @param {!Document} doc The document with which to create the Text. 734 | * @param {string} text The intial content of the Text. 735 | * @return {!Text} 736 | */ 737 | var createTextNode = function(doc, text) { 738 | var node = doc.createTextNode(text); 739 | getData(node).text = text; 740 | 741 | return node; 742 | }; 743 | 744 | 745 | /** 746 | * Creates a Node, either a Text or an Element depending on the node name 747 | * provided. 748 | * @param {!Document} doc The document with which to create the Node. 749 | * @param {string} nodeName The tag if creating an element or #text to create 750 | * a Text. 751 | * @param {?string} key A key to identify the Element. 752 | * @param {?Array<*>|string} statics The static data to initialize the Node 753 | * with. For an Element, an array of attribute name/value pairs of 754 | * the static attributes for the Element. For a Text, a string with the 755 | * intial content of the Text. 756 | * @return {!Node} 757 | */ 758 | var createNode = function(doc, nodeName, key, statics) { 759 | if (nodeName === '#text') { 760 | return createTextNode(doc, statics); 761 | } 762 | 763 | return createElement(doc, nodeName, key, statics); 764 | }; 765 | 766 | 767 | /** 768 | * Creates a mapping that can be used to look up children using a key. 769 | * @param {!Element} el 770 | * @return {!Object} A mapping of keys to the children of the 771 | * Element. 772 | */ 773 | var createKeyMap = function(el) { 774 | var map = {}; 775 | var children = el.children; 776 | var count = children.length; 777 | 778 | for (var i = 0; i < count; i += 1) { 779 | var child = children[i]; 780 | var key = getKey(child); 781 | 782 | if (key) { 783 | map[key] = child; 784 | } 785 | } 786 | 787 | return map; 788 | }; 789 | 790 | 791 | /** 792 | * @param {?Node} node A node to get the key for. 793 | * @return {?string} The key for the Node, if applicable. 794 | */ 795 | var getKey = function(node) { 796 | return getData(node).key; 797 | }; 798 | 799 | 800 | /** 801 | * @param {?Node} node A node to get the node name for. 802 | * @return {?string} The node name for the Node, if applicable. 803 | */ 804 | var getNodeName = function(node) { 805 | return getData(node).nodeName; 806 | }; 807 | 808 | 809 | /** 810 | * Retrieves the mapping of key to child node for a given Element, creating it 811 | * if necessary. 812 | * @param {!Element} el 813 | * @return {!Object} A mapping of keys to child Nodes 814 | */ 815 | var getKeyMap = function(el) { 816 | var data = getData(el); 817 | 818 | if (!data.keyMap) { 819 | data.keyMap = createKeyMap(el); 820 | } 821 | 822 | return data.keyMap; 823 | }; 824 | 825 | 826 | /** 827 | * Retrieves a child from the parent with the given key. 828 | * @param {!Element} parent 829 | * @param {?string} key 830 | * @return {?Node} The child corresponding to the key. 831 | */ 832 | var getChild = function(parent, key) { 833 | return getKeyMap(parent)[key]; 834 | }; 835 | 836 | 837 | /** 838 | * Registers a node as being a child. If a key is provided, the parent will 839 | * keep track of the child using the key. The child can be retrieved using the 840 | * same key using getKeyMap. The provided key should be unique within the 841 | * parent Element. 842 | * @param {!Element} parent The parent of child. 843 | * @param {?string} key A key to identify the child with. 844 | * @param {!Node} child The child to register. 845 | */ 846 | var registerChild = function(parent, key, child) { 847 | if (key) { 848 | getKeyMap(parent)[key] = child; 849 | } 850 | }; 851 | 852 | 853 | /** */ 854 | module.exports = { 855 | createNode: createNode, 856 | getKey: getKey, 857 | getNodeName: getNodeName, 858 | getChild: getChild, 859 | registerChild: registerChild 860 | }; 861 | 862 | 863 | },{"./attributes":9,"./node_data":10}],12:[function(require,module,exports){ 864 | /** 865 | * Copyright 2015 The Incremental DOM Authors. All Rights Reserved. 866 | * 867 | * Licensed under the Apache License, Version 2.0 (the "License"); 868 | * you may not use this file except in compliance with the License. 869 | * You may obtain a copy of the License at 870 | * 871 | * http://www.apache.org/licenses/LICENSE-2.0 872 | * 873 | * Unless required by applicable law or agreed to in writing, software 874 | * distributed under the License is distributed on an "AS-IS" BASIS, 875 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 876 | * See the License for the specific language governing permissions and 877 | * limitations under the License. 878 | */ 879 | 880 | var traversal = require('./traversal'), 881 | firstChild = traversal.firstChild, 882 | parentNode = traversal.parentNode; 883 | var TreeWalker = require('./tree_walker'); 884 | var walker = require('./walker'), 885 | getWalker = walker.getWalker, 886 | setWalker = walker.setWalker; 887 | 888 | 889 | /** 890 | * Patches the document starting at el with the provided function. This function 891 | * may be called during an existing patch operation. 892 | * @param {!Element} el the element to patch 893 | * @param {!function} fn A function containing elementOpen/elementClose/etc. 894 | * calls that describe the DOM. 895 | */ 896 | var patch = function(el, fn) { 897 | var prevWalker = getWalker(); 898 | setWalker(new TreeWalker(el)); 899 | 900 | firstChild(); 901 | fn(); 902 | parentNode(); 903 | 904 | setWalker(prevWalker); 905 | }; 906 | 907 | 908 | /** */ 909 | module.exports = { 910 | patch: patch 911 | }; 912 | 913 | 914 | },{"./traversal":13,"./tree_walker":14,"./walker":16}],13:[function(require,module,exports){ 915 | /** 916 | * Copyright 2015 The Incremental DOM Authors. All Rights Reserved. 917 | * 918 | * Licensed under the Apache License, Version 2.0 (the "License"); 919 | * you may not use this file except in compliance with the License. 920 | * You may obtain a copy of the License at 921 | * 922 | * http://www.apache.org/licenses/LICENSE-2.0 923 | * 924 | * Unless required by applicable law or agreed to in writing, software 925 | * distributed under the License is distributed on an "AS-IS" BASIS, 926 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 927 | * See the License for the specific language governing permissions and 928 | * limitations under the License. 929 | */ 930 | 931 | var getWalker = require('./walker').getWalker; 932 | var getData = require('./node_data').getData; 933 | 934 | 935 | /** 936 | * Enters a Element, clearing out the last visited child field. 937 | * @param {!Element} node 938 | */ 939 | var enterNode = function(node) { 940 | var data = getData(node); 941 | data.lastVisitedChild = null; 942 | }; 943 | 944 | 945 | /** 946 | * Clears out any unvisited Nodes, as the corresponding virtual element 947 | * functions were never called for them. 948 | * @param {!Element} node 949 | */ 950 | var exitNode = function(node) { 951 | var data = getData(node); 952 | var lastVisitedChild = data.lastVisitedChild; 953 | 954 | if (node.lastChild === lastVisitedChild) { 955 | return; 956 | } 957 | 958 | while (node.lastChild !== lastVisitedChild) { 959 | node.removeChild(node.lastChild); 960 | } 961 | 962 | // Invalidate the key map since we removed children. It will get recreated 963 | // next time we need it. 964 | data.keyMap = null; 965 | }; 966 | 967 | 968 | /** 969 | * Marks a parent as having visited a child. 970 | * @param {!Element} parent 971 | * @param {!Node} child 972 | */ 973 | var markVisited = function(parent, child) { 974 | var data = getData(parent); 975 | data.lastVisitedChild = child; 976 | }; 977 | 978 | 979 | /** 980 | * Changes to the first child of the current node. 981 | */ 982 | var firstChild = function() { 983 | var walker = getWalker(); 984 | enterNode(walker.currentNode); 985 | walker.firstChild(); 986 | }; 987 | 988 | 989 | /** 990 | * Changes to the next sibling of the current node. 991 | */ 992 | var nextSibling = function() { 993 | var walker = getWalker(); 994 | walker.nextSibling(); 995 | }; 996 | 997 | 998 | /** 999 | * Changes to the parent of the current node, removing any unvisited children. 1000 | */ 1001 | var parentNode = function() { 1002 | var walker = getWalker(); 1003 | walker.parentNode(); 1004 | exitNode(walker.currentNode); 1005 | }; 1006 | 1007 | 1008 | /** */ 1009 | module.exports = { 1010 | firstChild: firstChild, 1011 | nextSibling: nextSibling, 1012 | parentNode: parentNode, 1013 | markVisited: markVisited 1014 | }; 1015 | 1016 | 1017 | },{"./node_data":10,"./walker":16}],14:[function(require,module,exports){ 1018 | /** 1019 | * Copyright 2015 The Incremental DOM Authors. All Rights Reserved. 1020 | * 1021 | * Licensed under the Apache License, Version 2.0 (the "License"); 1022 | * you may not use this file except in compliance with the License. 1023 | * You may obtain a copy of the License at 1024 | * 1025 | * http://www.apache.org/licenses/LICENSE-2.0 1026 | * 1027 | * Unless required by applicable law or agreed to in writing, software 1028 | * distributed under the License is distributed on an "AS-IS" BASIS, 1029 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1030 | * See the License for the specific language governing permissions and 1031 | * limitations under the License. 1032 | */ 1033 | 1034 | /** 1035 | * Similar to the built-in Treewalker class, but simplified and allows direct 1036 | * access to modify the currentNode property. 1037 | * @param {!Node} node The root Node of the subtree the walker should start 1038 | * traversing. 1039 | * @constructor 1040 | */ 1041 | function TreeWalker(node) { 1042 | /** 1043 | * Keeps track of the current parent node. This is necessary as the traversal 1044 | * methods may traverse past the last child and we still need a way to get 1045 | * back to the parent. 1046 | * @const @private {!Array} 1047 | */ 1048 | this.stack_ = []; 1049 | 1050 | /** {?Node} */ 1051 | this.currentNode = node; 1052 | 1053 | /** {!Document} */ 1054 | this.doc = node.ownerDocument; 1055 | } 1056 | 1057 | 1058 | /** 1059 | * @return {!Node} The current parent of the current location in the subtree. 1060 | */ 1061 | TreeWalker.prototype.getCurrentParent = function() { 1062 | return this.stack_[this.stack_.length - 1]; 1063 | }; 1064 | 1065 | 1066 | /** 1067 | * Changes the current location the firstChild of the current location. 1068 | */ 1069 | TreeWalker.prototype.firstChild = function() { 1070 | this.stack_.push(this.currentNode); 1071 | this.currentNode = this.currentNode.firstChild; 1072 | }; 1073 | 1074 | 1075 | /** 1076 | * Changes the current location the nextSibling of the current location. 1077 | */ 1078 | TreeWalker.prototype.nextSibling = function() { 1079 | this.currentNode = this.currentNode.nextSibling; 1080 | }; 1081 | 1082 | 1083 | /** 1084 | * Changes the current location the parentNode of the current location. 1085 | */ 1086 | TreeWalker.prototype.parentNode = function() { 1087 | this.currentNode = this.stack_.pop(); 1088 | }; 1089 | 1090 | 1091 | /** */ 1092 | module.exports = TreeWalker; 1093 | 1094 | 1095 | },{}],15:[function(require,module,exports){ 1096 | (function (process){ 1097 | /** 1098 | * Copyright 2015 The Incremental DOM Authors. All Rights Reserved. 1099 | * 1100 | * Licensed under the Apache License, Version 2.0 (the "License"); 1101 | * you may not use this file except in compliance with the License. 1102 | * You may obtain a copy of the License at 1103 | * 1104 | * http://www.apache.org/licenses/LICENSE-2.0 1105 | * 1106 | * Unless required by applicable law or agreed to in writing, software 1107 | * distributed under the License is distributed on an "AS-IS" BASIS, 1108 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1109 | * See the License for the specific language governing permissions and 1110 | * limitations under the License. 1111 | */ 1112 | 1113 | var alignWithDOM = require('./alignment').alignWithDOM; 1114 | var updateAttribute = require('./attributes').updateAttribute; 1115 | var getData = require('./node_data').getData; 1116 | var getWalker = require('./walker').getWalker; 1117 | var traversal = require('./traversal'), 1118 | firstChild = traversal.firstChild, 1119 | nextSibling = traversal.nextSibling, 1120 | parentNode = traversal.parentNode; 1121 | 1122 | 1123 | /** 1124 | * The offset in the virtual element declaration where the attributes are 1125 | * specified. 1126 | * @const 1127 | */ 1128 | var ATTRIBUTES_OFFSET = 3; 1129 | 1130 | 1131 | /** 1132 | * Builds an array of arguments for use with elementOpenStart, attr and 1133 | * elementOpenEnd. 1134 | * @type {Array<*>} 1135 | * @const 1136 | */ 1137 | var argsBuilder = []; 1138 | 1139 | 1140 | if (process.env.NODE_ENV !== 'production') { 1141 | /** 1142 | * Keeps track whether or not we are in an attributes declaration (after 1143 | * elementOpenStart, but before elementOpenEnd). 1144 | * @type {boolean} 1145 | */ 1146 | var inAttributes = false; 1147 | 1148 | 1149 | /** Makes sure that the caller is not where attributes are expected. */ 1150 | var assertNotInAttributes = function() { 1151 | if (inAttributes) { 1152 | throw new Error('Was not expecting a call to attr or elementOpenEnd, ' + 1153 | 'they must follow a call to elementOpenStart.'); 1154 | } 1155 | }; 1156 | 1157 | 1158 | /** Makes sure that the caller is where attributes are expected. */ 1159 | var assertInAttributes = function() { 1160 | if (!inAttributes) { 1161 | throw new Error('Was expecting a call to attr or elementOpenEnd. ' + 1162 | 'elementOpenStart must be followed by zero or more calls to attr, ' + 1163 | 'then one call to elementOpenEnd.'); 1164 | } 1165 | }; 1166 | 1167 | 1168 | /** Updates the state to being in an attribute declaration. */ 1169 | var setInAttributes = function() { 1170 | inAttributes = true; 1171 | }; 1172 | 1173 | 1174 | /** Updates the state to not being in an attribute declaration. */ 1175 | var setNotInAttributes = function() { 1176 | inAttributes = false; 1177 | }; 1178 | } 1179 | 1180 | 1181 | /** 1182 | * Checks to see if one or more attributes have changed for a given 1183 | * Element. When no attributes have changed, this function is much faster than 1184 | * checking each individual argument. When attributes have changed, the overhead 1185 | * of this function is minimal. 1186 | * 1187 | * This function is called in the context of the Element and the arguments from 1188 | * elementOpen-like function so that the arguments are not de-optimized. 1189 | * 1190 | * @this {Element} The Element to check for changed attributes. 1191 | * @param {*} unused1 1192 | * @param {*} unused2 1193 | * @param {*} unused3 1194 | * @param {...*} var_args Attribute name/value pairs of the dynamic attributes 1195 | * for the Element. 1196 | * @return {boolean} True if the Element has one or more changed attributes, 1197 | * false otherwise. 1198 | */ 1199 | var hasChangedAttrs = function(unused1, unused2, unused3, var_args) { 1200 | var data = getData(this); 1201 | var attrsArr = data.attrsArr; 1202 | var attrsChanged = false; 1203 | var i; 1204 | 1205 | for (i = ATTRIBUTES_OFFSET; i < arguments.length; i += 2) { 1206 | // Translate the from the arguments index (for values) to the attribute's 1207 | // ordinal. The attribute values are at arguments index 3, 5, 7, etc. To get 1208 | // the ordinal, need to subtract the offset and divide by 2 1209 | if (attrsArr[(i - ATTRIBUTES_OFFSET) >> 1] !== arguments[i + 1]) { 1210 | attrsChanged = true; 1211 | break; 1212 | } 1213 | } 1214 | 1215 | if (attrsChanged) { 1216 | for (i = ATTRIBUTES_OFFSET; i < arguments.length; i += 2) { 1217 | attrsArr[(i - ATTRIBUTES_OFFSET) >> 1] = arguments[i + 1]; 1218 | } 1219 | } 1220 | 1221 | return attrsChanged; 1222 | }; 1223 | 1224 | 1225 | /** 1226 | * Updates the newAttrs object for an Element. 1227 | * 1228 | * This function is called in the context of the Element and the arguments from 1229 | * elementOpen-like function so that the arguments are not de-optimized. 1230 | * 1231 | * @this {Element} The Element to update newAttrs for. 1232 | * @param {*} unused1 1233 | * @param {*} unused2 1234 | * @param {*} unused3 1235 | * @param {...*} var_args Attribute name/value pairs of the dynamic attributes 1236 | * for the Element. 1237 | * @return {!Object} The updated newAttrs object. 1238 | */ 1239 | var updateNewAttrs = function(unused1, unused2, unused3, var_args) { 1240 | var node = this; 1241 | var data = getData(node); 1242 | var newAttrs = data.newAttrs; 1243 | 1244 | for (var attr in newAttrs) { 1245 | newAttrs[attr] = undefined; 1246 | } 1247 | 1248 | for (var i = ATTRIBUTES_OFFSET; i < arguments.length; i += 2) { 1249 | newAttrs[arguments[i]] = arguments[i + 1]; 1250 | } 1251 | 1252 | return newAttrs; 1253 | }; 1254 | 1255 | 1256 | /** 1257 | * Updates the attributes for a given Element. 1258 | * @param {!Element} node 1259 | * @param {!Object} newAttrs The new attributes for node 1260 | */ 1261 | var updateAttributes = function(node, newAttrs) { 1262 | for (var attr in newAttrs) { 1263 | updateAttribute(node, attr, newAttrs[attr]); 1264 | } 1265 | }; 1266 | 1267 | 1268 | /** 1269 | * Declares a virtual Element at the current location in the document. This 1270 | * corresponds to an opening tag and a elementClose tag is required. 1271 | * @param {string} tag The element's tag. 1272 | * @param {?string} key The key used to identify this element. This can be an 1273 | * empty string, but performance may be better if a unique value is used 1274 | * when iterating over an array of items. 1275 | * @param {?Array<*>} statics An array of attribute name/value pairs of the 1276 | * static attributes for the Element. These will only be set once when the 1277 | * Element is created. 1278 | * @param {...*} var_args Attribute name/value pairs of the dynamic attributes 1279 | * for the Element. 1280 | */ 1281 | var elementOpen = function(tag, key, statics, var_args) { 1282 | if (process.env.NODE_ENV !== 'production') { 1283 | assertNotInAttributes(); 1284 | } 1285 | 1286 | var node = alignWithDOM(tag, key, statics); 1287 | 1288 | if (hasChangedAttrs.apply(node, arguments)) { 1289 | var newAttrs = updateNewAttrs.apply(node, arguments); 1290 | updateAttributes(node, newAttrs); 1291 | } 1292 | 1293 | firstChild(); 1294 | }; 1295 | 1296 | 1297 | /** 1298 | * Declares a virtual Element at the current location in the document. This 1299 | * corresponds to an opening tag and a elementClose tag is required. This is 1300 | * like elementOpen, but the attributes are defined using the attr function 1301 | * rather than being passed as arguments. Must be folllowed by 0 or more calls 1302 | * to attr, then a call to elementOpenEnd. 1303 | * @param {string} tag The element's tag. 1304 | * @param {?string} key The key used to identify this element. This can be an 1305 | * empty string, but performance may be better if a unique value is used 1306 | * when iterating over an array of items. 1307 | * @param {?Array<*>} statics An array of attribute name/value pairs of the 1308 | * static attributes for the Element. These will only be set once when the 1309 | * Element is created. 1310 | */ 1311 | var elementOpenStart = function(tag, key, statics) { 1312 | if (process.env.NODE_ENV !== 'production') { 1313 | assertNotInAttributes(); 1314 | setInAttributes(); 1315 | } 1316 | 1317 | argsBuilder[0] = tag; 1318 | argsBuilder[1] = key; 1319 | argsBuilder[2] = statics; 1320 | argsBuilder.length = ATTRIBUTES_OFFSET; 1321 | }; 1322 | 1323 | 1324 | /*** 1325 | * Defines a virtual attribute at this point of the DOM. This is only valid 1326 | * when called between elementOpenStart and elementOpenEnd. 1327 | * 1328 | * @param {string} name 1329 | * @param {*} value 1330 | */ 1331 | var attr = function(name, value) { 1332 | if (process.env.NODE_ENV !== 'production') { 1333 | assertInAttributes(); 1334 | } 1335 | 1336 | argsBuilder.push(name, value); 1337 | }; 1338 | 1339 | 1340 | /** 1341 | * Closes an open tag started with elementOpenStart. 1342 | */ 1343 | var elementOpenEnd = function() { 1344 | if (process.env.NODE_ENV !== 'production') { 1345 | assertInAttributes(); 1346 | setNotInAttributes(); 1347 | } 1348 | 1349 | elementOpen.apply(null, argsBuilder); 1350 | }; 1351 | 1352 | 1353 | /** 1354 | * Closes an open virtual Element. 1355 | * 1356 | * @param {string} tag The element's tag. 1357 | */ 1358 | var elementClose = function(tag) { 1359 | if (process.env.NODE_ENV !== 'production') { 1360 | assertNotInAttributes(); 1361 | } 1362 | 1363 | parentNode(); 1364 | nextSibling(); 1365 | }; 1366 | 1367 | 1368 | /** 1369 | * Declares a virtual Element at the current location in the document that has 1370 | * no children. 1371 | * @param {string} tag The element's tag. 1372 | * @param {?string} key The key used to identify this element. This can be an 1373 | * empty string, but performance may be better if a unique value is used 1374 | * when iterating over an array of items. 1375 | * @param {?Array<*>} statics An array of attribute name/value pairs of the 1376 | * static attributes for the Element. These will only be set once when the 1377 | * Element is created. 1378 | * @param {...*} var_args Attribute name/value pairs of the dynamic attributes 1379 | * for the Element. 1380 | */ 1381 | var elementVoid = function(tag, key, statics, var_args) { 1382 | if (process.env.NODE_ENV !== 'production') { 1383 | assertNotInAttributes(); 1384 | } 1385 | 1386 | elementOpen.apply(null, arguments); 1387 | elementClose.apply(null, arguments); 1388 | }; 1389 | 1390 | 1391 | /** 1392 | * Declares a virtual Text at this point in the document. 1393 | * 1394 | * @param {string} value The text of the Text. 1395 | */ 1396 | var text = function(value) { 1397 | if (process.env.NODE_ENV !== 'production') { 1398 | assertNotInAttributes(); 1399 | } 1400 | 1401 | var node = alignWithDOM('#text', null, value); 1402 | var data = getData(node); 1403 | 1404 | if (data.text !== value) { 1405 | node.data = value; 1406 | data.text = value; 1407 | } 1408 | 1409 | nextSibling(); 1410 | }; 1411 | 1412 | 1413 | /** */ 1414 | module.exports = { 1415 | elementOpenStart: elementOpenStart, 1416 | elementOpenEnd: elementOpenEnd, 1417 | elementOpen: elementOpen, 1418 | elementVoid: elementVoid, 1419 | elementClose: elementClose, 1420 | text: text, 1421 | attr: attr 1422 | }; 1423 | 1424 | 1425 | }).call(this,require('_process')) 1426 | },{"./alignment":8,"./attributes":9,"./node_data":10,"./traversal":13,"./walker":16,"_process":1}],16:[function(require,module,exports){ 1427 | /** 1428 | * Copyright 2015 The Incremental DOM Authors. All Rights Reserved. 1429 | * 1430 | * Licensed under the Apache License, Version 2.0 (the "License"); 1431 | * you may not use this file except in compliance with the License. 1432 | * You may obtain a copy of the License at 1433 | * 1434 | * http://www.apache.org/licenses/LICENSE-2.0 1435 | * 1436 | * Unless required by applicable law or agreed to in writing, software 1437 | * distributed under the License is distributed on an "AS-IS" BASIS, 1438 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1439 | * See the License for the specific language governing permissions and 1440 | * limitations under the License. 1441 | */ 1442 | 1443 | /** 1444 | * @type {TreeWalker} 1445 | */ 1446 | var walker_; 1447 | 1448 | 1449 | /** 1450 | * @return {TreeWalker} the current TreeWalker 1451 | */ 1452 | var getWalker = function() { 1453 | return walker_; 1454 | }; 1455 | 1456 | 1457 | /** 1458 | * Sets the current TreeWalker 1459 | * @param {TreeWalker} walker 1460 | */ 1461 | var setWalker = function(walker) { 1462 | walker_ = walker; 1463 | }; 1464 | 1465 | 1466 | /** */ 1467 | module.exports = { 1468 | getWalker: getWalker, 1469 | setWalker: setWalker 1470 | }; 1471 | 1472 | 1473 | },{}],17:[function(require,module,exports){ 1474 | var html2idom = require("../light"); 1475 | 1476 | html2idom.patchHTML(document.querySelector("#test"), "

Hello, Incremental DOM

"); 1477 | },{"../light":2}]},{},[17]); 1478 | -------------------------------------------------------------------------------- /test/test-light.js: -------------------------------------------------------------------------------- 1 | var html2idom = require("../light"); 2 | 3 | html2idom.patchHTML(document.querySelector("#test"), "

Hello, Incremental DOM

"); -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test HTML To Incremental DOM 6 | 11 | 12 | 13 |
14 |

Hello, world

15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var html2idom = require("../index"); 2 | 3 | html2idom.patchHTML(document.querySelector("#test"), "

Hello, Incremental DOM

"); -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | core-util-is@~1.0.0: 6 | version "1.0.2" 7 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 8 | 9 | dom-serializer@0: 10 | version "0.1.0" 11 | resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" 12 | dependencies: 13 | domelementtype "~1.1.1" 14 | entities "~1.1.1" 15 | 16 | domelementtype@1, domelementtype@^1.3.0: 17 | version "1.3.0" 18 | resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" 19 | 20 | domelementtype@~1.1.1: 21 | version "1.1.3" 22 | resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" 23 | 24 | domhandler@^2.3.0: 25 | version "2.4.1" 26 | resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259" 27 | dependencies: 28 | domelementtype "1" 29 | 30 | domutils@^1.5.1: 31 | version "1.6.2" 32 | resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.6.2.tgz#1958cc0b4c9426e9ed367fb1c8e854891b0fa3ff" 33 | dependencies: 34 | dom-serializer "0" 35 | domelementtype "1" 36 | 37 | entities@^1.1.1, entities@~1.1.1: 38 | version "1.1.1" 39 | resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" 40 | 41 | html-parse-stringify@^1.0.2: 42 | version "1.0.2" 43 | resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-1.0.2.tgz#1208e98cd7430ad7efb12e1e7acee9214fd22283" 44 | dependencies: 45 | void-elements "^1.0.0" 46 | 47 | htmlparser2@^3.9.2: 48 | version "3.9.2" 49 | resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" 50 | dependencies: 51 | domelementtype "^1.3.0" 52 | domhandler "^2.3.0" 53 | domutils "^1.5.1" 54 | entities "^1.1.1" 55 | inherits "^2.0.1" 56 | readable-stream "^2.0.2" 57 | 58 | incremental-dom@^0.5.1: 59 | version "0.5.1" 60 | resolved "https://registry.yarnpkg.com/incremental-dom/-/incremental-dom-0.5.1.tgz#52f4539c3e9eee7cd112a6da052fb7162fefb0c3" 61 | 62 | inherits@^2.0.1, inherits@~2.0.3: 63 | version "2.0.3" 64 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 65 | 66 | isarray@~1.0.0: 67 | version "1.0.0" 68 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 69 | 70 | process-nextick-args@~1.0.6: 71 | version "1.0.7" 72 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" 73 | 74 | readable-stream@^2.0.2: 75 | version "2.3.3" 76 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" 77 | dependencies: 78 | core-util-is "~1.0.0" 79 | inherits "~2.0.3" 80 | isarray "~1.0.0" 81 | process-nextick-args "~1.0.6" 82 | safe-buffer "~5.1.1" 83 | string_decoder "~1.0.3" 84 | util-deprecate "~1.0.1" 85 | 86 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 87 | version "5.1.1" 88 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 89 | 90 | string_decoder@~1.0.3: 91 | version "1.0.3" 92 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" 93 | dependencies: 94 | safe-buffer "~5.1.0" 95 | 96 | util-deprecate@~1.0.1: 97 | version "1.0.2" 98 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 99 | 100 | void-elements@^1.0.0: 101 | version "1.0.0" 102 | resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-1.0.0.tgz#6e5db1e35d591f5ac690ce1a340f793a817b2c2a" 103 | --------------------------------------------------------------------------------