diff --git a/CHANGELOG.md b/CHANGELOG.md index 066519c..ff7e98e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog +### 2.0.2 (2017-02-25) +- Fix: IE11 compatibility problems. +- Fix: Windows scripting compatibility. + + ### 2.0.1 (2017-01-09) - Polish: include `dist/*.js` in package. diff --git a/README.md b/README.md index c6bbefd..a21648a 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,6 @@ When a bound key is assigned, it gets internally casted into an array if it is n This library makes use of these JavaScript features: - **Object.defineProperty** (ES5): used for binding keys on objects. -- **Object.freeze** (ES5.1): used to prevent internal state from being mutated. - **WeakMap** (ES6): memory efficient mapping of DOM nodes. It also makes use of these DOM API features: diff --git a/lib/bind_keys.js b/lib/bind_keys.js index 995165e..29193b9 100644 --- a/lib/bind_keys.js +++ b/lib/bind_keys.js @@ -3,7 +3,7 @@ var processNodes = require('./process_nodes') var keyMap = require('./key_map') -var markerMap = processNodes.markerMap +var markerKey = keyMap.marker var hasDefinitionKey = keyMap.hasDefinition var isBoundToParentKey = keyMap.isBoundToParent var replaceAttributeKey = keyMap.replaceAttribute @@ -69,7 +69,8 @@ function bindKeys (scope, obj, def, parentNode, path) { } -// This is an internal function, the arguments aren't pretty. +// This is an internal function that's used for defining the getters and +// setters. function bindKey (scope, obj, def, key, parentNode) { var memo = storeMemo.get(obj) var meta = storeMeta.get(obj)[key] @@ -123,7 +124,7 @@ function bindKey (scope, obj, def, key, parentNode) { } function setter (x) { - var marker = markerMap.get(branch) + var marker = branch[markerKey] var value, currentNode var a, b, i, j @@ -146,7 +147,7 @@ function bindKey (scope, obj, def, key, parentNode) { if (value.length !== previousValues.length) previousValues.length = activeNodes.length = value.length - // Assign array mutator methods. + // Assign array mutator methods if we get an array. if (valueIsArray) { // Some mutators such as `sort`, `reverse`, `fill`, `copyWithin` are // not present here. That is because they trigger the array index @@ -176,7 +177,7 @@ function bindKey (scope, obj, def, key, parentNode) { Object.defineProperty(array, i, { get: function () { return value }, set: function (x) { - var marker = markerMap.get(branch) + var marker = branch[markerKey] var a, b, currentNode value = x @@ -195,7 +196,7 @@ function bindKey (scope, obj, def, key, parentNode) { } function removeNode (value, previousValue, i) { - var marker = markerMap.get(branch) + var marker = branch[markerKey] var activeNode = activeNodes[i] var returnValue @@ -231,7 +232,7 @@ function bindKey (scope, obj, def, key, parentNode) { if (value === void 0) value = null if (previousValue === void 0) previousValue = null - // If value is null, just remove it. + // If value is null, just remove the Node. if (value === null) { removeNode(null, previousValue, i) return null @@ -299,7 +300,7 @@ function bindKey (scope, obj, def, key, parentNode) { } function push () { - var marker = markerMap.get(branch) + var marker = branch[markerKey] var i = this.length var j = i + arguments.length var currentNode @@ -327,7 +328,7 @@ function bindKey (scope, obj, def, key, parentNode) { } function unshift () { - var marker = markerMap.get(branch) + var marker = branch[markerKey] var i = this.length var j, k, currentNode @@ -350,7 +351,7 @@ function bindKey (scope, obj, def, key, parentNode) { } function splice (start, count) { - var marker = markerMap.get(branch) + var marker = branch[markerKey] var insert = [] var i, j, k, value, currentNode diff --git a/lib/feature_check.js b/lib/feature_check.js index 070ea74..0d8424c 100644 --- a/lib/feature_check.js +++ b/lib/feature_check.js @@ -1,29 +1,13 @@ 'use strict' - module.exports = featureCheck - /** * Check if capabilities are available, or throw an error. * * @param {*} globalScope */ -function featureCheck (globalScope) { - var features = [ - // ECMAScript features. - [ Object, 'defineProperty' ], - [ Object, 'freeze' ], - [ Object, 'isFrozen' ], - [ WeakMap ], - - // DOM features. - [ 'document', 'createTreeWalker' ], - [ 'Node', 'prototype', 'contains' ], - [ 'Node', 'prototype', 'insertBefore' ], - [ 'Node', 'prototype', 'isEqualNode' ], - [ 'Node', 'prototype', 'removeChild' ] - ] +function featureCheck (globalScope, features) { var i, j, k, l, feature, path for (i = 0, j = features.length; i < j; i++) { diff --git a/lib/helpers.js b/lib/helpers.js index 0f763e3..f3a41e8 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -20,13 +20,13 @@ function bindEvents (events, useCapture) { return function (node, value, previousValue, path) { var key - if (value == null) + if (value === null) for (key in events) // The point of removing event listeners here is not manual memory // management, but to ensure that after the value has been unset, it // no longer triggers events. node.removeEventListener(key, listeners[key], useCapture) - else if (previousValue == null) + else if (previousValue === null) for (key in events) { listeners[key] = makeEventListener(events[key], path) node.addEventListener(key, listeners[key], useCapture) @@ -47,7 +47,7 @@ function animate (insertClass, mutateClass, removeClass, retainTime) { if (!('classList' in node)) return void 0 - if (value == null) { + if (value === null) { if (insertClass) node.classList.remove(insertClass) if (removeClass) node.classList.add(removeClass) if (retainTime) { @@ -68,7 +68,7 @@ function animate (insertClass, mutateClass, removeClass, retainTime) { node.classList.add(mutateClass) } - else if (previousValue == null && insertClass) + else if (previousValue === null && insertClass) // Trigger class addition after the element is inserted. if (hasMutationObserver && hasDocument && !document.documentElement.contains(node)) { diff --git a/lib/index.js b/lib/index.js index 3e2d91d..cf4e0fe 100644 --- a/lib/index.js +++ b/lib/index.js @@ -13,6 +13,7 @@ var hasDefinitionKey = keyMap.hasDefinition var replaceAttributeKey = keyMap.replaceAttribute var isBoundToParentKey = keyMap.isBoundToParent var isProcessedKey = keyMap.isProcessed +var markerKey = keyMap.marker // Element tag names which should have value replaced. var replaceValue = [ 'INPUT', 'TEXTAREA', 'PROGRESS' ] @@ -53,7 +54,20 @@ function simulacra (obj, def, matchNode) { var Node = this ? this.Node : window.Node var node, query - featureCheck(this || window) + // Before continuing, check if required features are present. + featureCheck(this || window, [ + // ECMAScript features. + [ Object, 'defineProperty' ], + [ WeakMap ], + + // DOM features. Missing `contains` since apparently it is not on + // the Node.prototype in Internet Explorer. + [ 'document', 'createTreeWalker' ], + [ 'Node', 'prototype', 'cloneNode' ], + [ 'Node', 'prototype', 'insertBefore' ], + [ 'Node', 'prototype', 'isEqualNode' ], + [ 'Node', 'prototype', 'removeChild' ] + ]) if (obj === null || typeof obj !== 'object' || isArray(obj)) throw new TypeError('First argument must be a singular object.') @@ -76,7 +90,7 @@ function simulacra (obj, def, matchNode) { if ('content' in def[0]) def[0] = def[0].content ensureNodes(this, def[0], def[1]) - setFrozen(def) + setProperties(def) } node = processNodes(this, def[0], def[1]) @@ -168,7 +182,7 @@ function ensureNodes (scope, parentNode, def) { setReplaceAttribute(branch, boundNode) else console.warn( // eslint-disable-line 'A change function was not defined on the key "' + key + '".') - setFrozen(branch) + setProperties(branch) continue } @@ -180,12 +194,12 @@ function ensureNodes (scope, parentNode, def) { if (branch[hasDefinitionKey]) { ensureNodes(scope, boundNode, branch[1]) - setFrozen(branch) + setProperties(branch) continue } setReplaceAttribute(branch, boundNode) - setFrozen(branch) + setProperties(branch) } // Need to loop again to invalidate containment in adjacent nodes, after the @@ -200,8 +214,7 @@ function ensureNodes (scope, parentNode, def) { 'element for the adjacent key "' + adjacentNodes[i][0] + '".') } - // Freeze the definition. - setFrozen(def) + setProperties(def) } @@ -214,7 +227,7 @@ function setReplaceAttribute (branch, boundNode) { } -function setFrozen (obj) { +function setProperties (obj) { Object.defineProperty(obj, isProcessedKey, { value: true }) - Object.freeze(obj) + Object.defineProperty(obj, markerKey, { value: null, writable: true }) } diff --git a/lib/key_map.js b/lib/key_map.js index 5775fa0..a4e420e 100644 --- a/lib/key_map.js +++ b/lib/key_map.js @@ -4,6 +4,7 @@ var keys = [ 'hasDefinition', 'isBoundToParent', 'isProcessed', + 'marker', 'replaceAttribute', 'retainElement' ] diff --git a/lib/process_nodes.js b/lib/process_nodes.js index e268fb3..229af0d 100644 --- a/lib/process_nodes.js +++ b/lib/process_nodes.js @@ -4,17 +4,19 @@ var keyMap = require('./key_map') var isBoundToParentKey = keyMap.isBoundToParent - -// Map from definition branches to marker nodes. This is necessary because the -// definitions are frozen and cannot be written to. -var markerMap = processNodes.markerMap = new WeakMap() +var markerKey = keyMap.marker // Internal map from already processed definitions to ready-to-use nodes. var templateMap = new WeakMap() +// A fixed constant for `NodeFilter.SHOW_ALL`. +var whatToShow = 0xFFFFFFFF + // Option to use comment nodes as markers. processNodes.useCommentNode = false +// Avoiding duplication of compatibility hack. +processNodes.acceptNode = acceptNode module.exports = processNodes @@ -57,7 +59,7 @@ function processNodes (scope, node, def) { else marker = parent.insertBefore( document.createTextNode(''), mirrorNode) - markerMap.set(branch, marker) + branch[markerKey] = marker parent.removeChild(mirrorNode) } @@ -73,7 +75,8 @@ function processNodes (scope, node, def) { i = 0 j = 0 - treeWalker = document.createTreeWalker(node) + treeWalker = document.createTreeWalker( + node, whatToShow, acceptNode, false) for (key in def) { branch = def[key] @@ -81,7 +84,7 @@ function processNodes (scope, node, def) { while (treeWalker.nextNode()) { if (i === indices[j]) { - markerMap.set(branch, treeWalker.currentNode) + branch[markerKey] = treeWalker.currentNode i++ break } @@ -106,7 +109,8 @@ function processNodes (scope, node, def) { */ function matchNodes (scope, node, def) { var document = scope ? scope.document : window.document - var treeWalker = document.createTreeWalker(node) + var treeWalker = document.createTreeWalker( + node, whatToShow, acceptNode, false) var map = new WeakMap() var nodes = [] var i, j, key, currentNode, childWalker @@ -128,7 +132,8 @@ function matchNodes (scope, node, def) { node: treeWalker.currentNode }) if (processNodes.useCommentNode) offset++ - childWalker = document.createTreeWalker(currentNode) + childWalker = document.createTreeWalker( + currentNode, whatToShow, acceptNode, false) while (childWalker.nextNode()) offset-- nodes.splice(i, 1) break @@ -140,3 +145,8 @@ function matchNodes (scope, node, def) { return map } + + +// A crazy Internet Explorer workaround. +function acceptNode () { return 1 } +acceptNode.acceptNode = acceptNode diff --git a/lib/rehydrate.js b/lib/rehydrate.js index c8e1f7a..a815b47 100644 --- a/lib/rehydrate.js +++ b/lib/rehydrate.js @@ -6,9 +6,12 @@ var keyMap = require('./key_map') var hasDefinitionKey = keyMap.hasDefinition var isBoundToParentKey = keyMap.isBoundToParent -var markerMap = processNodes.markerMap +var markerKey = keyMap.marker +var acceptNode = processNodes.acceptNode var storeMeta = bindKeys.storeMeta +// A fixed constant for `NodeFilter.SHOW_ELEMENT`. +var whatToShow = 0x00000001 module.exports = rehydrate @@ -24,7 +27,6 @@ module.exports = rehydrate */ function rehydrate (scope, obj, def, node, matchNode) { var document = scope ? scope.document : window.document - var NodeFilter = scope ? scope.NodeFilter : window.NodeFilter var key, branch, x, value, change, definition, mount, keyPath var meta, valueIsArray, activeNodes, index, treeWalker, currentNode @@ -54,10 +56,12 @@ function rehydrate (scope, obj, def, node, matchNode) { valueIsArray = meta.valueIsArray x = valueIsArray ? obj[key] : [ obj[key] ] index = 0 - treeWalker = document.createTreeWalker(matchNode, NodeFilter.SHOW_ELEMENT) + treeWalker = document.createTreeWalker( + matchNode, whatToShow, acceptNode, false) while (index < activeNodes.length && treeWalker.nextNode()) { currentNode = activeNodes[index] + if (treeWalker.currentNode.isEqualNode(currentNode)) { activeNodes.splice(index, 1, treeWalker.currentNode) @@ -83,13 +87,14 @@ function rehydrate (scope, obj, def, node, matchNode) { } if (index !== activeNodes.length) throw new Error( - 'Matching nodes could not be found on key "' + key + '".') + 'Matching nodes could not be found on key "' + key + '", expected ' + + activeNodes.length + ', found ' + index + '.') // Rehydrate marker node. currentNode = treeWalker.currentNode // Ignore comment node setting, comment may already exist. - markerMap.set(branch, currentNode.parentNode.insertBefore( - document.createTextNode(''), currentNode.nextSibling)) + branch[markerKey] = currentNode.parentNode.insertBefore( + document.createTextNode(''), currentNode.nextSibling) } } diff --git a/package.json b/package.json index c7adde6..f5a42e0 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "simulacra", "description": "Reactive data binding for the web.", - "version": "2.0.1", + "version": "2.0.2", "license": "MIT", "author": { "name": "daliwali", - "email": "daliwali@airmail.cc", - "url": "http://daliwali.com" + "email": "d@liwa.li", + "url": "http://daliwa.li" }, "homepage": "http://simulacra.js.org/", "repository": { @@ -18,8 +18,8 @@ "clean": "rimraf coverage.json coverage dist", "deploy": "./website/deploy.sh", "build": "mkdirp dist && npm run build:dist && npm run build:min && npm run build:web", - "build:dist": "(node util/header; browserify util/global.js) > dist/simulacra.js", - "build:min": "(node util/header; uglifyjs -cm -- dist/simulacra.js) > dist/simulacra.min.js", + "build:dist": "(node util/header.js && browserify util/global.js) > dist/simulacra.js", + "build:min": "(node util/header.js && uglifyjs -cm -- dist/simulacra.js) > dist/simulacra.min.js", "build:web": "node website/build", "postpublish": "npm run deploy && npm run tag", "prepublish": "npm run build", diff --git a/website/example.html b/website/example.html index 7996019..f82944d 100644 --- a/website/example.html +++ b/website/example.html @@ -16,13 +16,13 @@ - +