diff --git a/mithril.js b/mithril.js index fa996e31..a80cd2f9 100644 --- a/mithril.js +++ b/mithril.js @@ -13,7 +13,9 @@ })(this, function (window, undefined) { // eslint-disable-line "use strict" - var VERSION = "v0.2.1" + m.version = function () { + return "v0.2.1" + } // Save these two. var type = {}.toString @@ -37,22 +39,20 @@ function noop() {} - function forEach(list, f, inst) { + function forEach(list, f) { for (var i = 0; i < list.length; i++) { - f.call(inst, list[i], i) + f(list[i], i) } } - function forOwn(obj, f, inst) { + function forOwn(obj, f) { for (var prop in obj) { if (hasOwn.call(obj, prop)) { - if (f.call(inst, obj[prop], prop)) break + f(obj[prop], prop) } } } - var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/ // eslint-disable-line max-len - // caching commonly used variables var $document, $location, $requestAnimationFrame, $cancelAnimationFrame @@ -74,8 +74,69 @@ return window } - m.version = function () { - return VERSION + function gettersetter(store) { + function prop() { + if (arguments.length) store = arguments[0] + return store + } + + prop.toJSON = function () { + return store + } + + return prop + } + + function isPromise(object) { + return object != null && (isObject(object) || isFunction(object)) && + isFunction(object.then) + } + + function simpleResolve(p, callback) { + if (p.then) { + return p.then(callback) + } else { + return callback() + } + } + + function propify(promise) { + var prop = m.prop() + promise.then(prop) + + prop.then = function (resolve, reject) { + return promise.then(function () { + return resolve(prop()) + }, reject) + } + + prop.catch = function (reject) { + return promise.then(function () { + return prop() + }, reject) + } + + prop.finally = function (callback) { + return promise.then(function (value) { + return simpleResolve(callback(), function () { + return value + }) + }, function (reason) { + return simpleResolve(callback(), function () { + throw reason + }) + }) + } + + return prop + } + + m.prop = function (store) { + if (isPromise(store)) { + return propify(store) + } else { + return gettersetter(store) + } } /** @@ -220,11 +281,11 @@ return cell } - function forKeys(list, f, inst) { + function forKeys(list, f) { for (var i = 0; i < list.length; i++) { var attrs = list[i] attrs = attrs && attrs.attrs - if (attrs && attrs.key != null && f.call(inst, attrs, i)) { + if (attrs && attrs.key != null && f(attrs, i)) { break } } @@ -282,30 +343,29 @@ // `cached` is *always* a non-primitive object, i.e. if the data was // a string, then cached is a String instance. If data was `null` or // `undefined`, cached is `new String("")` - // - `cached also has a `configContext` property, which is the state + // - `cached also has a `cfgCtx` property, which is the state // storage object exposed by config(element, isInitialized, context) // - when `cached` is an Object, it represents a virtual element; when // it's an Array, it represents a list of elements; when it's a // String, Number or Boolean, it represents a text node // - // `parentElement` is a DOM element used for W3C DOM API calls - // `parentTag` is only used for handling a corner case for textarea - // values - // `parentCache` is used to remove nodes in some multi-node cases - // `parentIndex` and `index` are used to figure out the offset of nodes. + // `parent` is a DOM element used for W3C DOM API calls + // `pTag` is only used for handling a corner case for textarea values + // `pCache` is used to remove nodes in some multi-node cases + // `pIndex` and `index` are used to figure out the offset of nodes. // They're artifacts from before arrays started being flattened and are // likely refactorable // `data` and `cached` are, respectively, the new and old nodes being // diffed - // `shouldReattach` is a flag indicating whether a parent node was - // recreated (if so, and if this node is reused, then this node must - // reattach itself to the new parent) + // `reattach` is a flag indicating whether a parent node was recreated (if + // so, and if this node is reused, then this node must reattach itself to + // the new parent) // `editable` is a flag that indicates whether an ancestor is // contenteditable - // `namespace` indicates the closest HTML namespace as it cascades down - // from an ancestor - // `configs` is a list of config functions to run after the topmost - // `build` call finishes running + // `ns` indicates the closest HTML namespace as it cascades down from an + // ancestor + // `cfgs` is a list of config functions to run after the topmost build call + // finishes running // // there's logic that relies on the assumption that null and undefined // data are equivalent to empty strings @@ -314,7 +374,7 @@ // function foo() {if (cond) return m("div")} // - it simplifies diffing code - function Builder( + function buildContext( parentElement, parentTag, parentCache, @@ -327,59 +387,61 @@ namespace, configs ) { - this.parentElement = parentElement - this.parentTag = parentTag - this.parentCache = parentCache - this.parentIndex = parentIndex - this.data = data - this.cached = cached - this.shouldReattach = shouldReattach - this.index = index - this.editable = editable - this.namespace = namespace - this.configs = configs - } - - Builder.prototype.build = function () { - this.data = dataToString(this.data) - if (this.data.subtree === "retain") return this.cached - this.makeCache() - - if (isArray(this.data)) { - return this.buildArray() - } else if (this.data != null && isObject(this.data)) { - return this.buildObject() - } else if (isFunction(this.data)) { - return this.cached - } else { - return this.handleTextNode() + return { + parent: parentElement, + pTag: parentTag, + pCache: parentCache, + pIndex: parentIndex, + data: data, + cached: cached, + reattach: shouldReattach, + index: index, + editable: editable, + ns: namespace, + cfgs: configs } } - Builder.prototype.makeCache = function () { - if (this.cached != null) { - if (type.call(this.cached) === type.call(this.data)) { + function builderBuild(inst) { + inst.data = dataToString(inst.data) + if (inst.data.subtree === "retain") return inst.cached + builderMakeCache(inst) + + if (isArray(inst.data)) { + return builderBuildArray(inst) + } else if (inst.data != null && isObject(inst.data)) { + return builderBuildObject(inst) + } else if (isFunction(inst.data)) { + return inst.cached + } else { + return builderHandleTextNode(inst) + } + } + + function builderMakeCache(inst) { + if (inst.cached != null) { + if (type.call(inst.cached) === type.call(inst.data)) { return } - if (this.parentCache && this.parentCache.nodes) { - var offset = this.index - this.parentIndex + if (inst.pCache && inst.pCache.nodes) { + var offset = inst.index - inst.pIndex var end = offset + - (isArray(this.data) ? this.data : this.cached.nodes).length + (isArray(inst.data) ? inst.data : inst.cached.nodes).length clear( - this.parentCache.nodes.slice(offset, end), - this.parentCache.slice(offset, end)) - } else if (this.cached.nodes) { - clear(this.cached.nodes, this.cached) + inst.pCache.nodes.slice(offset, end), + inst.pCache.slice(offset, end)) + } else if (inst.cached.nodes) { + clear(inst.cached.nodes, inst.cached) } } - this.cached = new this.data.constructor() + inst.cached = new inst.data.constructor() // if constructor creates a virtual dom element, use a blank object as // the base cached node instead of copying the virtual el (#277) - if (this.cached.tag) this.cached = {} - this.cached.nodes = [] + if (inst.cached.tag) inst.cached = {} + inst.cached.nodes = [] } var DELETION = 1 @@ -399,26 +461,29 @@ }) } - Builder.prototype.buildArrayChild = function (child, cached, count) { - return new Builder( - this.parentElement, - this.parentTag, - this.cached, - this.index, + function builderBuildArrayChild(inst, child, cached, count) { + return builderBuild(buildContext( + inst.parent, + inst.pTag, + inst.cached, + inst.index, child, cached, - this.shouldReattach, - this.index + count || count, - this.editable, - this.namespace, - this.configs - ).build() + inst.reattach, + inst.index + count || count, + inst.editable, + inst.ns, + inst.cfgs + )) } - Builder.prototype.buildArray = function () { - this.data = flatten(this.data) + // This is by far the most performance-sensitive method here. If you make + // any changes, be careful to avoid performance regressions. Note that + // variable caching doesn't help, even in the loop. + function builderBuildArray(inst) { // eslint-disable-line max-statements + inst.data = flatten(inst.data) var nodes = [] - var intact = this.cached.length === this.data.length + var intact = inst.cached.length === inst.data.length var subArrayCount = 0 // keys algorithm: @@ -431,7 +496,7 @@ // previous steps var existing = {} var shouldMaintainIdentities = false - forKeys(this.cached, function (attrs, i) { + forKeys(inst.cached, function (attrs, i) { shouldMaintainIdentities = true existing[attrs.key] = { action: DELETION, @@ -439,65 +504,62 @@ } }) - buildArrayKeys(this.data) + buildArrayKeys(inst.data) if (shouldMaintainIdentities) { - this.diffKeys(existing) + builderDiffKeys(inst, existing) } // end key algorithm // don't change: faster than forEach var cacheCount = 0 - for (var i = 0, len = this.data.length; i < len; i++) { + for (var i = 0, len = inst.data.length; i < len; i++) { // diff each item in the array - var item = this.buildArrayChild( - this.data[i], - this.cached[cacheCount], + var item = builderBuildArrayChild( + inst, + inst.data[i], + inst.cached[cacheCount], subArrayCount ) if (item !== undefined) { intact = intact && item.nodes.intact subArrayCount += getSubArrayCount(item) - this.cached[cacheCount++] = item + inst.cached[cacheCount++] = item } } - if (!intact) this.diffArray(nodes) + if (!intact) builderDiffArray(inst, nodes) - return this.cached + return inst.cached } - Builder.prototype.diffKeys = function (existing) { - var keysDiffer = this.data.length !== this.cached.length + function builderDiffKeys(inst, existing) { + var keysDiffer = inst.data.length !== inst.cached.length if (!keysDiffer) { - forKeys(this.data, function (attrs, i) { - var cachedCell = this[i] // eslint-disable-line no-invalid-this + forKeys(inst.data, function (attrs, i) { + var cachedCell = inst.cached[i] return keysDiffer = cachedCell && cachedCell.attrs && cachedCell.attrs.key !== attrs.key - }, this.cached) + }) } if (keysDiffer) { - this.handleKeysDiffer(existing) + builderHandleKeysDiffer(inst, existing) } } - // Simple `this` helper - function thisPush(value) { - this.push(value) // eslint-disable-line no-invalid-this - } - - Builder.prototype.handleKeysDiffer = function (existing) { - forKeys(this.data, function (key, i) { + function builderHandleKeysDiffer(inst, existing) { + var cached = inst.cached.nodes + forKeys(inst.data, function (key, i) { key = key.key if (existing[key]) { existing[key] = { action: MOVE, index: i, from: existing[key].index, - element: this[existing[key].index] || // eslint-disable-line + element: cached[existing[key].index] || $document.createElement("div") } } else { @@ -506,31 +568,32 @@ index: i } } - }, this.cached.nodes) + }) var actions = [] - forOwn(existing, thisPush, actions) + forOwn(existing, function (value) { + actions.push(value) + }) var changes = actions.sort(sortChanges) - var newCached = new Array(this.cached.length) - newCached.nodes = this.cached.nodes.slice() + var newCached = new Array(inst.cached.length) + newCached.nodes = inst.cached.nodes.slice() forEach(changes, function (change) { - /* eslint-disable no-invalid-this */ var index = change.index switch (change.action) { case DELETION: - clear(this.cached[index].nodes, this.cached[index]) + clear(inst.cached[index].nodes, inst.cached[index]) newCached.splice(index, 1) break case INSERTION: var dummy = $document.createElement("div") - dummy.key = this.data[index].attrs.key - insertNode(this.parentElement, dummy, index) + dummy.key = inst.data[index].attrs.key + insertNode(inst.parent, dummy, index) newCached.splice(index, 0, { - attrs: {key: this.data[index].attrs.key}, + attrs: {key: inst.data[index].attrs.key}, nodes: [dummy] }) newCached.nodes[index] = dummy @@ -538,29 +601,28 @@ case MOVE: var changeElement = change.element - var maybeChanged = this.parentElement.childNodes[index] - if (maybeChanged !== changeElement && changeElement !== null) { - this.parentElement.insertBefore( + if (inst.parent.childNodes[index] !== changeElement && + changeElement !== null) { + inst.parent.insertBefore( changeElement, - maybeChanged || null + inst.parent.childNodes[index] || null ) } - newCached[index] = this.cached[change.from] + newCached[index] = inst.cached[change.from] newCached.nodes[index] = changeElement } - /* eslint-enable no-invalid-this */ - }, this) + }) - this.cached = newCached + inst.cached = newCached } // diffs the array itself - Builder.prototype.diffArray = function (nodes) { + function builderDiffArray(inst, nodes) { // update the list of DOM nodes by collecting the nodes from each item - for (var i = 0; i < this.data.length; i++) { - var cached = this.cached[i] - if (cached != null) { - nodes.push.apply(nodes, cached.nodes) + for (var i = 0, len = inst.data.length; i < len; i++) { + var item = inst.cached[i] + if (item != null) { + nodes.push.apply(nodes, item.nodes) } } @@ -568,115 +630,135 @@ // than the old one. if errors ever happen here, the issue is most // likely a bug in the construction of the `cached` data structure // somewhere earlier in the program - forEach(this.cached.nodes, function (node, i) { - /* eslint-disable no-invalid-this */ + forEach(inst.cached.nodes, function (node, i) { if (node.parentNode != null && nodes.indexOf(node) < 0) { - clear([node], [this[i]]) + clear([node], [inst.cached[i]]) } - /* eslint-enable no-invalid-this */ - }, this.cached) + }) - if (this.data.length < this.cached.length) { - this.cached.length = this.data.length + if (inst.data.length < inst.cached.length) { + inst.cached.length = inst.data.length } - this.cached.nodes = nodes + inst.cached.nodes = nodes } - Builder.prototype.initAttrs = function () { - var dataAttrs = this.data.attrs = this.data.attrs || {} - this.cached.attrs = this.cached.attrs || {} + function builderInitAttrs(inst) { + var dataAttrs = inst.data.attrs = inst.data.attrs || {} + inst.cached.attrs = inst.cached.attrs || {} - var dataAttrKeys = Object.keys(this.data.attrs) - this.maybeRecreateObject(dataAttrKeys) + var dataAttrKeys = Object.keys(inst.data.attrs) + builderMaybeRecreateObject(inst, dataAttrKeys) return dataAttrKeys.length > +("key" in dataAttrs) } - Builder.prototype.buildObject = function () { + function builderGetObjectNamespace(inst) { + var data = inst.data + + return data.attrs.xmlns ? data.attrs.xmlns : + data.tag === "svg" ? "http://www.w3.org/2000/svg" : + data.tag === "math" ? "http://www.w3.org/1998/Math/MathML" : + inst.ns + } + + function builderBuildObject(inst) { var views = [] var controllers = [] - this.markViews(views, controllers) + builderMarkViews(inst, views, controllers) - if (!this.data.tag && controllers.length) { + if (!inst.data.tag && controllers.length) { throw new Error("Component template must return a virtual " + "element, not an array, string, etc.") } - var hasKeys = this.initAttrs() + var hasKeys = builderInitAttrs(inst) - if (isString(this.data.tag)) { - return new ObjectBuilder( - this, - hasKeys, - views, - controllers - ).build() + if (isString(inst.data.tag)) { + return objectBuild({ + builder: inst, + hasKeys: hasKeys, + views: views, + controllers: controllers, + ns: builderGetObjectNamespace(inst) + }) } } - Builder.prototype.markViews = function (views, controllers) { - var cached = this.cached && this.cached.controllers - while (this.data.view != null) { - this.checkView(cached, controllers, views) + function builderMarkViews(inst, views, controllers) { + var cached = inst.cached && inst.cached.controllers + while (inst.data.view != null) { + builderCheckView(inst, cached, controllers, views) } } var forcing = false var pendingRequests = 0 - Builder.prototype.checkView = function (cached, controllers, views) { - var view = this.data.view.$original || this.data.view + function builderCheckView(inst, cached, controllers, views) { + var view = inst.data.view.$original || inst.data.view var controller = getController( - this.cached.views, + inst.cached.views, view, cached, - this.data.controller + inst.data.controller ) // Faster to coerce to number and check for NaN - var key = +(this.data && this.data.attrs && this.data.attrs.key) + var key = +(inst.data && inst.data.attrs && inst.data.attrs.key) if (pendingRequests === 0 || forcing || cached && cached.indexOf(controller) > -1) { - this.data = this.data.view(controller) + inst.data = inst.data.view(controller) } else { - this.data = {tag: "placeholder"} + inst.data = {tag: "placeholder"} } - if (this.data.subtree === "retain") return this.cached + if (inst.data.subtree === "retain") return inst.cached if (key === key) { // eslint-disable-line no-self-compare - (this.data.attrs = this.data.attrs || {}).key = key + (inst.data.attrs = inst.data.attrs || {}).key = key } updateLists(views, controllers, view, controller) } var unloaders = [] - function updateLists(views, controllers, view, controller) { - views.push(view) - var idx = controllers.push(controller) - 1 - unloaders[idx] = { - views: views, - view: view, - controller: controller, - controllers: controllers, - handler: function (ev) { - var i = this.controllers.indexOf(this.controller) - this.controllers.splice(i, 1) - i = this.views.indexOf(this.view) - this.views.splice(i, 1) - var unload = this.controller && this.controller.onunload - if (type.call(unload) === "[object Function]") { - this.controller.onunload(ev) - } - } + function unloaderHandler(inst, ev) { + inst.ctrls.splice(inst.ctrls.indexOf(inst.ctrl), 1) + inst.views.splice(inst.views.indexOf(inst.view), 1) + if (inst.ctrl && isFunction(inst.ctrl.onunload)) { + inst.ctrl.onunload(ev) } } + function updateLists(views, controllers, view, controller) { + views.push(view) + unloaders[controllers.push(controller) - 1] = { + views: views, + view: view, + ctrl: controller, + ctrls: controllers + } + } + + var redrawing = false + + m.redraw = function (force) { + if (redrawing) return + redrawing = true + if (force) forcing = true + try { + attemptRedraw(force) + } finally { + redrawing = forcing = false + } + } + + var redrawStrategy = m.redraw.strategy = m.prop() + function getController(views, view, cached, controller) { - var index = m.redraw.strategy() === "diff" && views ? + var index = redrawStrategy() === "diff" && views ? views.indexOf(view) : -1 @@ -689,23 +771,21 @@ } } - function unloadSingleController(controller) { - if (controller.unload) { - controller.onunload({preventDefault: noop}) - } - } - - Builder.prototype.maybeRecreateObject = function (dataAttrKeys) { + function builderMaybeRecreateObject(inst, dataAttrKeys) { // if an element is different enough from the one in cache, recreate it - if (this.elemIsDifferentEnough(dataAttrKeys)) { - if (this.cached.nodes.length) clear(this.cached.nodes) - if (this.cached.configContext && - isFunction(this.cached.configContext.onunload)) { - this.cached.configContext.onunload() + if (builderElemIsDifferentEnough(inst, dataAttrKeys)) { + if (inst.cached.nodes.length) clear(inst.cached.nodes) + if (inst.cached.cfgCtx && + isFunction(inst.cached.cfgCtx.onunload)) { + inst.cached.cfgCtx.onunload() } - if (this.cached.controllers) { - forEach(this.cached.controllers, unloadSingleController) + if (inst.cached.controllers) { + forEach(inst.cached.controllers, function (controller) { + if (controller.unload) { + controller.onunload({preventDefault: noop}) + } + }) } } } @@ -722,9 +802,9 @@ return true } - Builder.prototype.elemIsDifferentEnough = function (dataAttrKeys) { - var data = this.data - var cached = this.cached + function builderElemIsDifferentEnough(inst, dataAttrKeys) { + var data = inst.data + var cached = inst.cached if (data.tag !== cached.tag) return true if (!arraySortCompare(dataAttrKeys, Object.keys(cached.attrs))) { return true @@ -733,81 +813,70 @@ if (data.attrs.id !== cached.attrs.id) return true if (data.attrs.key !== cached.attrs.key) return true - if (m.redraw.strategy() === "all") { - return !cached.configContext || cached.configContext.retain !== true - } else if (m.redraw.strategy() === "diff") { - return cached.configContext && cached.configContext.retain === false + if (redrawStrategy() === "all") { + return !cached.cfgCtx || cached.cfgCtx.retain !== true + } else if (redrawStrategy() === "diff") { + return cached.cfgCtx && cached.cfgCtx.retain === false } else { return false } } - function getObjectNamespace(builder) { - var data = builder.data - - return data.attrs.xmlns ? data.attrs.xmlns : - data.tag === "svg" ? "http://www.w3.org/2000/svg" : - data.tag === "math" ? "http://www.w3.org/1998/Math/MathML" : - builder.namespace - } - - function ObjectBuilder(builder, hasKeys, views, controllers) { - this.builder = builder - this.hasKeys = hasKeys - this.views = views - this.controllers = controllers - this.namespace = getObjectNamespace(builder) - } - - ObjectBuilder.prototype.buildNewNode = function () { - var node = this.createNode() - this.builder.cached = this.reconstruct( + function objectBuildNewNode(inst) { + var node = objectCreateNode(inst) + inst.builder.cached = objectReconstruct( + inst, node, - this.createAttrs(node), - this.buildChildren(node) + objectCreateAttrs(inst, node), + objectBuildChildren(inst, node) ) return node } - ObjectBuilder.prototype.build = function () { - var builder = this.builder + function objectBuild(inst) { + var builder = inst.builder var isNew = builder.cached.nodes.length === 0 - var node = isNew ? this.buildNewNode() : this.buildUpdatedNode() - if (isNew || builder.shouldReattach && node != null) { - insertNode(builder.parentElement, node, builder.index) + + var node = isNew ? + objectBuildNewNode(inst) : + objectBuildUpdatedNode(inst) + + if (isNew || builder.reattach && node != null) { + insertNode(builder.parent, node, builder.index) } - builder.scheduleConfigs(node, isNew) + + builderScheduleConfigs(builder, node, isNew) return builder.cached } - ObjectBuilder.prototype.createNode = function () { - var data = this.builder.data - if (this.namespace === undefined) { + function objectCreateNode(inst) { + var data = inst.builder.data + if (inst.ns === undefined) { if (data.attrs.is) { return $document.createElement(data.tag, data.attrs.is) } else { return $document.createElement(data.tag) } } else if (data.attrs.is) { - return $document.createElementNS(this.namespace, data.tag, + return $document.createElementNS(inst.ns, data.tag, data.attrs.is) } else { - return $document.createElementNS(this.namespace, data.tag) + return $document.createElementNS(inst.ns, data.tag) } } - ObjectBuilder.prototype.createAttrs = function (node) { - var data = this.builder.data - if (this.hasKeys) { - return setAttributes(node, data.tag, data.attrs, {}, this.namespace) + function objectCreateAttrs(inst, node) { + var data = inst.builder.data + if (inst.hasKeys) { + return setAttributes(node, data.tag, data.attrs, {}, inst.ns) } else { return data.attrs } } - ObjectBuilder.prototype.makeChild = function (node, shouldReattach) { - var builder = this.builder - return new Builder( + function objectMakeChild(inst, node, shouldReattach) { + var builder = inst.builder + return builderBuild(buildContext( node, builder.data.tag, undefined, @@ -817,22 +886,22 @@ shouldReattach, 0, builder.data.attrs.contenteditable ? node : builder.editable, - this.namespace, - builder.configs - ).build() + inst.ns, + builder.cfgs + )) } - ObjectBuilder.prototype.buildChildren = function (node) { - var data = this.builder.data - if (data.children != null && data.children.length !== 0) { - return this.makeChild(node, true) + function objectBuildChildren(inst, node) { + var children = inst.builder.data.children + if (children != null && children.length !== 0) { + return objectMakeChild(inst, node, true) } else { - return data.children + return children } } - ObjectBuilder.prototype.reconstruct = function (node, attrs, children) { - var data = this.builder.data + function objectReconstruct(inst, node, attrs, children) { + var data = inst.builder.data var cached = { tag: data.tag, attrs: attrs, @@ -840,7 +909,7 @@ nodes: [node] } - this.unloadCachedControllers(cached) + objectUnloadCachedControllers(inst, cached) if (cached.children && !cached.children.nodes) { cached.children.nodes = [] @@ -850,7 +919,7 @@ // exist, so set it again after children have been created if (data.tag === "select" && "value" in data.attrs) { setAttributes(node, data.tag, {value: data.attrs.value}, {}, - this.namespace) + inst.ns) } return cached } @@ -867,85 +936,102 @@ } } - ObjectBuilder.prototype.unloadCachedControllers = function (cached) { - if (this.controllers.length) { - cached.views = this.views - cached.controllers = this.controllers - forEach(this.controllers, unloadSingleCachedController) + function objectUnloadCachedControllers(inst, cached) { + if (inst.controllers.length) { + cached.views = inst.views + cached.controllers = inst.controllers + forEach(inst.controllers, unloadSingleCachedController) } } - ObjectBuilder.prototype.buildUpdatedNode = function () { - var builder = this.builder - var node = builder.cached.nodes[0] - if (this.hasKeys) { + function objectBuildUpdatedNode(inst) { + var cached = inst.builder.cached + var node = cached.nodes[0] + if (inst.hasKeys) { setAttributes( node, - builder.data.tag, - builder.data.attrs, - builder.cached.attrs, - this.namespace + inst.builder.data.tag, + inst.builder.data.attrs, + cached.attrs, + inst.ns ) } - builder.cached.children = this.makeChild(node, false) - builder.cached.nodes.intact = true + cached.children = objectMakeChild(inst, node, false) + cached.nodes.intact = true - if (this.controllers.length) { - builder.cached.views = this.views - builder.cached.controllers = this.controllers + if (inst.controllers.length) { + cached.views = inst.views + cached.controllers = inst.controllers } return node } - Builder.prototype.scheduleConfigs = function (node, isNew) { - var data = this.data - var cached = this.cached - // schedule configs to be called. They are called after `build` finishes - // running + function builderScheduleConfigs(inst, node, isNew) { + var data = inst.data + var cached = inst.cached + // They are called after the tree is fully built var config = data.attrs.config if (isFunction(config)) { - var context = cached.configContext = cached.configContext || {} + var context = cached.cfgCtx = cached.cfgCtx || {} - // bind - this.configs.push(function () { + inst.cfgs.push(function () { return config.call(data, node, !isNew, context, cached) }) } } - Builder.prototype.handleTextNode = function () { - if (this.cached.nodes.length === 0) { - return this.handleNonexistentNodes() - } else if (this.cached.valueOf() !== this.data.valueOf() || - this.shouldReattach) { - return this.reattachNodes() + function builderHandleTextNode(inst) { + if (inst.cached.nodes.length === 0) { + return builderHandleNonexistentNodes(inst) + } else if (inst.cached.valueOf() !== inst.data.valueOf() || + inst.reattach) { + return builderReattachNodes(inst) } else { - this.cached.nodes.intact = true - return this.cached + inst.cached.nodes.intact = true + return inst.cached } } - Builder.prototype.handleNonexistentNodes = function () { + function nodeHasBody(node) { + return node !== "AREA" && + node !== "BASE" && + node !== "BR" && + node !== "COL" && + node !== "COMMAND" && + node !== "EMBED" && + node !== "HR" && + node !== "IMG" && + node !== "INPUT" && + node !== "KEYGEN" && + node !== "LINK" && + node !== "META" && + node !== "PARAM" && + node !== "SOURCE" && + node !== "TRACK" && + node !== "WBR" + } + + function builderHandleNonexistentNodes(inst) { var nodes - if (this.data.$trusted) { - nodes = injectHTML(this.parentElement, this.index, this.data) + if (inst.data.$trusted) { + nodes = injectHTML(inst.parent, inst.index, inst.data) } else { - nodes = [$document.createTextNode(this.data)] - if (!voidElements.test(this.parentElement.nodeName)) { - insertNode(this.parentElement, nodes[0], this.index) + nodes = [$document.createTextNode(inst.data)] + if (nodeHasBody(inst.parent.nodeName)) { + insertNode(inst.parent, nodes[0], inst.index) } } var cached - if (typeof this.data === "string" || - typeof this.data === "number" || - typeof this.data === "boolean") { - cached = new this.data.constructor(this.data) + if (typeof inst.data === "string" || + typeof inst.data === "number" || + typeof inst.data === "boolean") { + cached = new inst.data.constructor(inst.data) } else { - cached = this.data + cached = inst.data } cached.nodes = nodes @@ -953,50 +1039,52 @@ return cached } - Builder.prototype.reattachNodes = function () { - var nodes = this.cached.nodes - if (!this.editable || this.editable !== $document.activeElement) { - if (this.data.$trusted) { - clear(nodes, this.cached) - nodes = injectHTML(this.parentElement, this.index, this.data) - } else if (this.parentTag === "textarea") { + function builderReattachNodes(inst) { + var nodes = inst.cached.nodes + if (!inst.editable || inst.editable !== $document.activeElement) { + if (inst.data.$trusted) { + clear(nodes, inst.cached) + nodes = injectHTML(inst.parent, inst.index, inst.data) + } else if (inst.pTag === "textarea") { //