diff --git a/Gruntfile.js b/Gruntfile.js index 558754b4..e83bc59e 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -129,7 +129,7 @@ module.exports = function(grunt) { zip: { distribution: { cwd: currentVersionArchiveFolder + "/", - src: [currentVersionArchiveFolder + "/mithril.min.js", currentVersionArchiveFolder + "/mithril.min.map", currentVersionArchiveFolder + "/mithril.js"], + src: [currentVersionArchiveFolder + "/mithril.min.js", currentVersionArchiveFolder + "/mithril.min.js.map", currentVersionArchiveFolder + "/mithril.js"], dest: currentVersionArchiveFolder + "/mithril.min.zip" } }, @@ -148,16 +148,10 @@ module.exports = function(grunt) { comparisons: {expand: true, cwd: inputFolder + "/layout/comparisons/", src: "./**", dest: currentVersionArchiveFolder + "/comparisons/"}, unminified: {src: "mithril.js", dest: currentVersionArchiveFolder + "/mithril.js"}, minified: {src: "mithril.min.js", dest: currentVersionArchiveFolder + "/mithril.min.js"}, - map: {src: "mithril.min.map", dest: currentVersionArchiveFolder + "/mithril.min.map"}, + map: {src: "mithril.min.js.map", dest: currentVersionArchiveFolder + "/mithril.min.js.map"}, typescript: {src: "mithril.d.ts", dest: currentVersionArchiveFolder + "/mithril.d.ts"}, publish: {expand: true, cwd: currentVersionArchiveFolder, src: "./**", dest: outputFolder}, archive: {expand: true, cwd: currentVersionArchiveFolder, src: "./**", dest: outputFolder + "/archive/v" + version}, - cdnjs1: {src: currentVersionArchiveFolder + "/mithril.js", dest: "../cdnjs/ajax/libs/mithril/" + version + "/mithril.js"}, - cdnjs2: {src: currentVersionArchiveFolder + "/mithril.min.js", dest: "../cdnjs/ajax/libs/mithril/" + version + "/mithril.min.js"}, - cdnjs3: {src: currentVersionArchiveFolder + "/mithril.min.map", dest: "../cdnjs/ajax/libs/mithril/" + version + "/mithril.min.map"}, - jsdelivr1: {src: currentVersionArchiveFolder + "/mithril.js", dest: "../jsdelivr/files/mithril/" + version + "/mithril.js"}, - jsdelivr2: {src: currentVersionArchiveFolder + "/mithril.min.js", dest: "../jsdelivr/files/mithril/" + version + "/mithril.min.js"}, - jsdelivr3: {src: currentVersionArchiveFolder + "/mithril.min.map", dest: "../jsdelivr/files/mithril/" + version + "/mithril.min.map"} }, execute: { tests: {src: [currentVersionArchiveFolder + "/mithril-tests.js"]} diff --git a/docs/layout/ghbtns.html b/docs/layout/ghbtns.html new file mode 100644 index 00000000..c4aa9146 --- /dev/null +++ b/docs/layout/ghbtns.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/layout/index.html b/docs/layout/index.html index 21b13509..78091558 100644 --- a/docs/layout/index.html +++ b/docs/layout/index.html @@ -30,7 +30,7 @@ Download v$version

- + Tweet @@ -251,7 +251,7 @@ m.module(document.getElementById("example"), app);

Mithril: The newest JavaScript MVC library 3Kb. @LeoHorie got it right: It's all about good guides/docs: lhorie.github.io/mithril/comparison.html

— David Corbacho (@dcorbacho) March 21, 2014
- + diff --git a/docs/practices.md b/docs/practices.md index fa2a7266..172531b3 100644 --- a/docs/practices.md +++ b/docs/practices.md @@ -100,7 +100,7 @@ In the unlikely case that you have another global variable called `m` in your pa Calling this method while using `m.module` or `m.route` should only be done if you have recurring asynchronous view updates (i.e. something that uses setInterval). -If you're integrating other non-recurring services (e.g. calling setTimeout), you should use [`m.startComputation` / `m.emdComputation`](mithril.computation.md) instead. +If you're integrating other non-recurring services (e.g. calling setTimeout), you should use [`m.startComputation` / `m.endComputation`](mithril.computation.md) instead. This is the most potentially expensive method in Mithril and should not be used at a rate faster than the rate at which the native `requestAnimationFrame` method fires (i.e. the rate at which browsers are comfortable calling recurring rendering-intensive code). Typically, this rate is around 60 calls per second. diff --git a/mithril.d.ts b/mithril.d.ts index 4fcb5b87..a3f6ae18 100644 --- a/mithril.d.ts +++ b/mithril.d.ts @@ -63,3 +63,7 @@ interface MithrilXHROptions { declare var Mithril: MithrilStatic; declare var m: MithrilStatic; + +declare module 'mithril' { + export = MithrilStatic; +} diff --git a/mithril.js b/mithril.js index 155e6909..5add8502 100644 --- a/mithril.js +++ b/mithril.js @@ -1,5 +1,5 @@ Mithril = m = new function app(window, undefined) { - var sObj = "[object Object]", sArr = "[object Array]", sStr = "[object String]", sFn = "function"; + var OBJECT = "[object Object]", ARRAY = "[object Array]", STRING = "[object String]", FUNCTION = "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)$/; @@ -33,7 +33,7 @@ Mithril = m = new function app(window, undefined) { */ 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 hasAttrs = args[1] != null && type.call(args[1]) == OBJECT && !("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: {}}; @@ -51,7 +51,7 @@ Mithril = m = new function app(window, undefined) { var children = hasAttrs ? args[2] : args[1]; - if (type.call(children) == sArr) { + if (type.call(children) == ARRAY) { cell.children = children } else { @@ -88,7 +88,7 @@ Mithril = m = new function app(window, undefined) { //`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 + //- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements (e.g. function foo() {if (cond) return m("div")} //- it simplifies diffing code if (data == null) data = ""; if (data.subtree === "retain") return cached; @@ -97,7 +97,7 @@ Mithril = m = new function app(window, undefined) { if (cached != null) { if (parentCache && parentCache.nodes) { var offset = index - parentIndex; - var end = offset + (dataType == sArr ? data : cached.nodes).length; + var end = offset + (dataType == ARRAY ? data : cached.nodes).length; clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end)) } else if (cached.nodes) clear(cached.nodes, cached) @@ -107,10 +107,10 @@ Mithril = m = new function app(window, undefined) { cached.nodes = [] } - if (dataType == sArr) { + if (dataType == ARRAY) { //recursively flatten array for (var i = 0; i < data.length; i++) { - if (type.call(data[i]) == sArr) { + if (type.call(data[i]) == ARRAY) { data = data.concat.apply([], data); i-- //check current index again and flatten until there are no more nested arrays at that index } @@ -193,7 +193,7 @@ Mithril = m = new function app(window, undefined) { //the second clause (after the pipe) matches text nodes subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || []).length } - else subArrayCount += type.call(item) == sArr ? item.length : 1; + else subArrayCount += type.call(item) == ARRAY ? item.length : 1; cached[cacheCount++] = item } if (!intact) { @@ -212,7 +212,7 @@ Mithril = m = new function app(window, undefined) { cached.nodes = nodes } } - else if (data != null && dataType == sObj) { + else if (data != null && dataType == OBJECT) { if (!data.attrs) data.attrs = {}; if (!cached.attrs) cached.attrs = {}; @@ -221,9 +221,9 @@ Mithril = m = new function app(window, undefined) { //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 (cached.configContext && typeof cached.configContext.onunload == FUNCTION) cached.configContext.onunload() } - if (type.call(data.tag) != sStr) return; + if (type.call(data.tag) != STRING) return; var node, isNew = cached.nodes.length === 0; if (data.attrs.xmlns) namespace = data.attrs.xmlns; @@ -254,7 +254,7 @@ Mithril = m = new function app(window, undefined) { 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 (typeof data.attrs["config"] == sFn) { + if (typeof data.attrs["config"] == FUNCTION) { var context = cached.configContext = cached.configContext || {}; // bind @@ -266,7 +266,7 @@ Mithril = m = new function app(window, undefined) { configs.push(callback(data, [node, !isNew, context, cached])) } } - else if (typeof dataType != sFn) { + else if (typeof dataType != FUNCTION) { //handle text nodes var nodes; if (cached.nodes.length === 0) { @@ -320,11 +320,11 @@ Mithril = m = new function app(window, undefined) { //`config` isn't a real attributes, so ignore it if (attrName === "config" || attrName == "key") continue; //hook event handlers to the auto-redrawing system - else if (typeof dataAttr == sFn && attrName.indexOf("on") == 0) { + else if (typeof dataAttr == FUNCTION && attrName.indexOf("on") == 0) { node[attrName] = autoredraw(dataAttr, node) } //handle `style: {...}` - else if (attrName === "style" && dataAttr != null && type.call(dataAttr) == sObj) { + else if (attrName === "style" && dataAttr != null && type.call(dataAttr) == OBJECT) { for (var rule in dataAttr) { if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule] } @@ -342,7 +342,8 @@ Mithril = m = new function app(window, undefined) { //- list and form are typically used as strings, but are DOM element references in js //- when using CSS selectors (e.g. `m("[style='']")`), style is used as a string, but it's an object in js else if (attrName in node && !(attrName == "list" || attrName == "style" || attrName == "form")) { - node[attrName] = dataAttr + //#348 don't set the value if not needed otherwise cursor placement breaks in Chrome + if (node[attrName] != dataAttr) node[attrName] = dataAttr } else node.setAttribute(attrName, dataAttr) } @@ -351,7 +352,8 @@ Mithril = m = new function app(window, undefined) { if (e.message.indexOf("Invalid argument") < 0) throw e } } - else if (attrName === "value" && tag === "input" && node.value !== dataAttr) { + //#348 dataAttr may not be a string, so use loose comparison (double equal) instead of strict (triple equal) + else if (attrName === "value" && tag === "input" && node.value != dataAttr) { node.value = dataAttr } } @@ -368,9 +370,9 @@ Mithril = m = new function app(window, undefined) { if (nodes.length != 0) nodes.length = 0 } function unload(cached) { - if (cached.configContext && typeof cached.configContext.onunload == sFn) cached.configContext.onunload(); + if (cached.configContext && typeof cached.configContext.onunload == FUNCTION) cached.configContext.onunload(); if (cached.children) { - if (type.call(cached.children) == sArr) { + if (type.call(cached.children) == ARRAY) { for (var i = 0; i < cached.children.length; i++) unload(cached.children[i]) } else if (cached.children.tag) unload(cached.children) @@ -462,7 +464,7 @@ Mithril = m = new function app(window, undefined) { m.prop = function (store) { //note: using non-strict equality check here because we're checking if store is null OR undefined - if (((store != null && type.call(store) == sObj) || typeof store == sFn) && typeof store.then == sFn) { + if (((store != null && type.call(store) == OBJECT) || typeof store == FUNCTION) && typeof store.then == FUNCTION) { return propify(store) } @@ -475,7 +477,7 @@ Mithril = m = new function app(window, undefined) { var index = roots.indexOf(root); if (index < 0) index = roots.length; var isPrevented = false; - if (controllers[index] && typeof controllers[index].onunload == sFn) { + if (controllers[index] && typeof controllers[index].onunload == FUNCTION) { var event = { preventDefault: function() {isPrevented = true} }; @@ -552,7 +554,7 @@ Mithril = m = new function app(window, undefined) { m.route = function() { //m.route() if (arguments.length === 0) return currentRoute; - else if (arguments.length === 3 && type.call(arguments[1]) == sStr) { + else if (arguments.length === 3 && type.call(arguments[1]) == STRING) { var root = arguments[0], defaultRoute = arguments[1], router = arguments[2]; redirect = function(source) { var path = currentRoute = normalizeRoute(source); @@ -579,9 +581,9 @@ Mithril = m = new function app(window, undefined) { element.addEventListener("click", routeUnobtrusive) } //m.route(route) - else if (type.call(arguments[0]) == sStr) { + else if (type.call(arguments[0]) == STRING) { currentRoute = arguments[0]; - var querystring = arguments[1] != null && type.call(arguments[1]) == sObj ? buildQueryString(arguments[1]) : null; + var querystring = arguments[1] != null && type.call(arguments[1]) == OBJECT ? buildQueryString(arguments[1]) : null; if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring; var shouldReplaceHistoryEntry = (arguments.length == 3 ? arguments[2] : arguments[1]) === true; @@ -644,7 +646,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(value != null && type.call(value) == sObj ? buildQueryString(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value)) + str.push(value != null && type.call(value) == OBJECT ? buildQueryString(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value)) } return str.join("&") } @@ -730,7 +732,7 @@ Mithril = m = new function app(window, undefined) { } function thennable(then, successCallback, failureCallback, notThennableCallback) { - if (((promiseValue != null && type.call(promiseValue) == sObj) || typeof promiseValue == sFn) && typeof then == sFn) { + if (((promiseValue != null && type.call(promiseValue) == OBJECT) || typeof promiseValue == FUNCTION) && typeof then == FUNCTION) { try { // count protects against abuse calls from spec checker var count = 0; @@ -774,7 +776,7 @@ Mithril = m = new function app(window, undefined) { fire() }, function() { try { - if (state == RESOLVING && typeof successCallback == sFn) { + if (state == RESOLVING && typeof successCallback == FUNCTION) { promiseValue = successCallback(promiseValue) } else if (state == REJECTING && typeof failureCallback == "function") { @@ -891,13 +893,13 @@ Mithril = m = new function app(window, undefined) { if (options.deserialize == JSON.parse) { xhr.setRequestHeader("Accept", "application/json, text/*"); } - if (typeof options.config == sFn) { + if (typeof options.config == FUNCTION) { var maybeXhr = options.config(xhr, options); if (maybeXhr != null) xhr = maybeXhr } var data = options.method == "GET" || !options.data ? "" : options.data - if (data && (type.call(data) != sStr && data.constructor != window.FormData)) { + if (data && (type.call(data) != STRING && data.constructor != window.FormData)) { throw "Request data should be either be a string or FormData. Check the `serialize` option in `m.request`"; } xhr.send(data); @@ -942,7 +944,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 (type.call(response) == sArr && xhrOptions.type) { + if (type.call(response) == ARRAY && 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)