From 44f6a9a64df4a59ecc0980db7990962ce7ad54c1 Mon Sep 17 00:00:00 2001 From: Barney Date: Fri, 7 Nov 2014 10:23:23 +0000 Subject: [PATCH 1/6] Warning not to invoke background m.request as m.prop in modules --- archive/v0.1.23/mithril-tests.js | 3200 +++++++++++++++++++++++++++++ archive/v0.1/getting-started.html | 10 +- archive/v0.1/mithril.prop.html | 10 +- docs/mithril.request.md | 2 + 4 files changed, 3212 insertions(+), 10 deletions(-) create mode 100644 archive/v0.1.23/mithril-tests.js diff --git a/archive/v0.1.23/mithril-tests.js b/archive/v0.1.23/mithril-tests.js new file mode 100644 index 00000000..99bac36d --- /dev/null +++ b/archive/v0.1.23/mithril-tests.js @@ -0,0 +1,3200 @@ +Mithril = m = new function app(window, undefined) { + var sObj = "[object Object]", sArr = "[object Array]", sStr = "[object String]" + function type(obj) {return {}.toString.call(obj)} + function isObj(obj) {return obj != null && type(obj) == sObj} + function isArr(obj) {return type(obj) == sArr} + function isFn(obj) {return typeof obj == "function"} + function isStr(obj){ return type(obj) == sStr} + var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ + var voidElements = /AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR/ + + /* + * @typedef {String} Tag + * A string that looks like -> div.classname#id[param=one][param2=two] + * Which describes a DOM node + */ + + /* + * + * @param {Tag} The DOM node tag + * @param {Object=[]} optional key-value pairs to be mapped to DOM attrs + * @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array, or splat (optional) + * + */ + function m() { + var args = [].slice.call(arguments) + var hasAttrs = isObj(args[1]) && !("tag" in args[1]) && !("subtree" in args[1]) + var attrs = hasAttrs ? args[1] : {} + var classAttrName = "class" in attrs ? "class" : "className" + var cell = {tag: "div", attrs: {}} + var match, classes = [] + while (match = parser.exec(args[0])) { + if (match[1] == "" && match[2]) cell.tag = match[2] + else if (match[1] == "#") cell.attrs.id = match[2] + else if (match[1] == ".") classes.push(match[2]) + else if (match[3][0] == "[") { + var pair = attrParser.exec(match[3]) + cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" :true) + } + } + if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ") + + + var children = hasAttrs ? args[2] : args[1] + if (isArr(children)) { + cell.children = children + } + else { + cell.children = hasAttrs ? args.slice(2) : args.slice(1) + } + + for (var attrName in attrs) { + if (attrName == classAttrName) cell.attrs[attrName] = (cell.attrs[attrName] || "") + " " + attrs[attrName] + else cell.attrs[attrName] = attrs[attrName] + } + return cell + } + function build(parentElement, parentTag, parentCache, parentIndex, data, cached, shouldReattach, index, editable, namespace, configs) { + //`build` is a recursive function that manages creation/diffing/removal of DOM elements based on comparison between `data` and `cached` + //the diff algorithm can be summarized as this: + //1 - compare `data` and `cached` + //2 - if they are different, copy `data` to `cached` and update the DOM based on what the difference is + //3 - recursively apply this algorithm for every array and for the children of every virtual element + + //the `cached` data structure is essentially the same as the previous redraw's `data` data structure, with a few additions: + //- `cached` always has a property called `nodes`, which is a list of DOM elements that correspond to the data represented by the respective virtual element + //- in order to support attaching `nodes` as a property of `cached`, `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 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. 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) + //`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 + + //there's logic that relies on the assumption that null and undefined data are equivalent to empty strings + //- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements + //- it simplifies diffing code + if (data == null) data = "" + if (data.subtree === "retain") return cached + var cachedType = type(cached), dataType = type(data) + if (cached == null || cachedType != dataType) { + if (cached != null) { + if (parentCache && parentCache.nodes) { + var offset = index - parentIndex + var end = offset + (dataType == sArr ? data : cached.nodes).length + clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end)) + } + else if (cached.nodes) clear(cached.nodes, cached) + } + cached = new data.constructor + if (cached.tag) cached = {} //if constructor creates a virtual dom element, use a blank object as the base cached node instead of copying the virtual el (#277) + cached.nodes = [] + } + + if (dataType == sArr) { + data = flatten(data) + var nodes = [], intact = cached.length === data.length, subArrayCount = 0 + + //keys algorithm: sort elements without recreating them if keys are present + //1) create a map of all existing keys, and mark all for deletion + //2) add new keys to map and mark them for addition + //3) if key exists in new list, change action from deletion to a move + //4) for each key, handle its corresponding action as marked in previous steps + //5) copy unkeyed items into their respective gaps + var DELETION = 1, INSERTION = 2 , MOVE = 3 + var existing = {}, unkeyed = [], shouldMaintainIdentities = false + for (var i = 0; i < cached.length; i++) { + if (cached[i] && cached[i].attrs && cached[i].attrs.key != null) { + shouldMaintainIdentities = true + existing[cached[i].attrs.key] = {action: DELETION, index: i} + } + } + if (shouldMaintainIdentities) { + for (var i = 0; i < data.length; i++) { + if (data[i] && data[i].attrs) { + if (data[i].attrs.key != null) { + var key = data[i].attrs.key + if (!existing[key]) existing[key] = {action: INSERTION, index: i} + else existing[key] = { + action: MOVE, + index: i, + from: existing[key].index, + element: parentElement.childNodes[existing[key].index] || window.document.createElement("div") + } + } + else unkeyed.push({index: i, element: parentElement.childNodes[i] || window.document.createElement("div")}) + } + } + var actions = Object.keys(existing).map(function(key) {return existing[key]}) + var changes = actions.sort(function(a, b) {return a.action - b.action || a.index - b.index}) + var newCached = cached.slice() + + for (var i = 0, change; change = changes[i]; i++) { + if (change.action == DELETION) { + clear(cached[change.index].nodes, cached[change.index]) + newCached.splice(change.index, 1) + } + if (change.action == INSERTION) { + var dummy = window.document.createElement("div") + dummy.key = data[change.index].attrs.key + parentElement.insertBefore(dummy, parentElement.childNodes[change.index] || null) + newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]}) + } + + if (change.action == MOVE) { + if (parentElement.childNodes[change.index] !== change.element && change.element !== null) { + parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null) + } + newCached[change.index] = cached[change.from] + } + } + for (var i = 0; i < unkeyed.length; i++) { + var change = unkeyed[i] + parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null) + newCached[change.index] = cached[change.index] + } + cached = newCached + cached.nodes = [] + for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes.push(child) + } + //end key algorithm + + for (var i = 0, cacheCount = 0; i < data.length; i++) { + //diff each item in the array + var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs) + if (item === undefined) continue + if (!item.nodes.intact) intact = false + subArrayCount += isArr(item) ? item.length : 1 + cached[cacheCount++] = item + } + if (!intact) { + //diff the array itself + + //update the list of DOM nodes by collecting the nodes from each item + for (var i = 0; i < data.length; i++) { + if (cached[i] != null) nodes = nodes.concat(cached[i].nodes) + } + //remove items from the end of the array if the new array is shorter 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 + for (var i = 0, node; node = cached.nodes[i]; i++) { + if (node.parentNode != null && nodes.indexOf(node) < 0) clear([node], [cached[i]]) + } + //add items to the end if the new array is longer than the old one + for (var i = cached.nodes.length, node; node = nodes[i]; i++) { + if (node.parentNode == null) parentElement.appendChild(node) + } + if (data.length < cached.length) cached.length = data.length + cached.nodes = nodes + } + } + else if (data != null && dataType == sObj) { + //if an element is different enough from the one in cache, recreate it + if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) { + clear(cached.nodes) + if (cached.configContext && isFn(cached.configContext.onunload)) cached.configContext.onunload() + } + if (!isStr(data.tag)) return + + var node, isNew = cached.nodes.length === 0 + if (data.attrs.xmlns) namespace = data.attrs.xmlns + else if (data.tag === "svg") namespace = "http://www.w3.org/2000/svg" + else if (data.tag === "math") namespace = "http://www.w3.org/1998/Math/MathML" + if (isNew) { + node = namespace === undefined ? window.document.createElement(data.tag) : window.document.createElementNS(namespace, data.tag) + cached = { + tag: data.tag, + //set attributes first, then create children + attrs: setAttributes(node, data.tag, data.attrs, {}, namespace), + children: build(node, data.tag, undefined, undefined, data.children, cached.children, true, 0, data.attrs.contenteditable ? node : editable, namespace, configs), + nodes: [node] + } + //edge case: setting value on doesn't work before children exist, so set it again after children have been created if (data.tag == "select" && data.attrs.value) setAttributes(node, data.tag, {value: data.attrs.value}, {}, namespace) parentElement.insertBefore(node, parentElement.childNodes[index] || null) } else { node = cached.nodes[0] - setAttributes(node, data.tag, data.attrs, cached.attrs, namespace) + if (dataAttrKeys.length) setAttributes(node, data.tag, data.attrs, cached.attrs, namespace) cached.children = build(node, data.tag, undefined, undefined, data.children, cached.children, false, 0, data.attrs.contenteditable ? node : editable, namespace, configs) cached.nodes.intact = true if (shouldReattach === true && node != null) parentElement.insertBefore(node, parentElement.childNodes[index] || null) } //schedule configs to be called. They are called after `build` finishes running - if (isFn(data.attrs["config"])) { + if (typeof data.attrs["config"] == sFn) { var context = cached.configContext = cached.configContext || {} // bind @@ -238,7 +247,7 @@ Mithril = m = new function app(window, undefined) { configs.push(callback(data, [node, !isNew, context, cached])) } } - else if (!isFn(dataType)) { + else if (typeof dataType != sFn) { //handle text nodes var nodes if (cached.nodes.length === 0) { @@ -293,11 +302,11 @@ Mithril = m = new function app(window, undefined) { //we don't ignore `key` because it must be unique and having it on the DOM helps debugging if (attrName === "config") continue //hook event handlers to the auto-redrawing system - else if (isFn(dataAttr) && attrName.indexOf("on") == 0) { + else if (typeof dataAttr == sFn && attrName.indexOf("on") == 0) { node[attrName] = autoredraw(dataAttr, node) } //handle `style: {...}` - else if (attrName === "style" && isObj(dataAttr)) { + else if (attrName === "style" && dataAttr != null && type.call(dataAttr) == sObj) { for (var rule in dataAttr) { if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule] } @@ -338,9 +347,9 @@ Mithril = m = new function app(window, undefined) { if (nodes.length != 0) nodes.length = 0 } function unload(cached) { - if (cached.configContext && isFn(cached.configContext.onunload)) cached.configContext.onunload() + if (cached.configContext && typeof cached.configContext.onunload == sFn) cached.configContext.onunload() if (cached.children) { - if (isArr(cached.children)) { + if (type.call(cached.children) == sArr) { for (var i = 0; i < cached.children.length; i++) unload(cached.children[i]) } else if (cached.children.tag) unload(cached.children) @@ -367,13 +376,19 @@ Mithril = m = new function app(window, undefined) { return nodes } function flatten(data) { - var flattened = [] - for (var i = 0; i < data.length; i++) { - var item = data[i] - if (isArr(item)) flattened.push.apply(flattened, flatten(item)) - else flattened.push(item) + var index = 0 + loop: while (true) { + for (var i = index; i < data.length; i++) { + var item = data[i] + if (type.call(data[i]) == sArr) { + index = i + data = data.concat.apply([], data) + continue loop + } + } + break } - return flattened + return data } function autoredraw(callback, object) { return function(e) { @@ -436,33 +451,25 @@ Mithril = m = new function app(window, undefined) { return store } - prop.toString = function(){ - return store.toString(); - } - - prop.valueOf = function(){ - return store.valueOf(); - } - return prop } m.prop = function (store) { //note: using non-strict equality check here because we're checking if store is null OR undefined - if ((isObj(store) || isFn(store)) && store != null && isFn(store.then)) { + if (((store != null && type.call(store) == sObj) || typeof store == sFn) && typeof store.then == sFn) { return propify(store) } return gettersetter(store) } - var roots = [], modules = [], controllers = [], lastRedrawId = null, lastRedrawCallTime = 0, computePostRedrawHook = null, prevented = false + var roots = [], modules = [], controllers = [], lastRedrawId = null, lastRedrawCallTime = 0, computePostRedrawHook = null, prevented = false, topModule var FRAME_BUDGET = 16 //60 frames per second = 1 call per 16 ms m.module = function(root, module) { var index = roots.indexOf(root) if (index < 0) index = roots.length var isPrevented = false - if (controllers[index] && isFn(controllers[index].onunload)) { + if (controllers[index] && typeof controllers[index].onunload == sFn) { var event = { preventDefault: function() {isPrevented = true} } @@ -472,8 +479,14 @@ Mithril = m = new function app(window, undefined) { m.redraw.strategy("all") m.startComputation() roots[index] = root - modules[index] = module - controllers[index] = new module.controller + var currentModule = topModule = module + var controller = new module.controller + //controllers may call m.module recursively (via m.route redirects, for example) + //this conditional ensures only the last recursive m.module call is applied + if (currentModule == topModule) { + controllers[index] = controller + modules[index] = module + } m.endComputation() return controllers[index] } @@ -500,7 +513,9 @@ Mithril = m = new function app(window, undefined) { function redraw() { var mode = m.redraw.strategy() for (var i = 0; i < roots.length; i++) { - if (controllers[i] && mode != "none") m.render(roots[i], modules[i].view(controllers[i]), mode == "all") + if (controllers[i] && mode != "none") { + m.render(roots[i], modules[i].view(controllers[i]), mode == "all") + } } //after rendering within a routed context, we need to scroll back to the top, and fetch the document title for history.pushState if (computePostRedrawHook) { @@ -531,8 +546,9 @@ Mithril = m = new function app(window, undefined) { var modes = {pathname: "", hash: "#", search: "?"} var redirect = function() {}, routeParams = {}, currentRoute m.route = function() { + //m.route() if (arguments.length === 0) return currentRoute - else if (arguments.length === 3 && isStr(arguments[1])) { + else if (arguments.length === 3 && type.call(arguments[1]) == sStr) { var root = arguments[0], defaultRoute = arguments[1], router = arguments[2] redirect = function(source) { var path = currentRoute = normalizeRoute(source) @@ -549,20 +565,19 @@ Mithril = m = new function app(window, undefined) { computePostRedrawHook = setScroll window[listener]() } + //config: m.route else if (arguments[0].addEventListener) { var element = arguments[0] var isInitialized = arguments[1] var context = arguments[2] - if (!isInitialized) { - context.href = element.getAttribute("href") - element.href = (m.route.mode !== 'pathname' ? window.location.pathname : '') + modes[m.route.mode] + context.href - element.removeEventListener("click", routeUnobtrusive) - element.addEventListener("click", routeUnobtrusive) - } + element.href = (m.route.mode !== 'pathname' ? window.location.pathname : '') + modes[m.route.mode] + this.attrs.href + element.removeEventListener("click", routeUnobtrusive) + element.addEventListener("click", routeUnobtrusive) } - else if (isStr(arguments[0])) { + //m.route(route) + else if (type.call(arguments[0]) == sStr) { currentRoute = arguments[0] - var querystring = isObj(arguments[1]) ? buildQueryString(arguments[1]) : null + var querystring = arguments[1] != null && type.call(arguments[1]) == sObj ? buildQueryString(arguments[1]) : null if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring var shouldReplaceHistoryEntry = (arguments.length == 3 ? arguments[2] : arguments[1]) === true @@ -614,7 +629,8 @@ Mithril = m = new function app(window, undefined) { if (e.preventDefault) e.preventDefault() else e.returnValue = false var currentTarget = e.currentTarget || this - m.route(currentTarget[m.route.mode].slice(modes[m.route.mode].length)) + var args = m.route.mode == "pathname" && currentTarget.search ? parseQueryString(currentTarget.search.slice(1)) : {} + m.route(currentTarget[m.route.mode].slice(modes[m.route.mode].length), args) } function setScroll() { if (m.route.mode != "hash" && window.location.hash) window.location.hash = window.location.hash @@ -624,7 +640,7 @@ Mithril = m = new function app(window, undefined) { var str = [] for(var prop in object) { var key = prefix ? prefix + "[" + prop + "]" : prop, value = object[prop] - str.push(isObj(value) ? buildQueryString(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value)) + str.push(value != null && type.call(value) == sObj ? buildQueryString(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value)) } return str.join("&") } @@ -651,7 +667,7 @@ Mithril = m = new function app(window, undefined) { return deferred } function propify(promise) { - prop = m.prop() + var prop = m.prop() promise.then(prop) prop.then = function(resolve, reject) { return propify(promise.then(resolve, reject)) @@ -710,7 +726,7 @@ Mithril = m = new function app(window, undefined) { } function thennable(then, successCallback, failureCallback, notThennableCallback) { - if ((isObj(promiseValue) || isFn(promiseValue)) && isFn(then)) { + if (((promiseValue != null && type.call(promiseValue) == sObj) || typeof promiseValue == sFn) && typeof then == sFn) { try { // count protects against abuse calls from spec checker var count = 0 @@ -754,10 +770,10 @@ Mithril = m = new function app(window, undefined) { fire() }, function() { try { - if (state == RESOLVING && isFn(successCallback)) { + if (state == RESOLVING && typeof successCallback == sFn) { promiseValue = successCallback(promiseValue) } - else if (state == REJECTING && isFn(failureCallback)) { + else if (state == REJECTING && typeof failureCallback == "function") { promiseValue = failureCallback(promiseValue) state = RESOLVING } @@ -783,7 +799,7 @@ Mithril = m = new function app(window, undefined) { } } m.deferred.onerror = function(e) { - if (type(e) == "[object Error]" && !e.constructor.toString().match(/ Error/)) throw e + if (type.call(e) == "[object Error]" && !e.constructor.toString().match(/ Error/)) throw e } m.sync = function(args) { @@ -808,7 +824,7 @@ Mithril = m = new function app(window, undefined) { args[i].then(synchronizer(i, true), synchronizer(i, false)) } } - else deferred.resolve() + else deferred.resolve([]) return deferred.promise } @@ -871,22 +887,23 @@ Mithril = m = new function app(window, undefined) { if (options.deserialize == JSON.parse) { xhr.setRequestHeader("Accept", "application/json, text/*"); } - if (isFn(options.config)) { + if (typeof options.config == sFn) { var maybeXhr = options.config(xhr, options) if (maybeXhr != null) xhr = maybeXhr } + if (options.data && (type.call(options.data) != sStr && options.data.constructor != window.FormData)) throw "Request data should be either be a string or FormData. Check the `serialize` option in `m.request`" xhr.send(options.method == "GET" || !options.data ? "" : options.data) return xhr } } function bindData(xhrOptions, data, serialize) { - if (data && Object.keys(data).length > 0) { - if (xhrOptions.method == "GET" && xhrOptions.dataType != "jsonp") { - xhrOptions.url = xhrOptions.url + (xhrOptions.url.indexOf("?") < 0 ? "?" : "&") + buildQueryString(data) - } - else xhrOptions.data = serialize(data) + if (xhrOptions.method == "GET" && xhrOptions.dataType != "jsonp") { + var prefix = xhrOptions.url.indexOf("?") < 0 ? "?" : "&" + var querystring = buildQueryString(data) + xhrOptions.url = xhrOptions.url + (querystring ? prefix + querystring : "") } + else xhrOptions.data = serialize(data) return xhrOptions } function parameterizeUrl(url, data) { @@ -918,7 +935,7 @@ Mithril = m = new function app(window, undefined) { var unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity var response = unwrap(deserialize(extract(e.target, xhrOptions))) if (e.type == "load") { - if (isArr(response) && xhrOptions.type) { + if (type.call(response) == sArr && xhrOptions.type) { for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i]) } else if (xhrOptions.type) response = new xhrOptions.type(response) @@ -932,6 +949,7 @@ Mithril = m = new function app(window, undefined) { if (xhrOptions.background !== true) m.endComputation() } ajax(xhrOptions) + deferred.promise(xhrOptions.initialValue) return deferred.promise } @@ -948,6 +966,9 @@ if (typeof define == "function" && define.amd) define(function() {return m}) ;;; +//make "use strict" and nodejs happy +var window = this + //test reporting for saucelabs if (typeof window != "undefined") { window.global_test_results = { @@ -969,7 +990,7 @@ function test(condition) { var result = true test.total++ - if (this.performance != null && performance.now) { + if (typeof performance != "undefined" && performance.now) { start = performance.now() } try { @@ -980,11 +1001,11 @@ function test(condition) { console.error(e) test.failures.push(condition) } - if (this.performance != null && performance.now) { + if (typeof performance != "undefined" && performance.now) { duration = performance.now() - start } - test_obj = { + window.test_obj = { name: "" + test.total, result: result, duration: duration @@ -992,7 +1013,7 @@ function test(condition) { if (typeof window != "undefined") { if (!result) { - window.global_test_results.tests.push(test_obj) + window.global_test_results.tests.push(window.test_obj) } window.global_test_results.duration += duration @@ -1961,6 +1982,11 @@ function testMithril(mock) { m.render(root, new Field()) return root.childNodes.length == 1 }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, {foo: 123}) + return root.childNodes.length == 0 + }) //end m.render //m.redraw @@ -2756,6 +2782,28 @@ function testMithril(mock) { }) return value == "foo+bar" }) + test(function() { + mock.requestAnimationFrame.$resolve() + mock.location.search = "?" + + var root = mock.document.createElement("div") + + var a = {} + a.controller = function() {m.route("/b")} + a.view = function() {return "a"} + + var b = {} + b.controller = function() {} + b.view = function(ctrl) {return "b"} + + m.route(root, "/a", { + "/a": a, + "/b": b + }) + mock.requestAnimationFrame.$resolve() + + return root.childNodes[0].nodeValue == "b" + }) //end m.route //m.prop @@ -2846,6 +2894,18 @@ function testMithril(mock) { xhr.onreadystatechange() return xhr.$headers["Content-Type"] === undefined }) + test(function() { + var prop = m.request({method: "POST", url: "test", initialValue: "foo"}) + var initialValue = prop(); + mock.XMLHttpRequest.$instances.pop().onreadystatechange() + + return initialValue === "foo" + }) + test(function() { + var prop = m.request({method: "POST", url: "test", initialValue: "foo"}).then(function(value) {return "bar"}) + mock.XMLHttpRequest.$instances.pop().onreadystatechange() + return prop() === "bar" + }) // m.request over jsonp test(function(){ @@ -3136,6 +3196,11 @@ function testMithril(mock) { m.sync([]).then(function() {value = 2}) return value == 2 }) + test(function() { + var success + m.sync([]).then(function(value) {success = value instanceof Array}) + return success + }) //m.startComputation/m.endComputation test(function() { diff --git a/mithril.js b/mithril.js index c28ec3ad..9f82faa7 100644 --- a/mithril.js +++ b/mithril.js @@ -949,7 +949,7 @@ Mithril = m = new function app(window, undefined) { if (xhrOptions.background !== true) m.endComputation() } ajax(xhrOptions) - deferred.promise(xhrOptions.default) + deferred.promise(xhrOptions.initialValue) return deferred.promise } diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 560a34ef..ecc27d3d 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -1688,11 +1688,14 @@ function testMithril(mock) { return xhr.$headers["Content-Type"] === undefined }) test(function() { - var prop = m.request({method: "POST", url: "test", default: "foo"}) - return prop() === "foo" + var prop = m.request({method: "POST", url: "test", initialValue: "foo"}) + var initialValue = prop(); + mock.XMLHttpRequest.$instances.pop().onreadystatechange() + + return initialValue === "foo" }) test(function() { - var prop = m.request({method: "POST", url: "test", default: "foo"}).then(function(value) {return "bar"}) + var prop = m.request({method: "POST", url: "test", initialValue: "foo"}).then(function(value) {return "bar"}) mock.XMLHttpRequest.$instances.pop().onreadystatechange() return prop() === "bar" }) From a51d515e4cc5262e205a7224c80464d75be5b240 Mon Sep 17 00:00:00 2001 From: Barney Date: Mon, 10 Nov 2014 15:38:07 +0000 Subject: [PATCH 5/6] Reverting docs edit --- docs/mithril.request.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/mithril.request.md b/docs/mithril.request.md index 8543fff4..be435f21 100644 --- a/docs/mithril.request.md +++ b/docs/mithril.request.md @@ -457,8 +457,6 @@ where: m.request({method: "GET", url: "/foo", background: true}) .then(m.redraw); //force redraw ``` - - The return value of a background `m.request` should not be exposed to controllers or views, since without the blocking effect of redraw-blocking effect of `m.startComputation` it is liable to be invoked before it resolves, which can lead to runtime errors. - **any unwrapSuccess(any data)** (optional) From 4b2ded6fccb5fc4cd9e0d2894279f0aed24a47e7 Mon Sep 17 00:00:00 2001 From: Barney Date: Mon, 10 Nov 2014 16:24:30 +0000 Subject: [PATCH 6/6] Removed accidentally committed test reports --- archive/v0.1.23/mithril-tests.js | 3265 ------------------------------ 1 file changed, 3265 deletions(-) delete mode 100644 archive/v0.1.23/mithril-tests.js diff --git a/archive/v0.1.23/mithril-tests.js b/archive/v0.1.23/mithril-tests.js deleted file mode 100644 index 4a488b4f..00000000 --- a/archive/v0.1.23/mithril-tests.js +++ /dev/null @@ -1,3265 +0,0 @@ -Mithril = m = new function app(window, undefined) { - var sObj = "[object Object]", sArr = "[object Array]", sStr = "[object String]", sFn = "function" - var type = {}.toString - var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ - var voidElements = /AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR/ - - /* - * @typedef {String} Tag - * A string that looks like -> div.classname#id[param=one][param2=two] - * Which describes a DOM node - */ - - /* - * - * @param {Tag} The DOM node tag - * @param {Object=[]} optional key-value pairs to be mapped to DOM attrs - * @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array, or splat (optional) - * - */ - function m() { - var args = [].slice.call(arguments) - var hasAttrs = args[1] != null && type.call(args[1]) == sObj && !("tag" in args[1]) && !("subtree" in args[1]) - var attrs = hasAttrs ? args[1] : {} - var classAttrName = "class" in attrs ? "class" : "className" - var cell = {tag: "div", attrs: {}} - var match, classes = [] - while (match = parser.exec(args[0])) { - if (match[1] == "" && match[2]) cell.tag = match[2] - else if (match[1] == "#") cell.attrs.id = match[2] - else if (match[1] == ".") classes.push(match[2]) - else if (match[3][0] == "[") { - var pair = attrParser.exec(match[3]) - cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" :true) - } - } - if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ") - - - var children = hasAttrs ? args[2] : args[1] - if (type.call(children) == sArr) { - cell.children = children - } - else { - cell.children = hasAttrs ? args.slice(2) : args.slice(1) - } - - for (var attrName in attrs) { - if (attrName == classAttrName) cell.attrs[attrName] = (cell.attrs[attrName] || "") + " " + attrs[attrName] - else cell.attrs[attrName] = attrs[attrName] - } - return cell - } - function build(parentElement, parentTag, parentCache, parentIndex, data, cached, shouldReattach, index, editable, namespace, configs) { - //`build` is a recursive function that manages creation/diffing/removal of DOM elements based on comparison between `data` and `cached` - //the diff algorithm can be summarized as this: - //1 - compare `data` and `cached` - //2 - if they are different, copy `data` to `cached` and update the DOM based on what the difference is - //3 - recursively apply this algorithm for every array and for the children of every virtual element - - //the `cached` data structure is essentially the same as the previous redraw's `data` data structure, with a few additions: - //- `cached` always has a property called `nodes`, which is a list of DOM elements that correspond to the data represented by the respective virtual element - //- in order to support attaching `nodes` as a property of `cached`, `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 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. 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) - //`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 - - //there's logic that relies on the assumption that null and undefined data are equivalent to empty strings - //- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements - //- it simplifies diffing code - if (data == null) data = "" - if (data.subtree === "retain") return cached - var cachedType = type.call(cached), dataType = type.call(data) - if (cached == null || cachedType != dataType) { - if (cached != null) { - if (parentCache && parentCache.nodes) { - var offset = index - parentIndex - var end = offset + (dataType == sArr ? data : cached.nodes).length - clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end)) - } - else if (cached.nodes) clear(cached.nodes, cached) - } - cached = new data.constructor - if (cached.tag) cached = {} //if constructor creates a virtual dom element, use a blank object as the base cached node instead of copying the virtual el (#277) - cached.nodes = [] - } - - if (dataType == sArr) { - data = flatten(data) - var nodes = [], intact = cached.length === data.length, subArrayCount = 0 - - //keys algorithm: sort elements without recreating them if keys are present - //1) create a map of all existing keys, and mark all for deletion - //2) add new keys to map and mark them for addition - //3) if key exists in new list, change action from deletion to a move - //4) for each key, handle its corresponding action as marked in previous steps - //5) copy unkeyed items into their respective gaps - var DELETION = 1, INSERTION = 2 , MOVE = 3 - var existing = {}, unkeyed = [], shouldMaintainIdentities = false - for (var i = 0; i < cached.length; i++) { - if (cached[i] && cached[i].attrs && cached[i].attrs.key != null) { - shouldMaintainIdentities = true - existing[cached[i].attrs.key] = {action: DELETION, index: i} - } - } - if (shouldMaintainIdentities) { - for (var i = 0; i < data.length; i++) { - if (data[i] && data[i].attrs) { - if (data[i].attrs.key != null) { - var key = data[i].attrs.key - if (!existing[key]) existing[key] = {action: INSERTION, index: i} - else existing[key] = { - action: MOVE, - index: i, - from: existing[key].index, - element: parentElement.childNodes[existing[key].index] || window.document.createElement("div") - } - } - else unkeyed.push({index: i, element: parentElement.childNodes[i] || window.document.createElement("div")}) - } - } - var actions = Object.keys(existing).map(function(key) {return existing[key]}) - var changes = actions.sort(function(a, b) {return a.action - b.action || a.index - b.index}) - var newCached = cached.slice() - - for (var i = 0, change; change = changes[i]; i++) { - if (change.action == DELETION) { - clear(cached[change.index].nodes, cached[change.index]) - newCached.splice(change.index, 1) - } - if (change.action == INSERTION) { - var dummy = window.document.createElement("div") - dummy.key = data[change.index].attrs.key - parentElement.insertBefore(dummy, parentElement.childNodes[change.index] || null) - newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]}) - } - - if (change.action == MOVE) { - if (parentElement.childNodes[change.index] !== change.element && change.element !== null) { - parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null) - } - newCached[change.index] = cached[change.from] - } - } - for (var i = 0; i < unkeyed.length; i++) { - var change = unkeyed[i] - parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null) - newCached[change.index] = cached[change.index] - } - cached = newCached - cached.nodes = [] - for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes.push(child) - } - //end key algorithm - - for (var i = 0, cacheCount = 0; i < data.length; i++) { - //diff each item in the array - var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs) - if (item === undefined) continue - if (!item.nodes.intact) intact = false - if (item.$trusted) { - //fix offset of next element if item was a trusted string w/ more than one html element - //the first clause in the regexp matches elements - //the second clause (after the pipe) matches text nodes - subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || []).length - } - else subArrayCount += type.call(item) == sArr ? item.length : 1 - cached[cacheCount++] = item - } - if (!intact) { - //diff the array itself - - //update the list of DOM nodes by collecting the nodes from each item - for (var i = 0; i < data.length; i++) { - if (cached[i] != null) nodes = nodes.concat(cached[i].nodes) - } - //remove items from the end of the array if the new array is shorter 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 - for (var i = 0, node; node = cached.nodes[i]; i++) { - if (node.parentNode != null && nodes.indexOf(node) < 0) clear([node], [cached[i]]) - } - //add items to the end if the new array is longer than the old one - for (var i = cached.nodes.length, node; node = nodes[i]; i++) { - if (node.parentNode == null) parentElement.appendChild(node) - } - if (data.length < cached.length) cached.length = data.length - cached.nodes = nodes - } - } - else if (data != null && dataType == sObj) { - if (!data.attrs) data.attrs = {} - if (!cached.attrs) cached.attrs = {} - - var dataAttrKeys = Object.keys(data.attrs) - //if an element is different enough from the one in cache, recreate it - if (data.tag != cached.tag || dataAttrKeys.join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) { - if (cached.nodes.length) clear(cached.nodes) - if (cached.configContext && typeof cached.configContext.onunload == sFn) cached.configContext.onunload() - } - if (type.call(data.tag) != sStr) return - - var node, isNew = cached.nodes.length === 0 - if (data.attrs.xmlns) namespace = data.attrs.xmlns - else if (data.tag === "svg") namespace = "http://www.w3.org/2000/svg" - else if (data.tag === "math") namespace = "http://www.w3.org/1998/Math/MathML" - if (isNew) { - node = namespace === undefined ? window.document.createElement(data.tag) : window.document.createElementNS(namespace, data.tag) - cached = { - tag: data.tag, - //set attributes first, then create children - attrs: dataAttrKeys.length ? setAttributes(node, data.tag, data.attrs, {}, namespace) : {}, - children: data.children != null && data.children.length > 0 ? - build(node, data.tag, undefined, undefined, data.children, cached.children, true, 0, data.attrs.contenteditable ? node : editable, namespace, configs) : - data.children, - nodes: [node] - } - if (cached.children && !cached.children.nodes) cached.children.nodes = [] - //edge case: setting value on