fix bugs about recycling in components, and styles when reusing style object
This commit is contained in:
parent
86673f8d16
commit
f9ef480b34
5 changed files with 157 additions and 11 deletions
109
mithril.js
109
mithril.js
|
|
@ -1,4 +1,100 @@
|
||||||
"use strict"
|
"use strict"
|
||||||
|
if (typeof Promise === "undefined") {
|
||||||
|
var Promise = function(executor) {
|
||||||
|
if (!(this instanceof Promise)) throw new Error("Promise must be called with `new`")
|
||||||
|
if (typeof executor !== "function") throw new TypeError("executor must be a function")
|
||||||
|
|
||||||
|
var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false)
|
||||||
|
var instance = self._instance = {resolvers: resolvers, rejectors: rejectors}
|
||||||
|
var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout
|
||||||
|
function handler(list, shouldAbsorb) {
|
||||||
|
return function execute(value) {
|
||||||
|
var then
|
||||||
|
try {
|
||||||
|
if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") {
|
||||||
|
if (value === self) throw new TypeError("Promise can't be resolved w/ itself")
|
||||||
|
executeOnce(then.bind(value))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callAsync(function() {
|
||||||
|
if (!shouldAbsorb && list.length === 0) console.error("Possible unhandled promise rejection:", value)
|
||||||
|
for (var i = 0; i < list.length; i++) list[i](value)
|
||||||
|
resolvers.length = 0, rejectors.length = 0
|
||||||
|
instance.state = shouldAbsorb
|
||||||
|
instance.retry = function() {execute(value)}
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
rejectCurrent(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function executeOnce(then) {
|
||||||
|
var runs = 0
|
||||||
|
function run(fn) {
|
||||||
|
return function(value) {
|
||||||
|
if (runs++ > 0) return
|
||||||
|
fn(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var onerror = run(rejectCurrent)
|
||||||
|
try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)}
|
||||||
|
}
|
||||||
|
|
||||||
|
executeOnce(executor)
|
||||||
|
}
|
||||||
|
Promise.prototype.then = function(onFulfilled, onRejection) {
|
||||||
|
var self = this, instance = self._instance
|
||||||
|
function handle(callback, list, next, state) {
|
||||||
|
list.push(function(value) {
|
||||||
|
if (typeof callback !== "function") next(value)
|
||||||
|
else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)}
|
||||||
|
})
|
||||||
|
if (typeof instance.retry === "function" && state === instance.state) instance.retry()
|
||||||
|
}
|
||||||
|
var resolveNext, rejectNext
|
||||||
|
var promise = new Promise(function(resolve, reject) {resolveNext = resolve, rejectNext = reject})
|
||||||
|
handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false)
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
Promise.prototype.catch = function(onRejection) {
|
||||||
|
return this.then(null, onRejection)
|
||||||
|
}
|
||||||
|
Promise.resolve = function(value) {
|
||||||
|
if (value instanceof Promise) return value
|
||||||
|
return new Promise(function(resolve, reject) {resolve(value)})
|
||||||
|
}
|
||||||
|
Promise.reject = function(value) {
|
||||||
|
return new Promise(function(resolve, reject) {reject(value)})
|
||||||
|
}
|
||||||
|
Promise.all = function(list) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var total = list.length, count = 0, values = []
|
||||||
|
if (list.length === 0) resolve([])
|
||||||
|
else for (var i = 0; i < list.length; i++) {
|
||||||
|
new function(i) {
|
||||||
|
function consume(value) {
|
||||||
|
count++
|
||||||
|
values[i] = value
|
||||||
|
if (count === total) resolve(values)
|
||||||
|
}
|
||||||
|
if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") {
|
||||||
|
list[i].then(consume, reject)
|
||||||
|
}
|
||||||
|
else consume(list[i])
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Promise.race = function(list) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
list[i].then(resolve, reject)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
function Node(tag, key, attrs, children, text, dom) {
|
function Node(tag, key, attrs, children, text, dom) {
|
||||||
return {tag: tag, key: key, attrs: attrs, children: children, text: text, dom: dom, domSize: undefined, state: {}, events: undefined}
|
return {tag: tag, key: key, attrs: attrs, children: children, text: text, dom: dom, domSize: undefined, state: {}, events: undefined}
|
||||||
}
|
}
|
||||||
|
|
@ -13,7 +109,7 @@ Node.normalizeChildren = function normalizeChildren(children) {
|
||||||
}
|
}
|
||||||
return children
|
return children
|
||||||
}
|
}
|
||||||
var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:\s*=\s*("|'|)(.*?)\2)?\]/
|
var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g
|
||||||
var selectorCache = {}
|
var selectorCache = {}
|
||||||
function hyperscript(selector) {
|
function hyperscript(selector) {
|
||||||
if (typeof selector === "string") {
|
if (typeof selector === "string") {
|
||||||
|
|
@ -25,8 +121,9 @@ function hyperscript(selector) {
|
||||||
else if (type === "#") attributes.id = value
|
else if (type === "#") attributes.id = value
|
||||||
else if (type === ".") classes.push(value)
|
else if (type === ".") classes.push(value)
|
||||||
else if (match[3][0] === "[") {
|
else if (match[3][0] === "[") {
|
||||||
var pair = attrParser.exec(match[3])
|
var attrValue = match[6]
|
||||||
attributes[pair[1]] = pair[3] || true
|
if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\")
|
||||||
|
attributes[match[4]] = attrValue || true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (classes.length > 0) attributes.className = classes.join(" ")
|
if (classes.length > 0) attributes.className = classes.join(" ")
|
||||||
|
|
@ -601,10 +698,10 @@ var buildQueryString = function(object) {
|
||||||
else args.push(encodeURIComponent(key) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : ""))
|
else args.push(encodeURIComponent(key) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var requestService = function($window, Promise) {
|
var requestService = function($window, Promise1) {
|
||||||
var callbackCount = 0
|
var callbackCount = 0
|
||||||
function xhr(args) {
|
function xhr(args) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise1(function(resolve, reject) {
|
||||||
var useBody = args.useBody != null ? args.useBody : args.method !== "GET" && args.method !== "TRACE"
|
var useBody = args.useBody != null ? args.useBody : args.method !== "GET" && args.method !== "TRACE"
|
||||||
|
|
||||||
if (typeof args.serialize !== "function") args.serialize = JSON.stringify
|
if (typeof args.serialize !== "function") args.serialize = JSON.stringify
|
||||||
|
|
@ -656,7 +753,7 @@ var requestService = function($window, Promise) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function jsonp(args) {
|
function jsonp(args) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise1(function(resolve, reject) {
|
||||||
var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++
|
var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++
|
||||||
var script = $window.document.createElement("script")
|
var script = $window.document.createElement("script")
|
||||||
$window[callbackName] = function(data) {
|
$window[callbackName] = function(data) {
|
||||||
|
|
|
||||||
|
|
@ -325,7 +325,7 @@ module.exports = function($window) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (vnode.dom.parentNode != null) parent.removeChild(vnode.dom)
|
if (vnode.dom.parentNode != null) parent.removeChild(vnode.dom)
|
||||||
if (context != null && vnode.domSize == null && !hasIntegrationMethods(vnode)) { //TODO test custom elements
|
if (context != null && vnode.domSize == null && !hasIntegrationMethods(vnode.attrs) && !(typeof vnode.tag !== "string" && hasIntegrationMethods(vnode.tag))) { //TODO test custom elements
|
||||||
if (!context.pool) context.pool = [vnode]
|
if (!context.pool) context.pool = [vnode]
|
||||||
else context.pool.push(vnode)
|
else context.pool.push(vnode)
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +352,7 @@ module.exports = function($window) {
|
||||||
}
|
}
|
||||||
function setAttr(vnode, key, old, value) {
|
function setAttr(vnode, key, old, value) {
|
||||||
var element = vnode.dom
|
var element = vnode.dom
|
||||||
if (key === "key" || (old === value && !isFormAttribute(vnode, key)) || typeof value === "undefined" || isLifecycleMethod(key)) return
|
if (key === "key" || (old === value && !isFormAttribute(vnode, key)) && typeof value !== "object" || typeof value === "undefined" || isLifecycleMethod(key)) return
|
||||||
var nsLastIndex = key.indexOf(":")
|
var nsLastIndex = key.indexOf(":")
|
||||||
if (nsLastIndex > -1 && key.substr(0, nsLastIndex) === "xlink") {
|
if (nsLastIndex > -1 && key.substr(0, nsLastIndex) === "xlink") {
|
||||||
element.setAttributeNS("http://www.w3.org/1999/xlink", key.slice(nsLastIndex + 1), value)
|
element.setAttributeNS("http://www.w3.org/1999/xlink", key.slice(nsLastIndex + 1), value)
|
||||||
|
|
@ -402,12 +402,13 @@ module.exports = function($window) {
|
||||||
function isAttribute(attr) {
|
function isAttribute(attr) {
|
||||||
return attr === "href" || attr === "list" || attr === "form"// || attr === "type" || attr === "width" || attr === "height"
|
return attr === "href" || attr === "list" || attr === "form"// || attr === "type" || attr === "width" || attr === "height"
|
||||||
}
|
}
|
||||||
function hasIntegrationMethods(vnode) {
|
function hasIntegrationMethods(source) {
|
||||||
return vnode.attrs != null && (vnode.attrs.oncreate || vnode.attrs.onupdate || vnode.attrs.onbeforeremove || vnode.attrs.onremove)
|
return source != null && (source.oncreate || source.onupdate || source.onbeforeremove || source.onremove)
|
||||||
}
|
}
|
||||||
|
|
||||||
//style
|
//style
|
||||||
function updateStyle(element, old, style) {
|
function updateStyle(element, old, style) {
|
||||||
|
if (old === style) element.style = "", old = null
|
||||||
if (style == null) element.style = ""
|
if (style == null) element.style = ""
|
||||||
else if (typeof style === "string") element.style = style
|
else if (typeof style === "string") element.style = style
|
||||||
else {
|
else {
|
||||||
|
|
|
||||||
|
|
@ -537,6 +537,23 @@ o.spec("component", function() {
|
||||||
o(called).equals(1)
|
o(called).equals(1)
|
||||||
o(root.childNodes.length).equals(0)
|
o(root.childNodes.length).equals(0)
|
||||||
})
|
})
|
||||||
|
o("does not recycle when there's an onupdate", function() {
|
||||||
|
var component = {
|
||||||
|
onupdate: function() {},
|
||||||
|
view: function() {
|
||||||
|
return {tag: "div"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var update = o.spy()
|
||||||
|
var vnode = {tag: component, key: 1}
|
||||||
|
var updated = {tag: component, key: 1}
|
||||||
|
|
||||||
|
render(root, [vnode])
|
||||||
|
render(root, [])
|
||||||
|
render(root, [updated])
|
||||||
|
|
||||||
|
o(vnode.dom).notEquals(updated.dom)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
o.spec("state", function() {
|
o.spec("state", function() {
|
||||||
o("deep copies state", function() {
|
o("deep copies state", function() {
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,24 @@ o.spec("updateElement", function() {
|
||||||
|
|
||||||
o(updated.dom.style.backgroundColor).equals("green")
|
o(updated.dom.style.backgroundColor).equals("green")
|
||||||
})
|
})
|
||||||
|
o("handles noop style change when style is string", function() {
|
||||||
|
var vnode = {tag: "a", attrs: {style: "background-color:green;"}}
|
||||||
|
var updated = {tag: "a", attrs: {style: "background-color:green;"}}
|
||||||
|
|
||||||
|
render(root, [vnode])
|
||||||
|
render(root, [updated])
|
||||||
|
|
||||||
|
o(updated.dom.style.backgroundColor).equals("green")
|
||||||
|
})
|
||||||
|
o("handles noop style change when style is object", function() {
|
||||||
|
var vnode = {tag: "a", attrs: {style: {backgroundColor: "red"}}}
|
||||||
|
var updated = {tag: "a", attrs: {style: {backgroundColor: "red"}}}
|
||||||
|
|
||||||
|
render(root, [vnode])
|
||||||
|
render(root, [updated])
|
||||||
|
|
||||||
|
o(updated.dom.style.backgroundColor).equals("red")
|
||||||
|
})
|
||||||
o("updates style from string to object", function() {
|
o("updates style from string to object", function() {
|
||||||
var vnode = {tag: "a", attrs: {style: "background-color:red;"}}
|
var vnode = {tag: "a", attrs: {style: "background-color:red;"}}
|
||||||
var updated = {tag: "a", attrs: {style: {backgroundColor: "green"}}}
|
var updated = {tag: "a", attrs: {style: {backgroundColor: "green"}}}
|
||||||
|
|
@ -150,6 +168,19 @@ o.spec("updateElement", function() {
|
||||||
o(updated.dom.style.backgroundColor).equals("red")
|
o(updated.dom.style.backgroundColor).equals("red")
|
||||||
o(updated.dom.style.border).equals("")
|
o(updated.dom.style.border).equals("")
|
||||||
})
|
})
|
||||||
|
o("updates style when it's same object but mutated", function() {
|
||||||
|
var style = {backgroundColor: "red", color: "gold"}
|
||||||
|
var vnode = {tag: "a", attrs: {style: style}}
|
||||||
|
|
||||||
|
render(root, [vnode])
|
||||||
|
|
||||||
|
delete style.backgroundColor
|
||||||
|
var updated = {tag: "a", attrs: {style: style}}
|
||||||
|
render(root, [updated])
|
||||||
|
|
||||||
|
o(updated.dom.style.backgroundColor).equals("")
|
||||||
|
o(updated.dom.style.color).equals("gold")
|
||||||
|
})
|
||||||
o("replaces el", function() {
|
o("replaces el", function() {
|
||||||
var vnode = {tag: "a"}
|
var vnode = {tag: "a"}
|
||||||
var updated = {tag: "b"}
|
var updated = {tag: "b"}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
module.exports = typeof process === "object" ? process.nextTick : window.setImmediate || window.setTimeout
|
module.exports = typeof setImmediate === "function" ? setImmediate : setTimeout
|
||||||
Loading…
Add table
Add a link
Reference in a new issue