From d258b9687b9814ec32c691ecd1fe40c14b62cde5 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 25 Mar 2014 22:23:14 -0400 Subject: [PATCH 01/38] expose to commonjs --- archive/v0.1.2/mithril-tests.js | 2 ++ archive/v0.1.2/mithril.min.js | 2 +- archive/v0.1.2/mithril.min.map | 2 +- archive/v0.1.2/mithril.min.zip | Bin 20069 -> 20151 bytes mithril.js | 2 ++ 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/archive/v0.1.2/mithril-tests.js b/archive/v0.1.2/mithril-tests.js index 9bec759a..c73033cf 100644 --- a/archive/v0.1.2/mithril-tests.js +++ b/archive/v0.1.2/mithril-tests.js @@ -417,6 +417,8 @@ new function(window) { } } + if (window.module) window.module.exports = m + //testing API m.deps = function(mock) {return window = mock} }(this) diff --git a/archive/v0.1.2/mithril.min.js b/archive/v0.1.2/mithril.min.js index de690171..c0431d1a 100644 --- a/archive/v0.1.2/mithril.min.js +++ b/archive/v0.1.2/mithril.min.js @@ -4,5 +4,5 @@ http://github.com/lhorie/mithril.js (c) Leo Horie License: MIT */ -!new function(a){function b(e,f,g){if(null!==f&&void 0!==f){var h=s.call(g),i=s.call(f);if(h!=i&&(null!==g&&void 0!==g&&d(g.nodes),g=new f.constructor,g.nodes=[]),"[object Array]"==i){for(var j=[],k=g.length===f.length,l=0;l-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(a,b,c){for(var d in b){var e=b[d];if(!(d in c)||c[d]!==e){if(c[d]=e,"config"==d)continue;if(0==d.indexOf("on")&&"function"==typeof e&&(e=f(e,a)),"style"==d)for(var g in e)a.style[g]=e[g];else d in a?a[d]=e:a.setAttribute(d,e)}}return c}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:\w+/g);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z||a.document,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(a,b,c){for(var d in b){var e=b[d];if(!(d in c)||c[d]!==e){if(c[d]=e,"config"==d)continue;if(0==d.indexOf("on")&&"function"==typeof e&&(e=f(e,a)),"style"==d)for(var g in e)a.style[g]=e[g];else d in a?a[d]=e:a.setAttribute(d,e)}}return c}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:\w+/g);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z||a.document,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c8;s`<6ioNj33PM}t@6@wcXT&)b_B_}8cbg2E2drG2NZJ*&iB*tbabw8c2=ql l@MdHZVZh?l$?ts)umNQM9a delta 148 zcmdl!m+|QwM)3e|W)?065V*Xd(q*pPs<1p+28O(i;uf-#^JK$#fP!aJ&Z+SmFfj0M zz91XQxmjO*t{Qh_mQ#_dj;o`iYvm+AF^)Vx9d}2^;M~b_e&Qf4aC0U*`WeU}n*lNh SY#of4HM!qUiS3#%hz|hB^C#p0 diff --git a/mithril.js b/mithril.js index a986161a..52939e69 100644 --- a/mithril.js +++ b/mithril.js @@ -417,6 +417,8 @@ new function(window) { } } + if (window.module) window.module.exports = m + //testing API m.deps = function(mock) {return window = mock} }(this) \ No newline at end of file From 78c422448a32c1b87f5f7ca44ae6e061879132bd Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Sun, 30 Mar 2014 14:17:40 -0400 Subject: [PATCH 02/38] make commonjs api work with non-global `module`, expose amd api --- mithril.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mithril.js b/mithril.js index 52939e69..368196cf 100644 --- a/mithril.js +++ b/mithril.js @@ -417,7 +417,8 @@ new function(window) { } } - if (window.module) window.module.exports = m + if (typeof module != "undefined" && module !== null) module.exports = m + if (typeof define == "function" && define.amd) define(function() {return m}) //testing API m.deps = function(mock) {return window = mock} From 62eae50b20ffcbe02774a79bd7986caf7847e495 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Sun, 30 Mar 2014 14:36:16 -0400 Subject: [PATCH 03/38] invalidate dom cache on new module --- mithril.js | 1 + 1 file changed, 1 insertion(+) diff --git a/mithril.js b/mithril.js index a986161a..d99f0dd4 100644 --- a/mithril.js +++ b/mithril.js @@ -180,6 +180,7 @@ new function(window) { var currentRoot, currentModule = {view: function() {}}, currentController = {}, now = 0, lastRedraw = 0, lastRedrawId = 0 m.module = function(root, module) { m.startComputation() + cellCache = {} currentRoot = root currentModule = module currentController = new module.controller From 18dd5a1410d0ca30fa88ece51ba1ac935ee815f6 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Sun, 30 Mar 2014 22:30:08 -0400 Subject: [PATCH 04/38] add manifest files for npm and component --- Gruntfile.js | 1 + archive/v0.1.2/component.json | 10 ++++++++++ archive/v0.1.2/package.json | 11 +++++++++++ docs/layout/component.json | 10 ++++++++++ docs/layout/package.json | 11 +++++++++++ 5 files changed, 43 insertions(+) create mode 100644 archive/v0.1.2/component.json create mode 100644 archive/v0.1.2/package.json create mode 100644 docs/layout/component.json create mode 100644 docs/layout/package.json diff --git a/Gruntfile.js b/Gruntfile.js index f0c472a6..0cf23688 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -77,6 +77,7 @@ module.exports = function(grunt) { options: {force: true, patterns: [{match: /\.md/g, replacement: ".html"}, {match: /\$version/g, replacement: version}]}, links: {expand: true, flatten: true, src: [tempFolder + "/**/*.html"], dest: currentVersionArchiveFolder + "/"}, index: {src: inputFolder + "/layout/index.html", dest: currentVersionArchiveFolder + "/index.html"}, + commonjs: {expand: true, flatten: true, src: [inputFolder + "/layout/*.json"], dest: currentVersionArchiveFolder}, }, copy: { style: {src: inputFolder + "/layout/style.css", dest: currentVersionArchiveFolder + "/style.css"}, diff --git a/archive/v0.1.2/component.json b/archive/v0.1.2/component.json new file mode 100644 index 00000000..a7f3f3b5 --- /dev/null +++ b/archive/v0.1.2/component.json @@ -0,0 +1,10 @@ +{ + "name": "mithril", + "description": "A Javascript framework for building brilliant applications", + "keywords": ["mvc", "framework"], + "repo": "lhorie/mithril", + "main": "mithril.min.js", + "scripts": ["mithril.min.js"], + "version": "0.1.2", + "license": "MIT" +} \ No newline at end of file diff --git a/archive/v0.1.2/package.json b/archive/v0.1.2/package.json new file mode 100644 index 00000000..3870314d --- /dev/null +++ b/archive/v0.1.2/package.json @@ -0,0 +1,11 @@ +{ + "name": "mithril", + "description": "A Javascript Framework for building brilliant applications", + "keywords": ["mvc", "framework"], + "version": "0.1.2", + "author": "Leo Horie ", + "repository": {"type": "git", "url": "https://github.com/lhorie/mithril"}, + "main": "mithril.min.js", + "licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}] + "files": ["mithril.min.js", "mithril.min.map"] +} \ No newline at end of file diff --git a/docs/layout/component.json b/docs/layout/component.json new file mode 100644 index 00000000..484349e5 --- /dev/null +++ b/docs/layout/component.json @@ -0,0 +1,10 @@ +{ + "name": "mithril", + "description": "A Javascript framework for building brilliant applications", + "keywords": ["mvc", "framework"], + "repo": "lhorie/mithril", + "main": "mithril.min.js", + "scripts": ["mithril.min.js"], + "version": "$version", + "license": "MIT" +} \ No newline at end of file diff --git a/docs/layout/package.json b/docs/layout/package.json new file mode 100644 index 00000000..aca6234f --- /dev/null +++ b/docs/layout/package.json @@ -0,0 +1,11 @@ +{ + "name": "mithril", + "description": "A Javascript Framework for building brilliant applications", + "keywords": ["mvc", "framework"], + "version": "$version", + "author": "Leo Horie ", + "repository": {"type": "git", "url": "https://github.com/lhorie/mithril"}, + "main": "mithril.min.js", + "licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}] + "files": ["mithril.min.js", "mithril.min.map"] +} \ No newline at end of file From 86ba3474a638a314ae9a98d568f0849b90c97e6e Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Sun, 30 Mar 2014 22:33:49 -0400 Subject: [PATCH 05/38] add tentative fix for #29 https://github.com/lhorie/mithril.js/issues/29 TODO: test cost of relying on native equality checks --- archive/v0.1.2/mithril-tests.js | 5 +++-- archive/v0.1.2/mithril.min.js | 2 +- archive/v0.1.2/mithril.min.map | 2 +- archive/v0.1.2/mithril.min.zip | Bin 20069 -> 19997 bytes mithril.js | 4 ++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/archive/v0.1.2/mithril-tests.js b/archive/v0.1.2/mithril-tests.js index 9bec759a..9d285e27 100644 --- a/archive/v0.1.2/mithril-tests.js +++ b/archive/v0.1.2/mithril-tests.js @@ -119,14 +119,14 @@ new function(window) { function setAttributes(node, dataAttrs, cachedAttrs) { for (var attrName in dataAttrs) { var dataAttr = dataAttrs[attrName] - if (!(attrName in cachedAttrs) || (cachedAttrs[attrName] !== dataAttr)) { + //if (!(attrName in cachedAttrs) || (cachedAttrs[attrName] !== dataAttr)) { cachedAttrs[attrName] = dataAttr if (attrName == "config") continue if (attrName.indexOf("on") == 0 && typeof dataAttr == "function") dataAttr = autoredraw(dataAttr, node) if (attrName == "style") for (var rule in dataAttr) node.style[rule] = dataAttr[rule] else if (attrName in node) node[attrName] = dataAttr else node.setAttribute(attrName, dataAttr) - } + //} } return cachedAttrs } @@ -180,6 +180,7 @@ new function(window) { var currentRoot, currentModule = {view: function() {}}, currentController = {}, now = 0, lastRedraw = 0, lastRedrawId = 0 m.module = function(root, module) { m.startComputation() + cellCache = {} currentRoot = root currentModule = module currentController = new module.controller diff --git a/archive/v0.1.2/mithril.min.js b/archive/v0.1.2/mithril.min.js index de690171..d8aec9c9 100644 --- a/archive/v0.1.2/mithril.min.js +++ b/archive/v0.1.2/mithril.min.js @@ -4,5 +4,5 @@ http://github.com/lhorie/mithril.js (c) Leo Horie License: MIT */ -!new function(a){function b(e,f,g){if(null!==f&&void 0!==f){var h=s.call(g),i=s.call(f);if(h!=i&&(null!==g&&void 0!==g&&d(g.nodes),g=new f.constructor,g.nodes=[]),"[object Array]"==i){for(var j=[],k=g.length===f.length,l=0;l-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(a,b,c){for(var d in b){var e=b[d];if(!(d in c)||c[d]!==e){if(c[d]=e,"config"==d)continue;if(0==d.indexOf("on")&&"function"==typeof e&&(e=f(e,a)),"style"==d)for(var g in e)a.style[g]=e[g];else d in a?a[d]=e:a.setAttribute(d,e)}}return c}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:\w+/g);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z||a.document,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(a,b,c){for(var d in b){var e=b[d];if(c[d]=e,"config"!=d)if(0==d.indexOf("on")&&"function"==typeof e&&(e=f(e,a)),"style"==d)for(var g in e)a.style[g]=e[g];else d in a?a[d]=e:a.setAttribute(d,e)}return c}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:\w+/g);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),y={},z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z||a.document,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c(pkqHOu9Nc2kC@5I!1wnUA=VNCubY%V|3rFW$4SuXtBA%NSA|AfAcb{Y&MQ` scO7p>XGibN!j9*8cv0Lo`Ms~cJgTJ(4E$g}z=+z(wth-%eZC+*0A@Q!4FCWD delta 283 zcmbO`hwJ=u`GbMsF2&)l0g3SZ;o0~vZY z<(wM70RscS!RD#5i9*KCRZcp-j*h;LI-ZV>kuEykj*gj5jykSDjx&gp>o_~Q zmbvP Date: Mon, 31 Mar 2014 16:07:24 -0400 Subject: [PATCH 06/38] add docs about msx --- archive/v0.1.1/tools.html | 18 ++++++++++++++++-- docs/tools.md | 27 +++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/archive/v0.1.1/tools.html b/archive/v0.1.1/tools.html index 260477ed..26faba39 100644 --- a/archive/v0.1.1/tools.html +++ b/archive/v0.1.1/tools.html @@ -44,10 +44,24 @@

Tools

-

HTML to Mithril Template Converter

-

If you already have your HTML written and want to convert it into a Mithril template, use the tool below.

+

HTML-to-Mithril Template Converter

+

If you already have your HTML written and want to convert it into a Mithril template, you can use the tool below for one-off manual conversion.

Template Converter


+

Automatic HTML-to-Mithril Template Converter

+

There's a tool called MSX by Jonathan Buchanan that allows you to write templates using HTML syntax, and then automatically compile them to Javascript when files change.

+

It is useful for teams where styling and functionality are done by different people, and for those who prefer to maintain templates in HTML syntax.

+

The tool allows you to write code like this:

+
todo.view = function(ctrl) {
+    return <html>
+        <body>
+            <input onchange={m.withAttr("value", ctrl.description)} value={ctrl.description()}/>
+            <button onclick={ctrl.add.bind(ctrl, ctrl.description)}>Add</button>
+        </body>
+    </html>
+};
+

Note, however, that since the code above is not valid Javascript, this syntax can only be used with a preprocessor build tool such as the provided Gulp.js script.

+

Mithril Template Compiler

You can pre-compile Mithril templates to make them run faster. For more information see this page:

Compiling Templates

diff --git a/docs/tools.md b/docs/tools.md index 202de480..64cf8250 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -1,13 +1,36 @@ ## Tools -### HTML to Mithril Template Converter +### HTML-to-Mithril Template Converter -If you already have your HTML written and want to convert it into a Mithril template, use the tool below. +If you already have your HTML written and want to convert it into a Mithril template, you can use the tool below for one-off manual conversion. [Template Converter](tools/template-converter.html) --- +### Automatic HTML-to-Mithril Template Converter + +There's a tool called [MSX by Jonathan Buchanan](https://github.com/insin/msx) that allows you to write templates using HTML syntax, and then automatically compile them to Javascript when files change. + +It is useful for teams where styling and functionality are done by different people, and for those who prefer to maintain templates in HTML syntax. + +The tool allows you to write code like this: + +```javascript +todo.view = function(ctrl) { + return + + + + + +}; +``` + +Note, however, that since the code above is not valid Javascript, this syntax can only be used with a preprocessor build tool such as the provided [Gulp.js](http://gulpjs.com) script. + +--- + ### Mithril Template Compiler You can pre-compile Mithril templates to make them run faster. For more information see this page: From 49dffe7e0efdcdc54966c5b55e982dddd07408be Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 1 Apr 2014 14:40:02 -0400 Subject: [PATCH 07/38] make sure element is child of correct parent if child is recycled but parent is not --- archive/v0.1.2/mithril-tests.js | 7 +++++++ archive/v0.1.2/mithril.min.js | 2 +- archive/v0.1.2/mithril.min.map | 2 +- archive/v0.1.2/mithril.min.zip | Bin 20069 -> 20106 bytes archive/v0.1.2/tools.html | 18 ++++++++++++++++-- mithril.js | 2 +- tests/mithril-tests.js | 6 ++++++ 7 files changed, 32 insertions(+), 5 deletions(-) diff --git a/archive/v0.1.2/mithril-tests.js b/archive/v0.1.2/mithril-tests.js index 9bec759a..75b07ece 100644 --- a/archive/v0.1.2/mithril-tests.js +++ b/archive/v0.1.2/mithril-tests.js @@ -73,6 +73,7 @@ new function(window) { setAttributes(node, data.attrs, cached.attrs) cached.children = build(node, data.children, cached.children) cached.nodes.intact = true + parent.appendChild(node) } if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew) } @@ -621,6 +622,12 @@ function testMithril(mock) { m.render(root, m("div", [undefined])) return root.childNodes[0].childNodes.length === 0 }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("div.classname", [m("a", {href: "/first"})])) + m.render(root, m("div", [m("a", {href: "/second"})])) + return root.childNodes[0].childNodes.length == 1 + }) //m.redraw test(function() { diff --git a/archive/v0.1.2/mithril.min.js b/archive/v0.1.2/mithril.min.js index de690171..46a52f67 100644 --- a/archive/v0.1.2/mithril.min.js +++ b/archive/v0.1.2/mithril.min.js @@ -4,5 +4,5 @@ http://github.com/lhorie/mithril.js (c) Leo Horie License: MIT */ -!new function(a){function b(e,f,g){if(null!==f&&void 0!==f){var h=s.call(g),i=s.call(f);if(h!=i&&(null!==g&&void 0!==g&&d(g.nodes),g=new f.constructor,g.nodes=[]),"[object Array]"==i){for(var j=[],k=g.length===f.length,l=0;l-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(a,b,c){for(var d in b){var e=b[d];if(!(d in c)||c[d]!==e){if(c[d]=e,"config"==d)continue;if(0==d.indexOf("on")&&"function"==typeof e&&(e=f(e,a)),"style"==d)for(var g in e)a.style[g]=e[g];else d in a?a[d]=e:a.setAttribute(d,e)}}return c}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:\w+/g);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z||a.document,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(a,b,c){for(var d in b){var e=b[d];if(!(d in c)||c[d]!==e){if(c[d]=e,"config"==d)continue;if(0==d.indexOf("on")&&"function"==typeof e&&(e=f(e,a)),"style"==d)for(var g in e)a.style[g]=e[g];else d in a?a[d]=e:a.setAttribute(d,e)}}return c}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:\w+/g);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z||a.document,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;cigQM0PKrjJ z=4Kz32uWU$LTd|~&&mc249c4~%HHHtbaiwtaf;A!b#(O4b<&A+bd2=S@pN

Tools

-

HTML to Mithril Template Converter

-

If you already have your HTML written and want to convert it into a Mithril template, use the tool below.

+

HTML-to-Mithril Template Converter

+

If you already have your HTML written and want to convert it into a Mithril template, you can use the tool below for one-off manual conversion.

Template Converter


+

Automatic HTML-to-Mithril Template Converter

+

There's a tool called MSX by Jonathan Buchanan that allows you to write templates using HTML syntax, and then automatically compile them to Javascript when files change.

+

It is useful for teams where styling and functionality are done by different people, and for those who prefer to maintain templates in HTML syntax.

+

The tool allows you to write code like this:

+
todo.view = function(ctrl) {
+    return <html>
+        <body>
+            <input onchange={m.withAttr("value", ctrl.description)} value={ctrl.description()}/>
+            <button onclick={ctrl.add.bind(ctrl, ctrl.description)}>Add</button>
+        </body>
+    </html>
+};
+

Note, however, that since the code above is not valid Javascript, this syntax can only be used with a preprocessor build tool such as the provided Gulp.js script.

+

Mithril Template Compiler

You can pre-compile Mithril templates to make them run faster. For more information see this page:

Compiling Templates

diff --git a/mithril.js b/mithril.js index d99f0dd4..a59cdae1 100644 --- a/mithril.js +++ b/mithril.js @@ -73,6 +73,7 @@ new function(window) { setAttributes(node, data.attrs, cached.attrs) cached.children = build(node, data.children, cached.children) cached.nodes.intact = true + parent.appendChild(node) } if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew) } @@ -180,7 +181,6 @@ new function(window) { var currentRoot, currentModule = {view: function() {}}, currentController = {}, now = 0, lastRedraw = 0, lastRedrawId = 0 m.module = function(root, module) { m.startComputation() - cellCache = {} currentRoot = root currentModule = module currentController = new module.controller diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 86323d81..63720923 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -105,6 +105,12 @@ function testMithril(mock) { m.render(root, m("div", [undefined])) return root.childNodes[0].childNodes.length === 0 }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("div.classname", [m("a", {href: "/first"})])) + m.render(root, m("div", [m("a", {href: "/second"})])) + return root.childNodes[0].childNodes.length == 1 + }) //m.redraw test(function() { From bde67fa8959ab484c2d3cd503b6c4738aa4cab3e Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 1 Apr 2014 16:45:02 -0400 Subject: [PATCH 08/38] use less expensive check for attribute diff qualification --- mithril.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mithril.js b/mithril.js index 7754b9bb..f5cc1020 100644 --- a/mithril.js +++ b/mithril.js @@ -119,14 +119,14 @@ new function(window) { function setAttributes(node, dataAttrs, cachedAttrs) { for (var attrName in dataAttrs) { var dataAttr = dataAttrs[attrName] - //if (!(attrName in cachedAttrs) || (cachedAttrs[attrName] !== dataAttr)) { + if (!(attrName in cachedAttrs) || (cachedAttrs[attrName] !== dataAttr) || node === window.document.activeElement) { cachedAttrs[attrName] = dataAttr if (attrName == "config") continue if (attrName.indexOf("on") == 0 && typeof dataAttr == "function") dataAttr = autoredraw(dataAttr, node) if (attrName == "style") for (var rule in dataAttr) node.style[rule] = dataAttr[rule] else if (attrName in node) node[attrName] = dataAttr else node.setAttribute(attrName, dataAttr) - //} + } } return cachedAttrs } @@ -180,7 +180,6 @@ new function(window) { var currentRoot, currentModule = {view: function() {}}, currentController = {}, now = 0, lastRedraw = 0, lastRedrawId = 0 m.module = function(root, module) { m.startComputation() - cellCache = {} currentRoot = root currentModule = module currentController = new module.controller From 2653dbc648b918e09f1bfc0e0f53e5fd66a4448a Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 1 Apr 2014 16:45:42 -0400 Subject: [PATCH 09/38] rebuild --- archive/v0.1.2/mithril-tests.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/archive/v0.1.2/mithril-tests.js b/archive/v0.1.2/mithril-tests.js index 9d285e27..9003ecd4 100644 --- a/archive/v0.1.2/mithril-tests.js +++ b/archive/v0.1.2/mithril-tests.js @@ -119,14 +119,14 @@ new function(window) { function setAttributes(node, dataAttrs, cachedAttrs) { for (var attrName in dataAttrs) { var dataAttr = dataAttrs[attrName] - //if (!(attrName in cachedAttrs) || (cachedAttrs[attrName] !== dataAttr)) { + if (!(attrName in cachedAttrs) || (cachedAttrs[attrName] !== dataAttr) || node === window.document.activeElement) { cachedAttrs[attrName] = dataAttr if (attrName == "config") continue if (attrName.indexOf("on") == 0 && typeof dataAttr == "function") dataAttr = autoredraw(dataAttr, node) if (attrName == "style") for (var rule in dataAttr) node.style[rule] = dataAttr[rule] else if (attrName in node) node[attrName] = dataAttr else node.setAttribute(attrName, dataAttr) - //} + } } return cachedAttrs } @@ -180,7 +180,6 @@ new function(window) { var currentRoot, currentModule = {view: function() {}}, currentController = {}, now = 0, lastRedraw = 0, lastRedrawId = 0 m.module = function(root, module) { m.startComputation() - cellCache = {} currentRoot = root currentModule = module currentController = new module.controller From 24b4b1a10ae6809408198371a51696c223710edd Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 1 Apr 2014 22:05:45 -0400 Subject: [PATCH 10/38] update change log --- archive/v0.1.2/change-log.html | 13 +++++++++++++ archive/v0.1.2/mithril.min.zip | Bin 20180 -> 20180 bytes docs/change-log.md | 15 +++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/archive/v0.1.2/change-log.html b/archive/v0.1.2/change-log.html index 74831ce4..a8b25ba6 100644 --- a/archive/v0.1.2/change-log.html +++ b/archive/v0.1.2/change-log.html @@ -60,6 +60,19 @@

Change Log

+

v0.1.2 - maintenance

+

News:

+ +

Bug Fixes:

+
    +
  • m.render now correctly reattaches reused DOM elements to replaced parent nodes #31
  • +
  • UI actions that can potentially de-synchronize the DOM from cache now force synchronization #29
  • +
+

v0.1.1 - maintenance

News:

    diff --git a/archive/v0.1.2/mithril.min.zip b/archive/v0.1.2/mithril.min.zip index 22edf929553241893728c9e97372d8336a006475..60867379cc9df92285fb05e38ccd898e09b7acf9 100644 GIT binary patch delta 45 vcmcaIm+{J6MxFp~W)?065LmfkBhPVJrj;8uUzM#f0Me6#{j5M#zn?1rRP7KP delta 45 vcmcaIm+{J6MxFp~W)?065IE4fk>|K9(}C8_S7mDqfb`^GKPwQ`@8=2tP)QHW diff --git a/docs/change-log.md b/docs/change-log.md index 88cfc936..58641782 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -1,5 +1,20 @@ ## Change Log +[v0.1.2](/mithril/archive/v0.1.2) - maintenance + +### News: + +- There's now a [community mailing list](mailto:mithriljs@googlegroups.com). There's also [web interface](https://groups.google.com/forum/#!forum/mithriljs) +- Mithril is now on Travis CI. The build status can be found in the [project homepage](https://github.com/lhorie/mithril.js) +- Mithril is now available via the CommonJS and AMD API + +### Bug Fixes: + +- `m.render` now correctly reattaches reused DOM elements to replaced parent nodes [#31](https://github.com/lhorie/mithril.js/issues/31) +- UI actions that can potentially de-synchronize the DOM from cache now force synchronization [#29](https://github.com/lhorie/mithril.js/issues/29) + +--- + [v0.1.1](/mithril/archive/v0.1.1) - maintenance ### News: From db317a0be2ce9d99b1c16e34dbd7179d89075f54 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 1 Apr 2014 22:26:31 -0400 Subject: [PATCH 11/38] fix typo --- archive/v0.1.2/mithril.min.zip | Bin 20430 -> 20430 bytes archive/v0.1.2/package.json | 2 +- docs/layout/package.json | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/archive/v0.1.2/mithril.min.zip b/archive/v0.1.2/mithril.min.zip index 12c80d5311d11e32f6dc2d3b1bc2f02fa40f241a..1c7f4b083509557e934c87b404ef687388670aa6 100644 GIT binary patch delta 45 vcmX>%pYhy$MxFp~W)?065b)T%k;hYx$z${8aJerAKzg#TzZHn;^mhdSEkX_u delta 45 vcmX>%pYhy$MxFp~W)?065ZJwGBaf#X)9y{1!{xph0O`rT{#GEW)87>UOO6lN diff --git a/archive/v0.1.2/package.json b/archive/v0.1.2/package.json index 3870314d..16c2bc6e 100644 --- a/archive/v0.1.2/package.json +++ b/archive/v0.1.2/package.json @@ -6,6 +6,6 @@ "author": "Leo Horie ", "repository": {"type": "git", "url": "https://github.com/lhorie/mithril"}, "main": "mithril.min.js", - "licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}] + "licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}], "files": ["mithril.min.js", "mithril.min.map"] } \ No newline at end of file diff --git a/docs/layout/package.json b/docs/layout/package.json index aca6234f..f0a6d666 100644 --- a/docs/layout/package.json +++ b/docs/layout/package.json @@ -6,6 +6,6 @@ "author": "Leo Horie ", "repository": {"type": "git", "url": "https://github.com/lhorie/mithril"}, "main": "mithril.min.js", - "licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}] + "licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}], "files": ["mithril.min.js", "mithril.min.map"] } \ No newline at end of file From 0f1d415d858b11164160880cf6fe0a885f247a01 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 1 Apr 2014 22:35:50 -0400 Subject: [PATCH 12/38] add docs about npm and bower --- archive/v0.1.2/change-log.html | 3 ++- archive/v0.1.2/installation.html | 7 +++++++ archive/v0.1.2/mithril.min.zip | Bin 20430 -> 20430 bytes docs/change-log.md | 3 ++- docs/installation.md | 18 ++++++++++++++++++ 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/archive/v0.1.2/change-log.html b/archive/v0.1.2/change-log.html index a8b25ba6..d81c9e97 100644 --- a/archive/v0.1.2/change-log.html +++ b/archive/v0.1.2/change-log.html @@ -63,9 +63,10 @@

    v0.1.2 - maintenance

    News:

    Bug Fixes:

      diff --git a/archive/v0.1.2/installation.html b/archive/v0.1.2/installation.html index ae556acd..0d908c99 100644 --- a/archive/v0.1.2/installation.html +++ b/archive/v0.1.2/installation.html @@ -60,6 +60,13 @@

      JsDelivr

      <script src="//cdn.jsdelivr.net/mithril/0.1.2/mithril.min.js"></script>

      +

      NPM

      +

      NPM is the default package manager for NodeJS. If you're using NodeJS already or planning on using Grunt to create a build system, you can use NPM to conveniently keep up-to-date with Mithril versions.

      +

      Assuming you have NodeJS installed, you can download Mithril by typing this:

      +
      npm install mithril
      +

      Then, to use Mithril, point a script tag to the downloaded file:

      +
      <script src="/node_modules/mithril/mithril.min.js"></script>
      +

      Bower

      Bower is a package manager for NodeJS. If you're using NodeJS already or planning on using Grunt to create a build system, you can use Bower to conveniently keep up-to-date with Mithril versions.

      Assuming you have NodeJS installed, you can install Bower by typing this in the command line:

      diff --git a/archive/v0.1.2/mithril.min.zip b/archive/v0.1.2/mithril.min.zip index 1c7f4b083509557e934c87b404ef687388670aa6..940bb1fbc13e4c4bb90fcc76ee510c082023ea11 100644 GIT binary patch delta 45 vcmX>%pYhy$MxFp~W)?065J=jxk;hYxDQU~*aJerAKzg#TzZHn;^mhdSG-?if delta 45 vcmX>%pYhy$MxFp~W)?065b)T%k;hYx$z${8aJerAKzg#TzZHn;^mhdSEkX_u diff --git a/docs/change-log.md b/docs/change-log.md index 58641782..98f6780c 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -4,9 +4,10 @@ ### News: -- There's now a [community mailing list](mailto:mithriljs@googlegroups.com). There's also [web interface](https://groups.google.com/forum/#!forum/mithriljs) +- There's now a [community mailing list](mailto:mithriljs@googlegroups.com). There's also a [web interface](https://groups.google.com/forum/#!forum/mithriljs) - Mithril is now on Travis CI. The build status can be found in the [project homepage](https://github.com/lhorie/mithril.js) - Mithril is now available via the CommonJS and AMD API +- Mithril can now [be installed via npm and bower](installation.md) ### Bug Fixes: diff --git a/docs/installation.md b/docs/installation.md index 82e5430e..1c5fe733 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -38,6 +38,24 @@ Content delivery networks allow the library to be cached across different websit --- +### NPM + +NPM is the default package manager for [NodeJS](http://nodejs.org/). If you're using NodeJS already or planning on using [Grunt](http://gruntjs.com/) to create a build system, you can use NPM to conveniently keep up-to-date with Mithril versions. + +Assuming you have NodeJS installed, you can download Mithril by typing this: + +``` +npm install mithril +``` + +Then, to use Mithril, point a script tag to the downloaded file: + +```markup + +``` + +--- + ### Bower [Bower](http://http://bower.io) is a package manager for [NodeJS](http://nodejs.org/). If you're using NodeJS already or planning on using [Grunt](http://gruntjs.com/) to create a build system, you can use Bower to conveniently keep up-to-date with Mithril versions. From 26ee6975a073bd102515470aebc96e42ec3eaf22 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Wed, 2 Apr 2014 15:24:21 -0400 Subject: [PATCH 13/38] don't assume document if no root element --- mithril.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mithril.js b/mithril.js index 1bc3adba..f666364e 100644 --- a/mithril.js +++ b/mithril.js @@ -187,7 +187,7 @@ new function(window) { m.endComputation() } m.redraw = function() { - m.render(currentRoot || window.document, currentModule.view(currentController)) + m.render(currentRoot, currentModule.view(currentController)) lastRedraw = now } function redraw() { From e34cc1fc4a12b5e2d2a2f5c7fba7bf1be3b20fc0 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Wed, 2 Apr 2014 15:25:26 -0400 Subject: [PATCH 14/38] version bump --- Gruntfile.js | 2 +- archive/v0.1.3/auto-redrawing.html | 138 +++ archive/v0.1.3/change-log.html | 113 +++ archive/v0.1.3/comparison.html | 128 +++ .../v0.1.3/comparisons/angular.parsing.html | 2 + .../v0.1.3/comparisons/angular.rendering.html | 14 + .../v0.1.3/comparisons/angular.safety.html | 13 + .../v0.1.3/comparisons/backbone.parsing.html | 4 + .../comparisons/backbone.rendering.html | 30 + .../v0.1.3/comparisons/backbone.safety.html | 29 + .../v0.1.3/comparisons/jquery.parsing.html | 2 + .../v0.1.3/comparisons/jquery.rendering.html | 17 + archive/v0.1.3/comparisons/jquery.safety.html | 16 + .../v0.1.3/comparisons/mithril.parsing.html | 2 + .../v0.1.3/comparisons/mithril.rendering.html | 20 + .../v0.1.3/comparisons/mithril.safety.html | 19 + archive/v0.1.3/compiling-templates.html | 110 +++ archive/v0.1.3/component.json | 10 + archive/v0.1.3/components.html | 191 +++++ archive/v0.1.3/getting-started.html | 446 ++++++++++ archive/v0.1.3/how-to-read-signatures.html | 153 ++++ archive/v0.1.3/index.html | 196 +++++ archive/v0.1.3/installation.html | 92 ++ archive/v0.1.3/integration.html | 147 ++++ archive/v0.1.3/lib/prism/prism.css | 126 +++ archive/v0.1.3/lib/prism/prism.js | 9 + archive/v0.1.3/mithril-tests.js | 803 ++++++++++++++++++ archive/v0.1.3/mithril.computation.html | 159 ++++ archive/v0.1.3/mithril.deferred.html | 136 +++ archive/v0.1.3/mithril.html | 281 ++++++ archive/v0.1.3/mithril.min.js | 8 + archive/v0.1.3/mithril.min.map | 1 + archive/v0.1.3/mithril.min.zip | Bin 0 -> 20403 bytes archive/v0.1.3/mithril.module.html | 175 ++++ archive/v0.1.3/mithril.prop.html | 140 +++ archive/v0.1.3/mithril.redraw.html | 89 ++ archive/v0.1.3/mithril.render.html | 121 +++ archive/v0.1.3/mithril.request.html | 346 ++++++++ archive/v0.1.3/mithril.route.html | 221 +++++ archive/v0.1.3/mithril.sync.html | 111 +++ archive/v0.1.3/mithril.trust.html | 120 +++ archive/v0.1.3/mithril.withAttr.html | 125 +++ archive/v0.1.3/mithril.xhr.html | 76 ++ archive/v0.1.3/package.json | 11 + archive/v0.1.3/pages.json | 4 + archive/v0.1.3/practices.html | 118 +++ archive/v0.1.3/refactoring.html | 63 ++ archive/v0.1.3/roadmap.html | 103 +++ archive/v0.1.3/routing.html | 127 +++ archive/v0.1.3/style.css | 91 ++ archive/v0.1.3/tools.html | 94 ++ archive/v0.1.3/tools/template-compiler.sjs | 64 ++ archive/v0.1.3/tools/template-converter.html | 9 + archive/v0.1.3/tools/template-converter.js | 89 ++ archive/v0.1.3/web-services.html | 212 +++++ 55 files changed, 5925 insertions(+), 1 deletion(-) create mode 100644 archive/v0.1.3/auto-redrawing.html create mode 100644 archive/v0.1.3/change-log.html create mode 100644 archive/v0.1.3/comparison.html create mode 100644 archive/v0.1.3/comparisons/angular.parsing.html create mode 100644 archive/v0.1.3/comparisons/angular.rendering.html create mode 100644 archive/v0.1.3/comparisons/angular.safety.html create mode 100644 archive/v0.1.3/comparisons/backbone.parsing.html create mode 100644 archive/v0.1.3/comparisons/backbone.rendering.html create mode 100644 archive/v0.1.3/comparisons/backbone.safety.html create mode 100644 archive/v0.1.3/comparisons/jquery.parsing.html create mode 100644 archive/v0.1.3/comparisons/jquery.rendering.html create mode 100644 archive/v0.1.3/comparisons/jquery.safety.html create mode 100644 archive/v0.1.3/comparisons/mithril.parsing.html create mode 100644 archive/v0.1.3/comparisons/mithril.rendering.html create mode 100644 archive/v0.1.3/comparisons/mithril.safety.html create mode 100644 archive/v0.1.3/compiling-templates.html create mode 100644 archive/v0.1.3/component.json create mode 100644 archive/v0.1.3/components.html create mode 100644 archive/v0.1.3/getting-started.html create mode 100644 archive/v0.1.3/how-to-read-signatures.html create mode 100644 archive/v0.1.3/index.html create mode 100644 archive/v0.1.3/installation.html create mode 100644 archive/v0.1.3/integration.html create mode 100644 archive/v0.1.3/lib/prism/prism.css create mode 100644 archive/v0.1.3/lib/prism/prism.js create mode 100644 archive/v0.1.3/mithril-tests.js create mode 100644 archive/v0.1.3/mithril.computation.html create mode 100644 archive/v0.1.3/mithril.deferred.html create mode 100644 archive/v0.1.3/mithril.html create mode 100644 archive/v0.1.3/mithril.min.js create mode 100644 archive/v0.1.3/mithril.min.map create mode 100644 archive/v0.1.3/mithril.min.zip create mode 100644 archive/v0.1.3/mithril.module.html create mode 100644 archive/v0.1.3/mithril.prop.html create mode 100644 archive/v0.1.3/mithril.redraw.html create mode 100644 archive/v0.1.3/mithril.render.html create mode 100644 archive/v0.1.3/mithril.request.html create mode 100644 archive/v0.1.3/mithril.route.html create mode 100644 archive/v0.1.3/mithril.sync.html create mode 100644 archive/v0.1.3/mithril.trust.html create mode 100644 archive/v0.1.3/mithril.withAttr.html create mode 100644 archive/v0.1.3/mithril.xhr.html create mode 100644 archive/v0.1.3/package.json create mode 100644 archive/v0.1.3/pages.json create mode 100644 archive/v0.1.3/practices.html create mode 100644 archive/v0.1.3/refactoring.html create mode 100644 archive/v0.1.3/roadmap.html create mode 100644 archive/v0.1.3/routing.html create mode 100644 archive/v0.1.3/style.css create mode 100644 archive/v0.1.3/tools.html create mode 100644 archive/v0.1.3/tools/template-compiler.sjs create mode 100644 archive/v0.1.3/tools/template-converter.html create mode 100644 archive/v0.1.3/tools/template-converter.js create mode 100644 archive/v0.1.3/web-services.html diff --git a/Gruntfile.js b/Gruntfile.js index 0cf23688..81c67351 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,6 +1,6 @@ module.exports = function(grunt) { - var version = "0.1.2" + var version = "0.1.3" var inputFolder = "./docs" var tempFolder = "./temp" diff --git a/archive/v0.1.3/auto-redrawing.html b/archive/v0.1.3/auto-redrawing.html new file mode 100644 index 00000000..e740767f --- /dev/null +++ b/archive/v0.1.3/auto-redrawing.html @@ -0,0 +1,138 @@ + + + + Mithril + + + + + +
      + +
      +
      +
      +
      +
      + +
      +

      Integrating with The Auto-Redrawing System

      +

      If you need to do custom asynchronous calls without using Mithril's API, and find that your views are not redrawing, or that you're being forced to call m.redraw manually, you should consider using m.startComputation / m.endComputation so that Mithril can intelligently auto-redraw once your custom code finishes running.

      +

      In order to integrate asynchronous code to Mithril's autoredrawing system, you should call m.startComputation BEFORE making an asynchronous call, and m.endComputation after the asynchronous callback completes.

      +
      //this service waits 1 second, logs "hello" and then notifies the view that
      +//it may start redrawing (if no other asynchronous operations are pending)
      +var doStuff = function() {
      +    m.startComputation(); //call `startComputation` before the asynchronous `setTimeout`
      +
      +    setTimeout(function() {
      +        console.log("hello");
      +
      +        m.endComputation(); //call `endComputation` at the end of the callback
      +    }, 1000);
      +};
      +

      To integrate synchronous code, call m.startComputation at the beginning of the method, and m.endComputation at the end.

      +
      window.onfocus = function() {
      +    m.startComputation(); //call before everything else in the event handler
      +
      +    doStuff();
      +
      +    m.endComputation(); //call after everything else in the event handler
      +}
      +

      For each m.startComputation call a library makes, it MUST also make one and ONLY one corresponding m.endComputation call.

      +

      You should not use these methods if your code is intended to run repeatedly (e.g. by using setInterval). If you want to repeatedly redraw the view without necessarily waiting for user input, you should manually call m.redraw within the repeatable context.

      +
      +

      Integrating multiple execution threads

      +

      When integrating with third party libraries, you might find that you need to call asynchronous methods from outside of Mithril's API.

      +

      In order to integrate non-trivial asynchronous code to Mithril's auto-redrawing system, you need to ensure all execution threads call m.startComputation / m.endComputation.

      +

      An execution thread is basically any amount of code that runs before other asynchronous threads start to run.

      +

      Integrating multiple execution threads can be done in a two different ways: in a layered fashion or in comprehensive fashion

      +

      Layered integration

      +

      Layered integration is recommended for modular code where many different APIs may be put together at the application level.

      +

      Below is an example where various methods implemented with a third party library can be integrated in layered fashion: any of the methods can be used in isolation or in combination.

      +

      Notice how doBoth repeatedly calls m.startComputation since that method calls both doSomething and doAnother. This is perfectly valid: there are three asynchronous computations pending after the jQuery.when method is called, and therefore, three pairs of m.startComputation / m.endComputation in play.

      +
      var doSomething = function(callback) {
      +    m.startComputation(); //call `startComputation` before the asynchronous AJAX request
      +
      +    return jQuery.ajax("/something").done(function() {
      +        if (callback) callback();
      +
      +        m.endComputation(); //call `endComputation` at the end of the callback
      +    });
      +};
      +var doAnother = function(callback) {
      +    m.startComputation(); //call `startComputation` before the asynchronous AJAX request
      +
      +    return jQuery.ajax("/another").done(function() {
      +        if (callback) callback();
      +        m.endComputation(); //call `endComputation` at the end of the callback
      +    });
      +};
      +var doBoth = function(callback) {
      +    m.startComputation(); //call `startComputation` before the asynchronous synchronization method
      +
      +    jQuery.when(doSomething(), doAnother()).then(function() {
      +        if (callback) callback();
      +
      +        m.endComputation(); //call `endComputation` at the end of the callback
      +    })
      +};
      +

      Comprehensive integration

      +

      Comprehensive integration is recommended if integrating a monolithic series of asynchronous operations. In contrast to layered integration, it minimizes the number of m.startComputation / m.endComputation to avoid clutter.

      +

      The example below shows a convoluted series of AJAX requests implemented with a third party library.

      +
      var doSomething = function(callback) {
      +    m.startComputation(); //call `startComputation` before everything else
      +
      +    jQuery.ajax("/something").done(function() {
      +        doStuff();
      +        jQuery.ajax("/another").done(function() {
      +            doMoreStuff();
      +            jQuery.ajax("/more").done(function() {
      +                if (callback) callback();
      +
      +                m.endComputation(); //call `endComputation` at the end of everything
      +            });
      +        });
      +    });
      +};
      + +
      +
      +
      +
      +
      +
      +
      + Released under the MIT license +
      © 2014 Leo Horie +
      +
      + + + \ No newline at end of file diff --git a/archive/v0.1.3/change-log.html b/archive/v0.1.3/change-log.html new file mode 100644 index 00000000..d0ac7884 --- /dev/null +++ b/archive/v0.1.3/change-log.html @@ -0,0 +1,113 @@ + + + + Mithril + + + + + +
      + +
      +
      +
      +
      +
      + +
      +

      Change Log

      +

      v0.1.2 - maintenance

      +

      News:

      + +

      Bug Fixes:

      +
        +
      • m.render now correctly reattaches reused DOM elements to replaced parent nodes #31
      • +
      • UI actions that can potentially de-synchronize the DOM from cache now force synchronization #29
      • +
      +
      +

      v0.1.1 - maintenance

      +

      News:

      + +

      Bug Fixes:

      +
        +
      • m.route.param now resets on route change correctly #15
      • +
      • m.render now correctly ignores undefined values in the virtual tree#16
      • +
      • errors thrown in promises now cause downstreams to be rejected #1
      • +
      +

      Breaking changes:

      +
        +
      • changed default value for xhr.withCredentials from true to false for m.request, since public APIs are more common than auth-walled ones. #14

        +

        In order to configure this flag, the following configuration should be used:

        +
        var privateAPI = function(xhr) {xhr.withCredentials = true};
        +
        +m.request({method: "GET", url: "http://foo.com/api", config: privateAPI});
        +
      • +
      +
      +

      v0.1 - Initial release

      + +
      +
      +
      +
      +
      +
      +
      + Released under the MIT license +
      © 2014 Leo Horie +
      +
      + + + \ No newline at end of file diff --git a/archive/v0.1.3/comparison.html b/archive/v0.1.3/comparison.html new file mode 100644 index 00000000..2059f3ea --- /dev/null +++ b/archive/v0.1.3/comparison.html @@ -0,0 +1,128 @@ + + + + Mithril + + + + + +
      + +
      +
      +
      +
      +
      + +
      +

      How is Mithril Different from Other Frameworks

      +

      There are a lot of different Javascript MVC frameworks and evaluating their merits and shortcomings can be a daunting task.

      +

      This page aims to provide a comparison between Mithril and some of the most widely used frameworks, as well as some of the younger, but relevant ones.

      +

      Code Size

      +

      One of the most obvious differences between Mithril and most frameworks is in file size: Mithril is less than 3kb gzipped and has no dependencies on other libraries.

      +

      Note that while a small gzipped size can look appealing, that number is often used to "hide the weight" of the uncompressed code: remember that the decompressed Javascript still needs to be parsed and evaluated on every page load, and this cost (which can be in the dozens of milliseconds range for some frameworks in some browsers) cannot be cached.

      +

      This cost might be less of a concern in single page apps, but not necessarily if the app is typically opened simultaneously in multiple tabs, or in less powerful devices.

      +

      The performance tests in the homepage show execution times for parsing and evaluation of Mithril's code, compared to some popular frameworks. As you can see, it paints a much less flattering picture for some frameworks than when we look at gzipped size alone.

      +

      Documentation

      +

      Another point of comparison is documentation. Most of the popular frameworks have at least a bare minimum amount of documentation nowadays, but many leave a bit to be desired: some lack usage examples, and some frameworks' communities need to rely heavily on third party sites for explanations of more advanced topics, and sometimes even for learning the basics.

      +

      This is a problem particularly for frameworks that had breaking changes in the past: It's common to find answers in StackOverflow that are out-of-date and no longer work with the latest version of said frameworks.

      +

      Mithril has more documentation in this site than the amount of code in the framework, and none of the documentation is auto-generated.

      +

      All API points are explained in prose, and have code examples. Because the entire documentation is hand-crafted, you get the benefit of actually having explanations for things that documentation generator tools don't support well (for example, interfaces and callback parameter documentation).

      +

      In addition, this guide section covers topics related to how to fit all the pieces together.

      +

      From the get-go, Mithril's build system produces archived versions of the code and documentation so that you'll never be stuck without docs for out-of-date versions.

      +

      Given how young Mithril is, hopefully you can appreciate the level of commitment for providing good documentation.

      +

      Architecture

      +

      In terms of architecture, one of Mithril's main differences is that it does not provide base classes to extend from.

      +

      It's often said that frameworks, in contrast to libraries, dictate how code should be written. In this sense, one could argue that Mithril isn't really a framework.

      +

      Instead of locking developers down to very specific implementations of design patterns, Mithril's approach is to provide an idiomatic pattern to follow, and tools to aid the developer when required. This approach means that developers can get discoverable codebases without necessarily getting locked into the framework.

      +

      One related difference is that other frameworks often have hard-coded base classes where every conceivable convenience method gets inherited by the developer's classes (remember, in Javascript, this can mean copying all of the utility methods over to the child class, regardless of whether they're going to be used or not).

      +

      Mithril's on-demand tooling approach means there are no hidden performance costs when implementing core MVC patterns, and there's also no extra learning curve for framework-specific syntax for those patterns.

      +

      View Layer Paradigm

      +

      Some of the older frameworks among the popular ones (out-of-the-box jQuery and Backbone, specifically) take a more procedural paradigm when it comes to the view layer; this means every action requires the developer to write custom view-level code to handle it.

      +

      This can get noticeably bulky when you look at thing like collections: you often need to implement insertion code and deletion code, in addition to a "draw everything" routine for performance. And this is for every list that needs to be displayed in some way.

      +

      Mithril's view layer paradigm is designed be declarative, much like HTML, such that the same code implicitly does everything it needs to. As it turns out, this design decision is actually a compromise: it offers the benefit of decreased application code complexity at the cost of some performance loss. However, as the performance tests in the homepage show, this does not necessarily hurt Mithril in a meaningful way.

      +
      +

      Specific Framework Comparisons

      +

      Warning: this section is likely biased. Take it with a grain of salt.

      +

      jQuery

      +

      jQuery is ubiquitous and has a large ecosystem, but it's not an MVC framework.

      +

      There's no idiomatic way to organize jQuery code in an MVC pattern and many frameworks were created specifically to overcome that shortcoming.

      +

      As summarized above, Mithril differs from jQuery by allowing DOM-related code to be written largely in a declarative style (thereby decreasing code complexity), in addition to providing an idiomatic way to structure applications.

      +

      One other difference that is extremely clear is the treatment of data. In jQuery it's common to use the DOM as a data storage mechanism, whereas Mithril encourages data to exist in an isolated model layer.

      +

      Backbone

      +

      Backbone was originally designed as a way to structure jQuery-based applications. One of its selling points is that it allows developers to leverage their existing jQuery knowledge, while providing some "walls" to organize the code in a more structured manner.

      +

      As with jQuery, Mithril differs from Backbone by enforcing view code to be written in a declarative style.

      +

      Another marking difference is that Backbone is workflow agnostic, that is, there's no one idiomatic way to organize applications. This is good for framework adoption, but not necessarily ideal for team scalability and codebase discoverability.

      +

      In contrast, Mithril encourages that applications be developed using the patterns found throughout this guide. This discourages "bastardized" MVC pattern variations and architecturing style fragmentation.

      +

      One technical aspect that is also different is that Backbone is heavily event-oriented. Mithril, on the other hand, purposely avoids the observer pattern in an attempt to abolish "come-from hell", i.e. a class of debugging problems where you don't know what triggers some code because of a long chain of events triggering other events.

      +

      A particularly nasty instance of this problem that sometimes occurs in "real-time" applications is when event triggering chains become circular due to a conditional statement bug, causing infinite loops and browser crashes.

      +

      Another significant difference between Backbone and Mithril is in their approach to familiarity: Backbone appeals to people familiar w/ jQuery; Mithril is designed to be familiar to people with server-side MVC framework experience.

      +

      Angular

      +

      Angular is an MVC framework maintained by Google, and it provides a declarative view layer and an emphasis on testability. It leverages developer experience with server-side MVC frameworks, and in many ways, is very similar in scope to Mithril.

      +

      The main difference between Angular templates and Mithril templates is that Angular templates follow the tradition of being defined in HTML. This has the benefit of cleaner syntax for writing static text, but it comes with the disadvantage of features getting awkwardly tied to HTML syntax, as well as providing poor debugging support.

      +

      One thing you may have noticed in the homepage is that, out of the box, Angular is not as performant as other frameworks. Steep performance degradation is a notoriously common issue in non-trivial Angular applications and there are several third party libraries which attempt to get around performance problems. Speaking from experience, it's generally difficult to reason about performance in Angular.

      +

      Mithril takes some learnings from that and implements a templating redrawing system that renders less aggressively, is less complex and is easier to profile.

      +

      A noteworthy difference between Angular and Mithril is in framework complexity: Angular implements several subsystems that would seem more logical in programming language implementations (e.g. a parser, a dynamic scoping mechanism, decorators, etc). Mithril, on the other hand, tries to provide only features to support a more classic MVC paradigm.

      +

      Ember

      +

      Ember is a highly comprehensive MVC framework, providing a large API that covers not only traditional MVC patterns, but also a vast range of helper utilities as well.

      +

      The biggest difference between Ember and Mithril is summarized in the Architecture section above: Ember's comprehensiveness come at a cost of a steep learning curve, and a high degree of vendor lock-in.

      +

      Ember is also more opinionated in terms of how application architecture should look, and as a result, tends to be less transparent in terms of what is actually happening under the hood.

      +

      React

      +

      React is a templating engine developed by Facebook. It's relevant for comparison because it uses the same architecture as Mithril's templating engine: i.e. it acknowledges that DOM operations are the bottleneck of templating systems, and implements a virtual DOM tree which keeps track of changes and only applies diffs to the real DOM where needed.

      +

      The most visible difference between React and Mithril is that React's JSX syntax does not run natively in the browser, whereas Mithril's uncompiled templates do. Both can be compiled, but React's compiled code still has function calls for each virtual DOM element; Mithril templates compile into static javascript data structures.

      +

      Another difference is that Mithril, being an MVC framework, rather than a templating engine, provides an auto-redrawing system that is aware of network asynchrony and that can render views efficiently without cluttering application code with redraw calls, and without letting the developer unintentionally bleed out of the MVC pattern.

      +

      Note also that, despite having a bigger scope, Mithril has a smaller file size than React.

      +

      Knockout

      +

      Knockout is a library focused on data binding. It is not an MVC framework in the traditional sense, but idiomatic Knockout code uses the similar concept of view models.

      +

      A Knockout view model is an amalgamation of model and controller layers in a single class. In contrast, Mithril separates the two layers more distinctly.

      +

      Generally speaking, Knockout applications tend to be more tightly coupled than Mithril since Knockout doesn't provide an equivalent to Mithril's modules and components.

      +

      As with Angular, Knockout templates are written in HTML, and therefore have the same pros and cons as Angular templates.

      +

      Vue

      +

      Vue is a relatively new and unknown templating engine, but it boasts impressive results in its performance benchmark.

      +

      It is not a full MVC framework, but it is similar to Angular templates, and uses the same terminology for its features (e.g. directives and filters).

      +

      The most relevant difference is that Vue uses browser features that don't work (and cannot be made to work) in Internet Explorer 8. Mithril allows developers to support browsers all the way back to IE6 and Blackberry.

      +

      Vue's implementation cleverly hijacks array methods, but it should be noted that Javascript Arrays cannot be truly subclassed and as such, Vue suffers from abstraction leaks.

      +

      In contrast, Mithril avoids "magic" types.

      + +
      +
      +
      +
      +
      +
      +
      + Released under the MIT license +
      © 2014 Leo Horie +
      +
      + + + \ No newline at end of file diff --git a/archive/v0.1.3/comparisons/angular.parsing.html b/archive/v0.1.3/comparisons/angular.parsing.html new file mode 100644 index 00000000..642d6448 --- /dev/null +++ b/archive/v0.1.3/comparisons/angular.parsing.html @@ -0,0 +1,2 @@ + +To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better) \ No newline at end of file diff --git a/archive/v0.1.3/comparisons/angular.rendering.html b/archive/v0.1.3/comparisons/angular.rendering.html new file mode 100644 index 00000000..8cf81d82 --- /dev/null +++ b/archive/v0.1.3/comparisons/angular.rendering.html @@ -0,0 +1,14 @@ + + + +

      To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better)

      +
      + +
      + + + diff --git a/archive/v0.1.3/comparisons/angular.safety.html b/archive/v0.1.3/comparisons/angular.safety.html new file mode 100644 index 00000000..01403424 --- /dev/null +++ b/archive/v0.1.3/comparisons/angular.safety.html @@ -0,0 +1,13 @@ + + + +
      + +
      + + + diff --git a/archive/v0.1.3/comparisons/backbone.parsing.html b/archive/v0.1.3/comparisons/backbone.parsing.html new file mode 100644 index 00000000..e30e1eaf --- /dev/null +++ b/archive/v0.1.3/comparisons/backbone.parsing.html @@ -0,0 +1,4 @@ + + + +To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better) \ No newline at end of file diff --git a/archive/v0.1.3/comparisons/backbone.rendering.html b/archive/v0.1.3/comparisons/backbone.rendering.html new file mode 100644 index 00000000..07f38489 --- /dev/null +++ b/archive/v0.1.3/comparisons/backbone.rendering.html @@ -0,0 +1,30 @@ + + + + + + + +

      To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better)

      +
      + + + + + diff --git a/archive/v0.1.3/comparisons/backbone.safety.html b/archive/v0.1.3/comparisons/backbone.safety.html new file mode 100644 index 00000000..e5a71e08 --- /dev/null +++ b/archive/v0.1.3/comparisons/backbone.safety.html @@ -0,0 +1,29 @@ + + + + + + + +
      + + + + + diff --git a/archive/v0.1.3/comparisons/jquery.parsing.html b/archive/v0.1.3/comparisons/jquery.parsing.html new file mode 100644 index 00000000..1fa16592 --- /dev/null +++ b/archive/v0.1.3/comparisons/jquery.parsing.html @@ -0,0 +1,2 @@ + +To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better) \ No newline at end of file diff --git a/archive/v0.1.3/comparisons/jquery.rendering.html b/archive/v0.1.3/comparisons/jquery.rendering.html new file mode 100644 index 00000000..dde611ec --- /dev/null +++ b/archive/v0.1.3/comparisons/jquery.rendering.html @@ -0,0 +1,17 @@ + + + +

      To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better)

      +
      + + + diff --git a/archive/v0.1.3/comparisons/jquery.safety.html b/archive/v0.1.3/comparisons/jquery.safety.html new file mode 100644 index 00000000..e9667269 --- /dev/null +++ b/archive/v0.1.3/comparisons/jquery.safety.html @@ -0,0 +1,16 @@ + + + +
      + + + diff --git a/archive/v0.1.3/comparisons/mithril.parsing.html b/archive/v0.1.3/comparisons/mithril.parsing.html new file mode 100644 index 00000000..c2957a15 --- /dev/null +++ b/archive/v0.1.3/comparisons/mithril.parsing.html @@ -0,0 +1,2 @@ + +To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better) \ No newline at end of file diff --git a/archive/v0.1.3/comparisons/mithril.rendering.html b/archive/v0.1.3/comparisons/mithril.rendering.html new file mode 100644 index 00000000..717f5d5e --- /dev/null +++ b/archive/v0.1.3/comparisons/mithril.rendering.html @@ -0,0 +1,20 @@ + + + +

      To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better)

      +
      + + + diff --git a/archive/v0.1.3/comparisons/mithril.safety.html b/archive/v0.1.3/comparisons/mithril.safety.html new file mode 100644 index 00000000..1e6ba153 --- /dev/null +++ b/archive/v0.1.3/comparisons/mithril.safety.html @@ -0,0 +1,19 @@ + + + +
      + + + diff --git a/archive/v0.1.3/compiling-templates.html b/archive/v0.1.3/compiling-templates.html new file mode 100644 index 00000000..6d130354 --- /dev/null +++ b/archive/v0.1.3/compiling-templates.html @@ -0,0 +1,110 @@ + + + + Mithril + + + + + +
      + +
      +
      +
      +
      +
      + +
      +

      Compiling Templates

      +

      If performance is absolutely critical, you can optionally pre-compile templates that use m() by running the template-compiler.sjs macro with Sweet.js

      +

      Compiling a template transforms the nested function calls into a raw virtual DOM tree (which is merely a collection of native Javascript objects that is ready to be rendered via m.render)

      +

      For example, given the following template:

      +
      var view = function() {
      +    return m("a", {href: "http://google.com"}, "test");
      +}
      +

      It would be compiled into:

      +
      var view = function() {
      +    return {tag: "a", attrs: {href: "http://google.com"}, children: "test"};
      +}
      +

      Note that compiled templates are meant to be generated by an automated build process and are not meant to be human editable.

      +
      +

      Installing NodeJS and SweetJS for one-off compilations

      +

      SweetJS requires a NodeJS environment. To install it, go to its website and use the installer provided.

      +

      To install SweetJS, NodeJS provides a command-line package manager tool. In a command line, type:

      +
      npm install -g sweet.js
      +

      To compile a file, type:

      +
      sjs --module /mithril.compile.sjs --output <output-filename>.js <input-filename>.js
      +
      +

      Automating Compilation

      +

      If you want to automate compilation, you can use GruntJS, a task automation tool. If you're not familiar with GruntJS, you can find a tutorial on their website.

      +

      Assuming NodeJS is already installed, run the following command to install GruntJS:

      +
      npm install -g grunt-cli
      +

      Once installed, create two files in the root of your project, package.json and Gruntfile.js

      +

      package.json

      +
      {
      +    "name": "project-name-goes-here",
      +    "version": "0.0.0", //must follow this format
      +    "devDependencies": {
      +        "grunt-sweet.js": "*"
      +    }
      +}
      +

      Gruntfile.js

      +
      module.exports = function(grunt) {
      +    grunt.initConfig({
      +        sweetjs: {
      +            modules: ["mithril.compile.sjs"],
      +            compile: {expand: true, cwd: ".", src: "**/*.js", dest: "destination-folder-goes-here/"}
      +        }
      +    });
      +
      +    grunt.loadNpmTasks('grunt-sweet.js');
      +
      +    grunt.registerTask('default', ['sweetjs']);
      +}
      +

      Make sure to replace the project-name-goes-here and destination-folder-goes-here placeholders with appropriate values.

      +

      To run the automation task, run the following command from the root folder of your project:

      +
      grunt
      +

      More documentation on the grunt-sweet.js task and its options can be found here

      + +
      +
      +
      +
      +
      +
      +
      + Released under the MIT license +
      © 2014 Leo Horie +
      +
      + + + \ No newline at end of file diff --git a/archive/v0.1.3/component.json b/archive/v0.1.3/component.json new file mode 100644 index 00000000..10799ed8 --- /dev/null +++ b/archive/v0.1.3/component.json @@ -0,0 +1,10 @@ +{ + "name": "mithril", + "description": "A Javascript framework for building brilliant applications", + "keywords": ["mvc", "framework"], + "repo": "lhorie/mithril", + "main": "mithril.min.js", + "scripts": ["mithril.min.js"], + "version": "0.1.3", + "license": "MIT" +} \ No newline at end of file diff --git a/archive/v0.1.3/components.html b/archive/v0.1.3/components.html new file mode 100644 index 00000000..3a327011 --- /dev/null +++ b/archive/v0.1.3/components.html @@ -0,0 +1,191 @@ + + + + Mithril + + + + + +
      + +
      +
      +
      +
      +
      + +
      +

      Components

      +

      Components are Mithril's mechanism for hierarchical MVC.

      +

      They allow complex, repeating logic to be abstracted into a logical unit of code, and they help modularize applications with widgets or multi-concern views (e.g. dashboards).

      +

      You can also use components for a number of other advanced techniques, like recursive templating (e.g. tree views) and partial template mixins (i.e. injecting part of a template into another).

      +
      +

      Nesting components

      +

      Here's an example of nested modules in a widgetization scenario:

      +
      //root module
      +var dashboard = {};
      +
      +dashboard.controller = function() {
      +    this.userProfile = new userProfile.controller();
      +    this.projectList = new projectList.controller();
      +}
      +
      +dashboard.view = function(ctrl) {
      +    return m("#example", [
      +        m(".profile", [
      +            new userProfile.view(ctrl.userProfile);
      +        ]),
      +        m(".projects", [
      +            new projectList.view(ctrl.projectList);
      +        ])
      +    ])
      +}
      +
      +
      +
      +//components
      +
      +//user profile component
      +var userProfile = {};
      +
      +userProfile.controller = function() {
      +    this.name = m.prop("John Doe");
      +};
      +
      +userProfile.view = function(ctrl) {
      +    return [
      +        m("h1", "Profile"),
      +        "Name: " + ctrl.name()
      +    ];
      +};
      +
      +
      +
      +//project list component
      +var projectList = {};
      +
      +projectList.controller = function() {};
      +
      +projectList.view = function(ctrl) {
      +    return "There are no projects";
      +};
      +
      +
      +
      +//initialize
      +m.module(document.body, dashboard);
      +

      As you can see, components look exactly like regular modules - it's turtles all the way down! Remember that modules are simply dumb containers for controller and view classes.

      +

      This means components are decoupled both horizontally and vertically. It's possible to refactor each component as a isolated unit of logic (which itself follows the MVC pattern). And we can do so without touching the rest of the application (as long as the component API stays the same).

      +

      Similarly, it's possible to mix and match different classes to make mix-in anonymous components (e.g. it's straightforward to build several views - for, say, a mobile app - that use the same controller).

      +

      It's also possible to keep references to parent and even sibling components. This is useful, for example, when implementing notification badges in a navigation component, which are triggered and managed by other components in the system.

      +
      +

      Librarization

      +

      Applications often reuse rich UI controls that aren't provided out of the box by HTML. Below is a basic example of a component of that type: a minimalist autocompleter component.

      +

      Note: Be mindful that, for the sake of code clarity and brevity, the example below does not support keyboard navigation and other real world features.

      +
      var autocompleter = {};
      +
      +autocompleter.controller = function(data, getter) {
      +    //binding for the text input
      +    this.value = m.prop("");
      +    //store for the list of items
      +    this.data = m.prop([]);
      +
      +    //method to determine what property of a list item to compare the text input's value to
      +    this.getter = getter;
      +
      +    //this method changes the relevance list depending on what's currently in the text input
      +    this.change = function(value) {
      +        this.value(value);
      +
      +        var data = value === "" ? [] : data.filter(function(item) {
      +            return this.getter(item).toLowerCase().indexOf(value.toLowerCase()) > -1;
      +        }, this);
      +        this.data(data);
      +    };
      +
      +    //this method is called when an option is selected. It triggers an `onchange` event
      +    this.select = function(value) {
      +        this.value(value);
      +        this.data([]);
      +        if (this.onchange) this.onchange({target: {value: value}});
      +    };
      +}
      +
      +autocompleter.view = function(ctrl, options) {
      +    if (options) ctrl.onchange = options.onchange;
      +    return [
      +        m("input", {oninput: m.withAttr("value", ctrl.change.bind(ctrl)), value: ctrl.value()}),
      +        ctrl.data().map(function(item) {
      +            return m("div", {data: ctrl.getter(item), onclick: m.withAttr("data", ctrl.select.bind(ctrl))}, ctrl.getter(item));
      +        })
      +    ];
      +}
      +
      +
      +
      +//here's an example of using the autocompleter
      +var dashboard = {}
      +
      +dashboard.controller = function() {
      +    this.names = m.prop([{id: 1, name: "John"}, {id: 2, name: "Bob"}, {id: 2, name: "Mary"}]);
      +    this.autocompleter = new autocompleter.controller(this.names(), function(item) {
      +        return item.name;
      +    });
      +};
      +
      +dashboard.view = function(ctrl) {
      +    //assuming there's an element w/ id = "example" somewhere on the page
      +    return m("#example", [
      +        new autocompleter.view(ctrl.autocompleter, {onchange: m.withAttr("value", console.log)}),
      +    ]);
      +};
      +
      +
      +
      +//initialize
      +m.module(document.body, dashboard);
      +

      It's recommended that libraries that provide extra functionality to Mithril be implemented using this modular pattern, as opposed to trying to hide implementation in a virtual element's config attribute.

      +

      You should only consider using config-based components when leveraging existing libraries.

      + +
      +
      +
      +
      +
      +
      +
      + Released under the MIT license +
      © 2014 Leo Horie +
      +
      + + + \ No newline at end of file diff --git a/archive/v0.1.3/getting-started.html b/archive/v0.1.3/getting-started.html new file mode 100644 index 00000000..2d43789a --- /dev/null +++ b/archive/v0.1.3/getting-started.html @@ -0,0 +1,446 @@ + + + + Mithril + + + + + +
      + +
      +
      +
      +
      +
      + +
      +

      Getting Started

      +

      What is Mithril?

      +

      Mithril is a client-side Javascript MVC framework, i.e. it's a tool to make application code divided into a data layer (called "Model"), a UI layer (called View), and a glue layer (called Controller)

      +

      Mithril is around 3kb gzipped thanks to its small, focused, API. It provides a templating engine with a virtual DOM diff implementation for performant rendering, utilities for high-level modelling via functional composition, as well as support for routing and componentization.

      +

      The goal of the framework is to make application code discoverable, readable and maintainable, and hopefully help you become an even better developer.

      +

      Unlike some frameworks, Mithril tries very hard to avoid locking you into a web of dependencies: you can use as little of the framework as you need.

      +

      However, using its entire toolset idiomatically can bring lots of benefits: learning to use functional programming in real world scenarios and solidifying good coding practices for OOP and MVC are just some of them.

      +
      +

      A Simple Application

      +

      Once you have a copy of Mithril, getting started is surprisingly boilerplate-free:

      +
      <!doctype html>
      +<script src="mithril.js"></script>
      +<script>
      +//app goes here
      +</script>
      +

      Yes, this is valid HTML 5! According to the specs, the <html>, <head> and <body> tags can be omitted, but their respective DOM elements will still be there implicitly when a browser renders that markup.

      +
      +

      Model

      +

      In Mithril, typically an application lives in an namespace and contains modules. Modules are merely structures that represent a viewable "page" or component.

      +

      For simplicity, our application will have only one module, and we're going to use it as the namespace for our application:

      +
      <script>
      +//this application only has one module: todo
      +var todo = {};
      +</script>
      +

      This object will namespace our two Model classes:

      +
      var todo = {};
      +
      +//for simplicity, we use this module to namespace the model classes
      +
      +//the Todo class has two properties
      +todo.Todo = function(data) {
      +    this.description = m.prop(data.description);
      +    this.done = m.prop(false);
      +};
      +
      +//the TodoList class is a list of Todo's
      +todo.TodoList = Array;
      +

      m.prop is simply a factory for a getter-setter function. Getter-setters work like this:

      +
      //define a getter-setter with initial value `John`
      +var name = m.prop("John");
      +
      +//read the value
      +var a = name(); //a == "John"
      +
      +//set the value to `Mary`
      +name("Mary"); //Mary
      +
      +//read the value
      +var b = name(); //b == "Mary"
      +

      Note that the Todo and TodoList classes we defined above are plain vanilla Javascript constructors. They can be initialized and used like this:

      +
      var myTask = new todo.Todo({description: "Write code"});
      +
      +//read the description
      +myTask.description(); //Write code
      +
      +//is it done?
      +var isDone = myTask.done(); //isDone == false
      +
      +//mark as done
      +myTask.done(true); //true
      +
      +//now it's done
      +isDone = myTask.done(); //isDone == true
      +

      The TodoList class is simply an alias of the native Array class.

      +
      var list = new todo.TodoList();
      +list.length; //0
      +
      +

      Controller

      +

      Our next step is to write a controller that will use our model classes.

      +
      //the controller uses 3 model-level entities, of which one is a custom defined class:
      +//`Todo` is the central class in this application
      +//`list` is merely a generic array, with standard array methods
      +//`description` is a temporary storage box that holds a string
      +//
      +//the `add` method simply adds a new todo to the list
      +todo.controller = function() {
      +    this.list = new todo.TodoList();
      +    this.description = m.prop("");
      +
      +    this.add = function(description) {
      +        if (description()) {
      +            this.list.push(new todo.Todo({description: description()}));
      +            this.description("");
      +        }
      +    };
      +}
      +

      The code above should hopefully be self-explanatory. You can use the controller like this:

      +
      var ctrl = new todo.controller();
      +
      +ctrl.description(); //[empty string]
      +
      +//try adding a to-do
      +ctrl.add(ctrl.description);
      +ctrl.list.length; //0
      +
      +//you can't add a to-do with an empty description
      +
      +//add it properly
      +ctrl.description("Write code");
      +ctrl.add(ctrl.description);
      +ctrl.list.length; //1
      +
      +

      View

      +

      The next step is to write a view so users can interact with the application

      +
      todo.view = function(ctrl) {
      +    return m("html", [
      +        m("body", [
      +            m("input"),
      +            m("button", "Add"),
      +            m("table", [
      +                m("tr", [
      +                    m("td", [
      +                        m("input[type=checkbox]")
      +                    ]),
      +                    m("td", "task description"),
      +                ])
      +            ])
      +        ])
      +    ]);
      +};
      +

      The utility method m() creates virtual DOM elements. As you can see, you can use CSS selectors to specify attributes. You can also use the . syntax to add CSS classes and the # to add an id.

      +

      The view can be rendered using the m.render method:

      +
      //assuming the `ctrl` variable from earlier
      +m.render(document, todo.view(ctrl));
      +

      Notice that we pass a root DOM element to attach our template to, as well as the template itself.

      +

      This renders the following markup:

      +
      <html>
      +    <body>
      +        <input />
      +        <button>Add</button>
      +        <table>
      +            <tr>
      +                <td><input type="checkbox" /></td>
      +                <td>task description</td>
      +            </tr>
      +        </table>
      +    </body>
      +</html>
      +
      +

      Data Bindings

      +

      Let's implement a data binding on the text input. Data bindings connect a DOM element to a javascript variable so that updating one updates the other.

      +
      m("input")
      +
      +//becomes
      +m("input", {value: ctrl.description()})
      +

      This binds the description getter-setter to the text input. Updating the value of the description updates the input when Mithril redraws.

      +
      var ctrl = new todo.controller();
      +ctrl.description(); // empty string
      +m.render(todo.view(ctrl)); // input is empty
      +ctrl.description("Write code"); //set the description in the controller
      +m.render(todo.view(ctrl)); // input now says "Write code"
      +

      Note that calling the todo.view method multiple times does not re-render the entire template.

      +

      Mithril internally keeps a virtual representation of the DOM in cache, scans for changes, and then only modifies the minimum required to apply the change.

      +

      In this case, Mithril only touches the value attribute of the input.

      +
      +

      Bindings can also be bi-directional: that is, they can be made such that, in addition to what we saw just now, a user typing on the input updates the description getter-setter.

      +

      Here's the idiomatic way of implementing the view-to-controller part of the binding:

      +
      m("input", {onchange: m.withAttr("value", ctrl.description), value: ctrl.description()})
      +

      The code bound to the onchange can be read like this: "with the attribute value, set ctrl.description".

      +

      Note that Mithril does not prescribe how the binding updates: you can bind it to onchange, onkeypress, oninput, onblur or any other event that you prefer.

      +

      You can also specify what attribute to bind. This means that just as you are able to bind the value attribute in an <select>, you are also able to bind the selectedIndex property, if needed for whatever reason.

      +

      The m.withAttr utility is a functional programming tool provided by Mithril to minimize the need for ugly anonymous functions in the view.

      +

      The m.withAttr("value", ctrl.description) call above returns a function that is the rough equivalent of this code:

      +
      onchange: function(e) {
      +    ctrl.description(e.target["value"]);
      +}
      +

      The difference, aside from the cosmetic avoidance of anonymous functions, is that the m.withAttr idiom also takes care of catching the correct even target and selecting the appropriate source of the data - i.e. whether it should come from a javascript property or from DOMElement::getAttribute()

      +
      +

      In addition to bi-directional data binding, we can also bind parameterized functions to events:

      +
      m("button", {onclick: ctrl.add.bind(ctrl, ctrl.description)}, "Add")
      +

      In the code above, we are simply using the native Javascript Function::bind method. This creates a new function with the parameter already set. In functional programming, this is called currying.

      +

      The ctrl.add.bind(ctrl, ctrl.description) expression above returns a function that is equivalent to this code:

      +
      onclick: function(e) {
      +    ctrl.add(ctrl.description)
      +}
      +

      Note that when we construct the parameterized binding, we are passing the description getter-setter by reference, and not its value. We only evaluate the getter-setter to get its value in the controller method. This is a form of lazy evaluation: it allows us to say "use this value later, when the event handler gets called".

      +

      Hopefully by now, you're starting to see why Mithril encourages the usage of m.prop: Because Mithril getter-setters are functions, they naturally compose well with functional programming tools, and allow for some very powerful idioms. In this case, we're using them in a way that resembles C pointers.

      +

      Mithril uses them in other interesting ways elsewhere.

      +
      +

      To implement flow control in Mithril views, we simply use Javascript:

      +
      //here's the view
      +m("table", [
      +    ctrl.list.map(function(task, index) {
      +        return m("tr", [
      +            m("td", [
      +                m("input[type=checkbox]")
      +            ]),
      +            m("td", task.description()),
      +        ])
      +    })
      +])
      +

      In the code above, ctrl.list is an Array, and map is one of its native functional methods. It allows us to iterate over the list and merge transformed versions of the list items into an output array.

      +

      As you can see, we return a partial template with two <td>'s. The second one has a data binding to the description getter-setter of the Todo class instance.

      +

      You're probably starting to notice that Javascript has strong support for functional programming and that it allows us to naturally do things that can be clunky in other frameworks (e.g. looping inside a <dl>/<dt>/<dd> construct).

      +
      +

      The rest of the code can be implemented using idioms we already covered. The complete view looks like this:

      +
      todo.view = function(ctrl) {
      +    return m("html", [
      +        m("body", [
      +            m("input", {onchange: m.withAttr("value", ctrl.description), value: ctrl.description()}),
      +            m("button", {onclick: ctrl.add.bind(ctrl, ctrl.description)}, "Add"),
      +            m("table", [
      +                ctrl.list.map(function(task, index) {
      +                    return m("tr", [
      +                        m("td", [
      +                            m("input[type=checkbox]", {onclick: m.withAttr("checked", task.done), checked: task.done()})
      +                        ]),
      +                        m("td", {style: {textDecoration: task.done() ? "line-through" : "none"}}, task.description()),
      +                    ])
      +                })
      +            ])
      +        ])
      +    ]);
      +};
      +

      Here are the highlights of the template above:

      +
        +
      • The template is rendered as a child of the implicit <html> element of the document
      • +
      • The text input saves its value to the ctrl.description getter-setter we defined earlier
      • +
      • The button calls the ctrl.add method when clicked. The .bind(ctrl, ctrl.description) idiom is a functional curry.

        +

        In this example, it's only used to maintain the scope binding for the this parameter in the controller method, but typically it's also used to bind parameters to the function without the need to declare a wrapper anonymous function.

        +
      • +
      • The table lists all the existing to-dos, if any.
      • +
      • The checkboxes save their value to the task.done getter setter
      • +
      • The description gets crossed out via CSS if the task is marked as done
      • +
      • When updates happen, the template is not wholly re-rendered - only the changes are applied
      • +
      +
      +

      When running the classes in this application separately, you have full control and full responsibility for determining when to redraw the view.

      +

      However, Mithril does provide another utility to make this task automatic.

      +

      In order to enable Mithril's auto-redrawing system, we run the code as a Mithril module:

      +
      m.module(document, todo);
      +

      Mithril's auto-redrawing system keeps track of controller stability, and only redraws the view once it detects that the controller has finished running all of its code, including asynchronous ajax payloads.

      +

      Also note that this mechanism itself is not asynchronous if it doesn't need to be: Mithril does not need to wait for the next browser repaint frame to redraw - it doesn't even need to wait for the document ready event on the first redraw - it will redraw immediately upon script completion, if able to.

      +
      +

      Summary

      +

      Here's the application code in its entirety:

      +
      <!doctype html>
      +<script src="mithril.js"></script>
      +<script>
      +//this application only has one module: todo
      +var todo = {};
      +
      +//for simplicity, we use this module to namespace the model classes
      +
      +//the Todo class has two properties
      +todo.Todo = function(data) {
      +    this.description = m.prop(data.description);
      +    this.done = m.prop(false);
      +};
      +
      +//the TodoList class is a list of Todo's
      +todo.TodoList = Array;
      +
      +//the controller uses 3 model-level entities, of which one is a custom defined class:
      +//`Todo` is the central class in this application
      +//`list` is merely a generic array, with standard array methods
      +//`description` is a temporary storage box that holds a string
      +//
      +//the `add` method simply adds a new todo to the list
      +todo.controller = function() {
      +    this.list = new todo.TodoList();
      +    this.description = m.prop("");
      +
      +    this.add = function(description) {
      +        if (description()) {
      +            this.list.push(new todo.Todo({description: description()}));
      +            this.description("");
      +        }
      +    };
      +};
      +
      +//here's the view
      +todo.view = function(ctrl) {
      +    return m("html", [
      +        m("body", [
      +            m("input", {onchange: m.withAttr("value", ctrl.description), value: ctrl.description()}),
      +            m("button", {onclick: ctrl.add.bind(ctrl, ctrl.description)}, "Add"),
      +            m("table", [
      +                ctrl.list.map(function(task, index) {
      +                    return m("tr", [
      +                        m("td", [
      +                            m("input[type=checkbox]", {onclick: m.withAttr("checked", task.done), checked: task.done()})
      +                        ]),
      +                        m("td", {style: {textDecoration: task.done() ? "line-through" : "none"}}, task.description()),
      +                    ])
      +                })
      +            ])
      +        ])
      +    ]);
      +};
      +
      +//initialize the application
      +m.module(document, todo);
      +</script>
      +
      +

      Notes on Architecture

      +

      Let's look at each MVC layer in detail to illustrate some of Mithril's design principles and philosophies:

      +

      Model

      +

      Idiomatic Mithril code is meant to apply good programming conventions and be easy to refactor.

      +

      In the application above, notice how the Todo class can easily be moved to a different module if code re-organization is required.

      +

      Todos are self-container and their data aren't tied to the DOM like in typical jQuery based code. The Todo class API is reusable and unit-test friendly, and in addition, it's a plain-vanilla Javascript class which requires almost no framework-specific learning curve.

      +

      m.prop is a simple but surprisingly versatile tool: it's composable, it enables uniform data access and allows a higher degree of decoupling when major refactoring is required.

      +

      When said refactoring is unavoidable, the developer can simply replace the m.prop call with an appropriate getter-setter implementation, instead of having to grep for API usage across the entire application.

      +

      For example, if todo descriptions needed to always be uppercased, one could simply change the description getter-setter:

      +
      this.description = m.prop(data.description)
      +

      becomes:

      +
      //private store
      +var description = data.description;
      +
      +//public getter-setter
      +this.description = function(value) {
      +    if (arguments.length > 0) description = value.toUpperCase();
      +    return description;
      +}
      +

      According to Mithril's philosophy, list and description are also considered model-level entities. This is a subtle but important point: model entities don't need to be full-blown custom classes.

      +

      Native javascript classes are quite appropriate for storing primitive and structured data. Since in this case they are indeed being used to store data - even if temporarily - they are model entities!

      +

      Be aware that by using the native Array class for a list, we're making an implicit statement that we are going to support all of the standard Array methods as part of our API.

      +

      While this decision allows better API discoverability, the trade-off is that we're largely giving up on custom constraints and behavior. For example, if we wanted to change the application to make the list be persisted, a native Array would most certainly not be a suitable class to use.

      +

      In order to deal with that type of refactoring, one can explicitly decide to support only a subset of the Array API, and implement another class with the same interface as this subset API.

      +

      Given the code above, the replacement class would only need to implement the .push() and .map() methods. By freezing APIs and swapping implementations, the developer can completely avoid touching other layers in the application while refactoring.

      +
      todo.TodoList = Array;
      +

      becomes:

      +
      todo.TodoList = function () {
      +    this.push = function() { /*...*/ },
      +    this.map = function() { /*...*/ }
      +};
      +

      Hopefully these examples give you an idea of ways requirements can change over time and how Mithril's philosophy allows developers to use standard OOP techniques to refactor their codebases, rather than needing to modify large portions of the application.

      +
      +

      Controller

      +

      Mithril follows a data binding paradigm that is familiar to developers that use server-side MVC frameworks like Rails and Django.

      +

      The difference, as mentioned earlier, is that Mithril philosophy considers any form of data storage as being a model entity - even data from a text input waiting to be saved!

      +

      In Mithril, controllers are not meant to progressively operate on model entities. Instead, model entities should expose methods that atomically act on themselves.

      +

      What this rule means is that controllers can have conditional logic, as is the case in the add method in the application above, but each action that touches a model entity should not leave it in an unstable state.

      +

      This is in contrast to the ActiveRecord pattern of other frameworks, which allows entities to be in potentially invalid states (for example, a to-do with no description), so long as they are not "saved".

      +

      The idea of disallowing unstable states hinges largely on the developer deciding what constitutes validity:

      +
        +
      • An empty description in the context of the text input in the UI is a perfectly valid state, and a string is an appropriate type to express that.

        +
      • +
      • A to-do with no description is not valid, therefore we avoid writing code that ever leaves the Todo class instance in a unstable state.

        +
      • +
      +

      Mithril doesn't programmatically define the scope of each model entity or in what states an entity is considered valid - validity is something the developer is responsible for defining.

      +

      Mithril's philosophical framework simply encourages that the developer map validity to static types. This is a key step in ensuring programs are robust and refactorable.

      +
      +

      View

      +

      The first and most obvious thing you may have noticed in the view layer is that the view is not written in HTML.

      +

      While superficially this may seem like an odd design, this actually has a lot of benefits:

      +
        +
      • No flash-of-unbehaviored-content (FOUC). In fact, Mithril is able to render a fully functional application - with working event handlers - before the "DOM ready" event fires!

        +
      • +
      • There's no need for a parse-and-compile pre-processing step to turn strings containing HTML + templating syntax into working DOM elements.

        +
      • +
      • Mithril views can provide accurate and informative error reporting, with line numbers and meaningful stack traces.

        +
      • +
      • You get the ability to automate linting, unit testing and minifying of the entire view layer - and you are even able to use Closure Compiler's Advanced Mode without needing extensive annotations.

        +
      • +
      • It provides full Turing completeness: full control over evaluation eagerness/lazyness and caching in templates. You can even build components that take other components as first-class-citizen parameters!

        +
      • +
      • Turtles all the way down: you don't need write custom data binding code in jQuery for every possible user interaction, and you don't need to support a complicated "directive" layer to be able to fit some types of components into the system.

        +
      • +
      +

      Views in Mithril use a virtual DOM diff implementation, which sidesteps performance problems related to opaque dirty-checking and excessive browser repaint that are present in some frameworks.

      +

      Another feature - the optional m() utility - allows writing terse templates in a declarative style using CSS shorthands, similar to popular HTML preprocessors from server-side MVC frameworks.

      +

      And because Mithril views are javascript, the developer has full freedom to abstract common patterns - from bidirectional binding helpers to full blown components - using standard javascript refactoring techniques.

      +

      Mithril templates are also more collision-proof than other component systems since there's no way to pollute the HTML tag namespace by defining ad-hoc tag names.

      +

      A more intellectually interesting aspect of the framework is that event handling is encouraged to be done via functional composition (i.e. by using tools like m.withAttr, m.prop and the native .bind() method for currying).

      +

      If you've been interested in learning or using Functional Programming in the real world, Mithril provides very pragmatic opportunities to get into it.

      +
      +

      Learn More

      +

      Mithril provides a few more facilities that are not demonstrated in this page. The following topics are good places to start a deeper dive.

      + +

      Advanced Topics

      + +

      Misc

      + + +
      +
      +
      +
      +
      +
      +
      + Released under the MIT license +
      © 2014 Leo Horie +
      +
      + + + \ No newline at end of file diff --git a/archive/v0.1.3/how-to-read-signatures.html b/archive/v0.1.3/how-to-read-signatures.html new file mode 100644 index 00000000..c839581c --- /dev/null +++ b/archive/v0.1.3/how-to-read-signatures.html @@ -0,0 +1,153 @@ + + + + Mithril + + + + + +
      + +
      +
      +
      +
      +
      + +
      +

      How to Read Signatures

      +

      Rather than providing concrete classes like other frameworks, Mithril provides methods that operate on plain old javascript objects (POJOs) that match given signatures.

      +

      A signature is a description of its static type. For functions, it shows what are the parameters of the function, its return value and their expected types. For objects and arrays, it shows the expected data structure and the expected types of their members.

      +

      Method signatures in this documentation follow a syntax similar to Java syntax, with some extra additions:

      +
      ReturnType methodName(ParameterType1 param1, ParameterType2 param2)
      +

      Optional Parameters

      +

      Square brackets denote optional parameters. In the example below, param2 and param3 can both be omitted, but passing a value to param2 is required if also passing a value to param3:

      +
      String test(String arg1 [, String arg2 [, String arg3]])
      +
      //examples of valid function calls
      +test("first");
      +test("first", "second");
      +test("first", "second", "third");
      +

      Type Placeholders

      +

      The word void is used as a type when a function does not return a value (i.e. undefined):

      +
      void test()
      +
      console.log(test()); // undefined
      +

      The word any is used as a type if there are no type restrictions on a parameter:

      +
      void test(any value)
      +
      //examples of valid function calls
      +test("hello");
      +test(1);
      +test(["hello", "world"]);
      +

      Arrays

      +

      Arrays use Generics syntax to denote the expected type of array members:

      +
      void test(Array<String> values)
      +
      //example of a valid function call
      +test(["first", "second"]);
      +

      Objects as Key-Value Maps

      +

      Objects also use Generics syntax when they are meant to be used as a key-value map. Keys are always strings and, in key-value maps, can have any name.

      +
      void test(Object<Number> values)
      +
      //example of a valid function call
      +test({first: 1, second: 2});
      +

      Objects as Class Interfaces

      +

      Objects that require specific keys are denoted using curly brace syntax:

      +
      void test(Object {String first, Number second} value)
      +
      //example of a valid function call
      +test({first: "first", second: 2});
      +

      Type Aliasing

      +

      Some types are aliases of more complex types. For example, in the example below, we created an alias called ComplexType for the type from the previous example

      +
      void test(ComplexType value)
      +
      +where:
      +    ComplexType :: Object {String first, Number second}
      +
      +//example of a valid function call
      +test({first: "first", second: 2})
      +

      Mixin Types

      +

      Curly brace syntax can also appear on other base types to denote that the value contains static members. For example, in the example below, a value of type ComplexType is a string, but it also has a boolean property called flag:

      +
      ComplexType :: String { Boolean flag }
      +
      //an example
      +var a = aComplexTypeValue
      +typeof a == "string" // true
      +"flag" in a // true
      +a.flag = true
      +

      In the following example, a value of type ComplexType is a function, with a property called label

      +
      ComplexType :: void test() { String label }
      +
      //an example
      +var a = aComplexTypeValue
      +typeof a == "function" // true
      +"label" in a // true
      +a.label = "first"
      +

      Polimorphic Types

      +

      When multiple (but not all) types are accepted, the pipe | is used to delimit the list of valid types

      +
      void test(Children children, Value value)
      +
      +where:
      +    Children :: Array<String text | Number number>
      +    Value :: String | Number
      +
      //examples of valid function calls
      +test(["test", 2], "second")
      +test([1, 2, 3], "second")
      +test([1, "test", 3], 2)
      +

      Pipe syntax within Object curly brace syntax means that for a specific key name has specific type requirements.

      +

      In the example below, the value parameter should be a key-value map. This map may contain a key called config, whose value should be a function.

      +
      void test(Object { any | void config(DOMElement) } value)
      +
      //example of a valid function call
      +test({ first: "first", config: function(element) { /*do stuff*/ } })
      + +
      +
      +
      +
      +
      +
      +
      + Released under the MIT license +
      © 2014 Leo Horie +
      +
      + + + \ No newline at end of file diff --git a/archive/v0.1.3/index.html b/archive/v0.1.3/index.html new file mode 100644 index 00000000..569b14e4 --- /dev/null +++ b/archive/v0.1.3/index.html @@ -0,0 +1,196 @@ + + + + Mithril + + + + + +
      + +
      + +
      +
      +
      +

      Mithril

      + +

      A Javascript Framework for Building Brilliant Applications

      + + Guide + Download v0.1.3 +
      +
      + +
      +
      +
      +

      Light-weight

      +
        +
      • Only 3kb gzipped, no dependencies
      • +
      • Small API, small learning curve
      • +
      +
      + +
      +

      Robust

      +
        +
      • Safe-by-default templates
      • +
      • Hierarchical MVC via components
      • +
      +
      + +
      +

      Fast

      +
        +
      • Virtual DOM diffing and compilable templates
      • +
      • Intelligent auto-redrawing system
      • +
      +
      +
      +
      + +
      +
      +
      +

      Sample code

      + +
      //namespace
      +var app = {};
      +
      +//model
      +app.PageList = function() {
      +	return m.request({method: "GET", url: "pages.json"});
      +};
      +
      +//controller
      +app.controller = function() {
      +	this.pages = app.PageList();
      +};
      +
      +//view
      +app.view = function(ctrl) {
      +	return ctrl.pages().map(function(page) {
      +		return m("a", {href: page.url}, page.title);
      +	});
      +};
      +
      +//initialize
      +m.module(document.getElementById("example"), app);
      + +
      +
      +

      Output

      + + + + +
      +
      +
+ + +
+
+

Performance

+

To run the execution time tests below, click on their respective links, run the profiler from your desired browser's developer tools and measure the running time of a page refresh. (Lower is better)

+
+
+

Loading

+ + + + + +
Mithril 0.28ms
jQuery 13.11ms
Backbone 18.54ms
Angular 7.49ms
+
+
+

Rendering

+ + + + + +
Mithril 9.44ms (uncompiled)
jQuery 40.27ms
Backbone 23.05ms
Angular 118.63ms
+
+
+
+
+ +
+
+
+
+

Safety

+

Mithril templates are safe by default, i.e. you can't unintentionally create security holes.

+

To run the tests for each framework, click on the respective links. If you see an alert box, ensuring security with that framework is more work for you.

+
+
+

Test Summary

+ Mithril (pass) ✓
+ jQuery (fail) ✗
+ Backbone (fail) ✗
+ Angular (pass) ✓
+
+
+
+
+ +
+
+
+

Guide

+

Build a simple app, learn the ropes

+

Read Guide

+
+ +
+

API

+

Docs and code samples for your reference

+

Read Docs

+
+
+
+ +
+
+ Released under the MIT license
+ © 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/installation.html b/archive/v0.1.3/installation.html new file mode 100644 index 00000000..50aea470 --- /dev/null +++ b/archive/v0.1.3/installation.html @@ -0,0 +1,92 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Installation

+

Mithril is available from a variety of sources:

+
+

Direct download

+

You can download a zip of the latest version version here.

+

Links to older versions can be found in the change log

+

In order to use Mithril, extract it from the zip file and point a script tag to the .js file:

+
<script src="mithril.min.js"></script>
+
+

CDNs (Content Delivery Networks)

+

You can also find Mithril in cdnjs and jsdelivr

+

Content delivery networks allow the library to be cached across different websites that use the same version of the framework, and help reduce latency by serving the files from a server that is physically near the user's location.

+

CdnJs

+
<script src="//cdnjs.cloudflare.com/ajax/libs/mithril/0.1.3/mithril.min.js"></script>
+

JsDelivr

+
<script src="//cdn.jsdelivr.net/mithril/0.1.3/mithril.min.js"></script>
+
+

NPM

+

NPM is the default package manager for NodeJS. If you're using NodeJS already or planning on using Grunt to create a build system, you can use NPM to conveniently keep up-to-date with Mithril versions.

+

Assuming you have NodeJS installed, you can download Mithril by typing this:

+
npm install mithril
+

Then, to use Mithril, point a script tag to the downloaded file:

+
<script src="/node_modules/mithril/mithril.min.js"></script>
+
+

Bower

+

Bower is a package manager for NodeJS. If you're using NodeJS already or planning on using Grunt to create a build system, you can use Bower to conveniently keep up-to-date with Mithril versions.

+

Assuming you have NodeJS installed, you can install Bower by typing this in the command line:

+
npm install -g bower
+

And you can download Mithril by typing this:

+
bower install mithril
+

Then, to use Mithril, point a script tag to the downloaded file:

+
<script src="/bower_components/mithril/mithril.min.js"></script>
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/integration.html b/archive/v0.1.3/integration.html new file mode 100644 index 00000000..c3210e04 --- /dev/null +++ b/archive/v0.1.3/integration.html @@ -0,0 +1,147 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Integrating with Other Libraries

+

Integration with third party libraries can be achieved via the config attribute of virtual elements.

+

It's recommended that you encapsulate integration code in a component.

+

The example below shows a simple component that integrates with the select2 library.

+
//Select2 component (assumes both jQuery and Select2 are included in the page)
+
+/** @namespace */
+var select2 = {};
+
+/**
+select2 config factory. The params in this doc refer to properties of the `ctrl` argument
+@param {Object} data - the data with which to populate the <option> list
+@param {number} value - the id of the item in `data` that we want to select
+@param {function(Object id)} onchange - the event handler to call when the selection changes.
+    `id` is the the same as `value`
+*/
+select2.config = function(ctrl) {
+    return function(element, isInitialized) {
+        var el = $(element);
+
+        if (!isInitialized) {
+            //set up select2 (only if not initialized already)
+            el.select2()
+                //this event handler updates the controller when the view changes
+                .on("change", function(e) {
+                    //integrate with the auto-redrawing system...
+                    m.startComputation();
+
+                    //...so that Mithril autoredraws the view after calling the controller callback
+                    if (typeof ctrl.onchange == "function") ctrl.onchange(el.select2("val"));
+
+                    m.endComputation();
+                    //end integration
+                });
+        }
+
+        //update the view with the latest controller value
+        el.select2("val", ctrl.value);
+    }
+}
+
+//this view implements select2's `<select>` progressive enhancement mode
+select2.view = function(ctrl) {
+    return m("select", {config: select2.config(ctrl)}, [
+        ctrl.data.map(function(item) {
+            return m("option", {value: item.id}, item.name)
+        })
+    ]);
+};
+
+//end component
+
+
+
+//usage
+var dashboard = {};
+
+dashboard.controller = function() {
+    //list of users to show
+    this.data = [{id: 1, name: "John"}, {id: 2, name: "Mary"}, {id: 3, name: "Jane"}];
+
+    //select Mary
+    this.currentUser = this.data[1];
+
+    this.changeUser = function(id) {
+        console.log(id)
+    };
+}
+
+dashboard.view = function(ctrl) {
+    return m("div", [
+        m("label", "User:"),
+        select2.view({data: ctrl.data, value: ctrl.currentUser.id, onchange: ctrl.changeUser})
+    ]);
+}
+
+m.module(document.body, dashboard);
+

select2.config is a factory that creates a config function based on a given controller. We declare this outside of the select2.view function to avoid cluttering the template.

+

The config function created by our factory only runs the initialization code if it hasn't already. This if statement is important, because this function may be called multiple times by Mithril's auto-redrawing system and we don't want to re-initialize select2 at every redraw.

+

The initialization code defines a change event handler. Because this handler is not created using Mithril's templating engine (i.e. we're not defining an attribute in a virtual element), we must manually integrate it to the auto-redrawing system.

+

This can be done by simply calling m.startComputation at the beginning, and m.endComputation at the end of the function. You must add a pair of these calls for each asynchronous execution thread, unless the thread is already integrated.

+

For example, if you were to call a web service using m.request, you would not need to add more calls to m.startComputation / m.endComputation (you would still need the first pair in the event handler, though).

+

On the other hand, if you were to call a web service using jQuery, then you would be responsible for adding a m.startComputation call before the jQuery ajax call, and for adding a m.endComputation call at the end of the completion callback, in addition to the calls within the change event handler. Refer to the auto-redrawing guide for an example.

+

One important note about the config method is that you should avoid calling m.redraw, m.startComputation and m.endComputation in the config function's execution thread. (An execution thread is basically any amount of code that runs before other asynchronous threads start to run)

+

While Mithril technically does support this use case, relying on multiple redraw passes degrades performance and makes it possible to code yourself into an infinite execution loop situation, which is extremely difficult to debug.

+

The dashboard module in the example shows how a developer would consume the select2 component.

+

You should always document integration components so that others can find out what attribute parameters can be used to initialize the component.

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/lib/prism/prism.css b/archive/v0.1.3/lib/prism/prism.css new file mode 100644 index 00000000..1e61e11d --- /dev/null +++ b/archive/v0.1.3/lib/prism/prism.css @@ -0,0 +1,126 @@ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.builtin { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { + color: #a67f59; + background: hsla(0,0%,100%,.5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + + +.token.regex, +.token.important { + color: #e90; +} + +.token.important { + font-weight: bold; +} + +.token.entity { + cursor: help; +} + diff --git a/archive/v0.1.3/lib/prism/prism.js b/archive/v0.1.3/lib/prism/prism.js new file mode 100644 index 00000000..b7f84870 --- /dev/null +++ b/archive/v0.1.3/lib/prism/prism.js @@ -0,0 +1,9 @@ +/** + * Prism: Lightweight, robust, elegant syntax highlighting + * MIT license http://www.opensource.org/licenses/mit-license.php/ + * @author Lea Verou http://lea.verou.me + */(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.util.clone(e[i]));return r;case"Array":return e.slice()}return e}},languages:{extend:function(e,n){var r=t.util.clone(t.languages[e]);for(var i in n)r[i]=n[i];return r},insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);t.util.type(e)==="Object"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent;if(!f)return;f=f.replace(/&/g,"&").replace(/e.length)break e;if(p instanceof i)continue;a.lastIndex=0;var d=a.exec(p);if(d){l&&(c=d[1].length);var v=d.index-1+c,d=d[0].slice(c),m=d.length,g=v+m,y=p.slice(0,v+1),b=p.slice(g+1),w=[h,1];y&&w.push(y);var E=new i(u,f?t.tokenize(d,f):d);w.push(E);b&&w.push(b);Array.prototype.splice.apply(s,w)}}}return s},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e,r,i){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]")return e.map(function(t){return n.stringify(t,r,e)}).join("");var s={type:e.type,content:n.stringify(e.content,r,i),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:i};s.type=="comment"&&(s.attributes.spellcheck="true");t.hooks.run("wrap",s);var o="";for(var u in s.attributes)o+=u+'="'+(s.attributes[u]||"")+'"';return"<"+s.tag+' class="'+s.classes.join(" ")+'" '+o+">"+s.content+""};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})();; +Prism.languages.markup={comment:/<!--[\w\W]*?-->/g,prolog:/<\?.+?\?>/,doctype:/<!DOCTYPE.+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|\w+))?\s*)*\/?>/gi,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,inside:{punctuation:/=|>|"/g}},punctuation:/\/?>/g,"attr-name":{pattern:/[\w:-]+/g,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/gi};Prism.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))});; +Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/ig,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,"function":{pattern:/[a-z0-9_]+\(/ig,inside:{punctuation:/\(/}}, number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|(&){1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g}; +; +Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|get|set|new|with|typeof|try|throw|catch|finally|null|break|continue)\b/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g});Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}});Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<|<)script[\w\W]*?(>|>)[\w\W]*?(<|<)\/script(>|>)/ig,inside:{tag:{pattern:/(<|<)script[\w\W]*?(>|>)|(<|<)\/script(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});; diff --git a/archive/v0.1.3/mithril-tests.js b/archive/v0.1.3/mithril-tests.js new file mode 100644 index 00000000..de1dfeed --- /dev/null +++ b/archive/v0.1.3/mithril-tests.js @@ -0,0 +1,803 @@ +new function(window) { + var selectorCache = {} + var type = {}.toString + var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.+?)\2)?\]/ + + Mithril = m = function() { + var args = arguments + var hasAttrs = type.call(args[1]) == "[object Object]" + var attrs = hasAttrs ? args[1] : {} + var classAttrName = "class" in attrs ? "class" : "className" + var cell = selectorCache[args[0]] + if (cell === undefined) { + selectorCache[args[0]] = cell = {tag: "div", attrs: {}} + var match, classes = [] + while (match = parser.exec(args[0])) { + if (match[1] == "") 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] || true + } + } + if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ") + } + cell = clone(cell) + cell.attrs = clone(cell.attrs) + cell.children = hasAttrs ? args[2] : args[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(parent, data, cached) { + if (data === null || data === undefined) return + + var cachedType = type.call(cached), dataType = type.call(data) + if (cachedType != dataType) { + if (cached !== null && cached !== undefined) clear(cached.nodes) + cached = new data.constructor + cached.nodes = [] + } + + if (dataType == "[object Array]") { + var nodes = [], intact = cached.length === data.length + for (var i = 0; i < data.length; i++) { + var item = build(parent, data[i], cached[i]) + if (item === undefined) continue + if (!item.nodes.intact) intact = false + cached[i] = item + } + if (!intact) { + for (var i = 0; i < data.length; i++) if (cached[i] !== undefined) nodes = nodes.concat(cached[i].nodes) + for (var i = nodes.length, node; node = cached.nodes[i]; i++) if (node.parentNode !== null) node.parentNode.removeChild(node) + for (var i = cached.nodes.length, node; node = nodes[i]; i++) if (node.parentNode === null) parent.appendChild(node) + cached.length = data.length + cached.nodes = nodes + } + } + else if (dataType == "[object Object]") { + if (typeof data.tag != "string") return + if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join()) clear(cached.nodes) + + var node, isNew = cached.nodes.length === 0 + if (isNew) { + node = window.document.createElement(data.tag) + cached = {tag: data.tag, attrs: setAttributes(node, data.attrs, {}), children: build(node, data.children, cached.children), nodes: [node]} + parent.appendChild(node) + } + else { + node = cached.nodes[0] + setAttributes(node, data.attrs, cached.attrs) + cached.children = build(node, data.children, cached.children) + cached.nodes.intact = true + parent.appendChild(node) + } + if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew) + } + else { + var node + if (cached.nodes.length === 0) { + if (data.$trusted) { + var lastChild = parent.lastChild + parent.insertAdjacentHTML("beforeend", data) + node = lastChild ? lastChild.nextSibling : parent.firstChild + } + else { + node = window.document.createTextNode(data) + parent.appendChild(node) + } + cached = "string number boolean".indexOf(typeof data) > -1 ? new data.constructor(data) : data + cached.nodes = [node] + } + else if (cached.valueOf() !== data.valueOf()) { + if (data.$trusted) { + var current = cached.nodes[0], nodes = [current] + if (current) { + while (current = current.nextSibling) nodes.push(current) + clear(nodes) + var lastChild = parent.lastChild + parent.insertAdjacentHTML("beforeend", data) + node = lastChild ? lastChild.nextSibling : parent.firstChild + } + else parent.innerHTML = data + } + else { + node = cached.nodes[0] + parent.appendChild(node) + node.nodeValue = data + } + cached = new data.constructor(data) + cached.nodes = [node] + } + else cached.nodes.intact = true + } + + return cached + } + function setAttributes(node, dataAttrs, cachedAttrs) { + for (var attrName in dataAttrs) { + var dataAttr = dataAttrs[attrName] + if (!(attrName in cachedAttrs) || (cachedAttrs[attrName] !== dataAttr) || node === window.document.activeElement) { + cachedAttrs[attrName] = dataAttr + if (attrName == "config") continue + if (attrName.indexOf("on") == 0 && typeof dataAttr == "function") dataAttr = autoredraw(dataAttr, node) + if (attrName == "style") for (var rule in dataAttr) node.style[rule] = dataAttr[rule] + else if (attrName in node) node[attrName] = dataAttr + else node.setAttribute(attrName, dataAttr) + } + } + return cachedAttrs + } + function clear(nodes) { + for (var i = 0; i < nodes.length; i++) nodes[i].parentNode.removeChild(nodes[i]) + nodes.length = 0 + } + function clone(object) { + var result = {} + for (var prop in object) result[prop] = object[prop] + return result + } + function autoredraw(callback, object) { + return function() { + m.startComputation() + var output = callback.apply(object || window, arguments) + m.endComputation() + return output + } + } + + var html + var documentNode = { + insertAdjacentHTML: function(_, data) { + window.document.write(data) + window.document.close() + }, + appendChild: function(node) { + if (html === undefined) html = window.document.createElement("html") + if (node.nodeName == "HTML") html = node + else html.appendChild(node) + if (window.document.documentElement !== html) { + window.document.replaceChild(html, window.document.documentElement) + } + } + } + var nodeCache = [], cellCache = {} + m.render = function(root, cell) { + var index = nodeCache.indexOf(root) + var id = index < 0 ? nodeCache.push(root) - 1 : index + var node = root == window.document || root == window.document.documentElement ? documentNode : root + cellCache[id] = build(node, cell, cellCache[id]) + } + + m.trust = function(value) { + value = new String(value) + value.$trusted = true + return value + } + + var currentRoot, currentModule = {view: function() {}}, currentController = {}, now = 0, lastRedraw = 0, lastRedrawId = 0 + m.module = function(root, module) { + m.startComputation() + currentRoot = root + currentModule = module + currentController = new module.controller + m.endComputation() + } + m.redraw = function() { + m.render(currentRoot, currentModule.view(currentController)) + lastRedraw = now + } + function redraw() { + now = window.performance && window.performance.now ? window.performance.now() : new window.Date().getTime() + if (now - lastRedraw > 16) m.redraw() + else { + var cancel = window.cancelAnimationFrame || window.clearTimeout + var defer = window.requestAnimationFrame || window.setTimeout + cancel(lastRedrawId) + lastRedrawId = defer(m.redraw, 0) + } + } + + var pendingRequests = 0, computePostRedrawHook = null + m.startComputation = function() {pendingRequests++} + m.endComputation = function() { + pendingRequests = Math.max(pendingRequests - 1, 0) + if (pendingRequests == 0) { + redraw() + if (computePostRedrawHook) { + computePostRedrawHook() + computePostRedrawHook = null + } + } + } + + m.withAttr = function(prop, withAttrCallback) { + return function(e) {withAttrCallback(prop in e.currentTarget ? e.currentTarget[prop] : e.currentTarget.getAttribute(prop))} + } + + //routing + var modes = {pathname: "", hash: "#", search: "?"} + var redirect = function() {}, routeParams = {} + m.route = function() { + if (arguments.length == 3) { + var root = arguments[0], defaultRoute = arguments[1], router = arguments[2] + redirect = function(source) { + var path = source.slice(modes[m.route.mode].length) + if (!routeByValue(root, router, path)) { + m.route(defaultRoute, true) + } + } + var listener = m.route.mode == "hash" ? "onhashchange" : "onpopstate" + window[listener] = function() { + redirect(window.location[m.route.mode]) + } + computePostRedrawHook = scrollToHash + window[listener]() + } + else if (arguments[0].addEventListener) { + var element = arguments[0] + var isInitialized = arguments[1] + if (!isInitialized) { + element.removeEventListener("click", routeUnobtrusive) + element.addEventListener("click", routeUnobtrusive) + } + } + else if (typeof arguments[0] == "string") { + var route = arguments[0] + var shouldReplaceHistoryEntry = arguments[1] === true + if (window.history.pushState) { + computePostRedrawHook = function() { + window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, window.document.title, modes[m.route.mode] + route) + scrollToHash() + } + redirect(modes[m.route.mode] + route) + } + else window.location[m.route.mode] = route + } + } + m.route.param = function(key) {return routeParams[key]} + m.route.mode = "search" + function routeByValue(root, router, path) { + routeParams = {} + for (var route in router) { + if (route == path) return !void m.module(root, router[route]) + + var matcher = new RegExp("^" + route.replace(/:[^\/]+/g, "([^\\/]+)") + "$") + if (matcher.test(path)) { + return !void path.replace(matcher, function() { + var keys = route.match(/:[^\/]+/g) + var values = [].slice.call(arguments, 1, -2) + for (var i = 0; i < keys.length; i++) routeParams[keys[i].slice(1)] = values[i] + m.module(root, router[route]) + }) + } + } + } + function routeUnobtrusive(e) { + e.preventDefault() + m.route(e.currentTarget.getAttribute("href")) + } + function scrollToHash() { + if (m.route.mode != "hash" && window.location.hash) window.location.hash = window.location.hash + } + + //model + m.prop = function(store) { + return function() { + if (arguments.length) store = arguments[0] + return store + } + } + + m.deferred = function() { + var resolvers = [], rejecters = [] + var object = { + resolve: function(value) { + for (var i = 0; i < resolvers.length; i++) resolvers[i](value) + }, + reject: function(value) { + for (var i = 0; i < rejecters.length; i++) rejecters[i](value) + }, + promise: m.prop() + } + object.promise.resolvers = resolvers + object.promise.then = function(success, error) { + var next = m.deferred() + if (!success) success = identity + if (!error) error = identity + function push(list, method, callback) { + list.push(function(value) { + try { + var result = callback(value) + next[method](result !== undefined ? result : value) + } + catch (e) { + if (e instanceof Error && e.constructor !== Error) throw e + else next.reject(e) + } + }) + } + push(resolvers, "resolve", success) + push(rejecters, "reject", error) + return next.promise + } + return object + } + m.sync = function(args) { + var method = "resolve" + function synchronizer(resolved) { + return function(value) { + results.push(value) + if (!resolved) method = "reject" + if (results.length == args.length) { + deferred.promise(results) + deferred[method](results) + } + return value + } + } + + var deferred = m.deferred() + var results = [] + for (var i = 0; i < args.length; i++) { + args[i].then(synchronizer(true), synchronizer(false)) + } + return deferred.promise + } + function identity(value) {return value} + + function ajax(options) { + var xhr = window.XDomainRequest ? new window.XDomainRequest : new window.XMLHttpRequest + xhr.open(options.method, options.url, true, options.user, options.password) + xhr.onload = typeof options.onload == "function" ? options.onload : function() {} + xhr.onerror = typeof options.onerror == "function" ? options.onerror : function() {} + if (typeof options.config == "function") options.config(xhr, options) + xhr.send(options.data) + return xhr + } + function querystring(object, prefix) { + var str = [] + for(var prop in object) { + var key = prefix ? prefix + "[" + prop + "]" : prop, value = object[prop] + str.push(typeof value == "object" ? querystring(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value)) + } + return str.join("&") + } + function bindData(xhrOptions, data, serialize) { + if (data && Object.keys(data).length > 0) { + if (xhrOptions.method == "GET") { + xhrOptions.url = xhrOptions.url + (xhrOptions.url.indexOf("?") < 0 ? "?" : "&") + querystring(data) + } + else xhrOptions.data = serialize(data) + } + return xhrOptions + } + function parameterizeUrl(url, data) { + var tokens = url.match(/:\w+/g) + if (tokens && data) { + for (var i = 0; i < tokens.length; i++) { + var key = tokens[i].slice(1) + url = url.replace(tokens[i], data[key]) + delete data[key] + } + } + return url + } + + m.request = function(xhrOptions) { + m.startComputation() + var deferred = m.deferred() + var serialize = xhrOptions.serialize || JSON.stringify + var deserialize = xhrOptions.deserialize || JSON.parse + xhrOptions.url = parameterizeUrl(xhrOptions.url, xhrOptions.data) + xhrOptions = bindData(xhrOptions, xhrOptions.data, serialize) + xhrOptions.onload = xhrOptions.onerror = function(e) { + var unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity + var response = unwrap(deserialize(e.target.responseText)) + if (response instanceof 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) + deferred.promise(response) + deferred[e.type == "load" ? "resolve" : "reject"](response) + m.endComputation() + } + ajax(xhrOptions) + deferred.promise.then = propBinder(deferred.promise) + return deferred.promise + } + function propBinder(promise) { + var bind = promise.then + return function(success, error) { + var next = bind(function(value) {return next(success(value))}, function(value) {return next(error(value))}) + next.then = propBinder(next) + return next + } + } + + if (typeof module != "undefined" && module !== null) module.exports = m + if (typeof define == "function" && define.amd) define(function() {return m}) + + //testing API + m.deps = function(mock) {return window = mock} +}(this) +function test(condition) { + try {if (!condition()) throw new Error} + catch (e) {console.error(e);test.failures.push(condition)} + test.total++ +} +test.total = 0 +test.failures = [] +test.print = function(print) { + for (var i = 0; i < test.failures.length; i++) { + print(test.failures[i].toString()) + } + print("tests: " + test.total + "\nfailures: " + test.failures.length) + + if (test.failures.length > 0) { + throw new Error(test.failures.length + " tests did not pass") + } +} + +var mock = {} +mock.window = new function() { + var window = {} + window.document = {} + window.document.childNodes = [] + window.document.createElement = function(tag) { + return { + childNodes: [], + nodeName: tag.toUpperCase(), + appendChild: window.document.appendChild, + removeChild: window.document.removeChild, + replaceChild: window.document.replaceChild, + setAttribute: function(name, value) { + this[name] = value.toString() + }, + getAttribute: function(name, value) { + return this[name] + }, + } + } + window.document.createTextNode = function(text) { + return {nodeValue: text.toString()} + } + window.document.documentElement = null + window.document.replaceChild = function(newChild, oldChild) { + var index = this.childNodes.indexOf(oldChild) + if (index > -1) this.childNodes.splice(index, 1, newChild) + else this.childNodes.push(newChild) + newChild.parentNode = this + oldChild.parentNode = null + } + window.document.appendChild = function(child) { + this.childNodes.push(child) + child.parentNode = this + } + window.document.removeChild = function(child) { + var index = this.childNodes.indexOf(child) + this.childNodes.splice(index, 1) + child.parentNode = null + } + window.performance = new function () { + var timestamp = 50 + this.$elapse = function(amount) {timestamp += amount} + this.now = function() {return timestamp} + } + window.cancelAnimationFrame = function() {} + window.requestAnimationFrame = function(callback) {window.requestAnimationFrame.$callback = callback} + window.requestAnimationFrame.$resolve = function() { + if (window.requestAnimationFrame.$callback) window.requestAnimationFrame.$callback() + window.requestAnimationFrame.$callback = null + window.performance.$elapse(20) + } + window.XMLHttpRequest = new function() { + var request = function() { + this.open = function(method, url) { + this.method = method + this.url = url + } + this.send = function() { + this.responseText = JSON.stringify(this) + request.$events.push({type: "load", target: this}) + } + } + request.$events = [] + return request + } + window.location = {search: "", pathname: "", hash: ""}, + window.history = {} + window.history.pushState = function(data, title, url) { + window.location.pathname = window.location.search = window.location.hash = url + }, + window.history.replaceState = function(data, title, url) { + window.location.pathname = window.location.search = window.location.hash = url + } + return window +} +function testMithril(mock) { + m.deps(mock) + + //m + test(function() {return m("div").tag === "div"}) + test(function() {return m(".foo").tag === "div"}) + test(function() {return m(".foo").attrs.className === "foo"}) + test(function() {return m("[title=bar]").tag === "div"}) + test(function() {return m("[title=bar]").attrs.title === "bar"}) + test(function() {return m("[title=\'bar\']").attrs.title === "bar"}) + test(function() {return m("[title=\"bar\"]").attrs.title === "bar"}) + test(function() {return m("div", "test").children === "test"}) + test(function() {return m("div", ["test"]).children[0] === "test"}) + test(function() {return m("div", {title: "bar"}, "test").attrs.title === "bar"}) + test(function() {return m("div", {title: "bar"}, "test").children === "test"}) + test(function() {return m("div", {title: "bar"}, ["test"]).children[0] === "test"}) + test(function() {return m("div", {title: "bar"}, m("div")).children.tag === "div"}) + test(function() {return m("div", {title: "bar"}, [m("div")]).children[0].tag === "div"}) + test(function() {return m("div", ["a", "b"]).children.length === 2}) + test(function() {return m("div", [m("div")]).children[0].tag === "div"}) + test(function() {return m("div", m("div")).attrs.tag === "div"}) //yes, this is expected behavior: see method signature + test(function() {return m("div", [undefined]).tag === "div"}) + test(function() {return m("div", [{foo: "bar"}])}) //as long as it doesn't throw errors, it's fine + + //m.module + test(function() { + mock.performance.$elapse(50) + + var root1 = mock.document.createElement("div") + m.module(root1, { + controller: function() {this.value = "test1"}, + view: function(ctrl) {return ctrl.value} + }) + + var root2 = mock.document.createElement("div") + m.module(root2, { + controller: function() {this.value = "test2"}, + view: function(ctrl) {return ctrl.value} + }) + + mock.requestAnimationFrame.$resolve() + + return root1.childNodes[0].nodeValue === "test1" && root2.childNodes[0].nodeValue === "test2" + }) + + //m.withAttr + test(function() { + var value + var handler = m.withAttr("test", function(data) {value = data}) + handler({currentTarget: {test: "foo"}}) + return value === "foo" + }) + + //m.trust + test(function() {return m.trust("test").valueOf() === "test"}) + + //m.render + test(function() { + var root = mock.document.createElement("div") + m.render(root, "test") + return root.childNodes[0].nodeValue === "test" + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("div", {id: "a"})) + var elementBefore = root.childNodes[0] + m.render(root, m("div", {id: "b"})) + var elementAfter = root.childNodes[0] + return elementBefore === elementAfter + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("#a")) + var elementBefore = root.childNodes[0] + m.render(root, m("#b")) + var elementAfter = root.childNodes[0] + return elementBefore === elementAfter + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("div", {id: "a"})) + var elementBefore = root.childNodes[0] + m.render(root, m("div", {title: "b"})) + var elementAfter = root.childNodes[0] + return elementBefore !== elementAfter + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("#a")) + var elementBefore = root.childNodes[0] + m.render(root, m("[title=b]")) + var elementAfter = root.childNodes[0] + return elementBefore !== elementAfter + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("#a")) + var elementBefore = root.childNodes[0] + m.render(root, "test") + var elementAfter = root.childNodes[0] + return elementBefore !== elementAfter + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("div", [undefined])) + return root.childNodes[0].childNodes.length === 0 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("div.classname", [m("a", {href: "/first"})])) + m.render(root, m("div", [m("a", {href: "/second"})])) + return root.childNodes[0].childNodes.length == 1 + }) + + //m.redraw + test(function() { + var controller + var root = mock.document.createElement("div") + m.module(root, { + controller: function() {controller = this}, + view: function(ctrl) {return ctrl.value} + }) + controller.value = "foo" + m.redraw() + return root.childNodes[0].nodeValue === "foo" + }) + + //m.route + test(function() { + mock.performance.$elapse(50) + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/test1", { + "/test1": {controller: function() {}, view: function() {return "foo"}} + }) + return mock.location.search == "?/test1" && root.childNodes[0].nodeValue === "foo" + }) + test(function() { + mock.performance.$elapse(50) + + var root = mock.document.createElement("div") + m.route.mode = "pathname" + m.route(root, "/test2", { + "/test2": {controller: function() {}, view: function() {return "foo"}} + }) + return mock.location.pathname == "/test2" && root.childNodes[0].nodeValue === "foo" + }) + test(function() { + mock.performance.$elapse(50) + + var root = mock.document.createElement("div") + m.route.mode = "hash" + m.route(root, "/test3", { + "/test3": {controller: function() {}, view: function() {return "foo"}} + }) + return mock.location.hash == "#/test3" && root.childNodes[0].nodeValue === "foo" + }) + test(function() { + mock.performance.$elapse(50) + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/test4/foo", { + "/test4/:test": {controller: function() {}, view: function() {return m.route.param("test")}} + }) + return mock.location.search == "?/test4/foo" && root.childNodes[0].nodeValue === "foo" + }) + test(function() { + mock.performance.$elapse(50) + + var module = {controller: function() {}, view: function() {return m.route.param("test")}} + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/test5/foo", { + "/": module, + "/test5/:test": module + }) + var paramValueBefore = m.route.param("test") + m.route("/") + var paramValueAfter = m.route.param("test") + + return mock.location.search == "?/" && paramValueBefore === "foo" && paramValueAfter === undefined + }) + + //m.prop + test(function() { + var prop = m.prop("test") + return prop() === "test" + }) + test(function() { + var prop = m.prop("test") + prop("foo") + return prop() == "foo" + }) + + //m.request + test(function() { + var prop = m.request({method: "GET", url: "test"}) + var e = mock.XMLHttpRequest.$events.pop() + e.target.onload(e) + return prop().method === "GET" && prop().url === "test" + }) + test(function() { + var prop = m.request({method: "GET", url: "test"}).then(function(value) {return "foo"}) + var e = mock.XMLHttpRequest.$events.pop() + e.target.onload(e) + return prop() === "foo" + }) + + //m.deferred + test(function() { + var value + var deferred = m.deferred() + deferred.promise.then(function(data) {value = data}) + deferred.resolve("test") + return value === "test" + }) + test(function() { + var value + var deferred = m.deferred() + deferred.promise.then(function(data) {return "foo"}).then(function(data) {value = data}) + deferred.resolve("test") + return value === "foo" + }) + test(function() { + var value + var deferred = m.deferred() + deferred.promise.then(null, function(data) {value = data}) + deferred.reject("test") + return value === "test" + }) + test(function() { + var value + var deferred = m.deferred() + deferred.promise.then(null, function(data) {return "foo"}).then(null, function(data) {value = data}) + deferred.reject("test") + return value === "foo" + }) + test(function() { + var value1, value2 + var deferred = m.deferred() + deferred.promise.then(function(data) {throw new Error}).then(function(data) {value1 = 1}, function(data) {value2 = data}) + deferred.resolve("test") + return value1 === undefined && value2 instanceof Error + }) + + //m.sync + test(function() { + var value + var deferred1 = m.deferred() + var deferred2 = m.deferred() + m.sync([deferred1.promise, deferred2.promise]).then(function(data) {value = data}) + deferred1.resolve("test") + deferred2.resolve("foo") + return value[0] === "test" && value[1] === "foo" + }) + + //m.startComputation/m.endComputation + test(function() { + mock.performance.$elapse(50) + + var controller + var root = mock.document.createElement("div") + m.module(root, { + controller: function() {controller = this}, + view: function(ctrl) {return ctrl.value} + }) + + mock.performance.$elapse(50) + + m.startComputation() + controller.value = "foo" + m.endComputation() + return root.childNodes[0].nodeValue === "foo" + }) +} + +//mocks +testMithril(mock.window) + +test.print(console.log) \ No newline at end of file diff --git a/archive/v0.1.3/mithril.computation.html b/archive/v0.1.3/mithril.computation.html new file mode 100644 index 00000000..e6db3bc8 --- /dev/null +++ b/archive/v0.1.3/mithril.computation.html @@ -0,0 +1,159 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.startComputation / m.endComputation

+

If you need to do custom asynchronous calls without using Mithril's API, and find that your views are not redrawing, or that you're being forced to call m.redraw manually, you should consider using m.startComputation / m.endComputation so that Mithril can intelligently auto-redraw once your custom code finishes running.

+

In order to integrate an asynchronous code to Mithril's autoredrawing system, you should call m.startComputation BEFORE making an asynchronous call, and m.endComputation after the asynchronous callback completes.

+
//this service waits 1 second, logs "hello" and then notifies the view that
+//it may start redrawing (if no other asynchronous operations are pending)
+var doStuff = function() {
+    m.startComputation(); //call `startComputation` before the asynchronous `setTimeout`
+
+    setTimeout(function() {
+        console.log("hello");
+
+        m.endComputation(); //call `endComputation` at the end of the callback
+    }, 1000);
+};
+

To integrate synchronous code, call m.startComputation at the beginning of the method, and m.endComputation at the end.

+
window.onfocus = function() {
+    m.startComputation(); //call before everything else in the event handler
+
+    doStuff();
+
+    m.endComputation(); //call after everything else in the event handler
+}
+

For each m.startComputation call a library makes, it MUST also make one and ONLY one corresponding m.endComputation call.

+

You should not use these methods if your code is intended to run repeatedly (e.g. by using setInterval). If you want to repeatedly redraw the view without necessarily waiting for user input, you should manually call m.redraw within the repeatable context.

+
+

Integrating multiple execution threads

+

When integrating with third party libraries, you might find that you need to call asynchronous methods from outside of Mithril's API.

+

In order to integrate non-trivial asynchronous code to Mithril's auto-redrawing system, you need to ensure all execution threads call m.startComputation / m.endComputation.

+

An execution thread is basically any amount of code that runs before other asynchronous threads start to run.

+

Integrating multiple execution threads can be done in a two different ways: in a layered fashion or in comprehensive fashion

+

Layered integration

+

Layered integration is recommended for modular code where many different APIs may be put together at the application level.

+

Below is an example where various methods implemented with a third party library can be integrated in layered fashion: any of the methods can be used in isolation or in combination.

+

Notice how doBoth repeatedly calls m.startComputation since that method calls both doSomething and doAnother. This is perfectly valid: there are three asynchronous computations pending after the jQuery.when method is called, and therefore, three pairs of m.startComputation / m.endComputation in play.

+
var doSomething = function(callback) {
+    m.startComputation(); //call `startComputation` before the asynchronous AJAX request
+
+    return jQuery.ajax("/something").done(function() {
+        if (callback) callback();
+
+        m.endComputation(); //call `endComputation` at the end of the callback
+    });
+};
+var doAnother = function(callback) {
+    m.startComputation(); //call `startComputation` before the asynchronous AJAX request
+
+    return jQuery.ajax("/another").done(function() {
+        if (callback) callback();
+        m.endComputation(); //call `endComputation` at the end of the callback
+    });
+};
+var doBoth = function(callback) {
+    m.startComputation(); //call `startComputation` before the asynchronous synchronization method
+
+    jQuery.when(doSomething(), doAnother()).then(function() {
+        if (callback) callback();
+
+        m.endComputation(); //call `endComputation` at the end of the callback
+    })
+};
+

Comprehensive integration

+

Comprehensive integration is recommended if integrating a monolithic series of asynchronous operations. In contrast to layered integration, it minimizes the number of m.startComputation / m.endComputation to avoid clutter.

+

The example below shows a convoluted series of AJAX requests implemented with a third party library.

+
var doSomething = function(callback) {
+    m.startComputation(); //call `startComputation` before everything else
+
+    jQuery.ajax("/something").done(function() {
+        doStuff();
+        jQuery.ajax("/another").done(function() {
+            doMoreStuff();
+            jQuery.ajax("/more").done(function() {
+                if (callback) callback();
+
+                m.endComputation(); //call `endComputation` at the end of everything
+            });
+        });
+    });
+};
+
+

Signature

+

How to read signatures

+
void startComputation()
+
void endComputation()
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/mithril.deferred.html b/archive/v0.1.3/mithril.deferred.html new file mode 100644 index 00000000..7a6b9f23 --- /dev/null +++ b/archive/v0.1.3/mithril.deferred.html @@ -0,0 +1,136 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.deferred

+

This is a low-level method in Mithril. It's a modified version of the Thenable API.

+

A deferred is an asynchrony monad. It exposes a promise property which can bind callbacks to build a computation tree.

+

The deferred object can then apply a value by calling either resolve or reject, which then dispatches the value to be processed to the computation tree.

+

Each computation function takes a value as a parameter and is expected to return another value, which in turns is forwarded along to the next computation function (or functions) in the tree.

+
+

Usage

+
//standalone usage
+var greetAsync = function() {
+    var deferred = m.deferred();
+    setTimeout(function() {
+        deferred.resolve("hello");
+    }, 1000);
+    return deferred.promise;
+};
+
+greetAsync()
+    .then(function(value) {return value + " world"})
+    .then(function(value) {console.log(value)}); //logs "hello world" after 1 second
+
+

Signature

+

How to read signatures

+
Deferred deferred()
+
+where:
+    Deferred :: Object { Promise promise, void resolve(any value), void reject(any value) }
+    Promise :: GetterSetter { Promise then(any successCallback(any value), any errorCallback(any value)) }
+    GetterSetter :: any getterSetter([any value])
+
    +
  • GetterSetter { Promise then([any successCallback(any value) [, any errorCallback(any value)]]) } promise

    +

    A promise has a method called then which takes two computation callbacks as parameters.

    +

    The then method returns another promise whose computations (if any) receive their inputs from the parent promise's computation.

    +

    A promise is also a getter-setter (see m.prop). After a call to either resolve or reject, it holds the result of the parent's computation (or the resolve/reject value, if the promise has no parent promises)

    +
      +
    • Promise then([any successCallback(any value) [, any errorCallback(any value)]])

      +

      This method accepts two callbacks which process a value passed to the resolve and reject methods, respectively, and pass the processed value to the returned promise

      +
        +
      • any successCallback(any value) (optional)

        +

        The successCallback is called if resolve is called in the root deferred.

        +

        The default value (if this parameter is falsy) is the identity function function(value) {return value}

        +

        If this function returns undefined, then it passes the value argument to the next step in the thennable queue, if any

        +
      • +
      • any errorCallback(any value) (optional)

        +

        The errorCallback is called if reject is called in the root deferred.

        +

        The default value (if this parameter is falsy) is the identity function function(value) {return value}

        +

        If this function returns undefined, then it passes the value argument to the next step in the thennable queue, if any

        +
      • +
      • returns Promise promise

        +
      • +
      +
    • +
    +
  • +
  • void resolve(any value)

    +

    This method passes a value to the successCallback of the deferred object's child promise

    +
  • +
  • void reject(any value)

    +

    This method passes a value to the errorCallback of the deferred object's child promise

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/mithril.html b/archive/v0.1.3/mithril.html new file mode 100644 index 00000000..a6d2ec27 --- /dev/null +++ b/archive/v0.1.3/mithril.html @@ -0,0 +1,281 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m

+

This is a convenience method to compose virtual elements that can be rendered via m.render().

+

You are encouraged to use CSS selectors to define virtual elements. See "Signature" section for details.

+
+

Usage

+

You can use simple tag selectors to make templates resemble HTML:

+
m("br"); //yields a virtual element that represents <br>
+
+m("div", "Hello"); //yields <div>Hello</div>
+
+m("div", {class: "container"}, "Hello"); //yields <div class="container">Hello</div>
+

Note that the output value from m() is not an actual DOM element. In order to turn the virtual element into a real DOM element, you must call m.render().

+
m.render(document.body, m("br")); //puts a <br> in <body>
+

You can also use more complex CSS selectors:

+
m(".container"); //yields <div class="container"></div>
+
+m("#layout"); //yields <div id="layout"></div>
+
+m("a[name=top]"); //yields <a name="top"></a>
+
+m("[contenteditable]"); //yields <div contenteditable></div>
+
+m("a#google.external[href='http://google.com']", "Google"); //yields <a id="google" class="external" href="http://google.com">Google</a>
+

Each m() call creates a virtual DOM element, that is, a javascript object that represents a DOM element, and which is eventually converted into one.

+

You can, of course, nest virtual elements:

+
m("ul", [
+    m("li", "item 1"),
+    m("li", "item 2"),
+]);
+
+/*
+yields
+<ul>
+    <li>item 1</li>
+    <li>item 2</li>
+</ul>
+*/
+

Be aware that when nesting virtual elements, the child elements must be in an Array.

+
+

The CSS selector syntax (e.g. a#google.external[href='http://google.com']) is meant to be used for declaring static attributes in the element, i.e. attribute values that don't change dynamically when the user interacts with the app.

+

The attributes argument (i.e. the second parameter in the m("div", {class: "container"}, "Hello") example) is meant to be used for attributes whose values we want to dynamically populate.

+

For example, let's say that you're generating a link from an entry that comes from a web service:

+
//assume the variable `link` came from a web service
+var link = {url: "http://google.com", title: "Google"}
+
+m("a", {href: link.url}, link.title); //yields <a href="http://google.com">Google</a>
+

Here's a less trivial example:

+
var links = [
+    {title: "item 1", url: "/item1"},
+    {title: "item 2", url: "/item2"}
+    {title: "item 3", url: "/item3"}
+];
+
+m.render(document.body, [
+    m("ul.nav", [
+        m("li", links.map(function(link) {
+            return m("a", {href: link.url}, link.title)
+        })
+    ])
+]);
+

yields:

+
<body>
+    <ul class="nav">
+        <li>
+            <a href="/item1">item 1</a>
+            <a href="/item2">item 2</a>
+            <a href="/item3">item 3</a>
+        </li>
+    </ul>
+</body>
+

As you can see, flow control is done with vanilla Javascript. This allows the developer to abstract away any aspect of the template at will.

+
+

Note that you can use both javascript property names and HTML attribute names to set values in the attributes argument, but you should pass a value of appropriate type. If an attribute has the same name in Javascript and in HTML, then Mithril assumes you're setting the Javascript property.

+
m("div", {class: "widget"}); //yields <div class="widget"></div>
+
+m("div", {className: "widget"}); //yields <div class="widget"></div>
+
+m("input", {readonly: true}); //yields <input readonly />
+
+m("button", {onclick: alert}); //yields <button></button>, which alerts its event argument when clicked
+
+

Note that you can use JSON syntax if the attribute name you are setting has non-alphanumeric characters:

+
m("div", {"data-index": 1}); //yields <div data-index="1"></div>
+

You can set inline styles like this:

+
m("div", {style: {border: "1px solid red"}}); //yields <div style="border:1px solid red;"></div>
+

Note that in order to keep the framework lean, Mithril does not auto-append units like px or % to any values. Typically, you should not even be using inline styles to begin with (unless you are dynamically changing them).

+
+

You can define a non-HTML-standard attribute called config. This special parameter allows you to call methods on the DOM element after it gets created.

+

This is useful, for example, if you declare a canvas element and want to use the Javascript API to draw:

+
function draw(element, isInitialized) {
+    //don't redraw if we did once already
+    if (isInitialized) return;
+
+    var ctx = element.getContext("2d");
+    /* draws stuff */
+}
+
+var view = [
+    m("canvas", {config: draw})
+]
+
+//this creates the canvas element, and therefore, `isInitialized` is false
+m.render(document.body, view);
+
+//here, isInitialized is `true`
+m.render(document.body, view);
+

One common way of using config is in conjunction with m.route, which is an unobtrusive extension to links that allow Mithril's routing system to work transparently regardless of which routing mode is used.

+
//this link can use any of Mithril's routing system modes
+//(i.e. it can use either the hash, the querystring or the pathname as the router implementation)
+//without needing to hard-code any syntax (`#` or `?`) in the `href` attribute.
+m("a[href='/dashboard']", {config: m.route}, "Dashboard");
+

The config mechanism can also be used to put focus on form inputs, and call methods that would not be possible to execute via the regular attribute syntax.

+

It is only meant to be used to call methods on DOM elements that cannot be called otherwise.

+

It is NOT a "free out-of-jail card". You should not use this method to modify element properties that could be modified via the attributes argument, nor values outside of the DOM element in question.

+

Also note that the config callback only runs after a rendering lifecycle is done. Therefore, you should not use config to modify controller and model values, if you expect these changes to render immediately. Changes to controller and model values in this fashion will only render on the next m.render or m.module call.

+

You can use this mechanism to attach custom event listeners to controller methods (for example, when integrating with third party libraries), but you are responsible for making sure the integration with Mithril's autoredrawing system is in place. See the integration guide for more information.

+
+

Signature

+

How to read signatures

+
VirtualElement m(String selector [, Attributes attributes] [, Children children])
+
+where:
+    VirtualElement :: Object { String tag, Attributes attributes, Children children }
+    Attributes :: Object<any | void config(DOMElement element, Boolean isInitialized)>
+    Children :: String text | Array<String text | VirtualElement virtualElement | Children children>
+
    +
  • String selector

    +

    This string should be a CSS rule that represents a DOM element.

    +

    Only tag, id, class and attribute selectors are supported.

    +

    If the tag selector is omitted, it defaults to div.

    +

    Note that if the same attribute is defined in the both selector and attributes parameters, the value in attributes is used.

    +

    For developer convenience, Mithril makes an exception for the class attribute: if there are classes defined in both parameters, they are concatenated as a space separated list. It does not, however, de-dupe classes if the same class is declared twice.

    +

    Examples:

    +

    "div"

    +

    "#container"

    +

    ".active"

    +

    "[title='Application']"

    +

    "div#container.active[title='Application']"

    +

    ".active#container"

    +
  • +
  • Attributes attributes (optional)

    +

    This key-value map should define a list of HTML attributes and their respective values.

    +

    You can use both HTML and Javascript attribute names. For example, both class and className are valid.

    +

    Values' types should match the expected type for the respective attribute.

    +

    For example, the value for className should be a string.

    +

    When a attribute name expects different types for the value in HTML and Javascript, the Javascript type should be used.

    +

    For example, the value for the onclick attribute should be a function.

    +

    Similar, setting the value of attribute readonly to false is equivalent to removing the attribute in HTML.

    +

    It's also possible to set values to Javascript-only properties, such as hash in a <a> element.

    +

    Note that if the same attribute is defined in the both selector and attributes parameters, the value in attributes is used.

    +

    For developer convenience, Mithril makes an exception for the class attribute: if there are classes defined in both parameters, they are concatenated as a space separated list. It does not, however, de-dupe classes if the same class is declared twice.

    +

    Examples:

    +

    { title: "Application" }

    +

    { onclick: function(e) { /*do stuff*/ } }

    +

    { style: {border: "1px solid red"} }

    +
  • +
  • The config attribute

    +

    void config(DOMElement element, Boolean isInitialized) (optional)

    +

    You can define a non-HTML-standard attribute called config. This special parameter allows you to call methods on the DOM element after it gets created.

    +

    This is useful, for example, if you declare a canvas element and want to use the Javascript API to draw:

    +
    function draw(element, isInitialized) {
    +   //don't redraw if we did once already
    +   if (isInitialized) return;
    +
    +   var ctx = element.getContext("2d");
    +   /* draws stuff */
    +}
    +
    +var view = [
    +   m("canvas", {config: draw})
    +]
    +
    +//this creates the canvas element, and therefore, `isInitialized` is false
    +m.render(document.body, view);
    +
    +//here, isInitialized is `true`
    +m.render(document.body, view);
    +

    One common way of using config is in conjunction with m.route, which is an unobtrusive extension to links that allow Mithril's routing system to work transparently regardless of which routing mode is used.

    +
    //this link can use any of Mithril's routing system modes
    +//(i.e. it can use either the hash, the querystring or the pathname as the router implementation)
    +//without needing to hard-code any syntax (`#` or `?`) in the `href` attribute.
    +m("a[href='/dashboard']", {config: m.route}, "Dashboard");
    +

    The config mechanism can also be used to put focus on form inputs, and call methods that would not be possible to execute via the regular attribute syntax.

    +

    It is only meant to be used to call methods on DOM elements that cannot be called otherwise.

    +

    It is NOT a "free out-of-jail card". You should not use this method to modify element properties that could be modified via the attributes argument, nor values outside of the DOM element in question.

    +

    Also note that the config callback only runs after a rendering lifecycle is done. Therefore, you should not use config to modify controller and model values, if you expect these changes to render immediately. Changes to controller and model values in this fashion will only render on the next m.render or m.module call.

    +

    You can use this mechanism to attach custom event listeners to controller methods (for example, when integrating with third party libraries), but you are responsible for making sure the integration with Mithril's autoredrawing system is in place. See the integration guide for more information.

    +
      +
    • DOMElement element
    • +
    +

    The DOM element that corresponds to virtual element defined by the m() call.

    +
      +
    • Boolean isInitialized
    • +
    +

    Whether this is the first time we are running this function on this element. This flag is false the first time it runs on an element, and true on redraws that happen after the element has been created.

    +
  • +
  • Children children (optional)

    +

    If this argument is a string, it will be rendered as a text node. To render a string as HTML, see m.trust

    +

    If it's a VirtualElement, it will be rendered as a DOM Element.

    +

    If it's a list, its contents will recursively be rendered as appropriate and appended as children of the element being created.

    +
  • +
  • returns VirtualElement

    +

    The returned VirtualElement is a javascript data structure that represents the DOM element to be rendered by m.render

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/mithril.min.js b/archive/v0.1.3/mithril.min.js new file mode 100644 index 00000000..0cba8242 --- /dev/null +++ b/archive/v0.1.3/mithril.min.js @@ -0,0 +1,8 @@ +/* +Mithril v0.1.3 +http://github.com/lhorie/mithril.js +(c) Leo Horie +License: MIT +*/ +!new function(a){function b(e,f,g){if(null!==f&&void 0!==f){var h=s.call(g),i=s.call(f);if(h!=i&&(null!==g&&void 0!==g&&d(g.nodes),g=new f.constructor,g.nodes=[]),"[object Array]"==i){for(var j=[],k=g.length===f.length,l=0;l-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e];if(!(e in d)||d[e]!==g||b===a.document.activeElement){if(d[e]=g,"config"==e)continue;if(0==e.indexOf("on")&&"function"==typeof g&&(g=f(g,b)),"style"==e)for(var h in g)b.style[h]=g[h];else e in b?b[e]=g:b.setAttribute(e,g)}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:\w+/g);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;cE%zxnwm{_~%|{kO`$ zfBoNY|Kor8=_mgCj~JVdlhI-vm8aubd9vL3=bxK8uyws#-Yf6_JW7)J!OqSQqpMyy zh^ITzC|-=iopj@5`ExNSZM_TQtrJfC{B9hCvt@X&)vTZX{Lec-KbwU&TZ7dsNXGH3 z=$F2w?X6xhvE2;(FWUX$usn(CpGqkeI=d8|1rJA9Hq z7PQJA^!#~P_UH3()?c5srfN#WHD4x=k72Y7w?K|Hds@OJ!yit7o8#HA@XSu`=3zY8 z8k7@%SXzY1YB4)3O79u)#o)t(ZZ2PicT3FpNwQd$%9D6JE0&(w`N`qtWGOF7hm?q! z72CVO?FsVnQE_Jb<$fHjrXXZFScHBOR-=$T1%{&d4bU72XqEtR5EN&Y0m%CHSh4~V z8mu_z$wb-(WzuHJ5)K|*%q}0Tr!*}c0F0E!yO&n}_!PC=SrAoTpB|l?0k9jAYCqfk z{<2JWj*W_wJw{^?5m2+?XVpA z%futG4nru@GF&81f8xXXBqyiMcf~?4ga(8Vw}LeQ$?W;jygUnUlg_voL2M4f@?g9$ zN2svm^C~RjtV3SNm%o@59ohJYOwG zm_O_nC$LVC>Hk0UBwuFXg6yydDT#Bk`IMm6Oi1yOsM`aO_eT`_o?hR{>7$x}t>NS2 z+S>%ho)uUSpVTbEw#>Ey6{n#cT!ccjXGIzBmmVJa7y|KOJ9^OeoMyvpkFQhB7KU)C zJ+ulc>Ck|%gl00HtwJNzB!!AZA)XbW3xyOitTs{tuT>n{gW}NYmEe_@$z2r6St-aP zf;=qs$})8^!kzdz6rmBYdq+Ki{M%rc~)?MCpq|HlD5rch>uh zpBnQX+?jvTyUaO;4OhUq(qX#X-px)8c`BunG}$f{O524$7bHjv%L#Bm zjJR=2u-=atFbxhf-3;u08Po|zx#MAf5sH+c<`4X72p8` z`0Yh-F)*8od!y~HzOPJ5-c!;ySQANEYTBV%R(E*b-Qo@+21Z) z77qFlHy;Xe7^pWbXbzEC^TE-qIIzOfK{yM*@9u{>%}_jJG#ElZ?85dBW(JTQ!`DE= z)57yYj`4A-(5dx&{yZPN_DV+j|GEo#D%7f{0LV8Sgbf1+(Z%mJgbkm}D;*U|f7v}M z92E|D;WqeQI)KjusbE~_k8)x*&(jO18%_Ok11lg(AAt6y)NA#jmiB}AV*CJ#deuT#^U|Fpte#%MQk%Yub?49D#LGf=7e|q>* zF2O4P=}Y;`#g|K1#?nLa%SCzn=*wjZ7IS6qe7Pv1Tf%y~Sa|rShmv%^yeMJG&Y?-i z?Ww(nT#*5>QeS8F>>s2^CIa7Bog?Zt(E zv3n^2k!G7rdqFWBwg;NU6#C=qg2`gOJ!YPMXpLa04~KSA4sSzH5NDMROU&3HjYC2t z2@skuO!UIJKV_4W8I{NVCo9V7ijnFZ_p5W&JW;0iFL|ACd~B~2g5kwJ7Mk?qVhji_ z?J>q49-i$!j`Av#uIXReBcp#?V$W?!0n9WzCQZyGPeSAumj|Tu+J;Ot%uLUnT-p=c z?jNQ5FD3w@K!3Sa*bdM;;Llo!9}g*F*Vc{w?fVobvxMT}a}nyzVhm3eKxuRNXcR9I zojh8(96WadC?=H`aQYgi;~L8N2dQjfluRSg#40eb2hDpDwNrMEuI=8z^+uKt!<#!P zJ<_Db2(NKjZr

q;C?wU)xJq z|6b7-E!>RUj)Kkpg%9Q19NG--=m`|-6Z_j7z|J7@2Ze&g@a+Kh%UU9W570a+JX-Y( zE-=!P0NezC`59QgeMnzw%#AHl`_;?FVbhosq|0DoV92vB>}c-3b;#mtxLiEBFgHu+ zd6`~vI6}hJM~w3DVCE4iJwpN^RwuMIZnZb!)+6Mv#5WlEvtfv6A)d|SIXIt$g+rg& z=bG}RZ}nVAy9oj~ShFVoNz04t{5*fL0&|tS9l#*ATEn(?2}}BH7YXtR z@W+cgF*zO5>8M!C6KTETB04G<11Rf2To!7yeJOSByhTjLNfcU7Si5~yvXF8>{GKu$ zGmPlcjAD}=_7zM`DkG=_`?-K4Gh}a}BH1!#F(`nE$QQ7AzKG`;UfHgkJQ&?vQ^^P- zlsgyC(0#)y@}hn?K+GTZvtyO+J}rNbKY@)nZW%|{VP-dB4b#{&fWZv1nnoZsjo=ai z-=nn%S@h4g0piVVJp&FL5q9EvbVojPkI0y+ix6NnZ|nBQ!a(P@T= z)}l=6yCvy1163rRtj(=y2B4C~orzx=j2;e9cPXyl0QyJ$13-m563KEXDLpGe&_@&* zP!g{)vts2PnNa%p0k12=CA|H_H(MbBBc%lnctkEtw>f~)r_@IQ!!Qm-mSSK-#QJFT z*&yH$TEVvf1TfI!07+Xwxedx@sDez7EM4Bsf{Y5Jc}jyGxB_Ofc%MBOG3wNz&`_pi z^#qFa64qrS^EQ)KqaaZI^j;uk^~t;tertf*h$(jkb_REU16wIHFj7D@AwBVs)dZil z1hTQ%E5b}#_}we5iL<^@R7A7x4NSjxkf1Dy-P^pL$vYD4u^)}^Q5kyMX}yPQkL|xGKI)VnARi63(Blg zGKHMoEc|(A6$IgO34@}26{v@YE6^=0B5%U0idu!LH#5ER?cU>KF@Z}e9sazt^QWz4yjlcd6FC9I ztNY=doj&aHMR@AZf8TgM?)~ro@xT7(?oU7Q-~aIB`52486|Td@5(TZo!9EZfqkMf( z$R9Xa7EsrgE0=|Xi$b~lDL5apE@PiS^ zoF{oYcnW#K1jR}gMiD;TW+tTCiXnMQ(sODLz;Vdy5A5;*-6c zqLX_qMK70Yp@*kG{A#@h4ZcNk4c{X1( ze@oEw83`81OE}7ncLfZg%8OS9G~{7{-!!O0dozE*z#1+*5}=!VML@5@sMso)rvbFc zlMg8-`VfH8AH4{0IQJlcuDD}-fA}nb@$6Lqo$R7I^+QV6?p?}|MH~}B8C{^kWG3zZ z&ghSvl39G!MzO&lBjkF03E&!i1we~c{&Wq#A|*UU>h<`_Y28`eW15WTGYs+H%`!U% zHj3-$=gaHpE(7MTr|}6Z>H-^2NrPH6nVaACG!b-6z7=pSClok|8OG9*VkrfP|$P#xY6rlcA# zwMTZ0+T$sV)RbXZeO!)`AwFZZ_hUjV1*gm+@;x4-{}umAp$lKA{gPv}I;lM|e~4kq zHkiy8m(1D}B|?$5wcddR@?wjx6n5}kV{K&ggKClKHRah1%UC4A#|(RT2iB4c$MPNi zB@CRdd37P``1>kYp78fLH4-OrOhd)O2tFu{;5RLML46+PUZ(J%B2rr*SX_HUi{zKLsL5LbNpULj$p&bms(e8jBlaG{>+B0T_cgL{5c6`Nwk{$ErC_-Re3{ zhs`VRv(-l1<8afns*cl^U$kqE_tNFKZ?zofw8G}DTeUjq9CPKuwLGV)C)Q-4Y;AZ} z!*LpKInrrx;h8Kq~y%cE{hr+>)i1o_k&r~b!u(v%5|L2a`PJjbmHM2r-5CSO4VvQ&UsaiIh9Ra zUU~M3?Zz2zx%LE`I!9^_f9s#uv!s$7<~iO%h*y{2)__a$glwD(m~wFI3~dngBk6y7 zMzlRSx$%zhG=;WpAt;gaCZ58^Z4Ss+bE-YL_Jp^zL4B~#;N;Y+T=jRsUEyY6*{^Ur zfB~DX+_!R;fYEG{39X))#&NZ`Bt=vcj`IAa+u|p31%$e6Zq~RhkgHpL+1wI*>H?8N z5MQ}mGk0s81}f){^VJnN&^ZI;W$2vj@@`Mo*S&Iu8u#WprJ^35zf|O*`7d2JaRs)i z%iGV1fVWfY6UJ7T&~T*_$LK;biB3V8PmHxZagD;Wdo)!#|#RtIFL&W|Z+Vp8km6`G(p z=!V~0H%|do3$6w_*k9Lsp?{=4$CZU@ug-Ur=8)tLB-urmcS2w&<%)+dgP>MOs9W-T zE~M{PsYwA0D<`fzD|Bj6MbA&g+z3+#b50mhRlLL##m|^o1%f~VH+QTX+>VozbPrC4zg5xJx>9B;#wD1mdx|}Z;~7cuT2!}g zCcdYU+}zb^H3iKKj1Jc0?puKokbwpr9{O3dTnQwu4G&RE$48T_6xYH^?FP7R?374#G|sD2QQ+7&_=Ek+0Qg_i&vfSl%r*FuUxOt=lS zg`+CK8d#i(kAjeb()V(FQ+&~Rg`37|KF;NYK#=fXi$Zla?O^PREE3tt2`;KQaxOOF zQvBld50t3SBGq7D+o{58)}0DijU{_}QAe<9Q$oj{1}7cD0f86PBpA_1sVi`J(J^)Q zGc*XO!(^?Iu5(|TusB?b*#?dT`I-_9VTQ6uOW<8s-wK5^k^}x+j1$+OU&BAiAXJW0 zDsfhq6s|%0$PyCQummI6sIg%`4-ZE3kw-2pFoBYeh=;D#a}HmmOgy*u8lduYv0(ID zRTzB+r>l)yc@=pE69_8gq@XQQpy4Z=C)QyE=(a=vcGS_5Kn5A{)MsrjftQ6`fe^%( zq2znPrYd|wXiB}56FW-Ux)59Jb2kyTx8GDC15h#$z2XQW)rJKb^BO$+6(TvJLeAlO z3Xv#Ab0TbST~$f^Fk1H*p)f{vaw~LY8)p>>*;_o% z`(4B7aXH;!ENyL&J(7B;AA8LhYAyI$r(Dr*(L?a zPh`9btAwjs;#xHTZ*gfA{2}{EpijFlkt}D3)=NUB>2a4YB>#jrq5Zh0dZVVaE1jwYiE27vt*VJKSrqh~2aa3}<+gBG9_3 z(ZnIt1nZi!Cu1N?>*-k2xvIWA7D!bg<+sg4TjWXANSO>HwE)L@_%Z6+ywxl2Y79pZ zgL39xB&zkJvE3TbYeE59&O1lU3&fB7(xj;&yby_NH&t+fsm9_6T(PhznMu1%Rf!PL zVY)6Z0M?fIY2<1Y!ypeSgjmD2bUGGPa1L9+Egd2p>y{ZZ6RJd)V>gWr(F8ke@2*E%gpY!H8dG_NZvWs(XuBxY~V8OD1bzmihMQc4PY{+k-QZP}^*Bxi^< z`NA4tZ7eaB7AXg5JQnq9iZ-F}0Gv9af3HIf(Roz5s5Jp0D&S?@l!>P2!eICh1rlx z9(}D=;)bTEB~I~u9XTpAtc}=!Bo&QrAe+GDO`$T;gXCdy;r=Q@ch*PG>jymYtS1=z zrj5&6X&a~3VTJ)jn?SX=q%Nza`$kn>0+H(8@k&5D1B6@)2(h{=uvPb6nKUdE`VU>9 zQe%%2Pj^S6?uv!&Xo6J-4p1f9HL{Mtd=*vd?(uA3gdz30TG!x(raJOQbqnbyu!d2W zl@tLvpnfB221AG)t>2`xHdZ)!k69bZCt^^?W3ctTZ#c9T5g$BX7MFkKOsDt3PvzhRqO@epn++KTN2RSBoE09_M#f8ZzP;E2cq5Ts>;PFlt6k!TGqVAbMh4+T?nOp`}QJ}w$$7YP9*pD}j!PA!@Ehcodkjq5ccs1pJ(f?@zuJJ2A$ zCgSJ$)V$h`R?^y|IwfC>a1D}0Qkl(D!D_5*Zex(50em50m?Cd)Md+J16fCbNP@Bzd z%~d#MP6SbIU6qLjl%6tU%%Rd=z}6$Mh2Bf912T2B%I9KLy)$!$0TMm{OA>^O_?*8H z5nrh~qf{60*F|m-rUS_nXahY8Bo-KS zR4Hl`hSHEOr3z5z+?~osBy-8;+%8oZE@o0^-Ncvmq_+2=$M)$fG4!!UT`e3zYMXPl z8i!>pI6&Pd>eCxvMWbR@#JvT!f{HutP*wT$2`@sa6L&=JW?K8}i9{f=%RC51dh@zh zCl<3oEv#-%VIyc!k5EJhuZ9Q}VKC;5=)j9AB03Q)(&9HU6-|v8O!y9^!ZJ-!yWl3R zJSq|ZVb4_!^ojNd`IREA(*^Y@73zs3VXJTRXz!)BFNn4@7HI2*BpEFF`IRU|Zjdkm zdPWadO0aNIr48CShFt_$-B0{&?#Q7{Q=@z#{B4Tk#sX9xUwerNcr})iXImJFC>(wW zG;TV_j>x5^X-EKoV{4DoX$hr?YfZe5NQ*-DHGKDx-bl!hcYR z`GY(fze1;)gb5*0xjoVPn~C!~0-Gj9BGC6P7=e9o7vQ3IEODL_AP)gO`%}Sd=n5f` z>%e(nEyN!1)JN(`#>aFnJ1(LKp3|ThRZrv!P4}1zc^{>aV8o%Npo)18RK-PV?vb(b z5TBy%OGTj$n&L79au~144kR3quu0{mN|Hm8@*xiW3-UeCpW9o{WK&00bbkoyNWk5YVe`t0fcKs-tYeCg3EstqA-D5fggV7E{g7u%kHSc zQ+&$Pp7MSu$g~h8a-NIpW1(dT{Ien9S_eV23_+sqt3&Z8XF@9;!k~-WN5ak4P$=4h zLeZr{wt#$7X_{u5k}@`Dd?AFLQqTXSfB@WfObI>R^-82Qz+EVEl4M!}cp-u1$c57< znSnU(4M{=hdnmsMWbP|!&st-tZ=91-=FzM3n}juK?h13faS*D1ktaG0s!D+c9uUZ4 z5f6g{P(C7bO6gXEb3=wrp=UrOa-hL#7fd&+U}Bd7^UzJqAnD6NhCmu&R4sT9>MKl6 zXHOjT!c%YDuE-rgl5^M0p%D#`Fdzv{;>dk1IwjnNnFc8L3X1A~+Xx4F7`Yl_{OTgv z5CBE&vE{Xg91#3%t={n>Yo;d*%zU~jdeN~hJjA4wBdBMaPj#q8nfLdy_OSqYqEVeOhJL5|DFWjH4?Jrm84 z`c6w#_~RZ4I%&4+A( zE^;T&OH(_!x4WYS53!_;H#!Doz~b7H_N97Xc_HukN|}9;KbVZabMm&HGI}RwOHVdq zxGjG&UH~Y#@SIAy7V0IRv1{RKWu|qfS+f*k9eJRFIldM&(m64dN^fC+q>UdfBe#Lr1m;icGJK%L{WHQ1zM-fF0NXGWHOHU3`vP_& z78;#V3ePZv_51F<5UlalOo2jOonpZgXCNVi?RyVr#R|MiE94LtD?=oHO6xBOngF!K zL42eYoxo)saT@p|i>CA58H)pHzgCtgH$K3f0P3y+(*b}mo4d-&TQNvo1b~F*rKHn) zu8MI7s!D5xO1NmD)W|v-@U*cXvi(UMLX!_vq9=ojys+~Q|5W3ovuLe^(7#?6$a#%x zF=Kgc(twf>&{d1A7z)9tj}hm><1_iQ3?FeZ0g=~*dkrW8kOdSx1I~;rw06cY zZ0JrXcmro52E-J(LM}Fujt(^>W@XU|^@NudDN{MV+zN%nYJFWcRjnpG4F$5IU`hSE zBgN-4c~enTAtYp+h)o?K2biQ@@Z(4wJ@grfj9txP&k*Q}79px2F0tuKOzX{#Ux==q z$yPjo5hi4>_*CJl!8KI;f;8Q)A5%+X{AmldRFh*CM!@`iHE3iM%D(*N8OEp%C5rD% z^oc}Qnz4~Q&!{y9DA=e6!aC_DEI^!rn*MqcKs9wz{#vLGd^fj&Jogoiy;5nH(hx)k zqW}vLH4ylyp-MHS7j^ucKV6)TGos2uG(3&_wL;gB5W#z=8C-h4np+{_8O$>7P;ted zOufo=&+3v~e|#x1E#hC`^2wod;u|fgjdYemeJ@Ehn9S1o$KAJF!skzv|huS^v`Ef s>Hk?l|C@PF_dfl + + + Mithril + + + + + +

+ +
+
+
+
+
+ +
+

m.module

+

A module is an Object with two keys: controller and view. Each of those should point to a Javascript class constructor function.

+

'm.module' activates a module by instantiating its controller, then instantiating its view and rendering it into a root DOM element.

+

Conceptually, the easiest way to think of a module is as a logical namespace with which to organize applications. For example, an app might have a dashboard module, a userEditForm module, an autocompleter module, a date formatting module, etc

+

In the context of single page applications (SPA), a module can often be thought of as the code for a single "page", i.e. a visual state that is bookmarkable. Module can, however, also represent parts of pages.

+

Note that a module might have external dependencies and that the dependencies aren't considered part of the module.

+

In more complex applications, modules can be nested in a hierarchical MVC pattern. Nested reusable modules that have views are called Components.

+

Modules and namespaces are often used interchangeably, but namespaces that do not implement the module interface (that is, objects that do not have a property called controller and a property called view) cannot be activated with m.module. For example, a namespace for date formatting utilities could be labeled a "module" (in the generic sense of the word) but it would not contain a view class, and therefore attempting to initialize it via m.module would result in undefined behavior.

+
+

Usage

+

You can make anonymous modules out of existing classes

+
//controller class
+var dashboardController = function() {
+    this.greeting = "Hello";
+};
+
+//view class
+var dashboardView = function(ctrl) {
+    return m("h1", ctrl.greeting);
+};
+
+//initialize an anonymous module
+m.module(document.body, {controller: dashboardController, view: dashboardView});
+

Typically, however, modules and namespaces are used interchangeably.

+
//`dashboard` is both a namespace and a module
+var dashboard = {}
+
+//controller class
+dashboard.controller = function() {
+    this.greeting = "Hello";
+};
+
+//view class
+dashboard.view = function(ctrl) {
+    return m("h1", ctrl.greeting);
+};
+
+//initialize it
+m.module(document.body, dashboard);
+

The example below shows a component module called user being included in a parent module dashboard.

+
//this is a sample module
+var dashboard = {
+    controller: function() {
+        this.greeting = "Hello";
+
+        this.user = new user.controller();
+    },
+    view: function(controller) {
+        return [
+            m("h1", controller.greeting),
+
+            new user.view(controller.user)
+        ];
+    }
+};
+
+//this module is being included as a component
+var user = {
+    //model
+    User: function(name) {
+        this.name = name;
+    },
+    //controller
+    controller: function() {
+        this.user = new user.User("John Doe");
+    },
+    //view
+    view: function(controller) {
+        return m("div", controller.user.name);
+    }
+};
+
+//activate the dashboard module
+m.module(document.body, dashboard);
+

yields:

+
<body>
+    <h1>Hello</h1>
+    <div>John Doe</div>
+</body>
+
+

Signature

+

How to read signatures

+
void module(DOMElement rootElement, Module module)
+
+where:
+    Module :: Object { void controller(), void view(Object controllerInstance) }
+
    +
  • DOMElement rootElement

    +

    A DOM element which will contain the view's template.

    +
  • +
  • Module module

    +

    A module is supposed to be an Object with two keys: controller and view. Each of those should point to a Javascript class constructor function

    +

    The controller class is instantiated immediately upon calling m.module.

    +

    Once the controller code finishes executing (and this may include waiting for AJAX requests to complete), the view class is instantiated, and the instance of the controller is passed as an argument to the view's constructor.

    +

    Note that controllers can manually instantiate child controllers (since they are simply Javascript constructors), and likewise, views can instantiate child views and manually pass the child controller instances down the the child view constructors.

    +

    This "turtles all the way down" approach is the heart of Mithril's component system.

    +

    Components are nothing more than decoupled classes that can be dynamically brought together as required. This permits the swapping of implementations at a routing level (for example, if implementing widgetized versions of existing components) and class dependency hierarchies can be structurally organized to provide uniform interfaces (for unit tests, for example).

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/mithril.prop.html b/archive/v0.1.3/mithril.prop.html new file mode 100644 index 00000000..19b2bcd8 --- /dev/null +++ b/archive/v0.1.3/mithril.prop.html @@ -0,0 +1,140 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.prop

+

This is a getter-setter factory utility. It returns a function that stores information

+
+

Usage

+
//define a getter-setter with initial value `John`
+var name = m.prop("John");
+
+//read the value
+var a = name(); //a == "John"
+
+//set the value to `Mary`
+name("Mary"); //Mary
+
+//read the value
+var b = name(); //b == "Mary"
+

It can be used in conjunction with m.withAttr to implement data binding in the view-to-model direction and to provide uniform data access for model entity properties.

+
//a contrived example of bi-directional data binding
+var user = {
+    model: function(name) {
+        this.name = m.prop(name);
+    },
+    controller: function() {
+        this.user = new user.model("John Doe");
+    },
+    view: function(controller) {
+        m.render("body", [
+            m("input", {onchange: m.withAttr("value", controller.user.name), value: controller.user.name()})
+        ]);
+    }
+};
+

In the example above, the usage of m.prop allows the developer to change the implementation of the user name getter/setter without the need for code changes in the controller and view.

+

m.prop can also be used in conjunction with m.request and m.deferred to bind data on completion of an asynchronous operation.

+
var users = m.prop([]);
+var error = m.prop("");
+
+m.request({method: "GET", url: "/users"})
+    .then(users, error); //on success, `users` will be populated, otherwise `error` will be populated
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of User instances
+//i.e. users()[0].name() == "John"
+
+

Signature

+

How to read signatures

+
GetterSetter prop([any initialValue])
+
+where:
+    GetterSetter :: any getterSetter([any value])
+
    +
  • any initialValue (optional)

    +

    An initialization value. If not provided, the value of the getter-setter's internal store defaults to undefined.

    +
  • +
  • returns any getterSetter([any value])

    +

    A getter-setter method.

    +
      +
    • any value (optional)

      +

      If provided, it updates the getter-setter's internal store to the provided value.

      +

      If not provided, return the current internally stored value.

      +
    • +
    • returns any value

      +

      This method always returns the value of the internal store, regardless of whether it was updated or not.

      +
    • +
    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/mithril.redraw.html b/archive/v0.1.3/mithril.redraw.html new file mode 100644 index 00000000..1b6b7fe5 --- /dev/null +++ b/archive/v0.1.3/mithril.redraw.html @@ -0,0 +1,89 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.redraw

+

Redraws the view for the currently active module. Use m.module() to activate a module.

+

This method is called internally by Mithril's auto-redrawing system and is only documented for completeness; you should avoid calling it manually unless you explicitly want a multi-pass redraw cycle.

+

A multi-pass redraw cycle is usually only useful if you need non-trivial UI metrics measurements. A multi-pass cycle may span multiple browser repaints and therefore could cause flash of unbehaviored content (FOUC) and performance degradation.

+

By default, if you're using either m.route or m.module, m.redraw() is called automatically by Mithril's auto-redrawing system once the controller finishes executing.

+

m.redraw is also called automatically on event handlers defined in virtual elements.

+

If there are pending m.request calls in either a controller constructor or event handler, the auto-redrawing system waits for all the AJAX requests to complete before calling m.redraw.

+

This method may also be called manually from within a controller if more granular updates to the view are needed, however doing so is generally not recommended, as it may degrade performance. Model classes should never call this method.

+

If you are developing an asynchronous model-level service and finding that Mithril is not redrawing the view after your code runs, you should use m.startComputation and m.endComputation to integrate with Mithril's auto-redrawing system instead.

+
+

Signature

+

How to read signatures

+
void redraw()
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/mithril.render.html b/archive/v0.1.3/mithril.render.html new file mode 100644 index 00000000..f7007f5f --- /dev/null +++ b/archive/v0.1.3/mithril.render.html @@ -0,0 +1,121 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.render

+

This method generates a DOM tree inside of a given HTML element.

+

If the method is run more than once with the same root element, it diffs the new tree against the existing one and intelligently modifies only the portions that have changed.

+

Note that, unlike many templating engines, this "smart diff" feature does not affect things like cursor placement in inputs and focus, and is therefore safe to call during user interactions.

+
+

Usage

+

Assuming a document has an empty <body> element, the code below:

+
var links = [
+    {title: "item 1", url: "/item1"}
+];
+
+m.render(document.body, [
+    m("ul.nav", [
+        m("li", links.map(function(link) {
+            return m("a", {href: link.url, config: m.route}, link.title)
+        })
+    ])
+]);
+

yields:

+
<body>
+    <ul class="nav">
+        <li>
+            <a href="/item1">item 1</a>
+        </li>
+    </ul>
+</body>
+
+

Signature

+

How to read signatures

+
void render(DOMElement rootElement, Children children)
+
+where:
+    Children :: String text | Array<String text | VirtualElement virtualElement | Children children>
+    VirtualElement :: Object { String tag, Attributes attributes, Children children }
+    Attributes :: Object<Any | void config(DOMElement element)>
+
    +
  • DOMElement rootElement

    +

    A DOM element which will contain the template represented by children.

    +
  • +
  • Children children

    +

    If this argument is a string, it will be rendered as a text node. To render a string as HTML, see m.trust

    +

    If it's a VirtualElement, it will be rendered as a DOM Element.

    +

    If it's a list, its contents will recursively be rendered as appropriate and appended as children of the root element.

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/mithril.request.html b/archive/v0.1.3/mithril.request.html new file mode 100644 index 00000000..2e279805 --- /dev/null +++ b/archive/v0.1.3/mithril.request.html @@ -0,0 +1,346 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.request

+

This is a high-level utility for working with web services, which allows writing asynchronous code relatively procedurally.

+

By default, it assumes server responses are in JSON format and optionally instantiates a class with the response data.

+

It provides a number of useful features out of the box:

+
    +
  • The ability to get an early reference to a container that will hold the asynchronous response
  • +
  • The ability to queue operations to be performed after the asynchronous request completes
  • +
  • The ability to "cast" the response to a class of your choice
  • +
  • The ability to unwrap data in a response that includes metadata properties
  • +
+
+

Basic usage

+

The basic usage pattern for m.request returns an m.prop getter-setter, which is populated when the AJAX request completes.

+

The returned getter-setter can be thought of as a box: you can pass this reference around cheaply, and you can "unwrap" its value when needed.

+
var users = m.request({method: "GET", url: "/user"});
+
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}]
+

Note that this getter-setter holds an undefined value until the AJAX request completes. Attempting to unwrap its value early will likely result in errors.

+

The returned getter-setter also implements the promise interface (also known as a thennable): this is the mechanism you should always use to queue operations to be performed on the data from the web service.

+

The simplest use case of this feature is to implement functional value assignment via m.prop (i.e. the same thing as above). You can bind a pre-existing getter-setter by passing it in as a parameter to a .then method:

+
var users = m.prop([]); //default value
+
+m.request({method: "GET", url: "/user"}).then(users)
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}]
+

This syntax allows you to bind intermediate results before piping them down for further processing, for example:

+
var users = m.prop([]); //default value
+var doSomething = function() { /*...*/ }
+
+m.request({method: "GET", url: "/user"}).then(users).then(doSomething)
+

While both basic assignment syntax and thennable syntax can be used to the same effect, typically it's recommended that you use the assignment syntax whenever possible, as it's easier to read.

+

The thennable mechanism is intended to be used in 3 ways:

+
    +
  • in the model layer: to process web service data in transformative ways (e.g. filtering a list based on a parameter that the web service doesn't support)
  • +
  • in the controller layer: to bind redirection code upon a condition
  • +
  • in the controller layer: to bind error messages
  • +
+

Processing web service data

+

This step is meant to be done in the model layer. Doing it in the controller level is also possible, but philosophically not recommended, because by tying logic to a controller, the code becomes harder to reuse due to unrelated controller dependencies.

+

In the example below, the listEven method returns a getter-setter that resolves to a list of users containing only users whose id is even.

+
//model
+var User = {}
+
+User.listEven = function() {
+    return m.request({method: "GET", url: "/user"}).then(function(list) {
+        return list.filter(function(user) {return user.id % 2 == 0});
+    });
+}
+
+//controller
+var controller = function() {
+    this.users = User.listEven()
+}
+

Bind redirection code

+

This step is meant to be done in the controller layer. Doing it in the model level is also possible, but philosophically not recommended, because by tying redirection to the model, the code becomes harder to reuse due to overly tight coupling.

+

In the example below, we use the previously defined listEven model method and queue a controller-level function that redirects to another page if the user list is empty.

+
//controller
+var controller = function() {
+    this.users = User.listEven().then(function(users) {
+        if (users.length == 0) m.route("/add");
+    })
+}
+

Binding errors

+

Mithril thennables take two functions as optional parameters: the first parameter is called if the web service request completes successfully. The second one is called if it completes with an error.

+

Error binding is meant to be done in the controller layer. Doing it in the model level is also possible, but generally leads to more code in order to connect all the dots.

+

In the example below, we bind an error getter-setter to our previous controller so that the error variable gets populated if the server throws an error.

+
//controller
+var controller = function() {
+    this.error = m.prop("")
+
+    this.users = User.listEven().then(function(users) {
+        if (users.length == 0) m.route("/add");
+    }, this.error)
+}
+

If the controller doesn't already have a success callback to run after a request resolves, you can still bind errors like this:

+
//controller
+var controller = function() {
+    this.error = m.prop("")
+
+    this.users = User.listEven().then(null, this.error)
+}
+
+

Queuing Operations

+

As you saw, you can chain operations that act on the response data. Typically this is required in three situations:

+
    +
  • in model-level methods if client-side processing is needed to make the data useful for a controller or view.
  • +
  • in the controller, to redirect after a model service resolves.
  • +
  • in the controller, to bind error messages
  • +
+

In the example below, we take advantage of queuing to debug the ajax response data prior to doing further processing on the user list

+
var users = m.request({method: "GET", url: "/user"})
+    .then(console.log);
+    .then(function(users) {
+        //add one more user to the response
+        return users.concat({name: "Jane"})
+    })
+
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}, {name: "Jane"}]
+
+

Casting the Response Data to a Class

+

It's possible to auto-cast a JSON response to a class. This is useful when we want to control access to certain properties in an object, as opposed to exposing all the fields in POJOs (plain old javascript objects) for arbitrary processing.

+

In the example below, User.list returns a list of User instances.

+
var User = function(data) {
+    this.name = m.prop(data.name);
+}
+
+User.list = function() {
+    return m.request({method: "GET", url: "/user", type: User});
+}
+
+var users = User.list();
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), `users` will contain a list of User instances
+//i.e. users()[0].name() == "John"
+
+

Unwrapping Response Data

+

Often, web services return the relevant data wrapped in objects that contain metadata.

+

Mithril allows you to unwrap the relevant data, by providing two callback hooks: unwrapSuccess and unwrapError.

+

These hooks allow you to unwrap different parts of the response data depending on whether it succeed or failed.

+
var users = m.request({
+    method: "GET",
+    url: "/user",
+    unwrapSuccess: function(response) {
+        return response.data;
+    },
+    unwrapError: function(response) {
+        return response.error;
+    }
+});
+
+//assuming the response is: `{data: [{name: "John"}, {name: "Mary"}], count: 2}`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}]
+
+

Using Different Data Transfer Formats

+

By default, m.request uses JSON to send and receive data to web services. You can override this by providing serialize and deserialize options:

+
var users = m.request({
+    method: "GET",
+    url: "/user",
+    serialize: mySerializer,
+    deserialize: myDeserializer
+});
+

One typical way to override this is to receive as-is responses. The example below shows how to receive a plain string from a txt file.

+
var file = m.request({
+    method: "GET",
+    url: "myfile.txt",
+    deserialize: function(value) {return value;}
+});
+
+

Signature

+

How to read signatures

+
Promise request(XHROptions options)
+
+where:
+    Promise :: GetterSetter { Promise then(any successCallback(any value), any errorCallback(any value)) }
+    GetterSetter :: any getterSetter([any value])
+    XHROptions :: Object {
+        String method,
+        String url,
+        [String user,]
+        [String password,]
+        [Object<any> data,]
+        [Response unwrapSuccess(Response data),]
+        [Response unwrapError(Response data),]
+        [String serialize(any dataToSerialize),]
+        [any deserialize(String dataToDeserialize),]
+        [void type(Object<any> data),]
+        [void config(XMLHttpRequest xhr, XHROptions options)]
+    }
+    Response :: Object<any> | Array<any>
+
    +
  • XHROptions options

    +

    A map of options for the XMLHttpRequest

    +
      +
    • String method

      +

      The HTTP method. Must be either "GET", "POST", "PUT", "DELETE", "HEAD" or "OPTIONS"

      +
    • +
    • String url

      +

      The URL to request. If the URL is not in the same domain as the application, the target server must be configured to accept cross-domain requests from the application's domain, i.e. its responses must include the header Access-Control-Allow-Origin: *.

      +
    • +
    • String user (optional)

      +

      A user for HTTP authentication. Defaults to undefined

      +
    • +
    • String password (optional)

      +

      A password for HTTP authentication. Defaults to undefined

      +
    • +
    • String password (optional)

      +

      A password for HTTP authentication. Defaults to undefined

      +
    • +
    • Object data (optional)

      +

      Data to be sent. It's automatically placed in the appropriate section of the request with the appropriate serialization based on method

      +
    • +
    • Response unwrapSuccess(Response data) (optional)

      +

      A preprocessor function to extract the data from a success response in case the response contains metadata wrapping the data.

      +

      The default value (if this parameter is falsy) is the identity function function(value) {return value}

      +

      For example, if the response is {data: [{name: "John"}, {name: "Mary"}]} and the unwrap function is function(response) {return response.data}, then the response will be considered to be [{name: "John"}, {name: "Mary"}] when processing the type parameter

      +
        +
      • Object | Array data

        +

        The data to unwrap

        +
      • +
      • returns Object | Array unwrappedData

        +

        The unwrapped data

        +
      • +
      +
    • +
    • String unwrapError(Response data) (optional)

      +

      A preprocessor function to extract the data from an error response in case the response contains metadata wrapping the data.

      +

      The default value (if this parameter is falsy) is the identity function function(value) {return value}

      +
        +
      • Object | Array data

        +

        The data to unwrap

        +
      • +
      • returns Object | Array unwrappedData

        +

        The unwrapped data

        +
      • +
      +
    • +
    • String serialize(any dataToSerialize) (optional)

      +

      Method to use to serialize the request data

      +

      The default value (if this parameter is falsy) is JSON.stringify

      +
        +
      • any dataToSerialize

        +

        Data to be serialized

        +
      • +
      • returns String serializedData

        +
      • +
      +
    • +
    • any deserialize(String dataToDeserialize) (optional)

      +

      Method to use to deserialize the response data

      +

      The default value (if this parameter is falsy) is JSON.parse

      +
        +
      • String dataToDeserialize

        +

        Data to be deserialized

        +
      • +
      • returns any deserializedData

        +
      • +
      +
    • +
    • void type(Object data) (optional)

      +

      The response object (or the child items if this object is an Array) will be passed as a parameter to the class constructor defined by type

      +

      If this parameter is falsy, the deserialized data will not be wrapped.

      +

      For example, if type is the following class:

      +
      var User = function(data) {
      +  this.name = m.prop(data.name);
      +}
      +

      And the data is [{name: "John"}, {name: "Mary"}], then the response will contain an array of two User instances.

      +
    • +
    • void config(XMLHttpRequest xhr, XHROptions options) (optional)

      +

      An initialization function that runs after open and before send. Useful for adding request headers and when using XHR2 features, such as the XMLHttpRequest's upload property.

      +
        +
      • XMLHttpRequest xhr

        +

        The XMLHttpRequest instance.

        +
      • +
      • XHROptions options

        +

        The options parameter that was passed into m.request call

        +
      • +
      +
    • +
    +
  • +
  • returns Promise promise

    +

    returns a promise that can bind callbacks which get called on completion of the AJAX request.

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/mithril.route.html b/archive/v0.1.3/mithril.route.html new file mode 100644 index 00000000..507c3825 --- /dev/null +++ b/archive/v0.1.3/mithril.route.html @@ -0,0 +1,221 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.route

+

Routing is a system that allows creating Single-Page-Applications (SPA), i.e. applications that can go from a page to another without causing a full browser refresh.

+

It enables seamless navigability while preserving the ability to bookmark each page individually, and the ability to navigate the application via the browser's history mechanism.

+

This method overloads 3 different units of functionality:

+
    +
  • m.route(rootElement, defaultRoute, routes) - defines the available URLs in an application, and their respective modules

    +
  • +
  • m.route(path) - redirects to another route

    +
  • +
  • m.route(element) - an extension to link elements that unobtrusively abstracts away the routing mode

    +
  • +
+

Routing is single-page-application (SPA) friendly, and can be implemented using either location.hash, HTML5 URL rewriting or location.querystring. See m.route.mode for the caveats of each implementation.

+
+

+

Defining routes

+

Usage

+

To define a list of routes, you need to specify a host DOM element, a default route and a key-value map of possible routes and respective modules to be rendered.

+

The example below defines 3 routes, to be rendered in <body>. home, login and dashboard are modules. We'll see how to define a module in a bit.

+
m.route(document.body, "/", {
+    "/": home,
+    "/login": login,
+    "/dashboard": dashboard,
+});
+

Routes can take arguments, by prefixing words with a colon :

+

The example below shows a route that takes an userID parameter

+
//a sample module
+var dashboard = {
+    controller: function() {
+        this.id = m.route.param("userID");
+    },
+    view: function(controller) {
+        m.render("body", controller.id);
+    }
+}
+
+//define a route
+m.route(document.body, "/dashboard/johndoe", {
+    "/dashboard/:userID": dashboard
+});
+
+//setup routes to start w/ the `#` symbol
+m.route.mode = "hash";
+

This redirects to the URL http://server/#/dashboard/johndoe and yields:

+
<body>johndoe</body>
+

Above, dashboard is a module. It contains a controller and a view properties. When the URL matches a route, the respective module's controller is instantiated and passed as a parameter to the view.

+

In this case, since there's only route, the app redirects to the default route "/dashboard/johndoe".

+

The string johndoe is bound to the :userID parameter, which can be retrived programmatically in the controller via m.route.param("userID").

+

The m.route.mode defines which part of the URL to use for routing.

+
+

Signature

+

How to read signatures

+
void route(DOMElement rootElement, String defaultRoute, Object<Module> routes) { String mode, String param(String key) }
+
+where:
+    Module :: Object { void controller(), void view(Object controllerInstance) }
+
    +
  • DOMElement root

    +

    A DOM element which will contain the view's template.

    +
  • +
  • String defaultRoute

    +

    The route to redirect to if the current URL does not match any of the defined routes

    +
  • +
  • Object routes

    +

    A key-value map of possible routes and their respective modules. Keys are expected to be absolute pathnames, but can include dynamic parameters. Dynamic parameters are words preceded by a colon :

    +

    {'/path/to/page/': pageModule} - a route with a basic pathname

    +

    {'/path/to/page/:id': pageModule} - a route with a pathname that contains a dynamic parameter called id. This route would be selected if the URL was /path/to/page/1, /path/to/page/test, etc

    +

    {'/user/:userId/book/:bookId': userBookModule} - a route with a pathname that contains two parameters

    +

    Dynamic parameters are wild cards that allow selecting a module based on a URL pattern. The values that replace the dynamic parameters in a URL are available via m.route.param()

    +

    Note that the URL component used to resolve routes is dependent on m.route.mode. By default, the querystring is considered the URL component to test against the routes collection

    +

    If the current page URL matches a route, its respective module is activated. See m.module for information on modules.

    +
  • +
  • +

    m.route.mode

    +

    String mode

    +

    The m.route.mode property defines which URL portion is used to implement the routing mechanism. Its value can be set to either "search", "hash" or "pathname". Default value is "search"

    +
      +
    • search mode uses the querystring. This allows named anchors (i.e. <a href="#top">Back to top</a>, <a name="top"></a>) to work on the page, but routing changes causes page refreshes in IE8, due to its lack of support for history.pushState.

      +

      Example URL: http://server/?/path/to/page

      +
    • +
    • hash mode uses the hash. It's the only mode in which routing changes do not cause page refreshes in any browser. However, this mode does not support named anchors.

      +

      Example URL: http://server/#/path/to/page

      +
    • +
    • pathname mode allows routing URLs that contains no special characters, however this mode requires server-side setup in order to support bookmarking and page refreshes. It also causes page refreshes in IE8.

      +

      Example URL: http://server/path/to/page

      +

      The simplest server-side setup possible to support pathname mode is to serve the same content regardless of what URL is requested. In Apache, this URL rewriting can be achieved using ModRewrite.

      +
    • +
    +
  • +
  • +

    m.route.param

    +

    String param(String key)

    +

    Route parameters are dynamic values that can be extracted from the URL based on the signature of the currently active route.

    +

    A route without parameters looks like this:

    +

    "/path/to/page/"

    +

    A route with parameters might look like this:

    +

    "/path/to/page/:id" - here id is the name of the route parameter

    +

    If the currently active route is /dashboard/:userID and the current URL is /dashboard/johndoe, then calling m.route.param("userID") returns "johndoe"

    +
      +
    • String key

      +

      The name of a route parameter

      +
    • +
    • returns String value

      +

      The value that maps to the parameter specified by key

      +
    • +
    +
  • +
+
+

+

Redirecting

+

Usage

+

You can programmatically redirect to another page. Given the example in the "Defining Routes" section:

+
m.route("/dashboard/marysue");
+

redirects to http://server/#/dashboard/marysue

+
+

Signature

+

How to read signatures

+
void route(String path)
+
    +
  • String path

    +

    The route to redirect to. Note that to redirect to a different page outside of the scope of Mithril's routing, you should use window.location

    +
  • +
+
+

+

Mode abstraction

+

Usage

+

This method is meant to be used with a virtual element's config attribute. For example:

+
//Note that the '#' is not required in `href`, thanks to the `config` setting.
+m("a[href='/dashboard/alicesmith']", {config: m.route});
+

This makes the href behave correctly regardless of which m.route.mode is selected. It's a good practice to always use the idiom above, instead of hardcoding ? or # in the href attribute.

+

See m() for more information on virtual elements.

+
+

Signature

+

How to read signatures

+
void route(DOMElement element, Boolean isInitialized)
+
    +
  • DOMElement element

    +

    an anchor element <a> with an href attribute that points to a route

    +
  • +
  • Boolean isInitialized

    +

    the method does not run if this flag is set to true. This is to make the method compatible with virtual DOM elements' config attribute (see m())

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/mithril.sync.html b/archive/v0.1.3/mithril.sync.html new file mode 100644 index 00000000..e93a905a --- /dev/null +++ b/archive/v0.1.3/mithril.sync.html @@ -0,0 +1,111 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.sync

+

This method takes a list of promises and returns a promise that resolves when all promises in the input list have resolved. See m.deferred for more information on promises.

+
+

Usage

+
var greetAsync = function(delay) {
+    var deferred = m.deferred();
+    setTimeout(function() {
+        deferred.resolve("hello");
+    }, delay);
+    return deferred.promise;
+};
+
+m.sync([
+    greetAsync(1000),
+    greetAsync(1500)
+]).then(function(args) {
+    console.log(args); // ["hello", "hello"]
+});
+
+

Signature

+

How to read signatures

+
Promise sync(Array<Promise> promises)
+
+where:
+    Promise :: GetterSetter { Promise then(any successCallback(any value), any errorCallback(any value)) }
+    GetterSetter :: any getterSetter([any value])
+
    +
  • Array promises

    +

    A list of promises to synchronize

    +
  • +
  • return Promise promise

    +

    The promise of the deferred object that is resolved when all input promises have been resolved

    +

    The callbacks for this promise receive as a parameter an Array containing the values of all the input promises

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/mithril.trust.html b/archive/v0.1.3/mithril.trust.html new file mode 100644 index 00000000..9397a32e --- /dev/null +++ b/archive/v0.1.3/mithril.trust.html @@ -0,0 +1,120 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.trust

+

If you're writing a template for a view, use m() instead.

+

This method flags a string as trusted HTML.

+

Trusted HTML is allowed to render arbitrary, potentially invalid markup, as well as run arbitrary javascript, and therefore the developer is responsible for either:

+
    +
  • sanitizing the markup contained in the string, or

    +
  • +
  • acknowledging that the string is authorized to run any code that may be contained within it.

    +
  • +
+

Note that browsers ignore <script> tags that have been inserted into the DOM via innerHTML. They do this because once the element is ready (and thus, has an accessible innerHTML property), their rendering engines cannot backtrack to the parsing-stage if the script calls something like document.write("</body>").

+

For this reason, m.trust will not auto-run <script> tags from trusted strings.

+

Browsers do, however, allow scripts to be run asynchronously via a number of execution points, such as the onload or onerror attributes in <img> and <iframe>.

+

IE also allows running of javascript via CSS behaviors in <link>/<style> tags and style attributes.

+

It's worth noting that the execution points listed above are commonly used for security attacks in combination with malformed markup, e.g. strings with mismatched attribute quotes like " onload="alert(1).

+

Mithril templates are defended against these attacks by default, except when markup is injected via m.trust.

+

It is the developer's responsibility to ensure the input to m.trust cannot be maliciously modified by user-entered data.

+
+

Usage

+
//assume this content comes from the server
+var content = "<h1>Error: invalid user</h1>";
+
+m.render("body", [
+    m("div", m.trust(content))
+]);
+

yields:

+
<body>
+    <div>
+        <h1>Error: invalid user</h1>
+    </div>
+</body>
+
+

Signature

+

How to read signatures

+
String trust(String html)
+
    +
  • String html

    +

    A string containing HTML markup

    +
  • +
  • returns String trustedHtml

    +

    The returned string is a String object instance (as opposed to a string primitive) containing the same html content, and exposing a flag property for internal use within Mithril. Do not create or manipulate trust flags manually.

    +

    Also note that concatenating or splitting a trusted string removes the trust flag. If doing such operations, the final string needs to be flagged as trusted.

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/mithril.withAttr.html b/archive/v0.1.3/mithril.withAttr.html new file mode 100644 index 00000000..ca3f3f7a --- /dev/null +++ b/archive/v0.1.3/mithril.withAttr.html @@ -0,0 +1,125 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.withAttr

+

This is an event handler factory. It returns a method that can be bound to a DOM element's event listener.

+

Typically, it's used in conjunction with m.prop to implement data binding in the view-to-model direction.

+

This method is provided to decouple the browser's event model from the controller/logic model.

+

You should use this method and implement similar ones when extracting values from a browser's Event object, instead of hard-coding the extraction code into controllers (or model methods).

+
+

Usage

+
//standalone usage
+document.body.onclick = m.withAttr("title", function(value) {
+    //alerts the title of the body element when it's clicked
+    alert(value);
+})
+

A contrived example of bi-directional data binding

+
var user = {
+    model: function(name) {
+        this.name = m.prop(name);
+    },
+    controller: function() {
+        this.user = new user.model("John Doe");
+    },
+    view: function(controller) {
+        m.render("body", [
+            m("input", {onchange: m.withAttr("value", controller.user.name), value: controller.user.name()})
+        ]);
+    }
+};
+
+

Signature

+

How to read signatures

+
EventHandler withAttr(String property, void callback(any value))
+
+where:
+    EventHandler :: void handler(Event e)
+
    +
  • String property

    +

    Defines the property of the DOM element whose value will be passed to the callback.

    +
  • +
  • void callback(any value)

    +

    This function will be called with the value of the defined property as an argument.

    +
      +
    • any value

      +

      This is the value of the defined DOM element's property.

      +
    • +
    +
  • +
  • returns EventHandler handler

    +

    This handler method can be assigned to properties like onclick, or passed as callbacks to addEventListener.

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/mithril.xhr.html b/archive/v0.1.3/mithril.xhr.html new file mode 100644 index 00000000..663d6a6a --- /dev/null +++ b/archive/v0.1.3/mithril.xhr.html @@ -0,0 +1,76 @@ + + + + Mithril + + + + + +
+ +
+
+
+ +
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/package.json b/archive/v0.1.3/package.json new file mode 100644 index 00000000..a7a06d25 --- /dev/null +++ b/archive/v0.1.3/package.json @@ -0,0 +1,11 @@ +{ + "name": "mithril", + "description": "A Javascript Framework for building brilliant applications", + "keywords": ["mvc", "framework"], + "version": "0.1.3", + "author": "Leo Horie ", + "repository": {"type": "git", "url": "https://github.com/lhorie/mithril"}, + "main": "mithril.min.js", + "licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}], + "files": ["mithril.min.js", "mithril.min.map"] +} \ No newline at end of file diff --git a/archive/v0.1.3/pages.json b/archive/v0.1.3/pages.json new file mode 100644 index 00000000..788db1c5 --- /dev/null +++ b/archive/v0.1.3/pages.json @@ -0,0 +1,4 @@ +[ +{"title": "Getting Started", "url": "getting-started.html"}, +{"title": "Documentation", "url": "mithril.html"} +] \ No newline at end of file diff --git a/archive/v0.1.3/practices.html b/archive/v0.1.3/practices.html new file mode 100644 index 00000000..c2be6dd7 --- /dev/null +++ b/archive/v0.1.3/practices.html @@ -0,0 +1,118 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

How Should Code Be Organized

+

While Mithril doesn't dictate how to organize your code, it does provide some recommendations for structuring it.

+

As a rule of thumb, controllers should not change model entity properties on an individual basis.

+

Data manipulation should be done in model classes, such that controllers never have entities lying around in temporarily invalid states.

+

Mithril's design strongly encourages all entity logic to be handled in atomic model layer methods (in the sense of entity state stability).

+

In fact, unavoidable abstraction leaks (such as network-bound asynchrony) are laid out in such a way as to make idiomatic code organization elegant, and conversely, to make it so that the abstraction leak problems themselves discourage attempts to misplace entity logic in the controller.

+

This design decision comes from experience with DRY and "bus factor" of large, highly relational model layers.

+

This is in stark contrast to the ActiveRecord pattern of other frameworks, where model entities are largely object representations of database entities and these entities are manipulated in controllers in an ad-hoc field-by-field fashion, and the "committed" via a save method.

+

Because Mithril encourages all entity logic to be done in the model layer, it's idiomatic to create modules with model-level classes that deal specifically with relationship between entities, when there isn't already a model entity that can logically hold the relational business logic.

+

Models are also responsible for centralizing tasks such as filtering of entity lists and validation routines, so that access to these methods is available across the application.

+

DOM manipulation should be done in the view via m() and config. Controllers may explicitly call m.redraw, but, if possible, it's preferable to abstract this into a service which integrates w/ Mithril's auto-redrawing system (see. m.startComputation / m.endComputation)

+
+

File Separation

+

The examples in this site usually conflate different MVC layers together for the sake of readability, but normally it's recommended that each layer on a module be in different files. For example:

+
//app.model.js
+var app = {};
+
+app.PageList = function() {
+    return m.request({method: "GET", url: "pages.json"});
+};
+
//app.controller.js
+app.controller = function() {
+    this.pages = new app.PageList();
+};
+
//app.view.js
+app.view = function(ctrl) {
+    return ctrl.pages().map(function(page) {
+        return m("a", {href: page.url}, page.title);
+    });
+};
+

You can use task automation tools such as GruntJS to concatenate the files back together for production.

+

Typically, when separating MVC layers, it's common that the namespace declaration be in the model layer, since this is usually the most used dependency for the other layers.

+

You may choose to declare the namespace in a separate file or have the build system generate it on demand, instead.

+

You should avoid grouping classes by the MVC layer they belong to, i.e. don't create 3 files called model.js, controllers.js and views.js.

+

That organization pattern needlessly ties unrelated aspects of the application together and dilutes the clarity of modules.

+
+

Global Namespace Hygiene

+

For developer convenience, Mithril uses the global m variable as a namespace, much like jQuery uses $.

+

If you want to ensure global namespace hygiene, you can wrap your code in "islands" like this:

+
new function(m) {
+
+    //your code goes here
+
+}(Mithril);
+

If you are creating components to be used by 3rd parties, it's recommended that you always use this idiom.

+

In the unlikely case that you have another global variable called m in your page, you should consider renaming it to make it more descriptive, or use the idiom below to keep it intact.

+
<script>_temp = m</script>
+<script src="mithril.js"></script>
+<script>m = _temp</script>
+
+

Usage of m.redraw

+

m.redraw is a method that allows you to render a template outside the scope of Mithril's auto-redrawing system.

+

Calling of 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 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.

+

If you call this method more often than that, Mithril may ignore calls or defer them to the next browser repaint cycle.

+

If calls are more expensive than a repaint window, the browser may drop frames, resulting in choppy animations. It's your responsibility to make sure single iterations of animation rendering code don't take longer than 16ms (for a frequency of 60 frames-per-second).

+

In addition, note that template performance, both in Mithril templates as well as in general, is dependent on markup complexity. You are responsible for ensuring that templates aren't too big to render efficiently.

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/refactoring.html b/archive/v0.1.3/refactoring.html new file mode 100644 index 00000000..71eb63f4 --- /dev/null +++ b/archive/v0.1.3/refactoring.html @@ -0,0 +1,63 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Refactoring

+

Below are some common refactoring patterns:

+

Porting legacy code to Mithril

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/roadmap.html b/archive/v0.1.3/roadmap.html new file mode 100644 index 00000000..19a1ce2a --- /dev/null +++ b/archive/v0.1.3/roadmap.html @@ -0,0 +1,103 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Roadmap

+

Things that would be useful to have (though likely not as part of Mithril core)

+

Utilities

+
    +
  • Formatters / parsers
      +
    • i18n
    • +
    • Date
        +
      • Absolute (e.g. Jan 1, 1970 12:00 AM)
      • +
      • Relative (e.g. 10 days ago)
      • +
      +
    • +
    • Number (e.g. 1,234.5)
    • +
    • Currency (e.g. $1,000.00)
    • +
    • Word wrap
    • +
    +
  • +
  • Dependency management
  • +
  • Functional / relational tools
  • +
  • Animation
  • +
+

Components

+
    +
  • Autocompleter
  • +
  • Date/time picker
  • +
  • Swipe-to-show panel
  • +
  • Tree
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/routing.html b/archive/v0.1.3/routing.html new file mode 100644 index 00000000..92370cfd --- /dev/null +++ b/archive/v0.1.3/routing.html @@ -0,0 +1,127 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Routing

+

Routing is a system that allows creating Single-Page-Applications (SPA), i.e. applications that can go from a page to another without causing a full browser refresh.

+

It enables seamless navigability while preserving the ability to bookmark each page individually, and the ability to navigate the application via the browser's history mechanism.

+

Mithril provides utilities to handle three different aspect of routing:

+
    +
  • defining a list of routes
  • +
  • programmatically redirecting between routes
  • +
  • making links in templates routed transparently and unobtrusively
  • +
+
+

Defining routes

+

To define a list of routes, you need to specify a host DOM element, a default route and a key-value map of possible routes and respective modules to be rendered.

+

The example below defines 3 routes, to be rendered in <body>. home, login and dashboard are modules. We'll see how to define a module in a bit.

+
m.route(document.body, "/", {
+    "/": home,
+    "/login": login,
+    "/dashboard": dashboard,
+});
+

Routes can take arguments, by prefixing words with a colon :

+

The example below shows a route that takes an userID parameter

+
//a sample module
+var dashboard = {
+    controller: function() {
+        this.id = m.route.param("userID");
+    },
+    view: function(controller) {
+        m.render("body", controller.id);
+    }
+}
+
+//define a route
+m.route(document.body, "/dashboard/johndoe", {
+    "/dashboard/:userID": dashboard
+});
+
+//setup routes to start w/ the `#` symbol
+m.route.mode = "hash";
+

This redirects to the URL http://server/#/dashboard/johndoe and yields:

+
<body>johndoe</body>
+

Above, dashboard is a module. It contains a controller and a view properties. When the URL matches a route, the respective module's controller is instantiated and passed as a parameter to the view.

+

In this case, since there's only route, the app redirects to the default route "/dashboard/johndoe" and, under the hood, it calls m.module(document.body, dashboard).

+

The string johndoe is bound to the :userID parameter, which can be retrived programmatically in the controller via m.route.param("userID").

+

The m.route.mode property defines which URL portion is used to implement the routing mechanism. Its value can be set to either "search", "hash" or "pathname". The default value is "search"

+
    +
  • search mode uses the querystring. This allows named anchors (i.e. <a href="#top">Back to top</a>, <a name="top"></a>) to work on the page, but routing changes causes page refreshes in IE8, due to its lack of support for history.pushState.

    +

    Example URL: http://server/?/path/to/page

    +
  • +
  • hash mode uses the hash. It's the only mode in which routing changes do not cause page refreshes in any browser. However, this mode does not support named anchors and browser history lists.

    +

    Example URL: http://server/#/path/to/page

    +
  • +
  • pathname mode allows routing URLs that contains no special characters, however this mode requires server-side setup in order to support bookmarking and page refreshes. It also causes page refreshes in IE8.

    +

    Example URL: http://server/path/to/page

    +

    The simplest server-side setup possible to support pathname mode is to serve the same content regardless of what URL is requested. In Apache, this URL rewriting can be achieved using ModRewrite.

    +
  • +
+
+

Redirecting

+

You can programmatically redirec to another page. Given the example in the "Defining Routes" section:

+
m.route("/dashboard/marysue");
+

redirects to http://server/#/dashboard/marysue

+
+

Mode abstraction

+

This method is meant to be used with a virtual element's config attribute. For example:

+
//Note that the '#' is not required in `href`, thanks to the `config` setting.
+m("a[href='/dashboard/alicesmith']", {config: m.route});
+

This makes the href behave correctly regardless of which m.route.mode is selected. It's a good practice to always use the idiom above, instead of hardcoding ? or # in the href attribute.

+

See m() for more information on virtual elements.

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/style.css b/archive/v0.1.3/style.css new file mode 100644 index 00000000..d24d0aaa --- /dev/null +++ b/archive/v0.1.3/style.css @@ -0,0 +1,91 @@ +.container {margin:auto;max-width:1000px;padding:0 20px;position:relative;} +.container:after,.row:after {content:"";display:table;clear:both;} +.container,.row,[class*='col('] {-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;} +[class*='col('] {float:left;} +[class*='(3,'] {width:25%;} +[class*='(4,'] {width:33.33333%;} +[class*='(6,'] {width:50%;} +[class*='(8,'] {width:66.66667%;} +[class*='(9,'] {width:75%;} +@media (max-width:1000px) { +[class*=',3,'] {width:25%;} +[class*=',4,'] {width:33.33333%;} +[class*=',6,'] {width:50%;} +[class*=',8,'] {width:66.66667%;} +[class*=',9,'] {width:75%;} +} +@media (max-width:750px) { +[class*=',6)'] {width:50%;} +[class*=',12)'] {width:100%;} +} + +html {background:#999;color:#222;font:14px Helvetica;} +html,body {margin:0;padding:0;} +header,footer {background:#999;} +nav {text-align:right;} +nav a:first-child,nav a:first-child:visited {color:#fff;font-size:27px;float:left;line-height:1.3em;padding:0;text-decoration:none;} +nav a {color:#fff;display:inline-block;padding:10px;} +nav a:visited {color:#ddd;} +footer {text-align:center;padding:10px 0;} +footer,footer a,footer a:visited {color:#fff;} +h1,h2 {font-family:Palatino;margin:0 0 10px;} +h1 {font-size:3em;text-shadow:0.024em 0.024em #777, -0.024em -0.024em #fff;} +h1 span {animation:logo 2s;display:inline-block;-webkit-animation:logo 2s;} +h2 {color:#888;font-style:italic;} +h3 {margin:10px 0;} +p {margin:15px 0;} +ul {margin:15px 0;padding:0 0 0 1em;} +li {margin:0 0 10px;} +a {color:#161;} +a:visited {color:#383;} +a:hover {text-decoration:none;} +pre {background:#ffe;border:1px solid #ddd;overflow:auto;margin:0 0 15px;padding:5px 10px;white-space:pre;-webkit-overflow-scrolling:touch;} +pre[class*="language-"],code {background:#ffe;font:12px/15px Lucida Console,Monaco,monospace;} +hr {border-top:1px solid #ccc;border-width:1px 0 0;margin:20px 0;} +table {margin:0 0 10px;width:100%;} +.cta {padding:30px 0 20px;text-align:center;} +.cta { +background: +linear-gradient(27deg, #e5e5e5 5px, rgba(255,255,255,0) 5px) 0 5px, +linear-gradient(207deg, #e5e5e5 5px, rgba(255,255,255,0) 5px) 10px 0px, +linear-gradient(27deg, #f2f2f2 5px, rgba(255,255,255,0) 5px) 0px 10px, +linear-gradient(207deg, #f2f2f2 5px, rgba(255,255,255,0) 5px) 10px 5px, +linear-gradient(90deg, #ebebeb 10px, rgba(255,255,255,0) 10px), +linear-gradient(#ededed 25%, #eaeaea 25%, #eaeaea 50%, rgba(255,255,255,0) 50%, rgba(255,255,255,0) 75%, #f4f4f4 75%, #f4f4f4); +background-color: #e3e3e3; +background-size: 20px 20px; +} +.logo {color:#d3d3d3;font-family:Georgia;font-style:italic;} +.logo span {font-family:Arial;font-style:normal;} +.logo :before {content:"\25CB";position:absolute;margin:-0.17em 0 0 -0.10em;} +.logo :after {content:"\25CB";position:absolute;margin:-0.17em 0 0 -0.5em;} +.button,.button:visited {background:#5a5;border-radius:5px;box-shadow:1px 1px #777, -1px -1px #fff;color:#fff;display:inline-block;font:normal bold 16px Helvetica;margin:0 10px 10px;padding:10px 30px;text-decoration:none;} +.features {background:#fff;padding:30px 0 0;} +.feature {margin:0 0 30px;padding:0 20px 0 0;} +.sample {background:#f5f5f5;padding:30px 0 10px;} +.example {background:#ffe;border:1px solid #ddd;display:block;font:Courier New;margin-bottom:20px;padding:5px 10px;} +.example span {color:#383;font-weight:bold;} +.example small {color:#888;font-size:1em;} +.more {background:#ddd;padding:30px 0;} +.output a,.more a {display:block;margin:0 0 10px;} +.output a:after,.more a:after {content:" \bb";} +.performance {background:#fff;padding:30px 0;} +.performance td:first-child {text-align:right;width:1%;} +.bar {background:red;height:4px;float:left;margin:0.5em 1em 0 0;} +.security {background:#f5f5f5;padding:30px 0;} +.success {color:#383;} +.error {color:#f00;} +.content {background:#f5f5f5;padding:30px 0;} + +@media (min-width:750px) { +.sample pre {margin-right:20px;} +} + +@keyframes logo { + from {opacity:0;transform:scale(2) rotate(359deg);} + to {opacity:1;transform:scale(1) rotate(0deg);} +} +@-webkit-keyframes logo { + from {opacity:0;-webkit-transform:scale(2) rotate(359deg);} + to {opacity:1;-webkit-transform:scale(1) rotate(0deg);} +} \ No newline at end of file diff --git a/archive/v0.1.3/tools.html b/archive/v0.1.3/tools.html new file mode 100644 index 00000000..26faba39 --- /dev/null +++ b/archive/v0.1.3/tools.html @@ -0,0 +1,94 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Tools

+

HTML-to-Mithril Template Converter

+

If you already have your HTML written and want to convert it into a Mithril template, you can use the tool below for one-off manual conversion.

+

Template Converter

+
+

Automatic HTML-to-Mithril Template Converter

+

There's a tool called MSX by Jonathan Buchanan that allows you to write templates using HTML syntax, and then automatically compile them to Javascript when files change.

+

It is useful for teams where styling and functionality are done by different people, and for those who prefer to maintain templates in HTML syntax.

+

The tool allows you to write code like this:

+
todo.view = function(ctrl) {
+    return <html>
+        <body>
+            <input onchange={m.withAttr("value", ctrl.description)} value={ctrl.description()}/>
+            <button onclick={ctrl.add.bind(ctrl, ctrl.description)}>Add</button>
+        </body>
+    </html>
+};
+

Note, however, that since the code above is not valid Javascript, this syntax can only be used with a preprocessor build tool such as the provided Gulp.js script.

+
+

Mithril Template Compiler

+

You can pre-compile Mithril templates to make them run faster. For more information see this page:

+

Compiling Templates

+
+

Internet Explorer Compatibility

+

Mithril relies on some Ecmascript 5 features, namely: Array::indexOf and Object::keys, as well as the JSON object.

+

You can use polyfill libraries to support these features in IE7.

+ +

Mithril also has a dependency on XMLHttpRequest. If you wish to support IE6, you'll need a shim for it. IE7 and lower do not support cross-domain AJAX requests.

+

In addition, note that most m.route modes rely on history.pushState in order to allow moving from one page to another without a browser refresh. IE9 and lower do not support this feature and will gracefully degrade to page refreshes instead.

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/tools/template-compiler.sjs b/archive/v0.1.3/tools/template-compiler.sjs new file mode 100644 index 00000000..4d5924c0 --- /dev/null +++ b/archive/v0.1.3/tools/template-compiler.sjs @@ -0,0 +1,64 @@ +/* +Compiles Mithril templates + +Requires sweet.js (https://github.com/mozilla/sweet.js) +Installation: npm install -g sweet.js +Usage: sjs --module /mithril.compile.sjs --output .js .js +*/ + +macro m { + case { _ ($selector) } => { + return #{m($selector, {}, [])}; + } + case { _ ($selector, $partial) } => { + var partialSyntax = #{$partial}; + var partial = unwrapSyntax(partialSyntax); + return partial.value == "{}" ? #{m($selector, $partial, [])} : #{m($selector, {}, partial)}; + } + case { _ ($selector, $dynAttrs, $children) } => { + var selectorSyntax = #{$selector}; + var selector = unwrapSyntax(selectorSyntax); + + var dynAttrsSyntax = #{$dynAttrs}; + var dynAttrs = unwrapSyntax(dynAttrsSyntax); + + var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g; + var attrParser = /\[(.+?)=("|'|)(.+?)\2\]/; + var _match = null; + var classes = []; + var cell = {tag: "div", attrs: {}, children: []}; + + while (_match = parser.exec(selector)) { + if (_match[1] == "") 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]; + } + } + if (classes.length > 0) cell.attrs["class"] = classes.join(" "); + + var tag = makeValue(cell.tag, #{here}); + var attrsBody = Object.keys(cell.attrs).reduce(function(memo, attrName) { + return memo.concat([ + makeValue(attrName, #{here}), + makePunc(":", #{here}), + makeValue(cell.attrs[attrName], #{here}), + makePunc(",", #{here}) + ]); + }, []).concat(dynAttrs.inner); + var attrs = [makeDelim("{}", attrsBody, #{here})]; + var children = cell.children.map(function(child) { + return makeValue(child, #{here}); + }) + letstx $tag = [tag], $attrs = attrs; + + return #{ ({tag: $tag, attrs: $attrs , children: $children}) }; + } + case { _ } => { + return #{Mithril}; + } +} + +export m; \ No newline at end of file diff --git a/archive/v0.1.3/tools/template-converter.html b/archive/v0.1.3/tools/template-converter.html new file mode 100644 index 00000000..e3ec7de5 --- /dev/null +++ b/archive/v0.1.3/tools/template-converter.html @@ -0,0 +1,9 @@ +

If you already have your HTML written and want to convert it into a Mithril template, paste the HTML below and press the "Convert" button.

+ +
+ + + + \ No newline at end of file diff --git a/archive/v0.1.3/tools/template-converter.js b/archive/v0.1.3/tools/template-converter.js new file mode 100644 index 00000000..5501df8e --- /dev/null +++ b/archive/v0.1.3/tools/template-converter.js @@ -0,0 +1,89 @@ +var templateConverter = {}; + +templateConverter.DOMFragment = function(markup) { + if (markup.indexOf(" -1) return [new DOMParser().parseFromString(markup, "text/html").childNodes[1]] + var container = document.createElement("div"); + container.insertAdjacentHTML("beforeend", markup); + return container.childNodes; +} +templateConverter.VirtualFragment = function recurse(domFragment) { + var virtualFragment = []; + for (var i = 0, el; el = domFragment[i]; i++) { + if (el.nodeType == 3) { + virtualFragment.push(el.nodeValue); + } + else { + var attrs = {}; + for (var j = 0, attr; attr = el.attributes[j]; j++) { + attrs[attr.name] = attr.value; + } + + virtualFragment.push({tag: el.tagName.toLowerCase(), attrs: attrs, children: recurse(el.childNodes)}); + } + } + return virtualFragment; +} +templateConverter.Template = function recurse() { + if (Object.prototype.toString.call(arguments[0]) == "[object String]") { + return new recurse(new templateConverter.VirtualFragment(new templateConverter.DOMFragment(arguments[0]))); + } + + var virtualFragment = arguments[0], level = arguments[1] + if (!level) level = 1; + + var tab = "\n" + new Array(level + 1).join("\t"); + var virtuals = []; + for (var i = 0, el; el = virtualFragment[i]; i++) { + if (typeof el == "string") { + if (el.match(/\t| {2,}/g) && el.trim().length == 0) virtuals.indented = true; + else virtuals.push('"' + el.replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n") + '"'); + } + else { + var virtual = ""; + if (el.tag != "div") virtual += el.tag; + if (el.attrs["class"]) { + virtual += "." + el.attrs["class"].replace(/\t+/g, " ").split(" ").join("."); + delete el.attrs["class"]; + } + var attrNames = Object.keys(el.attrs).sort() + for (var j = 0, attrName; attrName = attrNames[j]; j++) { + if (attrName != "style") virtual += "[" + attrName + "='" + el.attrs[attrName].replace(/'/g, "\\'") + "']"; + } + virtual = '"' + virtual + '"'; + + var style = "" + if (el.attrs.style) { + virtual += ", {style: " + ("{\"" + el.attrs.style.replace(/:/g, "\": \"").replace(/;/g, "\", \"") + "}").replace(/, "}|"}/, "}") + "}" + } + + if (el.children.length > 0) { + virtual += ", " + recurse(el.children, level + 1); + } + virtual = "m(" + virtual + ")"; + virtuals.push(virtual); + } + } + if (!virtuals.indented) tab = ""; + + var isInline = virtuals.length == 1 && virtuals[0].charAt(0) == '"'; + var template = isInline ? virtuals.join(", ") : "[" + tab + virtuals.join("," + tab) + tab.slice(0, -1) + "]"; + return new String(template); +} + +templateConverter.controller = function() { + this.source = m.prop(""); + this.output = m.prop(""); + + this.convert = function() { + return this.output(new templateConverter.Template(this.source())); + }; + +}; + +templateConverter.view = function(ctrl) { + return [ + m("textarea", {autofocus: true, style: {width:"100%", height: "40%"}, onchange: m.withAttr("value", ctrl.source)}, ctrl.source()), + m("button", {onclick: ctrl.convert.bind(ctrl)}, "Convert"), + m("textarea", {style: {width:"100%", height: "40%"}}, ctrl.output()) + ]; +}; \ No newline at end of file diff --git a/archive/v0.1.3/web-services.html b/archive/v0.1.3/web-services.html new file mode 100644 index 00000000..e96860cc --- /dev/null +++ b/archive/v0.1.3/web-services.html @@ -0,0 +1,212 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Web Services

+

Mithril provides a high-level utility for working with web services, which allows writing asynchronous code relatively procedurally.

+

It provides a number of useful features out of the box:

+
    +
  • The ability to get an early reference to a container that will hold the asynchronous response
  • +
  • The ability to queue operations to be performed after the asynchronous request completes
  • +
  • The ability to "cast" the response to a class of your choice
  • +
  • The ability to unwrap data in a response that includes metadata properties
  • +
+
+

Basic usage

+

The basic usage pattern for m.request returns an m.prop getter-setter, which is populated when the AJAX request completes.

+

The returned getter-setter can be thought of as a box: you can pass this reference around cheaply, and you can "unwrap" its value when needed.

+
var users = m.request({method: "GET", url: "/user"});
+
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}]
+

Note that this getter-setter holds an undefined value until the AJAX request completes. Attempting to unwrap its value early will likely result in errors.

+

The returned getter-setter also implements the promise interface (also known as a thennable): this is the mechanism you should always use to queue operations to be performed on the data from the web service.

+

The simplest use case of this feature is to implement functional value assignment via m.prop (i.e. the same thing as above). You can bind a pre-existing getter-setter by passing it in as a parameter to a .then method:

+
var users = m.prop([]); //default value
+
+m.request({method: "GET", url: "/user"}).then(users)
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}]
+

This syntax allows you to bind intermediate results before piping them down for further processing, for example:

+
var users = m.prop([]); //default value
+var doSomething = function() { /*...*/ }
+
+m.request({method: "GET", url: "/user"}).then(users).then(doSomething)
+

While both basic assignment syntax and thennable syntax can be used to the same effect, typically it's recommended that you use the assignment syntax in the first example whenever possible, as it's easier to read.

+

The thennable mechanism is intended to be used in 3 ways:

+
    +
  • in the model layer: to process web service data in transformative ways (e.g. filtering a list based on a parameter that the web service doesn't support)
  • +
  • in the controller layer: to bind redirection code upon a condition
  • +
  • in the controller layer: to bind error messages
  • +
+

Processing web service data

+

This step is meant to be done in the model layer. Doing it in the controller level is also possible, but philosophically not recommended, because by tying logic to a controller, the code becomes harder to reuse due to unrelated controller dependencies.

+

In the example below, the listEven method returns a getter-setter that resolves to a list of users containing only users whose id is even.

+
//model
+var User = {}
+
+User.listEven = function() {
+    return m.request({method: "GET", url: "/user"}).then(function(list) {
+        return list.filter(function(user) {return user.id % 2 == 0});
+    });
+}
+
+//controller
+var controller = function() {
+    this.users = User.listEven()
+}
+

Bind redirection code

+

This step is meant to be done in the controller layer. Doing it in the model level is also possible, but philosophically not recommended, because by tying redirection to the model, the code becomes harder to reuse due to overly tight coupling.

+

In the example below, we use the previously defined listEven model method and queue a controller-level function that redirects to another page if the user list is empty.

+
//controller
+var controller = function() {
+    this.users = User.listEven().then(function(users) {
+        if (users.length == 0) m.route("/add");
+    })
+}
+

Binding errors

+

Mithril thennables take two functions as optional parameters: the first parameter is called if the web service request completes successfully. The second one is called if it completes with an error.

+

Error binding is meant to be done in the controller layer. Doing it in the model level is also possible, but generally leads to more code in order to connect all the dots.

+

In the example below, we bind an error getter-setter to our previous controller so that the error variable gets populated if the server throws an error.

+
//controller
+var controller = function() {
+    this.error = m.prop("")
+
+    this.users = User.listEven().then(function(users) {
+        if (users.length == 0) m.route("/add");
+    }, this.error)
+}
+

If the controller doesn't already have a success callback to run after a request resolves, you can still bind errors like this:

+
//controller
+var controller = function() {
+    this.error = m.prop("")
+
+    this.users = User.listEven().then(null, this.error)
+}
+
+

Queuing Operations

+

As you saw, you can chain operations that act on the response data. Typically this is required in three situations:

+
    +
  • in model-level methods if client-side processing is needed to make the data useful for a controller or view.
  • +
  • in the controller, to redirect after a model service resolves.
  • +
  • in the controller, to bind error messages
  • +
+

In the example below, we take advantage of queuing to debug the ajax response data prior to doing further processing on the user list

+
var users = m.request({method: "GET", url: "/user"})
+    .then(console.log);
+    .then(function(users) {
+        //add one more user to the response
+        return users.concat({name: "Jane"})
+    })
+
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}, {name: "Jane"}]
+
+

Casting the Response Data to a Class

+

It's possible to auto-cast a JSON response to a class. This is useful when we want to control access to certain properties in an object, as opposed to exposing all the fields in POJOs (plain old javascript objects) for arbitrary processing.

+

In the example below, User.list returns a list of User instances.

+
var User = function(data) {
+    this.name = m.prop(data.name);
+}
+
+User.list = function() {
+    return m.request({method: "GET", url: "/user", type: User});
+}
+
+var users = User.list();
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), `users` will contain a list of User instances
+//i.e. users()[0].name() == "John"
+
+

Unwrapping Response Data

+

Often, web services return the relevant data wrapped in objects that contain metadata.

+

Mithril allows you to unwrap the relevant data, by providing two callback hooks: unwrapSuccess and unwrapError.

+

These hooks allow you to unwrap different parts of the response data depending on whether it succeed or failed.

+
var users = m.request({
+    method: "GET",
+    url: "/user",
+    unwrapSuccess: function(response) {
+        return response.data;
+    },
+    unwrapError: function(response) {
+        return response.error;
+    }
+});
+
+//assuming the response is: `{data: [{name: "John"}, {name: "Mary"}], count: 2}`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}]
+
+

Using Different Data Transfer Formats

+

By default, m.request uses JSON to send and receive data to web services. You can override this by providing serialize and deserialize options:

+
var users = m.request({
+    method: "GET",
+    url: "/user",
+    serialize: mySerializer,
+    deserialize: myDeserializer
+});
+

One typical way to override this is to receive as-is responses. The example below shows how to receive a plain string from a txt file.

+
var file = m.request({
+    method: "GET",
+    url: "myfile.txt",
+    deserialize: function(value) {return value;}
+});
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file From efae454bd44daedf14940f8d73fb10aef7d7ed16 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Thu, 3 Apr 2014 09:00:47 -0400 Subject: [PATCH 15/38] improve dom caching - don't call appendChild if not needed - don't overwrite style all the time - don't overwrite function if it has same source --- archive/v0.1.3/mithril-tests.js | 24 ++++++++++++++++-------- archive/v0.1.3/mithril.min.js | 2 +- archive/v0.1.3/mithril.min.map | 2 +- archive/v0.1.3/mithril.min.zip | Bin 20403 -> 20592 bytes mithril.js | 23 +++++++++++++++-------- 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/archive/v0.1.3/mithril-tests.js b/archive/v0.1.3/mithril-tests.js index de1dfeed..e40e1676 100644 --- a/archive/v0.1.3/mithril-tests.js +++ b/archive/v0.1.3/mithril-tests.js @@ -73,7 +73,7 @@ new function(window) { setAttributes(node, data.attrs, cached.attrs) cached.children = build(node, data.children, cached.children) cached.nodes.intact = true - parent.appendChild(node) + if (node.parentNode !== parent) parent.appendChild(node) } if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew) } @@ -120,13 +120,20 @@ new function(window) { function setAttributes(node, dataAttrs, cachedAttrs) { for (var attrName in dataAttrs) { var dataAttr = dataAttrs[attrName] - if (!(attrName in cachedAttrs) || (cachedAttrs[attrName] !== dataAttr) || node === window.document.activeElement) { - cachedAttrs[attrName] = dataAttr - if (attrName == "config") continue - if (attrName.indexOf("on") == 0 && typeof dataAttr == "function") dataAttr = autoredraw(dataAttr, node) - if (attrName == "style") for (var rule in dataAttr) node.style[rule] = dataAttr[rule] + var cachedAttr = cachedAttrs[attrName] + if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr) || node === window.document.activeElement) { + if (attrName === "config") continue + else if (typeof dataAttr == "function" && attrName.indexOf("on") == 0) { + if (String(dataAttr) !== String(cachedAttr)) node[attrName] = autoredraw(dataAttr, node) + } + else if (attrName === "style") { + for (var rule in dataAttr) { + if (cachedAttr === undefined || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule] + } + } else if (attrName in node) node[attrName] = dataAttr else node.setAttribute(attrName, dataAttr) + cachedAttrs[attrName] = dataAttr } } return cachedAttrs @@ -141,9 +148,9 @@ new function(window) { return result } function autoredraw(callback, object) { - return function() { + return function(e) { m.startComputation() - var output = callback.apply(object || window, arguments) + var output = callback.call(object, e) m.endComputation() return output } @@ -460,6 +467,7 @@ mock.window = new function() { getAttribute: function(name, value) { return this[name] }, + style: {} } } window.document.createTextNode = function(text) { diff --git a/archive/v0.1.3/mithril.min.js b/archive/v0.1.3/mithril.min.js index 0cba8242..ba474595 100644 --- a/archive/v0.1.3/mithril.min.js +++ b/archive/v0.1.3/mithril.min.js @@ -4,5 +4,5 @@ http://github.com/lhorie/mithril.js (c) Leo Horie License: MIT */ -!new function(a){function b(e,f,g){if(null!==f&&void 0!==f){var h=s.call(g),i=s.call(f);if(h!=i&&(null!==g&&void 0!==g&&d(g.nodes),g=new f.constructor,g.nodes=[]),"[object Array]"==i){for(var j=[],k=g.length===f.length,l=0;l-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e];if(!(e in d)||d[e]!==g||b===a.document.activeElement){if(d[e]=g,"config"==e)continue;if(0==e.indexOf("on")&&"function"==typeof g&&(g=f(g,b)),"style"==e)for(var h in g)b.style[h]=g[h];else e in b?b[e]=g:b.setAttribute(e,g)}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:\w+/g);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e],h=d[e];if(!(e in d)||h!==g||b===a.document.activeElement){if("config"===e)continue;if("function"==typeof g&&0==e.indexOf("on"))String(g)!==String(h)&&(b[e]=f(g,b));else if("style"===e)for(var i in g)(void 0===h||h[i]!==g[i])&&(b.style[i]=g[i]);else e in b?b[e]=g:b.setAttribute(e,g);d[e]=g}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:\w+/g);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c-kK?IY)p49_ zCw|4Toj4R(JlX{nNa!q)cq~{@sS*&01)GW;s@Rk*5{SZvM+FF}D;9kJe{7moWZ`@7 zIsZBTf4=|wA17x&82;xEhez*v4?Os}A^Iyg--!S2z4V8#jSUUG_OsEY2Oq87Y~-88 zT5F@eTfDluTAZ9b^B?^Eg;A@ty8BA;)!XI0>8sPl@p5f^cjk>ZO8TXXQ@x2oy|z~l zCsrXpgZEas)-ICd#9q5rXqD?VOtrd=VtsEsoSdA4*v)cnw|Ejj1d}r}=~lB`3#Y>w zsO-O&W+o@6cc5~0Z#uMgW@c^|D@Ra_B}c7prD(M6)tl3?PQE!_CX>RM=}x`8I}Q!2 zC0O@L`BiKf;=!BUB>m|7fn6zK&)Q@iv}d3lQaI2i zjd&#Q`m{%C9RtdVBo&#yH?L?aDjBA%OA?uNw#OqNf(oG`ysBbY*kJ5H_Ign0L=qeN zUy-S)I%A&Sq?rr@xvvszB<~Mw9SBQp&A&C=)o1hd9xP0|7*%_$!B&I}*1%eq*QEJi z=^8u13mK%83k7yf0g||2DciBiSm%*tOKOWSOxrkxy@>&mfkA$S-5gYHCPt0E=D-P? zIT0t7=R}Anal$->9fXUucm#F*!NS=6E!xg@v5h*Bj*wRmM7-B zOIp>eVpCEPGdOQsZ3uPcg87bWH~QSg9kaczM|VaV>|?vFhnF7LA4R`15@Ya#dEHnX zyHaC5c=ZH%I!nulq^hK1w*JZD#1hViv0Q{v^R~!7Cu*|~`M5kDuyQO}Hc*nTv1o>b z%W|K_)TtqceV`cT1V#ygBNHVZrlVa|I#`;p8=smhlClF?acDtMbvRJSal&tWW(=GQ z24ngcOU~S>5twtVBC{C=xe*Sm;Qx)y>lb7HzS?x4QQbSDweUU>AxB5a0XUeDTGnFu znc_?5;R`?MRs;Xfxu`ApaBAP((hj8WAl74^L zjmC^)h=E8zWJJh@r+I_i38w_Vn}lTnchS7?ORjNnh*CrZ-1BsAJu|vl99qY)F0gX z?Px2{{6kd3KVp^*&V;W1EXYY(X)?n=^LNbF8S21;{U%&J0NXsP>ZgusWcb*?U(eK=O=0a%e zU*B090g99as`}~GnGqNd>iWmpTb+9t`J9x6asAEJ?ZH%6|9N$yx9<{GOdKaP#`Krg z*?_XCE-4Js7TDCT*q9;+J4`*lO*vBSFx5P>IJL|{MN@QE3T4JFZBHRLQqV~6_nFIzo;M-kX%AR_XB37V(PHu)Dl1l}`1h@5)F;gO;t zmjhHSsztUnFi9eXl8i|*4w*VvH5K%eL24gh7G;nFIfm< ziA=UI>^^hhRQB^bho4FuaEd7GlWMhlz!rp(BUqKBNl#!5Wfq(dk04A?V9&}9uSBUv ziMNw{928OLMD#%59v{~R8!;5HY@X9)!9+T{=~RL1fFn=B`CW=vx2k_6FGo-VR!%q~gB<% za-MQ~#5k*LoR#Nkc3+i|E~3ex6+FcG9R-yK;Qvz_1i-dgD-A=?av>6HedqH;fP{nd z2F(O#Q&54R15gA)zklE!!3UR-;yMbs_-oO}9>-B;CAyz;a#8)4_}B>2I*;i~_T}M< zp0US!{k8&#os%xYe|AOH8QNpnq$e`yKC2GO>wwZW;KH`S0iB}+^XGuux_;GT7iEyE z`%=gi$9#@Zp3qyR)WB$IjBbqJeEWox$p|4cbo|g6Q3a`>Ay7r}gIVEr^tI%|6zVO`e;Nm@h!1lR zAdBV5lILW56AlB`ze=G*dMcZ<^s~-oW-~iZ_`cHtgiyWT!XS+nj3&% z4So;f4%5vx?#~ zkBcSg;+Fl?G?)d`;bd{Fym(R+&8>)vawzwSCVQf!ch|4ap!3G9U@>m4GkM;&e^=tO z_AfYoSO0wd`GXeI0vixZ!!?RalV|UlmdS4R|7By^>?~>&*WL>IDvP?tpIq8iErH7< zoq{nBSR}V%*j``L!XFzkZF;t$L*IQFodCgsGDN36=DSe3ujM{|$BwXT~$t;_R znC2P%5B~}B1Q7zTFd!js~2_(Ql*LCIGnOQtK$< zm>Up|9TM=lvY>E(CM_O8C5raH^>;QVp9Il}y)ACvfh+xY>RwyFzj5tSha$i^wb>1J rSN~@t`h~*ze{$;8VD>p0`Q+buLqnJNpP_@FZ|S##iHH6e(D#1D$*JqOjx$Y_llm)m zY}fHel2!891s_5J!7Fit5DYZL#e{&|j0=$X2e=sAA%Vmd+@MKt0~d(j^Sn;iR?CI$ z^L{)ZzvuUSoP6@*k)t1toWJ8e_xy_|=FHd9^fMwzg(wtk7QPpk8EIrDl7& zS7=XENO5^)>l$ypRe?@?TrcYA=oNi`^n+KotnHb1+Qm+{-IzF*KdmFLjOp;{Q)64# z3awWCV0!z}qk>gvm%F>gMrZFxoX)&@h8*7d+xx%$D173?N8zKNjK6*IzLzCqz{Wx{z1&u&pdd zh(cV_ex@#2R7_eKJT;}YEkw~$!rn>YSVE+R7jXsGA^onE6=E|%%X`vF3p+xB5=)Y# zMg~Sx<}P+b+6tgsSv83p8PeT1+c}%A=aTf9WkXawNd|1f4y-+7+dPu!x^yp1#_&<> zdJsttCTI;iSjsq1rF!-){e$_duhf)PfQM@=Ucox2q4YoIzc$j77~KM68;W* z8q{pEs92|`u%Jf@T!Iuoam1_vvoy(faTi~~uKspxe3K(?`w*k{CE}yl2G^$}nU2xY zwXHos-IqP{+$XJSPO&NJL$esRtu787$cTBK*DowwJXK;md~GdkjdnPc_JRKX!god& z808?W-&h=f4M9RMygkA~N^TO1E(=x|li5(vz6MR*9{WP|Ou`-aw8w$#?2i-+vaQ#(acRFgXnod{|UGnxV> z{KhkD@FF-~)BU&;-Zuvh9IHrXkTLF|LtnvXt%dbJsjFx@-!m36@Nhxj@*FWS1r7Adog85T~>IT$G}+t3O=1a3(@XDkM4?8`dAM zOpQeJe^w@AO9u8iN?(ztR23ePyT{aU`y2t^Cy~K2KREMx?fTW%&4as1J6gAE1 zuC0FQnvHes6enmkp>&wmywT5&@xh-NqI{xX(QE`{b?KVqOg|r%mCdEkYERQBU_JkfleU z4Ht4TL`x@cULG^t%C{+?DmOgT((m4UA1HU-MDP$h>*R(%+_s>;`l>an<2S+f98&Ub+@e zkTpSs3s%Db<+mvtxXh!*kz&V0a9-b+6C-u~t_%$@K9Elb(vXr2U_^Zm^P$8n0-dPj z07NCZQi^S~5%~6Cmn|quc1l$wk`QM>2C5PjlabXVCFz_*1tB>eMZ`|A3N*uDK$(C8 z_qf;FsQJi^fPF7;ox`w0$Z`Y@)iP@+P#g;nn2k-2NsX;^_y*bxqP3G)Fwx>3Brs{^ z0ClGDi|g(lf2C*!Q-Tu>a?`?#O2{QoW=%>lSxp)7 z!GG{9%55$n=rcfuurLL;D(hS?(oLp`ZPTb$mh6&w*zTwSU7!=BgHHef;wdVsC)_qm za8VABis%H0SC&;^H~>yQEqj;{s2Y>25tGL`5>brPprn>%&&1sH<21p~l@`yA*bpt* zVs>UL2@+GFa*h#^{3x4®MasmjbO0s;7IOfj6zplR_Em~v>BQ#xXgtyDNBL75>8 zz+kGFpa`6$93r2MRX)!Gej^DJkh!S0U$rls?HDo+k1Xn++v8M&`I`QN{e_Vo{dap} zFq|Cr4Tz5gV$`iG&I3$XJaM5w!XjJ`s<#29ZNNnzIQGRsX~YPFq2!6C#?i|n62nud z40~)uS$1ggMc_bAV^x1Qd0`OYYTZ_*G^Z0R5g;L9Grc6foH-ts=S};s$T*Xg!7Q`l zLssdU%t{Z#1NcE&)2?F@{hG}IpaIz$SD0ZCv!JW5IByL;1Kf0pBrK%1!4o@SC~C&% z&k6-j<0U03IrSe8XU{?m?R?Pb(UALTIM4W)1Zb1frJ-L=eQ{tWq)+1w`Ucnz%Mz$g zS9$)SC6U>skxPM5>|S2jYxtFL<0;B&A}S807d^U`#v&s@ML?m*yTV;ZXCaB`{WzE- z#&7DMrOr+5v)t5y%fVn(CR$KMnM4S>9sSo-^y(fZkA0Y!CrS<-iE?C-4o{q2cB9GT zvtbit)M4XKAi8oB*V|H<>Udy=G=4V`c`7p~A3f7C4)m|xONKnc>vQY!+ng%t2ABD$ z?DI))pQlof&F4M?Uce-zKADeK9(-lkhD?m0RUJ9Y3GE?B*xQvoE@m6(M=%s`7xa}` z!o>l?ry6_l{DCaPS*(s6^if%`5B z8Yqx8)?{EIYC_n=YX3IDl*LFv<}A>tgL1mWT+JW{u$jhtAJh9D<8C@<8uvlCjU9(t zg1>&uu~H%+D@jRV-E`bs{V~14?5!*ney_(pmNy)Zr)!1?GJy!p9krr{i%}ju9ds~2 ztFOZ%fndwGABT0DLTnF;O(HL)5^_a)+o?5<7a*zj7KK0B;BjiLMU(3)ByY zdwt#XFW1a~i_^rlGM9H0Q5b%&V%i203a6{m%-1S~k2etv-)!dCOqlfa zHDG&~XmGw96NX=R5DctBJBAx}$(VBN(tS#lJbh##9zyDgHmAw)5Y{yr!d*q(Hn0B2 z3OF1AJDNjvf)d&Rb=zuj_^7>LY7Fx>(cqBBdf-g|DY_TdUk@&wjZlU-hc=VXj_Kb7 lv6r9vf1>_xFn96kf4H4EahCt@c>Hlwr|yJaxE#>){{d1X#@GM= diff --git a/mithril.js b/mithril.js index f666364e..596c6049 100644 --- a/mithril.js +++ b/mithril.js @@ -73,7 +73,7 @@ new function(window) { setAttributes(node, data.attrs, cached.attrs) cached.children = build(node, data.children, cached.children) cached.nodes.intact = true - parent.appendChild(node) + if (node.parentNode !== parent) parent.appendChild(node) } if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew) } @@ -120,13 +120,20 @@ new function(window) { function setAttributes(node, dataAttrs, cachedAttrs) { for (var attrName in dataAttrs) { var dataAttr = dataAttrs[attrName] - if (!(attrName in cachedAttrs) || (cachedAttrs[attrName] !== dataAttr) || node === window.document.activeElement) { - cachedAttrs[attrName] = dataAttr - if (attrName == "config") continue - if (attrName.indexOf("on") == 0 && typeof dataAttr == "function") dataAttr = autoredraw(dataAttr, node) - if (attrName == "style") for (var rule in dataAttr) node.style[rule] = dataAttr[rule] + var cachedAttr = cachedAttrs[attrName] + if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr) || node === window.document.activeElement) { + if (attrName === "config") continue + else if (typeof dataAttr == "function" && attrName.indexOf("on") == 0) { + if (String(dataAttr) !== String(cachedAttr)) node[attrName] = autoredraw(dataAttr, node) + } + else if (attrName === "style") { + for (var rule in dataAttr) { + if (cachedAttr === undefined || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule] + } + } else if (attrName in node) node[attrName] = dataAttr else node.setAttribute(attrName, dataAttr) + cachedAttrs[attrName] = dataAttr } } return cachedAttrs @@ -141,9 +148,9 @@ new function(window) { return result } function autoredraw(callback, object) { - return function() { + return function(e) { m.startComputation() - var output = callback.apply(object || window, arguments) + var output = callback.call(object, e) m.endComputation() return output } From 605f2a392c0029f3150a2830235a05995629a360 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Fri, 4 Apr 2014 14:06:44 -0400 Subject: [PATCH 16/38] add community page --- Gruntfile.js | 1 + archive/v0.1.2/auto-redrawing.html | 1 + archive/v0.1.2/change-log.html | 1 + archive/v0.1.2/community.html | 68 +++++++++++++++++++++ archive/v0.1.2/comparison.html | 1 + archive/v0.1.2/compiling-templates.html | 1 + archive/v0.1.2/components.html | 1 + archive/v0.1.2/getting-started.html | 1 + archive/v0.1.2/how-to-read-signatures.html | 1 + archive/v0.1.2/index.html | 1 + archive/v0.1.2/installation.html | 1 + archive/v0.1.2/integration.html | 1 + archive/v0.1.2/mithril-tests.js | 1 + archive/v0.1.2/mithril.computation.html | 1 + archive/v0.1.2/mithril.deferred.html | 1 + archive/v0.1.2/mithril.html | 1 + archive/v0.1.2/mithril.min.zip | Bin 20430 -> 20430 bytes archive/v0.1.2/mithril.module.html | 1 + archive/v0.1.2/mithril.prop.html | 1 + archive/v0.1.2/mithril.redraw.html | 1 + archive/v0.1.2/mithril.render.html | 1 + archive/v0.1.2/mithril.request.html | 1 + archive/v0.1.2/mithril.route.html | 1 + archive/v0.1.2/mithril.sync.html | 1 + archive/v0.1.2/mithril.trust.html | 1 + archive/v0.1.2/mithril.withAttr.html | 1 + archive/v0.1.2/mithril.xhr.html | 1 + archive/v0.1.2/practices.html | 1 + archive/v0.1.2/refactoring.html | 1 + archive/v0.1.2/roadmap.html | 1 + archive/v0.1.2/routing.html | 1 + archive/v0.1.2/tools.html | 1 + archive/v0.1.2/web-services.html | 1 + docs/community.md | 13 ++++ docs/layout/api.html | 1 + docs/layout/guide.html | 1 + docs/layout/index.html | 1 + 37 files changed, 115 insertions(+) create mode 100644 archive/v0.1.2/community.html create mode 100644 docs/community.md diff --git a/Gruntfile.js b/Gruntfile.js index 0cf23688..1f2e5bed 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -10,6 +10,7 @@ module.exports = function(grunt) { var guideLayout = "guide" var guide = [ "auto-redrawing", + "community", "compiling-templates", "comparison", "components", diff --git a/archive/v0.1.2/auto-redrawing.html b/archive/v0.1.2/auto-redrawing.html index e740767f..c6c2bafb 100644 --- a/archive/v0.1.2/auto-redrawing.html +++ b/archive/v0.1.2/auto-redrawing.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/change-log.html b/archive/v0.1.2/change-log.html index d81c9e97..b4fed898 100644 --- a/archive/v0.1.2/change-log.html +++ b/archive/v0.1.2/change-log.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/community.html b/archive/v0.1.2/community.html new file mode 100644 index 00000000..7f781274 --- /dev/null +++ b/archive/v0.1.2/community.html @@ -0,0 +1,68 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Community

+

Mailing List

+

Got questions about Mithril? Suggestions?

+

Feel free to post on the mailing list

+
+

Bug Tracker

+

You can file bugs in the issues page on Github

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.2/comparison.html b/archive/v0.1.2/comparison.html index 2059f3ea..a8097a5f 100644 --- a/archive/v0.1.2/comparison.html +++ b/archive/v0.1.2/comparison.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/compiling-templates.html b/archive/v0.1.2/compiling-templates.html index 6d130354..af77f3d8 100644 --- a/archive/v0.1.2/compiling-templates.html +++ b/archive/v0.1.2/compiling-templates.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/components.html b/archive/v0.1.2/components.html index 3a327011..ef505066 100644 --- a/archive/v0.1.2/components.html +++ b/archive/v0.1.2/components.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/getting-started.html b/archive/v0.1.2/getting-started.html index 2d43789a..5e9e8456 100644 --- a/archive/v0.1.2/getting-started.html +++ b/archive/v0.1.2/getting-started.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/how-to-read-signatures.html b/archive/v0.1.2/how-to-read-signatures.html index 8a479b17..8c546c9e 100644 --- a/archive/v0.1.2/how-to-read-signatures.html +++ b/archive/v0.1.2/how-to-read-signatures.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/index.html b/archive/v0.1.2/index.html index 1de4252e..02a645fb 100644 --- a/archive/v0.1.2/index.html +++ b/archive/v0.1.2/index.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/installation.html b/archive/v0.1.2/installation.html index 0d908c99..74549ed8 100644 --- a/archive/v0.1.2/installation.html +++ b/archive/v0.1.2/installation.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/integration.html b/archive/v0.1.2/integration.html index c3210e04..868fe7cb 100644 --- a/archive/v0.1.2/integration.html +++ b/archive/v0.1.2/integration.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/mithril-tests.js b/archive/v0.1.2/mithril-tests.js index 9295ce35..b7269f65 100644 --- a/archive/v0.1.2/mithril-tests.js +++ b/archive/v0.1.2/mithril-tests.js @@ -460,6 +460,7 @@ mock.window = new function() { getAttribute: function(name, value) { return this[name] }, + style: {} } } window.document.createTextNode = function(text) { diff --git a/archive/v0.1.2/mithril.computation.html b/archive/v0.1.2/mithril.computation.html index 3f0f2d22..b441a489 100644 --- a/archive/v0.1.2/mithril.computation.html +++ b/archive/v0.1.2/mithril.computation.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/mithril.deferred.html b/archive/v0.1.2/mithril.deferred.html index d95e28d7..77476b46 100644 --- a/archive/v0.1.2/mithril.deferred.html +++ b/archive/v0.1.2/mithril.deferred.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/mithril.html b/archive/v0.1.2/mithril.html index fb57fe9f..e8872455 100644 --- a/archive/v0.1.2/mithril.html +++ b/archive/v0.1.2/mithril.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/mithril.min.zip b/archive/v0.1.2/mithril.min.zip index 940bb1fbc13e4c4bb90fcc76ee510c082023ea11..59827293a0fad03e2a81b7d60237a39a40e6ca05 100644 GIT binary patch delta 49 vcmX>%pYhy$M&1B#W)?065SUodvXR$Aju}XAj*$Co0Aft`^S1=kUH&crb~q3` delta 49 vcmX>%pYhy$M&1B#W)?065J=k6xRKXGju}XAj*$Co0Aft`^S1=kUH&crdb|*X diff --git a/archive/v0.1.2/mithril.module.html b/archive/v0.1.2/mithril.module.html index eb7d2f02..0b5bb362 100644 --- a/archive/v0.1.2/mithril.module.html +++ b/archive/v0.1.2/mithril.module.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/mithril.prop.html b/archive/v0.1.2/mithril.prop.html index d595e22b..eef4bedc 100644 --- a/archive/v0.1.2/mithril.prop.html +++ b/archive/v0.1.2/mithril.prop.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/mithril.redraw.html b/archive/v0.1.2/mithril.redraw.html index 35121e81..b80fbdc8 100644 --- a/archive/v0.1.2/mithril.redraw.html +++ b/archive/v0.1.2/mithril.redraw.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/mithril.render.html b/archive/v0.1.2/mithril.render.html index 20f71f0d..ded91848 100644 --- a/archive/v0.1.2/mithril.render.html +++ b/archive/v0.1.2/mithril.render.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/mithril.request.html b/archive/v0.1.2/mithril.request.html index 463a5c65..3cfe053b 100644 --- a/archive/v0.1.2/mithril.request.html +++ b/archive/v0.1.2/mithril.request.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/mithril.route.html b/archive/v0.1.2/mithril.route.html index 57e59fd9..09b8ec57 100644 --- a/archive/v0.1.2/mithril.route.html +++ b/archive/v0.1.2/mithril.route.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/mithril.sync.html b/archive/v0.1.2/mithril.sync.html index 0bc9e222..384488e1 100644 --- a/archive/v0.1.2/mithril.sync.html +++ b/archive/v0.1.2/mithril.sync.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/mithril.trust.html b/archive/v0.1.2/mithril.trust.html index 01a7a779..a9cd8ad7 100644 --- a/archive/v0.1.2/mithril.trust.html +++ b/archive/v0.1.2/mithril.trust.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/mithril.withAttr.html b/archive/v0.1.2/mithril.withAttr.html index 912092d5..f409581c 100644 --- a/archive/v0.1.2/mithril.withAttr.html +++ b/archive/v0.1.2/mithril.withAttr.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/mithril.xhr.html b/archive/v0.1.2/mithril.xhr.html index e13c5766..7e691660 100644 --- a/archive/v0.1.2/mithril.xhr.html +++ b/archive/v0.1.2/mithril.xhr.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/practices.html b/archive/v0.1.2/practices.html index c2be6dd7..3eccb669 100644 --- a/archive/v0.1.2/practices.html +++ b/archive/v0.1.2/practices.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/refactoring.html b/archive/v0.1.2/refactoring.html index 71eb63f4..ffa5f4aa 100644 --- a/archive/v0.1.2/refactoring.html +++ b/archive/v0.1.2/refactoring.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/roadmap.html b/archive/v0.1.2/roadmap.html index 2d12361e..00ae66cf 100644 --- a/archive/v0.1.2/roadmap.html +++ b/archive/v0.1.2/roadmap.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/routing.html b/archive/v0.1.2/routing.html index 92370cfd..40d1904e 100644 --- a/archive/v0.1.2/routing.html +++ b/archive/v0.1.2/routing.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/tools.html b/archive/v0.1.2/tools.html index 26faba39..f56681ee 100644 --- a/archive/v0.1.2/tools.html +++ b/archive/v0.1.2/tools.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.2/web-services.html b/archive/v0.1.2/web-services.html index e96860cc..aa1b0141 100644 --- a/archive/v0.1.2/web-services.html +++ b/archive/v0.1.2/web-services.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/docs/community.md b/docs/community.md new file mode 100644 index 00000000..095f0bed --- /dev/null +++ b/docs/community.md @@ -0,0 +1,13 @@ +## Community + +### Mailing List + +Got questions about Mithril? Suggestions? + +Feel free to post on the [mailing list](https://groups.google.com/forum/#!forum/mithriljs) + +--- + +### Bug Tracker + +You can file bugs in the [issues page on Github](https://github.com/lhorie/mithril.js/issues?state=open) \ No newline at end of file diff --git a/docs/layout/api.html b/docs/layout/api.html index ed41d9d0..56f3c006 100644 --- a/docs/layout/api.html +++ b/docs/layout/api.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/docs/layout/guide.html b/docs/layout/guide.html index 8a28f3dd..99172e1c 100644 --- a/docs/layout/guide.html +++ b/docs/layout/guide.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/docs/layout/index.html b/docs/layout/index.html index 22c49c0f..55ee6b42 100644 --- a/docs/layout/index.html +++ b/docs/layout/index.html @@ -12,6 +12,7 @@ Guide API + Community Download Github From 5ecc942abd76c340ad6f698d8efb9777034266cb Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Sat, 5 Apr 2014 21:16:49 -0400 Subject: [PATCH 17/38] unwrap thennable --- archive/v0.1.3/mithril-tests.js | 17 ++++++++++++++++- archive/v0.1.3/mithril.min.js | 2 +- archive/v0.1.3/mithril.min.map | 2 +- archive/v0.1.3/mithril.min.zip | Bin 20592 -> 20698 bytes mithril.js | 3 ++- tests/mithril-tests.js | 14 ++++++++++++++ 6 files changed, 34 insertions(+), 4 deletions(-) diff --git a/archive/v0.1.3/mithril-tests.js b/archive/v0.1.3/mithril-tests.js index e40e1676..f75659e5 100644 --- a/archive/v0.1.3/mithril-tests.js +++ b/archive/v0.1.3/mithril-tests.js @@ -319,7 +319,8 @@ new function(window) { list.push(function(value) { try { var result = callback(value) - next[method](result !== undefined ? result : value) + if (result && typeof result.then == "function") result.then(next[method], error) + else next[method](result !== undefined ? result : value) } catch (e) { if (e instanceof Error && e.constructor !== Error) throw e @@ -773,6 +774,20 @@ function testMithril(mock) { deferred.resolve("test") return value1 === undefined && value2 instanceof Error }) + test(function() { + var deferred1 = m.deferred() + var deferred2 = m.deferred() + var value1, value2 + deferred1.promise.then(function(data) { + value1 = data + return deferred2.promise + }).then(function(data) { + value2 = data + }) + deferred1.resolve(1) + deferred2.resolve(2) + return value1 === 1 && value2 === 2 + }) //m.sync test(function() { diff --git a/archive/v0.1.3/mithril.min.js b/archive/v0.1.3/mithril.min.js index ba474595..0f66bf76 100644 --- a/archive/v0.1.3/mithril.min.js +++ b/archive/v0.1.3/mithril.min.js @@ -4,5 +4,5 @@ http://github.com/lhorie/mithril.js (c) Leo Horie License: MIT */ -!new function(a){function b(e,f,g){if(null!==f&&void 0!==f){var h=s.call(g),i=s.call(f);if(h!=i&&(null!==g&&void 0!==g&&d(g.nodes),g=new f.constructor,g.nodes=[]),"[object Array]"==i){for(var j=[],k=g.length===f.length,l=0;l-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e],h=d[e];if(!(e in d)||h!==g||b===a.document.activeElement){if("config"===e)continue;if("function"==typeof g&&0==e.indexOf("on"))String(g)!==String(h)&&(b[e]=f(g,b));else if("style"===e)for(var i in g)(void 0===h||h[i]!==g[i])&&(b.style[i]=g[i]);else e in b?b[e]=g:b.setAttribute(e,g);d[e]=g}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:\w+/g);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e],h=d[e];if(!(e in d)||h!==g||b===a.document.activeElement){if("config"===e)continue;if("function"==typeof g&&0==e.indexOf("on"))String(g)!==String(h)&&(b[e]=f(g,b));else if("style"===e)for(var i in g)(void 0===h||h[i]!==g[i])&&(b.style[i]=g[i]);else e in b?b[e]=g:b.setAttribute(e,g);d[e]=g}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:\w+/g);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;cd(oxh^?Q=FU2vj*jWhI@wN+j%kiM?v9TBm99G8j*j_GRjxX&aIUvIh~r)1 z1X2=Q3Zk5gT%C1XCmVRFG3B{Tj_?X$jGDaGONu?)DG6vp*5q?unvBVte|UX`xC$P8 dlYjZ^%cHmx=vIDkFvEy#lRW~I*t`Qkd;nTef{OqE delta 288 zcmcb$knzI;M)3e|W)?065J+}!cDcPj_)(l314GX{wkh^0R*9Om$%!S&85*gY)tPA;sS25S#U+V($*K8i3a&*(`9*4KsgvuZq~wG*UM&m6B%R1=%7r@iDuWAp?Wf=Cg7SBp9#*-D(WopjtM&+<}ZvUHw&$SXuCq5vr1=;&GL3ZncgTy9bL6ObV407 xosxhWGbUSjYceKoPV)W=u?+5|$teN)@+j5=oxl(FI*jO=yeUA5?V&%24**WLS7!hK diff --git a/mithril.js b/mithril.js index 596c6049..04f23eee 100644 --- a/mithril.js +++ b/mithril.js @@ -319,7 +319,8 @@ new function(window) { list.push(function(value) { try { var result = callback(value) - next[method](result !== undefined ? result : value) + if (result && typeof result.then == "function") result.then(next[method], error) + else next[method](result !== undefined ? result : value) } catch (e) { if (e instanceof Error && e.constructor !== Error) throw e diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 63720923..28783785 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -245,6 +245,20 @@ function testMithril(mock) { deferred.resolve("test") return value1 === undefined && value2 instanceof Error }) + test(function() { + var deferred1 = m.deferred() + var deferred2 = m.deferred() + var value1, value2 + deferred1.promise.then(function(data) { + value1 = data + return deferred2.promise + }).then(function(data) { + value2 = data + }) + deferred1.resolve(1) + deferred2.resolve(2) + return value1 === 1 && value2 === 2 + }) //m.sync test(function() { From c4494bf2cebb919cfa1eb613df0dfa164fd8563b Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Sat, 5 Apr 2014 22:14:59 -0400 Subject: [PATCH 18/38] improve docs about a+ differences --- archive/v0.1.3/change-log.html | 7 +++++ archive/v0.1.3/mithril.deferred.html | 26 +++++++++++++++++ archive/v0.1.3/mithril.min.zip | Bin 20698 -> 20698 bytes docs/change-log.md | 9 ++++++ docs/mithril.deferred.md | 40 +++++++++++++++++++++++++++ 5 files changed, 82 insertions(+) diff --git a/archive/v0.1.3/change-log.html b/archive/v0.1.3/change-log.html index d0ac7884..370f6374 100644 --- a/archive/v0.1.3/change-log.html +++ b/archive/v0.1.3/change-log.html @@ -60,6 +60,13 @@

Change Log

+

v0.1.3 - maintenance

+

Bug Fixes:

+
    +
  • diff no longer touch the DOM when processing style attributes and event handlers
  • +
  • returning a thennable to a resolution callback in m.deferred().promise now causes the promise to adopt its state
  • +
+

v0.1.2 - maintenance

News:

    diff --git a/archive/v0.1.3/mithril.deferred.html b/archive/v0.1.3/mithril.deferred.html index 7a6b9f23..761df87d 100644 --- a/archive/v0.1.3/mithril.deferred.html +++ b/archive/v0.1.3/mithril.deferred.html @@ -79,6 +79,32 @@ greetAsync() .then(function(value) {return value + " world"}) .then(function(value) {console.log(value)}); //logs "hello world" after 1 second
    +

    Differences from Promises/A+

    +

    For the most part, Mithril promises behave as you'd expect a Promise/A+ promise to behave, but with a few key differences:

    +

    Mithril promises forward a value downstream if a resolution callback returns undefined. This allows simpler debugging of promise chains:

    +
    var data = m.request({method: "GET", url: "/data"})
    +    .then(console.log) //Mithril promises let us debug like this
    +    .then(doStuff)
    +
    +var data = m.request({method: "GET", url: "/data"})
    +    .then(function(value) { // Promises/A+ would require us to declare an anonymous function
    +        console.log(value) // here's the debugging snippet
    +        return value // and we need to remember to return the value as well
    +    })
    +    .then(doStuff) // or else `doStuff` will break
    +

    Another subtle difference is that the Promises/A+ require a callback to run in a different execution context than its respective then method. This requirement exists to support an obscure edge cases and incurs a significant performance hit on each link of a promise chain. To be more specific, the performance hit can come either in the form of a 4ms minimum delay (if the implementation uses setTimeout), or from having to load a bunch of hacky polyfill code for a feature that is not being considered for addition by some browser vendors.

    +

    To illustrate the difference between Mithril and A+ promises, consider the code below:

    +
    var deferred = m.deferred()
    +
    +deferred.promise.then(function() {
    +    console.log(1)
    +})
    +
    +deferred.resolve("value")
    +
    +console.log(2)
    +

    In the example above, A+ promises are required to log 2 before logging 1, whereas Mithril logs 1 before 2. Typically resolve/reject are called asynchronously after the then method is called, so normally this difference does not matter.

    +

    Signature

    How to read signatures

    Deferred deferred()
    diff --git a/archive/v0.1.3/mithril.min.zip b/archive/v0.1.3/mithril.min.zip
    index a1ab700e403c484d0642d596f00e59b3f29a94f2..f0ab5f55ad90646958fdf0d4ab57013f9f8b679f 100644
    GIT binary patch
    delta 45
    vcmcb$knz?+MxFp~W)?065LmZyBhMB&rga-PACjAD2&5-R23UcpsR6D4SnLo^
    
    delta 45
    vcmcb$knz?+MxFp~W)?065O}q6BhMB&rdKOBACjAD2&5-R23UcpsR6D4X50}T
    
    diff --git a/docs/change-log.md b/docs/change-log.md
    index 98f6780c..002e1f81 100644
    --- a/docs/change-log.md
    +++ b/docs/change-log.md
    @@ -1,5 +1,14 @@
     ## Change Log
     
    +[v0.1.3](/mithril/archive/v0.1.3) - maintenance
    +
    +### Bug Fixes:
    +
    +-	diff no longer touch the DOM when processing `style` attributes and event handlers
    +-	returning a thennable to a resolution callback in `m.deferred().promise` now causes the promise to adopt its state 
    +
    +---
    +
     [v0.1.2](/mithril/archive/v0.1.2) - maintenance
     
     ### News:
    diff --git a/docs/mithril.deferred.md b/docs/mithril.deferred.md
    index daa19b53..4c3aa920 100644
    --- a/docs/mithril.deferred.md
    +++ b/docs/mithril.deferred.md
    @@ -29,6 +29,46 @@ greetAsync()
     
     ---
     
    +### Differences from Promises/A+
    +
    +For the most part, Mithril promises behave as you'd expect a [Promise/A+](http://promises-aplus.github.io/promises-spec/) promise to behave, but with a few key differences:
    +
    +Mithril promises forward a value downstream if a resolution callback returns `undefined`. This allows simpler debugging of promise chains:
    +
    +```javascript
    +var data = m.request({method: "GET", url: "/data"})
    +	.then(console.log) //Mithril promises let us debug like this
    +	.then(doStuff)
    +	
    +var data = m.request({method: "GET", url: "/data"})
    +	.then(function(value) { // Promises/A+ would require us to declare an anonymous function
    +		console.log(value) // here's the debugging snippet
    +		return value // and we need to remember to return the value as well
    +	})
    +	.then(doStuff) // or else `doStuff` will break
    +
    +```
    +
    +Another subtle difference is that the Promises/A+ require a callback to run in a different execution context than its respective `then` method. This requirement exists to support an obscure edge cases and incurs [a significant performance hit on each link of a promise chain](http://thanpol.as/javascript/promises-a-performance-hits-you-should-be-aware-of/). To be more specific, the performance hit can come either in the form of a 4ms minimum delay (if the implementation uses `setTimeout`), or from having to load a [bunch of hacky polyfill code](https://raw.githubusercontent.com/NobleJS/setImmediate/master/setImmediate.js) for a [feature that is not being considered for addition by some browser vendors](https://developer.mozilla.org/en-US/docs/Web/API/Window.setImmediate).
    +
    +To illustrate the difference between Mithril and A+ promises, consider the code below:
    +
    +```javascript
    +var deferred = m.deferred()
    +
    +deferred.promise.then(function() {
    +	console.log(1)
    +})
    +
    +deferred.resolve("value")
    +
    +console.log(2)
    +```
    +
    +In the example above, A+ promises are required to log `2` before logging `1`, whereas Mithril logs `1` before `2`. Typically `resolve`/`reject` are called asynchronously after the `then` method is called, so normally this difference does not matter.
    +
    +---
    +
     ### Signature
     
     [How to read signatures](how-to-read-signatures.md)
    
    From fcf77dfa44e1e41847f5f1af5b9987ea0f928a8a Mon Sep 17 00:00:00 2001
    From: Leo Horie 
    Date: Sun, 6 Apr 2014 15:20:21 -0400
    Subject: [PATCH 19/38] add {subtree: "retain"} flag to allow skipping diff
     from app space
    
    ---
     archive/v0.1.3/change-log.html     |   1 +
     archive/v0.1.3/mithril-tests.js    |  38 ++++++++++++++++++++--
     archive/v0.1.3/mithril.html        |   4 ++-
     archive/v0.1.3/mithril.min.js      |   2 +-
     archive/v0.1.3/mithril.min.map     |   2 +-
     archive/v0.1.3/mithril.min.zip     | Bin 20698 -> 20819 bytes
     archive/v0.1.3/mithril.render.html |  40 +++++++++++++++++++++--
     docs/change-log.md                 |   1 +
     docs/mithril.md                    |   5 ++-
     docs/mithril.render.md             |  49 ++++++++++++++++++++++++++++-
     mithril.js                         |   8 +++--
     tests/mithril-tests.js             |  30 ++++++++++++++++++
     12 files changed, 169 insertions(+), 11 deletions(-)
    
    diff --git a/archive/v0.1.3/change-log.html b/archive/v0.1.3/change-log.html
    index 370f6374..992906bb 100644
    --- a/archive/v0.1.3/change-log.html
    +++ b/archive/v0.1.3/change-log.html
    @@ -65,6 +65,7 @@
     
    • diff no longer touch the DOM when processing style attributes and event handlers
    • returning a thennable to a resolution callback in m.deferred().promise now causes the promise to adopt its state
    • +
    • diff now correctly clears subtree if null or undefined is passed as a node

    v0.1.2 - maintenance

    diff --git a/archive/v0.1.3/mithril-tests.js b/archive/v0.1.3/mithril-tests.js index f75659e5..d6364ce4 100644 --- a/archive/v0.1.3/mithril-tests.js +++ b/archive/v0.1.3/mithril-tests.js @@ -33,7 +33,11 @@ new function(window) { return cell } function build(parent, data, cached) { - if (data === null || data === undefined) return + if (data === null || data === undefined) { + if (cached) clear(cached.nodes) + return + } + if (data.subtree === "retain") return var cachedType = type.call(cached), dataType = type.call(data) if (cachedType != dataType) { @@ -59,8 +63,8 @@ new function(window) { } } else if (dataType == "[object Object]") { - if (typeof data.tag != "string") return if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join()) clear(cached.nodes) + if (typeof data.tag != "string") return var node, isNew = cached.nodes.length === 0 if (isNew) { @@ -640,6 +644,36 @@ function testMithril(mock) { m.render(root, m("div", [m("a", {href: "/second"})])) return root.childNodes[0].childNodes.length == 1 }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [m("li"), undefined])) + return root.childNodes[0].childNodes.length === 1 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li"), m("li")])) + m.render(root, m("ul", [m("li"), undefined])) + return root.childNodes[0].childNodes.length === 1 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [undefined])) + return root.childNodes[0].childNodes.length === 0 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [{}])) + return root.childNodes[0].childNodes.length === 0 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li", [m("a")])])) + m.render(root, m("ul", [{subtree: "retain"}])) + return root.childNodes[0].childNodes[0].childNodes[0].nodeName === "A" + }) //m.redraw test(function() { diff --git a/archive/v0.1.3/mithril.html b/archive/v0.1.3/mithril.html index a6d2ec27..76d7bc34 100644 --- a/archive/v0.1.3/mithril.html +++ b/archive/v0.1.3/mithril.html @@ -183,7 +183,8 @@ m("a[href='/dashboard']", {config: m.route}, "Dashboard&q where: VirtualElement :: Object { String tag, Attributes attributes, Children children } Attributes :: Object<any | void config(DOMElement element, Boolean isInitialized)> - Children :: String text | Array<String text | VirtualElement virtualElement | Children children>
    + Children :: String text | Array<String text | VirtualElement virtualElement | SubtreeDirective directive | Children children> + SubtreeDirective :: Object { String subtree }
    • String selector

      This string should be a CSS rule that represents a DOM element.

      @@ -259,6 +260,7 @@ m("a[href='/dashboard']", {config: m.route}, "Dashboard&q

      If this argument is a string, it will be rendered as a text node. To render a string as HTML, see m.trust

      If it's a VirtualElement, it will be rendered as a DOM Element.

      If it's a list, its contents will recursively be rendered as appropriate and appended as children of the element being created.

      +

      If it's a SubtreeDirective with the value "retain", it will retain the existing DOM tree in place, if any. See subtree directives for more information.

    • returns VirtualElement

      The returned VirtualElement is a javascript data structure that represents the DOM element to be rendered by m.render

      diff --git a/archive/v0.1.3/mithril.min.js b/archive/v0.1.3/mithril.min.js index 0f66bf76..f2d5c729 100644 --- a/archive/v0.1.3/mithril.min.js +++ b/archive/v0.1.3/mithril.min.js @@ -4,5 +4,5 @@ http://github.com/lhorie/mithril.js (c) Leo Horie License: MIT */ -!new function(a){function b(e,f,g){if(null!==f&&void 0!==f){var h=s.call(g),i=s.call(f);if(h!=i&&(null!==g&&void 0!==g&&d(g.nodes),g=new f.constructor,g.nodes=[]),"[object Array]"==i){for(var j=[],k=g.length===f.length,l=0;l-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e],h=d[e];if(!(e in d)||h!==g||b===a.document.activeElement){if("config"===e)continue;if("function"==typeof g&&0==e.indexOf("on"))String(g)!==String(h)&&(b[e]=f(g,b));else if("style"===e)for(var i in g)(void 0===h||h[i]!==g[i])&&(b.style[i]=g[i]);else e in b?b[e]=g:b.setAttribute(e,g);d[e]=g}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:\w+/g);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e],h=d[e];if(!(e in d)||h!==g||b===a.document.activeElement){if("config"===e)continue;if("function"==typeof g&&0==e.indexOf("on"))String(g)!==String(h)&&(b[e]=f(g,b));else if("style"===e)for(var i in g)(void 0===h||h[i]!==g[i])&&(b.style[i]=g[i]);else e in b?b[e]=g:b.setAttribute(e,g);d[e]=g}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:\w+/g);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c7%g5Y7(*w-N`810lE+5JFrKoH&4x5CSnXZ=Ii(0|$Iq`^}p- z^UcinX4c=_ZvX1t_U@F~`NZKi{LJm#jQ{@d`z!Z*+S=~F-Yxj}_;~)tje2Ey;ZzWR z_g8c48`bhDl$={Ua|Rw4%aw&(t-pV4Ie)GXwzJFSzSE$3v9>X{Ud`n`ymz`o{LwMf zu840tdYV(6)e}#npNa8Td+vtX+U|EVGd-j#24icgl3}c%PT+=VDh^{a3TGO_L-J^r(;in$&`6=& zbx@KaW2Tf(gT&s6gUKnwLY?`B4hx;rt z=l_1t|3YR-#f3!?Ix@~I`KJT62fuk#y#}7yjEC`XMyF@dk!7Q8hf;;8d4Wrxo(K-a z8onP)0GI}h8(;_yEX2_S*oJ%-Wo%#rVPsI(Uhuae4f+g7cuA+!Bd(f?kXT8jR2h^Q z%fVyw1|7@OQwMmR%yPv~1F2pa3X0ePRg#lVZ=*zcuq((I=Am&usOQm@O`u#qfe5Dx zVPF7c5s(9yu48zMssHBFikKH5!+?yYDKO%Vz-Y-60OsgxgmO$V~wiR ztEr?x)N!P2AB*``LRmzo6NnXE0FY%BV)c1p(1ztA;zg>$mbxV^w=!Ze9#*q(kS8V4 z2w{ZTF%`?@a%CRU8Z~K>7}^z&?2qR2p9HPu8a=&I+xdZw?rC)#!FYn zRW>31O%-ebgL11xKL=4^w_*l04(f5tn?;Gk8UTog5)uQpY36$H)aZg4GC38FNaw4v zU9EAXSq8H)Oc5LY#^Y%s*J-&eZ!dLfiNh_TJ@MV(Kx3GaV*zaJ_!u59#3@?YDftwV z-WZ~y{4Z%gko8e*luK{+HU;NxaiEL7r9#!Pr29Dn0$~0m*3qtr`@!Sc9gb6>1j7b& z9~~w#(ZHw2Gc3EEPi&ln*fVtCJ6@kzh*+B@>5v!+-3<6$EG9k;eH;i=cgjb^&7pVl z02Um>NC9LE)E#hbNF<0;C%*0P-EusbvYV5lZs1tXP^CQ@2++d7;IKAB)*bO$_|Bn1 zoGLWJ#pTEtFVjS|E1Hr;w(YL4bKqcR)}Drx0lkP525ewr*TlWZ*!Y$_xveQRqVJAe z2hlDc--2abR!7Hc3*8jFTGdBnI2c+o z@!qf=3`vC{9j_WRbWCAbCzSsti-_YRzXsN^dVF@H`TXeN_TUc9NGq6xQ#KIFf>)DJ z5)1(K;6K+&IFs)@i#je9y-TvpIl1Fg&dMA^t$J?gyKgw5GatkRcszp|Lp7o5l#7gro{Z2<h2%g23(dGo768?TwFm{Cfla(Oax;T*8GsMIa0lCT zO`NntM_Zog)dL^tn#kG@16xz5>uw5`>Y~3AyMI~4LTa%0z@H2FgYgXgwP`(m79XYh Kp8O<*>wf^|o~fh& delta 3164 zcmb7H&2L*p5YI=2+zJY6Au04DsfALddDM2CPlBPl>*wc}*O!g!xUNg3$Vr?}f7Nzk zrxG$8K%zoO?Mj?LrHBI(Qc-2`@h2c8E^vV&ka|WvpmIRsP-bSI)3o#eJ-m8%XJ%)9 z^P8E?kDqkjzR}q`>vZqg*Ma|T-g-NF_ul>aE5|xIu3YU^&m50LmIns5N^48~LHvE| z?(wc;{%WnVR$TEPkJPryxzcj~@~K*OWw4T~ZB~j?YJc}!>&@=!;iu5rACun)Rzn>f ztJnHIQeXECJekjBD}KXYEG^}#t)Ke__cmFD8-kh4uo$x~{APt=GUf=wf$uc#BpuvI z$9C^dSw}=^%vGZq8X@UzQ35zH$s@z3ocLu*wf|LkFXJK)ICGp%Q zDJN-Un8@?9o>Yoi31fpZb|I#I8|;aghRayS#M9&)$)xa#^m+X4q|xe@An$G5u^4{s z3;I-rP98b8jK5tHN$4;$bUj&+Y4|Nh`8gxS*jyUp5oE$SgBQCdUX~!F&}p$Qv=eFd z>B+ICcMSpnrftSH!4N1cMe$Cs4RCJoX>Fj(WaoEKNu>R50)-&nbLp8(GG3Krlc{^W1YC zJhz#-U|a&W6on?3PCxb%%09Tci0%yY8ij%Q(H_8I&Z0%tI`!h}1Jbt{j&y_&K)}|9 z$0)26?(4+aMAl3|0}TXD0)aU!K}P~j3gDH7I7^$r?wmRu=xwf3n~)pA$T2G))z;cv z_6U=NGnUKa6{03|y=!AsTV|P4Z6%F5+*0RY3ka4g5;+s-fkUoSXQ3X*GIO{C_)P<} zp_ujo+a^om*Svrvv^5X~fmOU%*8Qv`kaB5^e4Jvoy@N5;AMAZWFQ5Yhu1FbX>wY%I zRU-J(>kY!kouC?ZFcPsI)yPuU6f<-&cH97b=FXc(4jaE5e*=4;@`;WHung9tx;l{oI{SDB~k~~t_ZFco&JqBQe!l_GwmTjaVIg~I+aeH5kn>1%# zOYIr?Y;Q4217Ibpz8sknd75{!U{YZfcUBlA&R^y}kkEVBJjg9LKqN~lJUSKG(wcSC zs1gesdcBQyML>_uL`ZKAo5m&xZS$~RkF-7?H9LL97&%r_e~tYagfRvF%Yua!3q2EV zb<2kKw%7P{MU zP?k_bF1$c4V{c)JQhR{l%LN)7Sa;Lv+m{yy@+fVFVs%nZgAH^IY(NMp(l*1qt`bv+ z_J`3O7+$Dt53Bmrz=1GL$OXJZS87ImF;(f&6aP{i3J&H;1+QSvB{lxa54~GiDq!e3 z;5iG>b*U9w^+%HXD>S;j_f+E0xO(^WiJ?4lLIm{emtLz0 z#=#vVQK+wqt`2h^{#fxd^H^zf{In*MBPf4)*f3I7w$EUy0nU5t5R diff --git a/archive/v0.1.3/mithril.render.html b/archive/v0.1.3/mithril.render.html index f7007f5f..ed3e93f5 100644 --- a/archive/v0.1.3/mithril.render.html +++ b/archive/v0.1.3/mithril.render.html @@ -86,14 +86,50 @@ m.render(document.body, [ </ul> </body>
      +

      Subtree Directives

      +

      m.render accepts a special low level SubtreeDirective object as a node in a virtual DOM tree: if a tree contains a node that looks exactly like the object below, Mithril will abort the diff algorithm for that node. This allows you to implement optimizations that avoid creating virtual DOM trees in favor of their cached counterparts, if you know they have not changed between redraws. Note that using this feature is discouraged if you don't have visible performance problems.

      +
      {subtree: "retain"}
      +

      This mechanism is only intended to be used as a last resort optimization tool. If you do use it, you are responsible for determining what constitutes a scenario where the virtual DOM tree is changed/unchanged.

      +

      The example below shows how to use a SubtreeDirective object to create a static header that doesn't incur diff costs once it has been rendered. This means that we are avoiding the creation of the header subtree (and therefore skipping the diff algorithm) altogether, but it also means that dynamic variables will NOT be updated within the header.

      +
      var app = {}
      +
      +//here's an example plugin that determines whether data has changes.
      +//in this case, it simply assume data has changed the first time, and never changes after that.
      +app.bindOnce = new function() {
      +    var cache = {}
      +    function(view) {
      +        if (!cache[view.toString()]) {
      +            cache[view.toString()] = true
      +            return view()
      +        }
      +        else return {subtree: "retain"}
      +    }
      +}
      +
      +//here's the view
      +app.view = function(ctrl) {
      +    m(".layout", [
      +        app.bindOnce(function() {
      +            //this only runs once in order to boost performance
      +            //dynamic variables are not updated here
      +            return m("header", [
      +                m("h1", "this never changes")
      +            ])
      +        }),
      +        //dynamic variables here still update on every redraw
      +        m("main", "rest of app goes here")
      +    ])
      +}
      +

      Signature

      How to read signatures

      void render(DOMElement rootElement, Children children)
       
       where:
      -    Children :: String text | Array<String text | VirtualElement virtualElement | Children children>
      +    Children :: String text | Array<String text | VirtualElement virtualElement | SubtreeDirective directive | Children children>
           VirtualElement :: Object { String tag, Attributes attributes, Children children }
      -    Attributes :: Object<Any | void config(DOMElement element)>
      + Attributes :: Object<Any | void config(DOMElement element)> + SubtreeDirective :: Object { String subtree }
      • DOMElement rootElement

        A DOM element which will contain the template represented by children.

        diff --git a/docs/change-log.md b/docs/change-log.md index 002e1f81..b6c8cf3b 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -6,6 +6,7 @@ - diff no longer touch the DOM when processing `style` attributes and event handlers - returning a thennable to a resolution callback in `m.deferred().promise` now causes the promise to adopt its state +- diff now correctly clears subtree if null or undefined is passed as a node --- diff --git a/docs/mithril.md b/docs/mithril.md index 3cd7a437..fe093005 100644 --- a/docs/mithril.md +++ b/docs/mithril.md @@ -195,7 +195,8 @@ VirtualElement m(String selector [, Attributes attributes] [, Children children] where: VirtualElement :: Object { String tag, Attributes attributes, Children children } Attributes :: Object - Children :: String text | Array + Children :: String text | Array + SubtreeDirective :: Object { String subtree } ``` - **String selector** @@ -316,6 +317,8 @@ where: If it's a VirtualElement, it will be rendered as a DOM Element. If it's a list, its contents will recursively be rendered as appropriate and appended as children of the element being created. + + If it's a SubtreeDirective with the value "retain", it will retain the existing DOM tree in place, if any. See [subtree directives](mithril.render#subtree-directives) for more information. - **returns** VirtualElement diff --git a/docs/mithril.render.md b/docs/mithril.render.md index 49d2cd2f..d2acb043 100644 --- a/docs/mithril.render.md +++ b/docs/mithril.render.md @@ -40,6 +40,52 @@ yields: --- +### Subtree Directives + +`m.render` accepts a special low level SubtreeDirective object as a node in a virtual DOM tree: if a tree contains a node that looks exactly like the object below, Mithril will abort the diff algorithm for that node. This allows you to implement optimizations that avoid creating virtual DOM trees in favor of their cached counterparts, if you know they have not changed between redraws. Note that using this feature is discouraged if you don't have visible performance problems. + +```javascript +{subtree: "retain"} +``` + +This mechanism is only intended to be used as a last resort optimization tool. If you do use it, you are responsible for determining what constitutes a scenario where the virtual DOM tree is changed/unchanged. + +The example below shows how to use a SubtreeDirective object to create a static header that doesn't incur diff costs once it has been rendered. This means that we are avoiding the creation of the header subtree (and therefore skipping the diff algorithm) altogether, but it also means that dynamic variables will NOT be updated within the header. + +``` +var app = {} + +//here's an example plugin that determines whether data has changes. +//in this case, it simply assume data has changed the first time, and never changes after that. +app.bindOnce = new function() { + var cache = {} + function(view) { + if (!cache[view.toString()]) { + cache[view.toString()] = true + return view() + } + else return {subtree: "retain"} + } +} + +//here's the view +app.view = function(ctrl) { + m(".layout", [ + app.bindOnce(function() { + //this only runs once in order to boost performance + //dynamic variables are not updated here + return m("header", [ + m("h1", "this never changes") + ]) + }), + //dynamic variables here still update on every redraw + m("main", "rest of app goes here") + ]) +} +``` + +--- + ### Signature [How to read signatures](how-to-read-signatures.md) @@ -48,9 +94,10 @@ yields: void render(DOMElement rootElement, Children children) where: - Children :: String text | Array + Children :: String text | Array VirtualElement :: Object { String tag, Attributes attributes, Children children } Attributes :: Object + SubtreeDirective :: Object { String subtree } ``` - **DOMElement rootElement** diff --git a/mithril.js b/mithril.js index 04f23eee..70a87db3 100644 --- a/mithril.js +++ b/mithril.js @@ -33,7 +33,11 @@ new function(window) { return cell } function build(parent, data, cached) { - if (data === null || data === undefined) return + if (data === null || data === undefined) { + if (cached) clear(cached.nodes) + return + } + if (data.subtree === "retain") return var cachedType = type.call(cached), dataType = type.call(data) if (cachedType != dataType) { @@ -59,8 +63,8 @@ new function(window) { } } else if (dataType == "[object Object]") { - if (typeof data.tag != "string") return if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join()) clear(cached.nodes) + if (typeof data.tag != "string") return var node, isNew = cached.nodes.length === 0 if (isNew) { diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 28783785..64124611 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -111,6 +111,36 @@ function testMithril(mock) { m.render(root, m("div", [m("a", {href: "/second"})])) return root.childNodes[0].childNodes.length == 1 }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [m("li"), undefined])) + return root.childNodes[0].childNodes.length === 1 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li"), m("li")])) + m.render(root, m("ul", [m("li"), undefined])) + return root.childNodes[0].childNodes.length === 1 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [undefined])) + return root.childNodes[0].childNodes.length === 0 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [{}])) + return root.childNodes[0].childNodes.length === 0 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li", [m("a")])])) + m.render(root, m("ul", [{fromCache: true}])) + return root.childNodes[0].childNodes[0].childNodes[0].nodeName === "A" + }) //m.redraw test(function() { From 751cc683ddf98f2a8e05d0fc3ad039a9caac17e3 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Sun, 6 Apr 2014 15:27:37 -0400 Subject: [PATCH 20/38] fix test --- tests/mithril-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 64124611..eea18afb 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -138,7 +138,7 @@ function testMithril(mock) { test(function() { var root = mock.document.createElement("div") m.render(root, m("ul", [m("li", [m("a")])])) - m.render(root, m("ul", [{fromCache: true}])) + m.render(root, m("ul", [{subtree: "retain"}])) return root.childNodes[0].childNodes[0].childNodes[0].nodeName === "A" }) From 85a22599642c571c5bad11ed5d2facad97b1662a Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Sun, 6 Apr 2014 15:34:06 -0400 Subject: [PATCH 21/38] add docs about component --- archive/v0.1.3/change-log.html | 5 +++++ archive/v0.1.3/component.json | 2 +- archive/v0.1.3/installation.html | 11 ++++++++++- archive/v0.1.3/mithril.min.zip | Bin 20819 -> 20819 bytes docs/change-log.md | 5 +++++ docs/installation.md | 26 +++++++++++++++++++++++++- docs/layout/component.json | 2 +- 7 files changed, 47 insertions(+), 4 deletions(-) diff --git a/archive/v0.1.3/change-log.html b/archive/v0.1.3/change-log.html index 992906bb..564ee657 100644 --- a/archive/v0.1.3/change-log.html +++ b/archive/v0.1.3/change-log.html @@ -61,6 +61,11 @@

        Change Log

        v0.1.3 - maintenance

        +

        News:

        +
          +
        • Mithril is now available via Component
        • +
        • There's now an extra low-level optimization hook called a SubtreeDirective, which allows implementing plugins that only create virtual trees if necessary.
        • +

        Bug Fixes:

        • diff no longer touch the DOM when processing style attributes and event handlers
        • diff --git a/archive/v0.1.3/component.json b/archive/v0.1.3/component.json index 10799ed8..b3ef8e7d 100644 --- a/archive/v0.1.3/component.json +++ b/archive/v0.1.3/component.json @@ -2,7 +2,7 @@ "name": "mithril", "description": "A Javascript framework for building brilliant applications", "keywords": ["mvc", "framework"], - "repo": "lhorie/mithril", + "repository": "lhorie/mithril", "main": "mithril.min.js", "scripts": ["mithril.min.js"], "version": "0.1.3", diff --git a/archive/v0.1.3/installation.html b/archive/v0.1.3/installation.html index 50aea470..c8b1fb12 100644 --- a/archive/v0.1.3/installation.html +++ b/archive/v0.1.3/installation.html @@ -68,13 +68,22 @@
          <script src="/node_modules/mithril/mithril.min.js"></script>

          Bower

          -

          Bower is a package manager for NodeJS. If you're using NodeJS already or planning on using Grunt to create a build system, you can use Bower to conveniently keep up-to-date with Mithril versions.

          +

          Bower is a package manager for NodeJS. If you're using NodeJS already or planning on using Grunt to create a build system, you can use Bower to conveniently keep up-to-date with Mithril versions.

          Assuming you have NodeJS installed, you can install Bower by typing this in the command line:

          npm install -g bower

          And you can download Mithril by typing this:

          bower install mithril

          Then, to use Mithril, point a script tag to the downloaded file:

          <script src="/bower_components/mithril/mithril.min.js"></script>
          +
          +

          Component

          +

          Component is another package manager for NodeJS. If you're using NodeJS already or planning on using Grunt to create a build system, you can use Component to conveniently keep up-to-date with Mithril versions.

          +

          Assuming you have NodeJS installed, you can install Bower by typing this in the command line:

          +
          npm install -g component
          +

          And you can download Mithril by typing this:

          +
          component install lhorie/mithril@gh-pages
          +

          Then, to use Mithril, point a script tag to the downloaded file:

          +
          <script src="/components/lhorie/mithril/master/mithril.min.js"></script>
diff --git a/archive/v0.1.3/mithril.min.zip b/archive/v0.1.3/mithril.min.zip index abb9af890cc6615013b6d644e85816e7fc00c9cb..5d7370e0e36fa6fb8dfbc0aec3250ce87f7a58a5 100644 GIT binary patch delta 45 vcmcb-i1G3wMxFp~W)?065YVgH$n#u|Nv~$}SGn7UKzj0}04os17w8HAH-!!j delta 45 vcmcb-i1G3wMxFp~W)?065J;-p$n#u|DXD7nSGn7UKzj0}04os17w8HAMAZ(} diff --git a/docs/change-log.md b/docs/change-log.md index b6c8cf3b..9ddc7952 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -2,6 +2,11 @@ [v0.1.3](/mithril/archive/v0.1.3) - maintenance +### News: + +- Mithril is now available via [Component](http://component.io) +- There's now an extra low-level optimization hook called a SubtreeDirective, which allows implementing plugins that only create virtual trees if necessary. + ### Bug Fixes: - diff no longer touch the DOM when processing `style` attributes and event handlers diff --git a/docs/installation.md b/docs/installation.md index 1c5fe733..23b2c0fb 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -58,7 +58,7 @@ Then, to use Mithril, point a script tag to the downloaded file: ### Bower -[Bower](http://http://bower.io) is a package manager for [NodeJS](http://nodejs.org/). If you're using NodeJS already or planning on using [Grunt](http://gruntjs.com/) to create a build system, you can use Bower to conveniently keep up-to-date with Mithril versions. +[Bower](http://bower.io) is a package manager for [NodeJS](http://nodejs.org/). If you're using NodeJS already or planning on using [Grunt](http://gruntjs.com/) to create a build system, you can use Bower to conveniently keep up-to-date with Mithril versions. Assuming you have NodeJS installed, you can install Bower by typing this in the command line: @@ -77,3 +77,27 @@ Then, to use Mithril, point a script tag to the downloaded file: ```markup ``` + +--- + +### Component + +[Component](http://component.io) is another package manager for [NodeJS](http://nodejs.org/). If you're using NodeJS already or planning on using [Grunt](http://gruntjs.com/) to create a build system, you can use Component to conveniently keep up-to-date with Mithril versions. + +Assuming you have NodeJS installed, you can install Bower by typing this in the command line: + +``` +npm install -g component +``` + +And you can download Mithril by typing this: + +``` +component install lhorie/mithril@gh-pages +``` + +Then, to use Mithril, point a script tag to the downloaded file: + +```markup + +``` diff --git a/docs/layout/component.json b/docs/layout/component.json index 484349e5..489ab5ce 100644 --- a/docs/layout/component.json +++ b/docs/layout/component.json @@ -2,7 +2,7 @@ "name": "mithril", "description": "A Javascript framework for building brilliant applications", "keywords": ["mvc", "framework"], - "repo": "lhorie/mithril", + "repository": "lhorie/mithril", "main": "mithril.min.js", "scripts": ["mithril.min.js"], "version": "$version", From 86514b41f238d371f55852842d1d2bbf8196fd85 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Sun, 6 Apr 2014 15:36:11 -0400 Subject: [PATCH 22/38] fix docs about component branch --- docs/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index 23b2c0fb..b3f033c4 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -99,5 +99,5 @@ component install lhorie/mithril@gh-pages Then, to use Mithril, point a script tag to the downloaded file: ```markup - + ``` From bdd016de3fbc23d9a2968fc1daf48fc942ec97c5 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Sun, 6 Apr 2014 20:24:23 -0400 Subject: [PATCH 23/38] community link --- archive/v0.1.3/auto-redrawing.html | 1 + archive/v0.1.3/change-log.html | 1 + archive/v0.1.3/community.html | 68 +++++++++++++++++++++ archive/v0.1.3/comparison.html | 1 + archive/v0.1.3/compiling-templates.html | 1 + archive/v0.1.3/components.html | 1 + archive/v0.1.3/getting-started.html | 1 + archive/v0.1.3/how-to-read-signatures.html | 1 + archive/v0.1.3/index.html | 1 + archive/v0.1.3/installation.html | 3 +- archive/v0.1.3/integration.html | 1 + archive/v0.1.3/mithril-tests.js | 3 +- archive/v0.1.3/mithril.computation.html | 1 + archive/v0.1.3/mithril.deferred.html | 1 + archive/v0.1.3/mithril.html | 1 + archive/v0.1.3/mithril.min.zip | Bin 20819 -> 20819 bytes archive/v0.1.3/mithril.module.html | 1 + archive/v0.1.3/mithril.prop.html | 1 + archive/v0.1.3/mithril.redraw.html | 1 + archive/v0.1.3/mithril.render.html | 1 + archive/v0.1.3/mithril.request.html | 1 + archive/v0.1.3/mithril.route.html | 1 + archive/v0.1.3/mithril.sync.html | 1 + archive/v0.1.3/mithril.trust.html | 1 + archive/v0.1.3/mithril.withAttr.html | 1 + archive/v0.1.3/mithril.xhr.html | 1 + archive/v0.1.3/practices.html | 1 + archive/v0.1.3/refactoring.html | 1 + archive/v0.1.3/roadmap.html | 1 + archive/v0.1.3/routing.html | 1 + archive/v0.1.3/tools.html | 1 + archive/v0.1.3/web-services.html | 1 + tests/mock.js | 2 +- 33 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 archive/v0.1.3/community.html diff --git a/archive/v0.1.3/auto-redrawing.html b/archive/v0.1.3/auto-redrawing.html index e740767f..c6c2bafb 100644 --- a/archive/v0.1.3/auto-redrawing.html +++ b/archive/v0.1.3/auto-redrawing.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/change-log.html b/archive/v0.1.3/change-log.html index 564ee657..35baa6cd 100644 --- a/archive/v0.1.3/change-log.html +++ b/archive/v0.1.3/change-log.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/community.html b/archive/v0.1.3/community.html new file mode 100644 index 00000000..7f781274 --- /dev/null +++ b/archive/v0.1.3/community.html @@ -0,0 +1,68 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Community

+

Mailing List

+

Got questions about Mithril? Suggestions?

+

Feel free to post on the mailing list

+
+

Bug Tracker

+

You can file bugs in the issues page on Github

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.3/comparison.html b/archive/v0.1.3/comparison.html index 2059f3ea..a8097a5f 100644 --- a/archive/v0.1.3/comparison.html +++ b/archive/v0.1.3/comparison.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/compiling-templates.html b/archive/v0.1.3/compiling-templates.html index 6d130354..af77f3d8 100644 --- a/archive/v0.1.3/compiling-templates.html +++ b/archive/v0.1.3/compiling-templates.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/components.html b/archive/v0.1.3/components.html index 3a327011..ef505066 100644 --- a/archive/v0.1.3/components.html +++ b/archive/v0.1.3/components.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/getting-started.html b/archive/v0.1.3/getting-started.html index 2d43789a..5e9e8456 100644 --- a/archive/v0.1.3/getting-started.html +++ b/archive/v0.1.3/getting-started.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/how-to-read-signatures.html b/archive/v0.1.3/how-to-read-signatures.html index c839581c..e18e2881 100644 --- a/archive/v0.1.3/how-to-read-signatures.html +++ b/archive/v0.1.3/how-to-read-signatures.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/index.html b/archive/v0.1.3/index.html index 569b14e4..029e097b 100644 --- a/archive/v0.1.3/index.html +++ b/archive/v0.1.3/index.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/installation.html b/archive/v0.1.3/installation.html index c8b1fb12..1790116d 100644 --- a/archive/v0.1.3/installation.html +++ b/archive/v0.1.3/installation.html @@ -12,6 +12,7 @@ Guide API + Community Download Github @@ -83,7 +84,7 @@

And you can download Mithril by typing this:

component install lhorie/mithril@gh-pages

Then, to use Mithril, point a script tag to the downloaded file:

-
<script src="/components/lhorie/mithril/master/mithril.min.js"></script>
+
<script src="/components/lhorie/mithril/gh-pages/mithril.min.js"></script>
diff --git a/archive/v0.1.3/integration.html b/archive/v0.1.3/integration.html index c3210e04..868fe7cb 100644 --- a/archive/v0.1.3/integration.html +++ b/archive/v0.1.3/integration.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/mithril-tests.js b/archive/v0.1.3/mithril-tests.js index d6364ce4..c7f8dce1 100644 --- a/archive/v0.1.3/mithril-tests.js +++ b/archive/v0.1.3/mithril-tests.js @@ -471,8 +471,7 @@ mock.window = new function() { }, getAttribute: function(name, value) { return this[name] - }, - style: {} + } } } window.document.createTextNode = function(text) { diff --git a/archive/v0.1.3/mithril.computation.html b/archive/v0.1.3/mithril.computation.html index e6db3bc8..2be77e51 100644 --- a/archive/v0.1.3/mithril.computation.html +++ b/archive/v0.1.3/mithril.computation.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/mithril.deferred.html b/archive/v0.1.3/mithril.deferred.html index 761df87d..81c4563e 100644 --- a/archive/v0.1.3/mithril.deferred.html +++ b/archive/v0.1.3/mithril.deferred.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/mithril.html b/archive/v0.1.3/mithril.html index 76d7bc34..fa97cf46 100644 --- a/archive/v0.1.3/mithril.html +++ b/archive/v0.1.3/mithril.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/mithril.min.zip b/archive/v0.1.3/mithril.min.zip index 5d7370e0e36fa6fb8dfbc0aec3250ce87f7a58a5..f41f490c61fb87decdbacaf69b229affef628fab 100644 GIT binary patch delta 45 vcmcb-i1G3wMxFp~W)?065cs)hBhPa=rk{&8f0es!2&5-p3a|oEe1Wb2cJUF> delta 45 vcmcb-i1G3wMxFp~W)?065YVgH$n#u|Nv~$}SGn7UKzj0}04os17w8HAH-!!j diff --git a/archive/v0.1.3/mithril.module.html b/archive/v0.1.3/mithril.module.html index f2d1abe0..4c7a8f37 100644 --- a/archive/v0.1.3/mithril.module.html +++ b/archive/v0.1.3/mithril.module.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/mithril.prop.html b/archive/v0.1.3/mithril.prop.html index 19b2bcd8..cdecfbcc 100644 --- a/archive/v0.1.3/mithril.prop.html +++ b/archive/v0.1.3/mithril.prop.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/mithril.redraw.html b/archive/v0.1.3/mithril.redraw.html index 1b6b7fe5..098c669b 100644 --- a/archive/v0.1.3/mithril.redraw.html +++ b/archive/v0.1.3/mithril.redraw.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/mithril.render.html b/archive/v0.1.3/mithril.render.html index ed3e93f5..64cc9793 100644 --- a/archive/v0.1.3/mithril.render.html +++ b/archive/v0.1.3/mithril.render.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/mithril.request.html b/archive/v0.1.3/mithril.request.html index 2e279805..0be98822 100644 --- a/archive/v0.1.3/mithril.request.html +++ b/archive/v0.1.3/mithril.request.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/mithril.route.html b/archive/v0.1.3/mithril.route.html index 507c3825..97db24bd 100644 --- a/archive/v0.1.3/mithril.route.html +++ b/archive/v0.1.3/mithril.route.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/mithril.sync.html b/archive/v0.1.3/mithril.sync.html index e93a905a..e3c61f33 100644 --- a/archive/v0.1.3/mithril.sync.html +++ b/archive/v0.1.3/mithril.sync.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/mithril.trust.html b/archive/v0.1.3/mithril.trust.html index 9397a32e..3991ea49 100644 --- a/archive/v0.1.3/mithril.trust.html +++ b/archive/v0.1.3/mithril.trust.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/mithril.withAttr.html b/archive/v0.1.3/mithril.withAttr.html index ca3f3f7a..d3c8913d 100644 --- a/archive/v0.1.3/mithril.withAttr.html +++ b/archive/v0.1.3/mithril.withAttr.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/mithril.xhr.html b/archive/v0.1.3/mithril.xhr.html index 663d6a6a..856f4c3a 100644 --- a/archive/v0.1.3/mithril.xhr.html +++ b/archive/v0.1.3/mithril.xhr.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/practices.html b/archive/v0.1.3/practices.html index c2be6dd7..3eccb669 100644 --- a/archive/v0.1.3/practices.html +++ b/archive/v0.1.3/practices.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/refactoring.html b/archive/v0.1.3/refactoring.html index 71eb63f4..ffa5f4aa 100644 --- a/archive/v0.1.3/refactoring.html +++ b/archive/v0.1.3/refactoring.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/roadmap.html b/archive/v0.1.3/roadmap.html index 19a1ce2a..50b677cd 100644 --- a/archive/v0.1.3/roadmap.html +++ b/archive/v0.1.3/roadmap.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/routing.html b/archive/v0.1.3/routing.html index 92370cfd..40d1904e 100644 --- a/archive/v0.1.3/routing.html +++ b/archive/v0.1.3/routing.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/tools.html b/archive/v0.1.3/tools.html index 26faba39..f56681ee 100644 --- a/archive/v0.1.3/tools.html +++ b/archive/v0.1.3/tools.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/archive/v0.1.3/web-services.html b/archive/v0.1.3/web-services.html index e96860cc..aa1b0141 100644 --- a/archive/v0.1.3/web-services.html +++ b/archive/v0.1.3/web-services.html @@ -12,6 +12,7 @@ Guide API + Community Download Github diff --git a/tests/mock.js b/tests/mock.js index 765e95b7..dfbe99c1 100644 --- a/tests/mock.js +++ b/tests/mock.js @@ -15,7 +15,7 @@ mock.window = new function() { }, getAttribute: function(name, value) { return this[name] - }, + } } } window.document.createTextNode = function(text) { From 232b02a082f9739601e57e06c5545b2ef9ff273b Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Sun, 6 Apr 2014 22:09:20 -0400 Subject: [PATCH 24/38] fix autocompleter example in docs --- archive/v0.1.3/components.html | 6 +-- archive/v0.1.3/mithril.min.zip | Bin 20819 -> 20819 bytes docs/components.md | 78 ++++++++++++++++----------------- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/archive/v0.1.3/components.html b/archive/v0.1.3/components.html index ef505066..4d5dea0e 100644 --- a/archive/v0.1.3/components.html +++ b/archive/v0.1.3/components.html @@ -126,17 +126,17 @@ autocompleter.controller = function(data, getter) { this.change = function(value) { this.value(value); - var data = value === "" ? [] : data.filter(function(item) { + var list = value === "" ? [] : data.filter(function(item) { return this.getter(item).toLowerCase().indexOf(value.toLowerCase()) > -1; }, this); - this.data(data); + this.data(list); }; //this method is called when an option is selected. It triggers an `onchange` event this.select = function(value) { this.value(value); this.data([]); - if (this.onchange) this.onchange({target: {value: value}}); + if (this.onchange) this.onchange({currentTarget: {value: value}}); }; } diff --git a/archive/v0.1.3/mithril.min.zip b/archive/v0.1.3/mithril.min.zip index f41f490c61fb87decdbacaf69b229affef628fab..263ab5e3416af8bc83736cf081dc8224c927b7f2 100644 GIT binary patch delta 45 vcmcb-i1G3wMxFp~W)?065Rl!tk>|M_lkCRLU*&Ea0_n+@0<1t3U!W@hKtT?j delta 45 vcmcb-i1G3wMxFp~W)?065cs)hBhPa=rk{&8f0es!2&5-p3a|oEe1Wb2cJUF> diff --git a/docs/components.md b/docs/components.md index f6667867..6be76e8a 100644 --- a/docs/components.md +++ b/docs/components.md @@ -87,40 +87,40 @@ Applications often reuse rich UI controls that aren't provided out of the box by var autocompleter = {}; autocompleter.controller = function(data, getter) { - //binding for the text input - this.value = m.prop(""); - //store for the list of items - this.data = m.prop([]); - - //method to determine what property of a list item to compare the text input's value to - this.getter = getter; - - //this method changes the relevance list depending on what's currently in the text input - this.change = function(value) { - this.value(value); - - var data = value === "" ? [] : data.filter(function(item) { - return this.getter(item).toLowerCase().indexOf(value.toLowerCase()) > -1; - }, this); - this.data(data); - }; - - //this method is called when an option is selected. It triggers an `onchange` event - this.select = function(value) { - this.value(value); - this.data([]); - if (this.onchange) this.onchange({target: {value: value}}); - }; + //binding for the text input + this.value = m.prop(""); + //store for the list of items + this.data = m.prop([]); + + //method to determine what property of a list item to compare the text input's value to + this.getter = getter; + + //this method changes the relevance list depending on what's currently in the text input + this.change = function(value) { + this.value(value); + + var list = value === "" ? [] : data.filter(function(item) { + return this.getter(item).toLowerCase().indexOf(value.toLowerCase()) > -1; + }, this); + this.data(list); + }; + + //this method is called when an option is selected. It triggers an `onchange` event + this.select = function(value) { + this.value(value); + this.data([]); + if (this.onchange) this.onchange({currentTarget: {value: value}}); + }; } autocompleter.view = function(ctrl, options) { - if (options) ctrl.onchange = options.onchange; - return [ - m("input", {oninput: m.withAttr("value", ctrl.change.bind(ctrl)), value: ctrl.value()}), - ctrl.data().map(function(item) { - return m("div", {data: ctrl.getter(item), onclick: m.withAttr("data", ctrl.select.bind(ctrl))}, ctrl.getter(item)); - }) - ]; + if (options) ctrl.onchange = options.onchange; + return [ + m("input", {oninput: m.withAttr("value", ctrl.change.bind(ctrl)), value: ctrl.value()}), + ctrl.data().map(function(item) { + return m("div", {data: ctrl.getter(item), onclick: m.withAttr("data", ctrl.select.bind(ctrl))}, ctrl.getter(item)); + }) + ]; } @@ -129,17 +129,17 @@ autocompleter.view = function(ctrl, options) { var dashboard = {} dashboard.controller = function() { - this.names = m.prop([{id: 1, name: "John"}, {id: 2, name: "Bob"}, {id: 2, name: "Mary"}]); - this.autocompleter = new autocompleter.controller(this.names(), function(item) { - return item.name; - }); + this.names = m.prop([{id: 1, name: "John"}, {id: 2, name: "Bob"}, {id: 2, name: "Mary"}]); + this.autocompleter = new autocompleter.controller(this.names(), function(item) { + return item.name; + }); }; dashboard.view = function(ctrl) { - //assuming there's an element w/ id = "example" somewhere on the page - return m("#example", [ - new autocompleter.view(ctrl.autocompleter, {onchange: m.withAttr("value", console.log)}), - ]); + //assuming there's an element w/ id = "example" somewhere on the page + return m("#example", [ + new autocompleter.view(ctrl.autocompleter, {onchange: m.withAttr("value", console.log)}), + ]); }; From 05630b2b95e24fa595045774ec052811182760ba Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Mon, 7 Apr 2014 10:22:08 -0400 Subject: [PATCH 25/38] update bower --- archive/v0.1.3/bower.json | 20 ++++++++++++++++++++ archive/v0.1.3/mithril.min.zip | Bin 20819 -> 20819 bytes docs/layout/bower.json | 20 ++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 archive/v0.1.3/bower.json create mode 100644 docs/layout/bower.json diff --git a/archive/v0.1.3/bower.json b/archive/v0.1.3/bower.json new file mode 100644 index 00000000..d93b1edb --- /dev/null +++ b/archive/v0.1.3/bower.json @@ -0,0 +1,20 @@ +{ + "name": "mithril", + "version": "0.1.3", + "main": "mithril.min.js", + "description": "A Javascript Framework for building brilliant applications", + "authors": ["Leo Horie "], + "license": "MIT", + "ignore": [ + "*.html", + "*.css", + "*.zip", + "*.json", + "mithril-tests.js", + "archive", + "comparisons", + "lib", + "tools" + ], + "keywords": ["mithril", "mvc", "framework"] +} \ No newline at end of file diff --git a/archive/v0.1.3/mithril.min.zip b/archive/v0.1.3/mithril.min.zip index 263ab5e3416af8bc83736cf081dc8224c927b7f2..3067b3e6c856559054e2e1a97cf16261e6b74219 100644 GIT binary patch delta 49 wcmcb-i1G3wM&1B#W)?065ZD^jzLEEt95ayK{7vqbA&4>ga)2e6<_~lM0GsU*p#T5? delta 49 wcmcb-i1G3wM&1B#W)?065Rl#2wvqRl95ayK{7vqbA&4>ga)2e6<_~lM0Eo>Hxc~qF diff --git a/docs/layout/bower.json b/docs/layout/bower.json new file mode 100644 index 00000000..98d16489 --- /dev/null +++ b/docs/layout/bower.json @@ -0,0 +1,20 @@ +{ + "name": "mithril", + "version": "$version", + "main": "mithril.min.js", + "description": "A Javascript Framework for building brilliant applications", + "authors": ["Leo Horie "], + "license": "MIT", + "ignore": [ + "*.html", + "*.css", + "*.zip", + "*.json", + "mithril-tests.js", + "archive", + "comparisons", + "lib", + "tools" + ], + "keywords": ["mithril", "mvc", "framework"] +} \ No newline at end of file From 2054edae10bb2f8aca503a8f86472f20f5ca7bd1 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Mon, 7 Apr 2014 11:51:36 -0400 Subject: [PATCH 26/38] update component docs - it doesn't support non-master branches --- archive/v0.1.3/installation.html | 4 ++-- archive/v0.1.3/mithril.min.zip | Bin 20819 -> 20819 bytes docs/installation.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/archive/v0.1.3/installation.html b/archive/v0.1.3/installation.html index 1790116d..347cd311 100644 --- a/archive/v0.1.3/installation.html +++ b/archive/v0.1.3/installation.html @@ -82,9 +82,9 @@

Assuming you have NodeJS installed, you can install Bower by typing this in the command line:

npm install -g component

And you can download Mithril by typing this:

-
component install lhorie/mithril@gh-pages
+
component install lhorie/mithril

Then, to use Mithril, point a script tag to the downloaded file:

-
<script src="/components/lhorie/mithril/gh-pages/mithril.min.js"></script>
+
<script src="/components/lhorie/mithril/master/mithril.min.js"></script>
diff --git a/archive/v0.1.3/mithril.min.zip b/archive/v0.1.3/mithril.min.zip index 3067b3e6c856559054e2e1a97cf16261e6b74219..8d5c9e6dc5b312d5325cda5e142bcde022f0e9b9 100644 GIT binary patch delta 45 vcmcb-i1G3wMxFp~W)?065QvD|$n#u|DI#w3SGn7UKzj0}04os17w8HAJ1q`9 delta 45 vcmcb-i1G3wMxFp~W)?065ZD^Dk>|M_)7GHPU*&Ea0_n+@0<1t3U!W@hP~i`O diff --git a/docs/installation.md b/docs/installation.md index b3f033c4..c7b1ae28 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -93,11 +93,11 @@ npm install -g component And you can download Mithril by typing this: ``` -component install lhorie/mithril@gh-pages +component install lhorie/mithril ``` Then, to use Mithril, point a script tag to the downloaded file: ```markup - + ``` From 5eef004bbbb58928ec86b8aaabc3dd4c30418ac2 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 8 Apr 2014 09:51:16 -0400 Subject: [PATCH 27/38] unminified files for maps --- Gruntfile.js | 1 + archive/v0.1.3/component.json | 2 +- archive/v0.1.3/mithril-tests.js | 2 +- archive/v0.1.3/mithril.js | 438 ++++++++++++++++++++++++++++++++ archive/v0.1.3/mithril.min.zip | Bin 20819 -> 20819 bytes archive/v0.1.3/package.json | 2 +- docs/layout/component.json | 2 +- docs/layout/package.json | 2 +- tests/mithril-tests.js | 2 +- 9 files changed, 445 insertions(+), 6 deletions(-) create mode 100644 archive/v0.1.3/mithril.js diff --git a/Gruntfile.js b/Gruntfile.js index 5b4f03f2..610f0d22 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -86,6 +86,7 @@ module.exports = function(grunt) { lib: {expand: true, cwd: inputFolder + "/layout/lib/", src: "./**", dest: currentVersionArchiveFolder + "/lib/"}, tools: {expand: true, cwd: inputFolder + "/layout/tools/", src: "./**", dest: currentVersionArchiveFolder + "/tools/"}, comparisons: {expand: true, cwd: inputFolder + "/layout/comparisons/", src: "./**", dest: currentVersionArchiveFolder + "/comparisons/"}, + unminified: {src: "mithril.js", dest: currentVersionArchiveFolder + "/mithril.js"}, publish: {expand: true, cwd: currentVersionArchiveFolder, src: "./**", dest: outputFolder}, archive: {expand: true, cwd: currentVersionArchiveFolder, src: "./**", dest: outputFolder + "/archive/v" + version} }, diff --git a/archive/v0.1.3/component.json b/archive/v0.1.3/component.json index b3ef8e7d..2d1da3ad 100644 --- a/archive/v0.1.3/component.json +++ b/archive/v0.1.3/component.json @@ -4,7 +4,7 @@ "keywords": ["mvc", "framework"], "repository": "lhorie/mithril", "main": "mithril.min.js", - "scripts": ["mithril.min.js"], + "scripts": ["mithril.min.js", "mithril.min.map", "mithril.js"], "version": "0.1.3", "license": "MIT" } \ No newline at end of file diff --git a/archive/v0.1.3/mithril-tests.js b/archive/v0.1.3/mithril-tests.js index c7f8dce1..2a8c9ecd 100644 --- a/archive/v0.1.3/mithril-tests.js +++ b/archive/v0.1.3/mithril-tests.js @@ -673,7 +673,7 @@ function testMithril(mock) { m.render(root, m("ul", [{subtree: "retain"}])) return root.childNodes[0].childNodes[0].childNodes[0].nodeName === "A" }) - + //m.redraw test(function() { var controller diff --git a/archive/v0.1.3/mithril.js b/archive/v0.1.3/mithril.js new file mode 100644 index 00000000..70a87db3 --- /dev/null +++ b/archive/v0.1.3/mithril.js @@ -0,0 +1,438 @@ +new function(window) { + var selectorCache = {} + var type = {}.toString + var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.+?)\2)?\]/ + + Mithril = m = function() { + var args = arguments + var hasAttrs = type.call(args[1]) == "[object Object]" + var attrs = hasAttrs ? args[1] : {} + var classAttrName = "class" in attrs ? "class" : "className" + var cell = selectorCache[args[0]] + if (cell === undefined) { + selectorCache[args[0]] = cell = {tag: "div", attrs: {}} + var match, classes = [] + while (match = parser.exec(args[0])) { + if (match[1] == "") 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] || true + } + } + if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ") + } + cell = clone(cell) + cell.attrs = clone(cell.attrs) + cell.children = hasAttrs ? args[2] : args[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(parent, data, cached) { + if (data === null || data === undefined) { + if (cached) clear(cached.nodes) + return + } + if (data.subtree === "retain") return + + var cachedType = type.call(cached), dataType = type.call(data) + if (cachedType != dataType) { + if (cached !== null && cached !== undefined) clear(cached.nodes) + cached = new data.constructor + cached.nodes = [] + } + + if (dataType == "[object Array]") { + var nodes = [], intact = cached.length === data.length + for (var i = 0; i < data.length; i++) { + var item = build(parent, data[i], cached[i]) + if (item === undefined) continue + if (!item.nodes.intact) intact = false + cached[i] = item + } + if (!intact) { + for (var i = 0; i < data.length; i++) if (cached[i] !== undefined) nodes = nodes.concat(cached[i].nodes) + for (var i = nodes.length, node; node = cached.nodes[i]; i++) if (node.parentNode !== null) node.parentNode.removeChild(node) + for (var i = cached.nodes.length, node; node = nodes[i]; i++) if (node.parentNode === null) parent.appendChild(node) + cached.length = data.length + cached.nodes = nodes + } + } + else if (dataType == "[object Object]") { + if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join()) clear(cached.nodes) + if (typeof data.tag != "string") return + + var node, isNew = cached.nodes.length === 0 + if (isNew) { + node = window.document.createElement(data.tag) + cached = {tag: data.tag, attrs: setAttributes(node, data.attrs, {}), children: build(node, data.children, cached.children), nodes: [node]} + parent.appendChild(node) + } + else { + node = cached.nodes[0] + setAttributes(node, data.attrs, cached.attrs) + cached.children = build(node, data.children, cached.children) + cached.nodes.intact = true + if (node.parentNode !== parent) parent.appendChild(node) + } + if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew) + } + else { + var node + if (cached.nodes.length === 0) { + if (data.$trusted) { + var lastChild = parent.lastChild + parent.insertAdjacentHTML("beforeend", data) + node = lastChild ? lastChild.nextSibling : parent.firstChild + } + else { + node = window.document.createTextNode(data) + parent.appendChild(node) + } + cached = "string number boolean".indexOf(typeof data) > -1 ? new data.constructor(data) : data + cached.nodes = [node] + } + else if (cached.valueOf() !== data.valueOf()) { + if (data.$trusted) { + var current = cached.nodes[0], nodes = [current] + if (current) { + while (current = current.nextSibling) nodes.push(current) + clear(nodes) + var lastChild = parent.lastChild + parent.insertAdjacentHTML("beforeend", data) + node = lastChild ? lastChild.nextSibling : parent.firstChild + } + else parent.innerHTML = data + } + else { + node = cached.nodes[0] + parent.appendChild(node) + node.nodeValue = data + } + cached = new data.constructor(data) + cached.nodes = [node] + } + else cached.nodes.intact = true + } + + return cached + } + function setAttributes(node, dataAttrs, cachedAttrs) { + for (var attrName in dataAttrs) { + var dataAttr = dataAttrs[attrName] + var cachedAttr = cachedAttrs[attrName] + if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr) || node === window.document.activeElement) { + if (attrName === "config") continue + else if (typeof dataAttr == "function" && attrName.indexOf("on") == 0) { + if (String(dataAttr) !== String(cachedAttr)) node[attrName] = autoredraw(dataAttr, node) + } + else if (attrName === "style") { + for (var rule in dataAttr) { + if (cachedAttr === undefined || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule] + } + } + else if (attrName in node) node[attrName] = dataAttr + else node.setAttribute(attrName, dataAttr) + cachedAttrs[attrName] = dataAttr + } + } + return cachedAttrs + } + function clear(nodes) { + for (var i = 0; i < nodes.length; i++) nodes[i].parentNode.removeChild(nodes[i]) + nodes.length = 0 + } + function clone(object) { + var result = {} + for (var prop in object) result[prop] = object[prop] + return result + } + function autoredraw(callback, object) { + return function(e) { + m.startComputation() + var output = callback.call(object, e) + m.endComputation() + return output + } + } + + var html + var documentNode = { + insertAdjacentHTML: function(_, data) { + window.document.write(data) + window.document.close() + }, + appendChild: function(node) { + if (html === undefined) html = window.document.createElement("html") + if (node.nodeName == "HTML") html = node + else html.appendChild(node) + if (window.document.documentElement !== html) { + window.document.replaceChild(html, window.document.documentElement) + } + } + } + var nodeCache = [], cellCache = {} + m.render = function(root, cell) { + var index = nodeCache.indexOf(root) + var id = index < 0 ? nodeCache.push(root) - 1 : index + var node = root == window.document || root == window.document.documentElement ? documentNode : root + cellCache[id] = build(node, cell, cellCache[id]) + } + + m.trust = function(value) { + value = new String(value) + value.$trusted = true + return value + } + + var currentRoot, currentModule = {view: function() {}}, currentController = {}, now = 0, lastRedraw = 0, lastRedrawId = 0 + m.module = function(root, module) { + m.startComputation() + currentRoot = root + currentModule = module + currentController = new module.controller + m.endComputation() + } + m.redraw = function() { + m.render(currentRoot, currentModule.view(currentController)) + lastRedraw = now + } + function redraw() { + now = window.performance && window.performance.now ? window.performance.now() : new window.Date().getTime() + if (now - lastRedraw > 16) m.redraw() + else { + var cancel = window.cancelAnimationFrame || window.clearTimeout + var defer = window.requestAnimationFrame || window.setTimeout + cancel(lastRedrawId) + lastRedrawId = defer(m.redraw, 0) + } + } + + var pendingRequests = 0, computePostRedrawHook = null + m.startComputation = function() {pendingRequests++} + m.endComputation = function() { + pendingRequests = Math.max(pendingRequests - 1, 0) + if (pendingRequests == 0) { + redraw() + if (computePostRedrawHook) { + computePostRedrawHook() + computePostRedrawHook = null + } + } + } + + m.withAttr = function(prop, withAttrCallback) { + return function(e) {withAttrCallback(prop in e.currentTarget ? e.currentTarget[prop] : e.currentTarget.getAttribute(prop))} + } + + //routing + var modes = {pathname: "", hash: "#", search: "?"} + var redirect = function() {}, routeParams = {} + m.route = function() { + if (arguments.length == 3) { + var root = arguments[0], defaultRoute = arguments[1], router = arguments[2] + redirect = function(source) { + var path = source.slice(modes[m.route.mode].length) + if (!routeByValue(root, router, path)) { + m.route(defaultRoute, true) + } + } + var listener = m.route.mode == "hash" ? "onhashchange" : "onpopstate" + window[listener] = function() { + redirect(window.location[m.route.mode]) + } + computePostRedrawHook = scrollToHash + window[listener]() + } + else if (arguments[0].addEventListener) { + var element = arguments[0] + var isInitialized = arguments[1] + if (!isInitialized) { + element.removeEventListener("click", routeUnobtrusive) + element.addEventListener("click", routeUnobtrusive) + } + } + else if (typeof arguments[0] == "string") { + var route = arguments[0] + var shouldReplaceHistoryEntry = arguments[1] === true + if (window.history.pushState) { + computePostRedrawHook = function() { + window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, window.document.title, modes[m.route.mode] + route) + scrollToHash() + } + redirect(modes[m.route.mode] + route) + } + else window.location[m.route.mode] = route + } + } + m.route.param = function(key) {return routeParams[key]} + m.route.mode = "search" + function routeByValue(root, router, path) { + routeParams = {} + for (var route in router) { + if (route == path) return !void m.module(root, router[route]) + + var matcher = new RegExp("^" + route.replace(/:[^\/]+/g, "([^\\/]+)") + "$") + if (matcher.test(path)) { + return !void path.replace(matcher, function() { + var keys = route.match(/:[^\/]+/g) + var values = [].slice.call(arguments, 1, -2) + for (var i = 0; i < keys.length; i++) routeParams[keys[i].slice(1)] = values[i] + m.module(root, router[route]) + }) + } + } + } + function routeUnobtrusive(e) { + e.preventDefault() + m.route(e.currentTarget.getAttribute("href")) + } + function scrollToHash() { + if (m.route.mode != "hash" && window.location.hash) window.location.hash = window.location.hash + } + + //model + m.prop = function(store) { + return function() { + if (arguments.length) store = arguments[0] + return store + } + } + + m.deferred = function() { + var resolvers = [], rejecters = [] + var object = { + resolve: function(value) { + for (var i = 0; i < resolvers.length; i++) resolvers[i](value) + }, + reject: function(value) { + for (var i = 0; i < rejecters.length; i++) rejecters[i](value) + }, + promise: m.prop() + } + object.promise.resolvers = resolvers + object.promise.then = function(success, error) { + var next = m.deferred() + if (!success) success = identity + if (!error) error = identity + function push(list, method, callback) { + list.push(function(value) { + try { + var result = callback(value) + if (result && typeof result.then == "function") result.then(next[method], error) + else next[method](result !== undefined ? result : value) + } + catch (e) { + if (e instanceof Error && e.constructor !== Error) throw e + else next.reject(e) + } + }) + } + push(resolvers, "resolve", success) + push(rejecters, "reject", error) + return next.promise + } + return object + } + m.sync = function(args) { + var method = "resolve" + function synchronizer(resolved) { + return function(value) { + results.push(value) + if (!resolved) method = "reject" + if (results.length == args.length) { + deferred.promise(results) + deferred[method](results) + } + return value + } + } + + var deferred = m.deferred() + var results = [] + for (var i = 0; i < args.length; i++) { + args[i].then(synchronizer(true), synchronizer(false)) + } + return deferred.promise + } + function identity(value) {return value} + + function ajax(options) { + var xhr = window.XDomainRequest ? new window.XDomainRequest : new window.XMLHttpRequest + xhr.open(options.method, options.url, true, options.user, options.password) + xhr.onload = typeof options.onload == "function" ? options.onload : function() {} + xhr.onerror = typeof options.onerror == "function" ? options.onerror : function() {} + if (typeof options.config == "function") options.config(xhr, options) + xhr.send(options.data) + return xhr + } + function querystring(object, prefix) { + var str = [] + for(var prop in object) { + var key = prefix ? prefix + "[" + prop + "]" : prop, value = object[prop] + str.push(typeof value == "object" ? querystring(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value)) + } + return str.join("&") + } + function bindData(xhrOptions, data, serialize) { + if (data && Object.keys(data).length > 0) { + if (xhrOptions.method == "GET") { + xhrOptions.url = xhrOptions.url + (xhrOptions.url.indexOf("?") < 0 ? "?" : "&") + querystring(data) + } + else xhrOptions.data = serialize(data) + } + return xhrOptions + } + function parameterizeUrl(url, data) { + var tokens = url.match(/:\w+/g) + if (tokens && data) { + for (var i = 0; i < tokens.length; i++) { + var key = tokens[i].slice(1) + url = url.replace(tokens[i], data[key]) + delete data[key] + } + } + return url + } + + m.request = function(xhrOptions) { + m.startComputation() + var deferred = m.deferred() + var serialize = xhrOptions.serialize || JSON.stringify + var deserialize = xhrOptions.deserialize || JSON.parse + xhrOptions.url = parameterizeUrl(xhrOptions.url, xhrOptions.data) + xhrOptions = bindData(xhrOptions, xhrOptions.data, serialize) + xhrOptions.onload = xhrOptions.onerror = function(e) { + var unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity + var response = unwrap(deserialize(e.target.responseText)) + if (response instanceof 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) + deferred.promise(response) + deferred[e.type == "load" ? "resolve" : "reject"](response) + m.endComputation() + } + ajax(xhrOptions) + deferred.promise.then = propBinder(deferred.promise) + return deferred.promise + } + function propBinder(promise) { + var bind = promise.then + return function(success, error) { + var next = bind(function(value) {return next(success(value))}, function(value) {return next(error(value))}) + next.then = propBinder(next) + return next + } + } + + if (typeof module != "undefined" && module !== null) module.exports = m + if (typeof define == "function" && define.amd) define(function() {return m}) + + //testing API + m.deps = function(mock) {return window = mock} +}(this) \ No newline at end of file diff --git a/archive/v0.1.3/mithril.min.zip b/archive/v0.1.3/mithril.min.zip index 8d5c9e6dc5b312d5325cda5e142bcde022f0e9b9..b254092bd96fd756d10d85780af35b7176ce2cda 100644 GIT binary patch delta 49 wcmcb-i1G3wM&1B#W)?065SZ-Sv61(g95ayK{7vqbA&4>ga)2e6<_~lM0FY%72><{9 delta 49 wcmcb-i1G3wM&1B#W)?065QvCt-^lw+ju}XA{w8t<8 diff --git a/archive/v0.1.3/package.json b/archive/v0.1.3/package.json index a7a06d25..d3a10bae 100644 --- a/archive/v0.1.3/package.json +++ b/archive/v0.1.3/package.json @@ -7,5 +7,5 @@ "repository": {"type": "git", "url": "https://github.com/lhorie/mithril"}, "main": "mithril.min.js", "licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}], - "files": ["mithril.min.js", "mithril.min.map"] + "files": ["mithril.min.js", "mithril.min.map", "mithril.js"] } \ No newline at end of file diff --git a/docs/layout/component.json b/docs/layout/component.json index 489ab5ce..fedc938d 100644 --- a/docs/layout/component.json +++ b/docs/layout/component.json @@ -4,7 +4,7 @@ "keywords": ["mvc", "framework"], "repository": "lhorie/mithril", "main": "mithril.min.js", - "scripts": ["mithril.min.js"], + "scripts": ["mithril.min.js", "mithril.min.map", "mithril.js"], "version": "$version", "license": "MIT" } \ No newline at end of file diff --git a/docs/layout/package.json b/docs/layout/package.json index f0a6d666..ff43e8a8 100644 --- a/docs/layout/package.json +++ b/docs/layout/package.json @@ -7,5 +7,5 @@ "repository": {"type": "git", "url": "https://github.com/lhorie/mithril"}, "main": "mithril.min.js", "licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}], - "files": ["mithril.min.js", "mithril.min.map"] + "files": ["mithril.min.js", "mithril.min.map", "mithril.js"] } \ No newline at end of file diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index eea18afb..3ef3c946 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -141,7 +141,7 @@ function testMithril(mock) { m.render(root, m("ul", [{subtree: "retain"}])) return root.childNodes[0].childNodes[0].childNodes[0].nodeName === "A" }) - + //m.redraw test(function() { var controller From 86280e8261acbfcdbdab171cc4fdd7b7a63a677e Mon Sep 17 00:00:00 2001 From: Levi Tan Ong Date: Tue, 8 Apr 2014 23:49:42 +0800 Subject: [PATCH 28/38] Changes regex for parametrizeUrl to catch tokens that start with letters only, to allow for urls with ports --- mithril.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mithril.js b/mithril.js index 70a87db3..79b2179d 100644 --- a/mithril.js +++ b/mithril.js @@ -388,7 +388,7 @@ new function(window) { return xhrOptions } function parameterizeUrl(url, data) { - var tokens = url.match(/:\w+/g) + var tokens = url.match(/:[a-zA-Z]+/g) if (tokens && data) { for (var i = 0; i < tokens.length; i++) { var key = tokens[i].slice(1) From b2daa1b55a5679caaa7f8b1a23cc069c93f33819 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 8 Apr 2014 15:03:20 -0400 Subject: [PATCH 29/38] fix port in POST request urls --- Gruntfile.js | 2 +- archive/v0.1.3/mithril.min.zip | Bin 20819 -> 20819 bytes archive/v0.1.4/auto-redrawing.html | 139 +++ archive/v0.1.4/bower.json | 20 + archive/v0.1.4/change-log.html | 133 +++ archive/v0.1.4/community.html | 68 ++ archive/v0.1.4/comparison.html | 129 +++ .../v0.1.4/comparisons/angular.parsing.html | 2 + .../v0.1.4/comparisons/angular.rendering.html | 14 + .../v0.1.4/comparisons/angular.safety.html | 13 + .../v0.1.4/comparisons/backbone.parsing.html | 4 + .../comparisons/backbone.rendering.html | 30 + .../v0.1.4/comparisons/backbone.safety.html | 29 + .../v0.1.4/comparisons/jquery.parsing.html | 2 + .../v0.1.4/comparisons/jquery.rendering.html | 17 + archive/v0.1.4/comparisons/jquery.safety.html | 16 + .../v0.1.4/comparisons/mithril.parsing.html | 2 + .../v0.1.4/comparisons/mithril.rendering.html | 20 + .../v0.1.4/comparisons/mithril.safety.html | 19 + archive/v0.1.4/compiling-templates.html | 111 +++ archive/v0.1.4/component.json | 10 + archive/v0.1.4/components.html | 192 ++++ archive/v0.1.4/getting-started.html | 447 +++++++++ archive/v0.1.4/how-to-read-signatures.html | 154 +++ archive/v0.1.4/index.html | 197 ++++ archive/v0.1.4/installation.html | 102 ++ archive/v0.1.4/integration.html | 148 +++ archive/v0.1.4/lib/prism/prism.css | 126 +++ archive/v0.1.4/lib/prism/prism.js | 9 + archive/v0.1.4/mithril-tests.js | 895 ++++++++++++++++++ archive/v0.1.4/mithril.computation.html | 160 ++++ archive/v0.1.4/mithril.deferred.html | 163 ++++ archive/v0.1.4/mithril.html | 284 ++++++ archive/v0.1.4/mithril.js | 438 +++++++++ archive/v0.1.4/mithril.min.js | 8 + archive/v0.1.4/mithril.min.map | 1 + archive/v0.1.4/mithril.min.zip | Bin 0 -> 20825 bytes archive/v0.1.4/mithril.module.html | 176 ++++ archive/v0.1.4/mithril.prop.html | 141 +++ archive/v0.1.4/mithril.redraw.html | 90 ++ archive/v0.1.4/mithril.render.html | 158 ++++ archive/v0.1.4/mithril.request.html | 347 +++++++ archive/v0.1.4/mithril.route.html | 222 +++++ archive/v0.1.4/mithril.sync.html | 112 +++ archive/v0.1.4/mithril.trust.html | 121 +++ archive/v0.1.4/mithril.withAttr.html | 126 +++ archive/v0.1.4/mithril.xhr.html | 77 ++ archive/v0.1.4/package.json | 11 + archive/v0.1.4/pages.json | 4 + archive/v0.1.4/practices.html | 119 +++ archive/v0.1.4/refactoring.html | 64 ++ archive/v0.1.4/roadmap.html | 104 ++ archive/v0.1.4/routing.html | 128 +++ archive/v0.1.4/style.css | 91 ++ archive/v0.1.4/tools.html | 95 ++ archive/v0.1.4/tools/template-compiler.sjs | 64 ++ archive/v0.1.4/tools/template-converter.html | 9 + archive/v0.1.4/tools/template-converter.js | 89 ++ archive/v0.1.4/web-services.html | 213 +++++ docs/change-log.md | 7 + mithril.js | 2 +- tests/mithril-tests.js | 36 + 62 files changed, 6708 insertions(+), 2 deletions(-) create mode 100644 archive/v0.1.4/auto-redrawing.html create mode 100644 archive/v0.1.4/bower.json create mode 100644 archive/v0.1.4/change-log.html create mode 100644 archive/v0.1.4/community.html create mode 100644 archive/v0.1.4/comparison.html create mode 100644 archive/v0.1.4/comparisons/angular.parsing.html create mode 100644 archive/v0.1.4/comparisons/angular.rendering.html create mode 100644 archive/v0.1.4/comparisons/angular.safety.html create mode 100644 archive/v0.1.4/comparisons/backbone.parsing.html create mode 100644 archive/v0.1.4/comparisons/backbone.rendering.html create mode 100644 archive/v0.1.4/comparisons/backbone.safety.html create mode 100644 archive/v0.1.4/comparisons/jquery.parsing.html create mode 100644 archive/v0.1.4/comparisons/jquery.rendering.html create mode 100644 archive/v0.1.4/comparisons/jquery.safety.html create mode 100644 archive/v0.1.4/comparisons/mithril.parsing.html create mode 100644 archive/v0.1.4/comparisons/mithril.rendering.html create mode 100644 archive/v0.1.4/comparisons/mithril.safety.html create mode 100644 archive/v0.1.4/compiling-templates.html create mode 100644 archive/v0.1.4/component.json create mode 100644 archive/v0.1.4/components.html create mode 100644 archive/v0.1.4/getting-started.html create mode 100644 archive/v0.1.4/how-to-read-signatures.html create mode 100644 archive/v0.1.4/index.html create mode 100644 archive/v0.1.4/installation.html create mode 100644 archive/v0.1.4/integration.html create mode 100644 archive/v0.1.4/lib/prism/prism.css create mode 100644 archive/v0.1.4/lib/prism/prism.js create mode 100644 archive/v0.1.4/mithril-tests.js create mode 100644 archive/v0.1.4/mithril.computation.html create mode 100644 archive/v0.1.4/mithril.deferred.html create mode 100644 archive/v0.1.4/mithril.html create mode 100644 archive/v0.1.4/mithril.js create mode 100644 archive/v0.1.4/mithril.min.js create mode 100644 archive/v0.1.4/mithril.min.map create mode 100644 archive/v0.1.4/mithril.min.zip create mode 100644 archive/v0.1.4/mithril.module.html create mode 100644 archive/v0.1.4/mithril.prop.html create mode 100644 archive/v0.1.4/mithril.redraw.html create mode 100644 archive/v0.1.4/mithril.render.html create mode 100644 archive/v0.1.4/mithril.request.html create mode 100644 archive/v0.1.4/mithril.route.html create mode 100644 archive/v0.1.4/mithril.sync.html create mode 100644 archive/v0.1.4/mithril.trust.html create mode 100644 archive/v0.1.4/mithril.withAttr.html create mode 100644 archive/v0.1.4/mithril.xhr.html create mode 100644 archive/v0.1.4/package.json create mode 100644 archive/v0.1.4/pages.json create mode 100644 archive/v0.1.4/practices.html create mode 100644 archive/v0.1.4/refactoring.html create mode 100644 archive/v0.1.4/roadmap.html create mode 100644 archive/v0.1.4/routing.html create mode 100644 archive/v0.1.4/style.css create mode 100644 archive/v0.1.4/tools.html create mode 100644 archive/v0.1.4/tools/template-compiler.sjs create mode 100644 archive/v0.1.4/tools/template-converter.html create mode 100644 archive/v0.1.4/tools/template-converter.js create mode 100644 archive/v0.1.4/web-services.html diff --git a/Gruntfile.js b/Gruntfile.js index 610f0d22..6922e4c3 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,6 +1,6 @@ module.exports = function(grunt) { - var version = "0.1.3" + var version = "0.1.4" var inputFolder = "./docs" var tempFolder = "./temp" diff --git a/archive/v0.1.3/mithril.min.zip b/archive/v0.1.3/mithril.min.zip index b254092bd96fd756d10d85780af35b7176ce2cda..b57154a162c4b14395e631e873ae45b92bb97adf 100644 GIT binary patch delta 45 vcmcb-i1G3wMxFp~W)?065UB9m$n#u|slspbSGn7UKzj0}04os17w8HAKdlaZ delta 45 vcmcb-i1G3wMxFp~W)?065SZ+{k>|M_(`4VxU*&Ea0_n+@0<1t3U!W@hMwt%f diff --git a/archive/v0.1.4/auto-redrawing.html b/archive/v0.1.4/auto-redrawing.html new file mode 100644 index 00000000..c6c2bafb --- /dev/null +++ b/archive/v0.1.4/auto-redrawing.html @@ -0,0 +1,139 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Integrating with The Auto-Redrawing System

+

If you need to do custom asynchronous calls without using Mithril's API, and find that your views are not redrawing, or that you're being forced to call m.redraw manually, you should consider using m.startComputation / m.endComputation so that Mithril can intelligently auto-redraw once your custom code finishes running.

+

In order to integrate asynchronous code to Mithril's autoredrawing system, you should call m.startComputation BEFORE making an asynchronous call, and m.endComputation after the asynchronous callback completes.

+
//this service waits 1 second, logs "hello" and then notifies the view that
+//it may start redrawing (if no other asynchronous operations are pending)
+var doStuff = function() {
+    m.startComputation(); //call `startComputation` before the asynchronous `setTimeout`
+
+    setTimeout(function() {
+        console.log("hello");
+
+        m.endComputation(); //call `endComputation` at the end of the callback
+    }, 1000);
+};
+

To integrate synchronous code, call m.startComputation at the beginning of the method, and m.endComputation at the end.

+
window.onfocus = function() {
+    m.startComputation(); //call before everything else in the event handler
+
+    doStuff();
+
+    m.endComputation(); //call after everything else in the event handler
+}
+

For each m.startComputation call a library makes, it MUST also make one and ONLY one corresponding m.endComputation call.

+

You should not use these methods if your code is intended to run repeatedly (e.g. by using setInterval). If you want to repeatedly redraw the view without necessarily waiting for user input, you should manually call m.redraw within the repeatable context.

+
+

Integrating multiple execution threads

+

When integrating with third party libraries, you might find that you need to call asynchronous methods from outside of Mithril's API.

+

In order to integrate non-trivial asynchronous code to Mithril's auto-redrawing system, you need to ensure all execution threads call m.startComputation / m.endComputation.

+

An execution thread is basically any amount of code that runs before other asynchronous threads start to run.

+

Integrating multiple execution threads can be done in a two different ways: in a layered fashion or in comprehensive fashion

+

Layered integration

+

Layered integration is recommended for modular code where many different APIs may be put together at the application level.

+

Below is an example where various methods implemented with a third party library can be integrated in layered fashion: any of the methods can be used in isolation or in combination.

+

Notice how doBoth repeatedly calls m.startComputation since that method calls both doSomething and doAnother. This is perfectly valid: there are three asynchronous computations pending after the jQuery.when method is called, and therefore, three pairs of m.startComputation / m.endComputation in play.

+
var doSomething = function(callback) {
+    m.startComputation(); //call `startComputation` before the asynchronous AJAX request
+
+    return jQuery.ajax("/something").done(function() {
+        if (callback) callback();
+
+        m.endComputation(); //call `endComputation` at the end of the callback
+    });
+};
+var doAnother = function(callback) {
+    m.startComputation(); //call `startComputation` before the asynchronous AJAX request
+
+    return jQuery.ajax("/another").done(function() {
+        if (callback) callback();
+        m.endComputation(); //call `endComputation` at the end of the callback
+    });
+};
+var doBoth = function(callback) {
+    m.startComputation(); //call `startComputation` before the asynchronous synchronization method
+
+    jQuery.when(doSomething(), doAnother()).then(function() {
+        if (callback) callback();
+
+        m.endComputation(); //call `endComputation` at the end of the callback
+    })
+};
+

Comprehensive integration

+

Comprehensive integration is recommended if integrating a monolithic series of asynchronous operations. In contrast to layered integration, it minimizes the number of m.startComputation / m.endComputation to avoid clutter.

+

The example below shows a convoluted series of AJAX requests implemented with a third party library.

+
var doSomething = function(callback) {
+    m.startComputation(); //call `startComputation` before everything else
+
+    jQuery.ajax("/something").done(function() {
+        doStuff();
+        jQuery.ajax("/another").done(function() {
+            doMoreStuff();
+            jQuery.ajax("/more").done(function() {
+                if (callback) callback();
+
+                m.endComputation(); //call `endComputation` at the end of everything
+            });
+        });
+    });
+};
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/bower.json b/archive/v0.1.4/bower.json new file mode 100644 index 00000000..24535774 --- /dev/null +++ b/archive/v0.1.4/bower.json @@ -0,0 +1,20 @@ +{ + "name": "mithril", + "version": "0.1.4", + "main": "mithril.min.js", + "description": "A Javascript Framework for building brilliant applications", + "authors": ["Leo Horie "], + "license": "MIT", + "ignore": [ + "*.html", + "*.css", + "*.zip", + "*.json", + "mithril-tests.js", + "archive", + "comparisons", + "lib", + "tools" + ], + "keywords": ["mithril", "mvc", "framework"] +} \ No newline at end of file diff --git a/archive/v0.1.4/change-log.html b/archive/v0.1.4/change-log.html new file mode 100644 index 00000000..916149b8 --- /dev/null +++ b/archive/v0.1.4/change-log.html @@ -0,0 +1,133 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Change Log

+

v0.1.4 - maintenance

+

Bug Fixes:

+
    +
  • URLs with port numbers are now handled correctly #40
  • +
  • NPM package now contains unminified version for map files #39
  • +
+

v0.1.3 - maintenance

+

News:

+
    +
  • Mithril is now available via Component
  • +
  • There's now an extra low-level optimization hook called a SubtreeDirective, which allows implementing plugins that only create virtual trees if necessary.
  • +
+

Bug Fixes:

+
    +
  • diff no longer touch the DOM when processing style attributes and event handlers
  • +
  • returning a thennable to a resolution callback in m.deferred().promise now causes the promise to adopt its state
  • +
  • diff now correctly clears subtree if null or undefined is passed as a node
  • +
+
+

v0.1.2 - maintenance

+

News:

+ +

Bug Fixes:

+
    +
  • m.render now correctly reattaches reused DOM elements to replaced parent nodes #31
  • +
  • UI actions that can potentially de-synchronize the DOM from cache now force synchronization #29
  • +
+
+

v0.1.1 - maintenance

+

News:

+ +

Bug Fixes:

+
    +
  • m.route.param now resets on route change correctly #15
  • +
  • m.render now correctly ignores undefined values in the virtual tree#16
  • +
  • errors thrown in promises now cause downstreams to be rejected #1
  • +
+

Breaking changes:

+
    +
  • changed default value for xhr.withCredentials from true to false for m.request, since public APIs are more common than auth-walled ones. #14

    +

    In order to configure this flag, the following configuration should be used:

    +
    var privateAPI = function(xhr) {xhr.withCredentials = true};
    +
    +m.request({method: "GET", url: "http://foo.com/api", config: privateAPI});
    +
  • +
+
+

v0.1 - Initial release

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/community.html b/archive/v0.1.4/community.html new file mode 100644 index 00000000..7f781274 --- /dev/null +++ b/archive/v0.1.4/community.html @@ -0,0 +1,68 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Community

+

Mailing List

+

Got questions about Mithril? Suggestions?

+

Feel free to post on the mailing list

+
+

Bug Tracker

+

You can file bugs in the issues page on Github

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/comparison.html b/archive/v0.1.4/comparison.html new file mode 100644 index 00000000..a8097a5f --- /dev/null +++ b/archive/v0.1.4/comparison.html @@ -0,0 +1,129 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

How is Mithril Different from Other Frameworks

+

There are a lot of different Javascript MVC frameworks and evaluating their merits and shortcomings can be a daunting task.

+

This page aims to provide a comparison between Mithril and some of the most widely used frameworks, as well as some of the younger, but relevant ones.

+

Code Size

+

One of the most obvious differences between Mithril and most frameworks is in file size: Mithril is less than 3kb gzipped and has no dependencies on other libraries.

+

Note that while a small gzipped size can look appealing, that number is often used to "hide the weight" of the uncompressed code: remember that the decompressed Javascript still needs to be parsed and evaluated on every page load, and this cost (which can be in the dozens of milliseconds range for some frameworks in some browsers) cannot be cached.

+

This cost might be less of a concern in single page apps, but not necessarily if the app is typically opened simultaneously in multiple tabs, or in less powerful devices.

+

The performance tests in the homepage show execution times for parsing and evaluation of Mithril's code, compared to some popular frameworks. As you can see, it paints a much less flattering picture for some frameworks than when we look at gzipped size alone.

+

Documentation

+

Another point of comparison is documentation. Most of the popular frameworks have at least a bare minimum amount of documentation nowadays, but many leave a bit to be desired: some lack usage examples, and some frameworks' communities need to rely heavily on third party sites for explanations of more advanced topics, and sometimes even for learning the basics.

+

This is a problem particularly for frameworks that had breaking changes in the past: It's common to find answers in StackOverflow that are out-of-date and no longer work with the latest version of said frameworks.

+

Mithril has more documentation in this site than the amount of code in the framework, and none of the documentation is auto-generated.

+

All API points are explained in prose, and have code examples. Because the entire documentation is hand-crafted, you get the benefit of actually having explanations for things that documentation generator tools don't support well (for example, interfaces and callback parameter documentation).

+

In addition, this guide section covers topics related to how to fit all the pieces together.

+

From the get-go, Mithril's build system produces archived versions of the code and documentation so that you'll never be stuck without docs for out-of-date versions.

+

Given how young Mithril is, hopefully you can appreciate the level of commitment for providing good documentation.

+

Architecture

+

In terms of architecture, one of Mithril's main differences is that it does not provide base classes to extend from.

+

It's often said that frameworks, in contrast to libraries, dictate how code should be written. In this sense, one could argue that Mithril isn't really a framework.

+

Instead of locking developers down to very specific implementations of design patterns, Mithril's approach is to provide an idiomatic pattern to follow, and tools to aid the developer when required. This approach means that developers can get discoverable codebases without necessarily getting locked into the framework.

+

One related difference is that other frameworks often have hard-coded base classes where every conceivable convenience method gets inherited by the developer's classes (remember, in Javascript, this can mean copying all of the utility methods over to the child class, regardless of whether they're going to be used or not).

+

Mithril's on-demand tooling approach means there are no hidden performance costs when implementing core MVC patterns, and there's also no extra learning curve for framework-specific syntax for those patterns.

+

View Layer Paradigm

+

Some of the older frameworks among the popular ones (out-of-the-box jQuery and Backbone, specifically) take a more procedural paradigm when it comes to the view layer; this means every action requires the developer to write custom view-level code to handle it.

+

This can get noticeably bulky when you look at thing like collections: you often need to implement insertion code and deletion code, in addition to a "draw everything" routine for performance. And this is for every list that needs to be displayed in some way.

+

Mithril's view layer paradigm is designed be declarative, much like HTML, such that the same code implicitly does everything it needs to. As it turns out, this design decision is actually a compromise: it offers the benefit of decreased application code complexity at the cost of some performance loss. However, as the performance tests in the homepage show, this does not necessarily hurt Mithril in a meaningful way.

+
+

Specific Framework Comparisons

+

Warning: this section is likely biased. Take it with a grain of salt.

+

jQuery

+

jQuery is ubiquitous and has a large ecosystem, but it's not an MVC framework.

+

There's no idiomatic way to organize jQuery code in an MVC pattern and many frameworks were created specifically to overcome that shortcoming.

+

As summarized above, Mithril differs from jQuery by allowing DOM-related code to be written largely in a declarative style (thereby decreasing code complexity), in addition to providing an idiomatic way to structure applications.

+

One other difference that is extremely clear is the treatment of data. In jQuery it's common to use the DOM as a data storage mechanism, whereas Mithril encourages data to exist in an isolated model layer.

+

Backbone

+

Backbone was originally designed as a way to structure jQuery-based applications. One of its selling points is that it allows developers to leverage their existing jQuery knowledge, while providing some "walls" to organize the code in a more structured manner.

+

As with jQuery, Mithril differs from Backbone by enforcing view code to be written in a declarative style.

+

Another marking difference is that Backbone is workflow agnostic, that is, there's no one idiomatic way to organize applications. This is good for framework adoption, but not necessarily ideal for team scalability and codebase discoverability.

+

In contrast, Mithril encourages that applications be developed using the patterns found throughout this guide. This discourages "bastardized" MVC pattern variations and architecturing style fragmentation.

+

One technical aspect that is also different is that Backbone is heavily event-oriented. Mithril, on the other hand, purposely avoids the observer pattern in an attempt to abolish "come-from hell", i.e. a class of debugging problems where you don't know what triggers some code because of a long chain of events triggering other events.

+

A particularly nasty instance of this problem that sometimes occurs in "real-time" applications is when event triggering chains become circular due to a conditional statement bug, causing infinite loops and browser crashes.

+

Another significant difference between Backbone and Mithril is in their approach to familiarity: Backbone appeals to people familiar w/ jQuery; Mithril is designed to be familiar to people with server-side MVC framework experience.

+

Angular

+

Angular is an MVC framework maintained by Google, and it provides a declarative view layer and an emphasis on testability. It leverages developer experience with server-side MVC frameworks, and in many ways, is very similar in scope to Mithril.

+

The main difference between Angular templates and Mithril templates is that Angular templates follow the tradition of being defined in HTML. This has the benefit of cleaner syntax for writing static text, but it comes with the disadvantage of features getting awkwardly tied to HTML syntax, as well as providing poor debugging support.

+

One thing you may have noticed in the homepage is that, out of the box, Angular is not as performant as other frameworks. Steep performance degradation is a notoriously common issue in non-trivial Angular applications and there are several third party libraries which attempt to get around performance problems. Speaking from experience, it's generally difficult to reason about performance in Angular.

+

Mithril takes some learnings from that and implements a templating redrawing system that renders less aggressively, is less complex and is easier to profile.

+

A noteworthy difference between Angular and Mithril is in framework complexity: Angular implements several subsystems that would seem more logical in programming language implementations (e.g. a parser, a dynamic scoping mechanism, decorators, etc). Mithril, on the other hand, tries to provide only features to support a more classic MVC paradigm.

+

Ember

+

Ember is a highly comprehensive MVC framework, providing a large API that covers not only traditional MVC patterns, but also a vast range of helper utilities as well.

+

The biggest difference between Ember and Mithril is summarized in the Architecture section above: Ember's comprehensiveness come at a cost of a steep learning curve, and a high degree of vendor lock-in.

+

Ember is also more opinionated in terms of how application architecture should look, and as a result, tends to be less transparent in terms of what is actually happening under the hood.

+

React

+

React is a templating engine developed by Facebook. It's relevant for comparison because it uses the same architecture as Mithril's templating engine: i.e. it acknowledges that DOM operations are the bottleneck of templating systems, and implements a virtual DOM tree which keeps track of changes and only applies diffs to the real DOM where needed.

+

The most visible difference between React and Mithril is that React's JSX syntax does not run natively in the browser, whereas Mithril's uncompiled templates do. Both can be compiled, but React's compiled code still has function calls for each virtual DOM element; Mithril templates compile into static javascript data structures.

+

Another difference is that Mithril, being an MVC framework, rather than a templating engine, provides an auto-redrawing system that is aware of network asynchrony and that can render views efficiently without cluttering application code with redraw calls, and without letting the developer unintentionally bleed out of the MVC pattern.

+

Note also that, despite having a bigger scope, Mithril has a smaller file size than React.

+

Knockout

+

Knockout is a library focused on data binding. It is not an MVC framework in the traditional sense, but idiomatic Knockout code uses the similar concept of view models.

+

A Knockout view model is an amalgamation of model and controller layers in a single class. In contrast, Mithril separates the two layers more distinctly.

+

Generally speaking, Knockout applications tend to be more tightly coupled than Mithril since Knockout doesn't provide an equivalent to Mithril's modules and components.

+

As with Angular, Knockout templates are written in HTML, and therefore have the same pros and cons as Angular templates.

+

Vue

+

Vue is a relatively new and unknown templating engine, but it boasts impressive results in its performance benchmark.

+

It is not a full MVC framework, but it is similar to Angular templates, and uses the same terminology for its features (e.g. directives and filters).

+

The most relevant difference is that Vue uses browser features that don't work (and cannot be made to work) in Internet Explorer 8. Mithril allows developers to support browsers all the way back to IE6 and Blackberry.

+

Vue's implementation cleverly hijacks array methods, but it should be noted that Javascript Arrays cannot be truly subclassed and as such, Vue suffers from abstraction leaks.

+

In contrast, Mithril avoids "magic" types.

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/comparisons/angular.parsing.html b/archive/v0.1.4/comparisons/angular.parsing.html new file mode 100644 index 00000000..642d6448 --- /dev/null +++ b/archive/v0.1.4/comparisons/angular.parsing.html @@ -0,0 +1,2 @@ + +To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better) \ No newline at end of file diff --git a/archive/v0.1.4/comparisons/angular.rendering.html b/archive/v0.1.4/comparisons/angular.rendering.html new file mode 100644 index 00000000..8cf81d82 --- /dev/null +++ b/archive/v0.1.4/comparisons/angular.rendering.html @@ -0,0 +1,14 @@ + + + +

To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better)

+
+ +
+ + + diff --git a/archive/v0.1.4/comparisons/angular.safety.html b/archive/v0.1.4/comparisons/angular.safety.html new file mode 100644 index 00000000..01403424 --- /dev/null +++ b/archive/v0.1.4/comparisons/angular.safety.html @@ -0,0 +1,13 @@ + + + +
+ +
+ + + diff --git a/archive/v0.1.4/comparisons/backbone.parsing.html b/archive/v0.1.4/comparisons/backbone.parsing.html new file mode 100644 index 00000000..e30e1eaf --- /dev/null +++ b/archive/v0.1.4/comparisons/backbone.parsing.html @@ -0,0 +1,4 @@ + + + +To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better) \ No newline at end of file diff --git a/archive/v0.1.4/comparisons/backbone.rendering.html b/archive/v0.1.4/comparisons/backbone.rendering.html new file mode 100644 index 00000000..07f38489 --- /dev/null +++ b/archive/v0.1.4/comparisons/backbone.rendering.html @@ -0,0 +1,30 @@ + + + + + + + +

To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better)

+
+ + + + + diff --git a/archive/v0.1.4/comparisons/backbone.safety.html b/archive/v0.1.4/comparisons/backbone.safety.html new file mode 100644 index 00000000..e5a71e08 --- /dev/null +++ b/archive/v0.1.4/comparisons/backbone.safety.html @@ -0,0 +1,29 @@ + + + + + + + +
+ + + + + diff --git a/archive/v0.1.4/comparisons/jquery.parsing.html b/archive/v0.1.4/comparisons/jquery.parsing.html new file mode 100644 index 00000000..1fa16592 --- /dev/null +++ b/archive/v0.1.4/comparisons/jquery.parsing.html @@ -0,0 +1,2 @@ + +To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better) \ No newline at end of file diff --git a/archive/v0.1.4/comparisons/jquery.rendering.html b/archive/v0.1.4/comparisons/jquery.rendering.html new file mode 100644 index 00000000..dde611ec --- /dev/null +++ b/archive/v0.1.4/comparisons/jquery.rendering.html @@ -0,0 +1,17 @@ + + + +

To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better)

+
+ + + diff --git a/archive/v0.1.4/comparisons/jquery.safety.html b/archive/v0.1.4/comparisons/jquery.safety.html new file mode 100644 index 00000000..e9667269 --- /dev/null +++ b/archive/v0.1.4/comparisons/jquery.safety.html @@ -0,0 +1,16 @@ + + + +
+ + + diff --git a/archive/v0.1.4/comparisons/mithril.parsing.html b/archive/v0.1.4/comparisons/mithril.parsing.html new file mode 100644 index 00000000..c2957a15 --- /dev/null +++ b/archive/v0.1.4/comparisons/mithril.parsing.html @@ -0,0 +1,2 @@ + +To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better) \ No newline at end of file diff --git a/archive/v0.1.4/comparisons/mithril.rendering.html b/archive/v0.1.4/comparisons/mithril.rendering.html new file mode 100644 index 00000000..717f5d5e --- /dev/null +++ b/archive/v0.1.4/comparisons/mithril.rendering.html @@ -0,0 +1,20 @@ + + + +

To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better)

+
+ + + diff --git a/archive/v0.1.4/comparisons/mithril.safety.html b/archive/v0.1.4/comparisons/mithril.safety.html new file mode 100644 index 00000000..1e6ba153 --- /dev/null +++ b/archive/v0.1.4/comparisons/mithril.safety.html @@ -0,0 +1,19 @@ + + + +
+ + + diff --git a/archive/v0.1.4/compiling-templates.html b/archive/v0.1.4/compiling-templates.html new file mode 100644 index 00000000..af77f3d8 --- /dev/null +++ b/archive/v0.1.4/compiling-templates.html @@ -0,0 +1,111 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Compiling Templates

+

If performance is absolutely critical, you can optionally pre-compile templates that use m() by running the template-compiler.sjs macro with Sweet.js

+

Compiling a template transforms the nested function calls into a raw virtual DOM tree (which is merely a collection of native Javascript objects that is ready to be rendered via m.render)

+

For example, given the following template:

+
var view = function() {
+    return m("a", {href: "http://google.com"}, "test");
+}
+

It would be compiled into:

+
var view = function() {
+    return {tag: "a", attrs: {href: "http://google.com"}, children: "test"};
+}
+

Note that compiled templates are meant to be generated by an automated build process and are not meant to be human editable.

+
+

Installing NodeJS and SweetJS for one-off compilations

+

SweetJS requires a NodeJS environment. To install it, go to its website and use the installer provided.

+

To install SweetJS, NodeJS provides a command-line package manager tool. In a command line, type:

+
npm install -g sweet.js
+

To compile a file, type:

+
sjs --module /mithril.compile.sjs --output <output-filename>.js <input-filename>.js
+
+

Automating Compilation

+

If you want to automate compilation, you can use GruntJS, a task automation tool. If you're not familiar with GruntJS, you can find a tutorial on their website.

+

Assuming NodeJS is already installed, run the following command to install GruntJS:

+
npm install -g grunt-cli
+

Once installed, create two files in the root of your project, package.json and Gruntfile.js

+

package.json

+
{
+    "name": "project-name-goes-here",
+    "version": "0.0.0", //must follow this format
+    "devDependencies": {
+        "grunt-sweet.js": "*"
+    }
+}
+

Gruntfile.js

+
module.exports = function(grunt) {
+    grunt.initConfig({
+        sweetjs: {
+            modules: ["mithril.compile.sjs"],
+            compile: {expand: true, cwd: ".", src: "**/*.js", dest: "destination-folder-goes-here/"}
+        }
+    });
+
+    grunt.loadNpmTasks('grunt-sweet.js');
+
+    grunt.registerTask('default', ['sweetjs']);
+}
+

Make sure to replace the project-name-goes-here and destination-folder-goes-here placeholders with appropriate values.

+

To run the automation task, run the following command from the root folder of your project:

+
grunt
+

More documentation on the grunt-sweet.js task and its options can be found here

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/component.json b/archive/v0.1.4/component.json new file mode 100644 index 00000000..a6434184 --- /dev/null +++ b/archive/v0.1.4/component.json @@ -0,0 +1,10 @@ +{ + "name": "mithril", + "description": "A Javascript framework for building brilliant applications", + "keywords": ["mvc", "framework"], + "repository": "lhorie/mithril", + "main": "mithril.min.js", + "scripts": ["mithril.min.js", "mithril.min.map", "mithril.js"], + "version": "0.1.4", + "license": "MIT" +} \ No newline at end of file diff --git a/archive/v0.1.4/components.html b/archive/v0.1.4/components.html new file mode 100644 index 00000000..4d5dea0e --- /dev/null +++ b/archive/v0.1.4/components.html @@ -0,0 +1,192 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Components

+

Components are Mithril's mechanism for hierarchical MVC.

+

They allow complex, repeating logic to be abstracted into a logical unit of code, and they help modularize applications with widgets or multi-concern views (e.g. dashboards).

+

You can also use components for a number of other advanced techniques, like recursive templating (e.g. tree views) and partial template mixins (i.e. injecting part of a template into another).

+
+

Nesting components

+

Here's an example of nested modules in a widgetization scenario:

+
//root module
+var dashboard = {};
+
+dashboard.controller = function() {
+    this.userProfile = new userProfile.controller();
+    this.projectList = new projectList.controller();
+}
+
+dashboard.view = function(ctrl) {
+    return m("#example", [
+        m(".profile", [
+            new userProfile.view(ctrl.userProfile);
+        ]),
+        m(".projects", [
+            new projectList.view(ctrl.projectList);
+        ])
+    ])
+}
+
+
+
+//components
+
+//user profile component
+var userProfile = {};
+
+userProfile.controller = function() {
+    this.name = m.prop("John Doe");
+};
+
+userProfile.view = function(ctrl) {
+    return [
+        m("h1", "Profile"),
+        "Name: " + ctrl.name()
+    ];
+};
+
+
+
+//project list component
+var projectList = {};
+
+projectList.controller = function() {};
+
+projectList.view = function(ctrl) {
+    return "There are no projects";
+};
+
+
+
+//initialize
+m.module(document.body, dashboard);
+

As you can see, components look exactly like regular modules - it's turtles all the way down! Remember that modules are simply dumb containers for controller and view classes.

+

This means components are decoupled both horizontally and vertically. It's possible to refactor each component as a isolated unit of logic (which itself follows the MVC pattern). And we can do so without touching the rest of the application (as long as the component API stays the same).

+

Similarly, it's possible to mix and match different classes to make mix-in anonymous components (e.g. it's straightforward to build several views - for, say, a mobile app - that use the same controller).

+

It's also possible to keep references to parent and even sibling components. This is useful, for example, when implementing notification badges in a navigation component, which are triggered and managed by other components in the system.

+
+

Librarization

+

Applications often reuse rich UI controls that aren't provided out of the box by HTML. Below is a basic example of a component of that type: a minimalist autocompleter component.

+

Note: Be mindful that, for the sake of code clarity and brevity, the example below does not support keyboard navigation and other real world features.

+
var autocompleter = {};
+
+autocompleter.controller = function(data, getter) {
+    //binding for the text input
+    this.value = m.prop("");
+    //store for the list of items
+    this.data = m.prop([]);
+
+    //method to determine what property of a list item to compare the text input's value to
+    this.getter = getter;
+
+    //this method changes the relevance list depending on what's currently in the text input
+    this.change = function(value) {
+        this.value(value);
+
+        var list = value === "" ? [] : data.filter(function(item) {
+            return this.getter(item).toLowerCase().indexOf(value.toLowerCase()) > -1;
+        }, this);
+        this.data(list);
+    };
+
+    //this method is called when an option is selected. It triggers an `onchange` event
+    this.select = function(value) {
+        this.value(value);
+        this.data([]);
+        if (this.onchange) this.onchange({currentTarget: {value: value}});
+    };
+}
+
+autocompleter.view = function(ctrl, options) {
+    if (options) ctrl.onchange = options.onchange;
+    return [
+        m("input", {oninput: m.withAttr("value", ctrl.change.bind(ctrl)), value: ctrl.value()}),
+        ctrl.data().map(function(item) {
+            return m("div", {data: ctrl.getter(item), onclick: m.withAttr("data", ctrl.select.bind(ctrl))}, ctrl.getter(item));
+        })
+    ];
+}
+
+
+
+//here's an example of using the autocompleter
+var dashboard = {}
+
+dashboard.controller = function() {
+    this.names = m.prop([{id: 1, name: "John"}, {id: 2, name: "Bob"}, {id: 2, name: "Mary"}]);
+    this.autocompleter = new autocompleter.controller(this.names(), function(item) {
+        return item.name;
+    });
+};
+
+dashboard.view = function(ctrl) {
+    //assuming there's an element w/ id = "example" somewhere on the page
+    return m("#example", [
+        new autocompleter.view(ctrl.autocompleter, {onchange: m.withAttr("value", console.log)}),
+    ]);
+};
+
+
+
+//initialize
+m.module(document.body, dashboard);
+

It's recommended that libraries that provide extra functionality to Mithril be implemented using this modular pattern, as opposed to trying to hide implementation in a virtual element's config attribute.

+

You should only consider using config-based components when leveraging existing libraries.

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/getting-started.html b/archive/v0.1.4/getting-started.html new file mode 100644 index 00000000..5e9e8456 --- /dev/null +++ b/archive/v0.1.4/getting-started.html @@ -0,0 +1,447 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Getting Started

+

What is Mithril?

+

Mithril is a client-side Javascript MVC framework, i.e. it's a tool to make application code divided into a data layer (called "Model"), a UI layer (called View), and a glue layer (called Controller)

+

Mithril is around 3kb gzipped thanks to its small, focused, API. It provides a templating engine with a virtual DOM diff implementation for performant rendering, utilities for high-level modelling via functional composition, as well as support for routing and componentization.

+

The goal of the framework is to make application code discoverable, readable and maintainable, and hopefully help you become an even better developer.

+

Unlike some frameworks, Mithril tries very hard to avoid locking you into a web of dependencies: you can use as little of the framework as you need.

+

However, using its entire toolset idiomatically can bring lots of benefits: learning to use functional programming in real world scenarios and solidifying good coding practices for OOP and MVC are just some of them.

+
+

A Simple Application

+

Once you have a copy of Mithril, getting started is surprisingly boilerplate-free:

+
<!doctype html>
+<script src="mithril.js"></script>
+<script>
+//app goes here
+</script>
+

Yes, this is valid HTML 5! According to the specs, the <html>, <head> and <body> tags can be omitted, but their respective DOM elements will still be there implicitly when a browser renders that markup.

+
+

Model

+

In Mithril, typically an application lives in an namespace and contains modules. Modules are merely structures that represent a viewable "page" or component.

+

For simplicity, our application will have only one module, and we're going to use it as the namespace for our application:

+
<script>
+//this application only has one module: todo
+var todo = {};
+</script>
+

This object will namespace our two Model classes:

+
var todo = {};
+
+//for simplicity, we use this module to namespace the model classes
+
+//the Todo class has two properties
+todo.Todo = function(data) {
+    this.description = m.prop(data.description);
+    this.done = m.prop(false);
+};
+
+//the TodoList class is a list of Todo's
+todo.TodoList = Array;
+

m.prop is simply a factory for a getter-setter function. Getter-setters work like this:

+
//define a getter-setter with initial value `John`
+var name = m.prop("John");
+
+//read the value
+var a = name(); //a == "John"
+
+//set the value to `Mary`
+name("Mary"); //Mary
+
+//read the value
+var b = name(); //b == "Mary"
+

Note that the Todo and TodoList classes we defined above are plain vanilla Javascript constructors. They can be initialized and used like this:

+
var myTask = new todo.Todo({description: "Write code"});
+
+//read the description
+myTask.description(); //Write code
+
+//is it done?
+var isDone = myTask.done(); //isDone == false
+
+//mark as done
+myTask.done(true); //true
+
+//now it's done
+isDone = myTask.done(); //isDone == true
+

The TodoList class is simply an alias of the native Array class.

+
var list = new todo.TodoList();
+list.length; //0
+
+

Controller

+

Our next step is to write a controller that will use our model classes.

+
//the controller uses 3 model-level entities, of which one is a custom defined class:
+//`Todo` is the central class in this application
+//`list` is merely a generic array, with standard array methods
+//`description` is a temporary storage box that holds a string
+//
+//the `add` method simply adds a new todo to the list
+todo.controller = function() {
+    this.list = new todo.TodoList();
+    this.description = m.prop("");
+
+    this.add = function(description) {
+        if (description()) {
+            this.list.push(new todo.Todo({description: description()}));
+            this.description("");
+        }
+    };
+}
+

The code above should hopefully be self-explanatory. You can use the controller like this:

+
var ctrl = new todo.controller();
+
+ctrl.description(); //[empty string]
+
+//try adding a to-do
+ctrl.add(ctrl.description);
+ctrl.list.length; //0
+
+//you can't add a to-do with an empty description
+
+//add it properly
+ctrl.description("Write code");
+ctrl.add(ctrl.description);
+ctrl.list.length; //1
+
+

View

+

The next step is to write a view so users can interact with the application

+
todo.view = function(ctrl) {
+    return m("html", [
+        m("body", [
+            m("input"),
+            m("button", "Add"),
+            m("table", [
+                m("tr", [
+                    m("td", [
+                        m("input[type=checkbox]")
+                    ]),
+                    m("td", "task description"),
+                ])
+            ])
+        ])
+    ]);
+};
+

The utility method m() creates virtual DOM elements. As you can see, you can use CSS selectors to specify attributes. You can also use the . syntax to add CSS classes and the # to add an id.

+

The view can be rendered using the m.render method:

+
//assuming the `ctrl` variable from earlier
+m.render(document, todo.view(ctrl));
+

Notice that we pass a root DOM element to attach our template to, as well as the template itself.

+

This renders the following markup:

+
<html>
+    <body>
+        <input />
+        <button>Add</button>
+        <table>
+            <tr>
+                <td><input type="checkbox" /></td>
+                <td>task description</td>
+            </tr>
+        </table>
+    </body>
+</html>
+
+

Data Bindings

+

Let's implement a data binding on the text input. Data bindings connect a DOM element to a javascript variable so that updating one updates the other.

+
m("input")
+
+//becomes
+m("input", {value: ctrl.description()})
+

This binds the description getter-setter to the text input. Updating the value of the description updates the input when Mithril redraws.

+
var ctrl = new todo.controller();
+ctrl.description(); // empty string
+m.render(todo.view(ctrl)); // input is empty
+ctrl.description("Write code"); //set the description in the controller
+m.render(todo.view(ctrl)); // input now says "Write code"
+

Note that calling the todo.view method multiple times does not re-render the entire template.

+

Mithril internally keeps a virtual representation of the DOM in cache, scans for changes, and then only modifies the minimum required to apply the change.

+

In this case, Mithril only touches the value attribute of the input.

+
+

Bindings can also be bi-directional: that is, they can be made such that, in addition to what we saw just now, a user typing on the input updates the description getter-setter.

+

Here's the idiomatic way of implementing the view-to-controller part of the binding:

+
m("input", {onchange: m.withAttr("value", ctrl.description), value: ctrl.description()})
+

The code bound to the onchange can be read like this: "with the attribute value, set ctrl.description".

+

Note that Mithril does not prescribe how the binding updates: you can bind it to onchange, onkeypress, oninput, onblur or any other event that you prefer.

+

You can also specify what attribute to bind. This means that just as you are able to bind the value attribute in an <select>, you are also able to bind the selectedIndex property, if needed for whatever reason.

+

The m.withAttr utility is a functional programming tool provided by Mithril to minimize the need for ugly anonymous functions in the view.

+

The m.withAttr("value", ctrl.description) call above returns a function that is the rough equivalent of this code:

+
onchange: function(e) {
+    ctrl.description(e.target["value"]);
+}
+

The difference, aside from the cosmetic avoidance of anonymous functions, is that the m.withAttr idiom also takes care of catching the correct even target and selecting the appropriate source of the data - i.e. whether it should come from a javascript property or from DOMElement::getAttribute()

+
+

In addition to bi-directional data binding, we can also bind parameterized functions to events:

+
m("button", {onclick: ctrl.add.bind(ctrl, ctrl.description)}, "Add")
+

In the code above, we are simply using the native Javascript Function::bind method. This creates a new function with the parameter already set. In functional programming, this is called currying.

+

The ctrl.add.bind(ctrl, ctrl.description) expression above returns a function that is equivalent to this code:

+
onclick: function(e) {
+    ctrl.add(ctrl.description)
+}
+

Note that when we construct the parameterized binding, we are passing the description getter-setter by reference, and not its value. We only evaluate the getter-setter to get its value in the controller method. This is a form of lazy evaluation: it allows us to say "use this value later, when the event handler gets called".

+

Hopefully by now, you're starting to see why Mithril encourages the usage of m.prop: Because Mithril getter-setters are functions, they naturally compose well with functional programming tools, and allow for some very powerful idioms. In this case, we're using them in a way that resembles C pointers.

+

Mithril uses them in other interesting ways elsewhere.

+
+

To implement flow control in Mithril views, we simply use Javascript:

+
//here's the view
+m("table", [
+    ctrl.list.map(function(task, index) {
+        return m("tr", [
+            m("td", [
+                m("input[type=checkbox]")
+            ]),
+            m("td", task.description()),
+        ])
+    })
+])
+

In the code above, ctrl.list is an Array, and map is one of its native functional methods. It allows us to iterate over the list and merge transformed versions of the list items into an output array.

+

As you can see, we return a partial template with two <td>'s. The second one has a data binding to the description getter-setter of the Todo class instance.

+

You're probably starting to notice that Javascript has strong support for functional programming and that it allows us to naturally do things that can be clunky in other frameworks (e.g. looping inside a <dl>/<dt>/<dd> construct).

+
+

The rest of the code can be implemented using idioms we already covered. The complete view looks like this:

+
todo.view = function(ctrl) {
+    return m("html", [
+        m("body", [
+            m("input", {onchange: m.withAttr("value", ctrl.description), value: ctrl.description()}),
+            m("button", {onclick: ctrl.add.bind(ctrl, ctrl.description)}, "Add"),
+            m("table", [
+                ctrl.list.map(function(task, index) {
+                    return m("tr", [
+                        m("td", [
+                            m("input[type=checkbox]", {onclick: m.withAttr("checked", task.done), checked: task.done()})
+                        ]),
+                        m("td", {style: {textDecoration: task.done() ? "line-through" : "none"}}, task.description()),
+                    ])
+                })
+            ])
+        ])
+    ]);
+};
+

Here are the highlights of the template above:

+
    +
  • The template is rendered as a child of the implicit <html> element of the document
  • +
  • The text input saves its value to the ctrl.description getter-setter we defined earlier
  • +
  • The button calls the ctrl.add method when clicked. The .bind(ctrl, ctrl.description) idiom is a functional curry.

    +

    In this example, it's only used to maintain the scope binding for the this parameter in the controller method, but typically it's also used to bind parameters to the function without the need to declare a wrapper anonymous function.

    +
  • +
  • The table lists all the existing to-dos, if any.
  • +
  • The checkboxes save their value to the task.done getter setter
  • +
  • The description gets crossed out via CSS if the task is marked as done
  • +
  • When updates happen, the template is not wholly re-rendered - only the changes are applied
  • +
+
+

When running the classes in this application separately, you have full control and full responsibility for determining when to redraw the view.

+

However, Mithril does provide another utility to make this task automatic.

+

In order to enable Mithril's auto-redrawing system, we run the code as a Mithril module:

+
m.module(document, todo);
+

Mithril's auto-redrawing system keeps track of controller stability, and only redraws the view once it detects that the controller has finished running all of its code, including asynchronous ajax payloads.

+

Also note that this mechanism itself is not asynchronous if it doesn't need to be: Mithril does not need to wait for the next browser repaint frame to redraw - it doesn't even need to wait for the document ready event on the first redraw - it will redraw immediately upon script completion, if able to.

+
+

Summary

+

Here's the application code in its entirety:

+
<!doctype html>
+<script src="mithril.js"></script>
+<script>
+//this application only has one module: todo
+var todo = {};
+
+//for simplicity, we use this module to namespace the model classes
+
+//the Todo class has two properties
+todo.Todo = function(data) {
+    this.description = m.prop(data.description);
+    this.done = m.prop(false);
+};
+
+//the TodoList class is a list of Todo's
+todo.TodoList = Array;
+
+//the controller uses 3 model-level entities, of which one is a custom defined class:
+//`Todo` is the central class in this application
+//`list` is merely a generic array, with standard array methods
+//`description` is a temporary storage box that holds a string
+//
+//the `add` method simply adds a new todo to the list
+todo.controller = function() {
+    this.list = new todo.TodoList();
+    this.description = m.prop("");
+
+    this.add = function(description) {
+        if (description()) {
+            this.list.push(new todo.Todo({description: description()}));
+            this.description("");
+        }
+    };
+};
+
+//here's the view
+todo.view = function(ctrl) {
+    return m("html", [
+        m("body", [
+            m("input", {onchange: m.withAttr("value", ctrl.description), value: ctrl.description()}),
+            m("button", {onclick: ctrl.add.bind(ctrl, ctrl.description)}, "Add"),
+            m("table", [
+                ctrl.list.map(function(task, index) {
+                    return m("tr", [
+                        m("td", [
+                            m("input[type=checkbox]", {onclick: m.withAttr("checked", task.done), checked: task.done()})
+                        ]),
+                        m("td", {style: {textDecoration: task.done() ? "line-through" : "none"}}, task.description()),
+                    ])
+                })
+            ])
+        ])
+    ]);
+};
+
+//initialize the application
+m.module(document, todo);
+</script>
+
+

Notes on Architecture

+

Let's look at each MVC layer in detail to illustrate some of Mithril's design principles and philosophies:

+

Model

+

Idiomatic Mithril code is meant to apply good programming conventions and be easy to refactor.

+

In the application above, notice how the Todo class can easily be moved to a different module if code re-organization is required.

+

Todos are self-container and their data aren't tied to the DOM like in typical jQuery based code. The Todo class API is reusable and unit-test friendly, and in addition, it's a plain-vanilla Javascript class which requires almost no framework-specific learning curve.

+

m.prop is a simple but surprisingly versatile tool: it's composable, it enables uniform data access and allows a higher degree of decoupling when major refactoring is required.

+

When said refactoring is unavoidable, the developer can simply replace the m.prop call with an appropriate getter-setter implementation, instead of having to grep for API usage across the entire application.

+

For example, if todo descriptions needed to always be uppercased, one could simply change the description getter-setter:

+
this.description = m.prop(data.description)
+

becomes:

+
//private store
+var description = data.description;
+
+//public getter-setter
+this.description = function(value) {
+    if (arguments.length > 0) description = value.toUpperCase();
+    return description;
+}
+

According to Mithril's philosophy, list and description are also considered model-level entities. This is a subtle but important point: model entities don't need to be full-blown custom classes.

+

Native javascript classes are quite appropriate for storing primitive and structured data. Since in this case they are indeed being used to store data - even if temporarily - they are model entities!

+

Be aware that by using the native Array class for a list, we're making an implicit statement that we are going to support all of the standard Array methods as part of our API.

+

While this decision allows better API discoverability, the trade-off is that we're largely giving up on custom constraints and behavior. For example, if we wanted to change the application to make the list be persisted, a native Array would most certainly not be a suitable class to use.

+

In order to deal with that type of refactoring, one can explicitly decide to support only a subset of the Array API, and implement another class with the same interface as this subset API.

+

Given the code above, the replacement class would only need to implement the .push() and .map() methods. By freezing APIs and swapping implementations, the developer can completely avoid touching other layers in the application while refactoring.

+
todo.TodoList = Array;
+

becomes:

+
todo.TodoList = function () {
+    this.push = function() { /*...*/ },
+    this.map = function() { /*...*/ }
+};
+

Hopefully these examples give you an idea of ways requirements can change over time and how Mithril's philosophy allows developers to use standard OOP techniques to refactor their codebases, rather than needing to modify large portions of the application.

+
+

Controller

+

Mithril follows a data binding paradigm that is familiar to developers that use server-side MVC frameworks like Rails and Django.

+

The difference, as mentioned earlier, is that Mithril philosophy considers any form of data storage as being a model entity - even data from a text input waiting to be saved!

+

In Mithril, controllers are not meant to progressively operate on model entities. Instead, model entities should expose methods that atomically act on themselves.

+

What this rule means is that controllers can have conditional logic, as is the case in the add method in the application above, but each action that touches a model entity should not leave it in an unstable state.

+

This is in contrast to the ActiveRecord pattern of other frameworks, which allows entities to be in potentially invalid states (for example, a to-do with no description), so long as they are not "saved".

+

The idea of disallowing unstable states hinges largely on the developer deciding what constitutes validity:

+
    +
  • An empty description in the context of the text input in the UI is a perfectly valid state, and a string is an appropriate type to express that.

    +
  • +
  • A to-do with no description is not valid, therefore we avoid writing code that ever leaves the Todo class instance in a unstable state.

    +
  • +
+

Mithril doesn't programmatically define the scope of each model entity or in what states an entity is considered valid - validity is something the developer is responsible for defining.

+

Mithril's philosophical framework simply encourages that the developer map validity to static types. This is a key step in ensuring programs are robust and refactorable.

+
+

View

+

The first and most obvious thing you may have noticed in the view layer is that the view is not written in HTML.

+

While superficially this may seem like an odd design, this actually has a lot of benefits:

+
    +
  • No flash-of-unbehaviored-content (FOUC). In fact, Mithril is able to render a fully functional application - with working event handlers - before the "DOM ready" event fires!

    +
  • +
  • There's no need for a parse-and-compile pre-processing step to turn strings containing HTML + templating syntax into working DOM elements.

    +
  • +
  • Mithril views can provide accurate and informative error reporting, with line numbers and meaningful stack traces.

    +
  • +
  • You get the ability to automate linting, unit testing and minifying of the entire view layer - and you are even able to use Closure Compiler's Advanced Mode without needing extensive annotations.

    +
  • +
  • It provides full Turing completeness: full control over evaluation eagerness/lazyness and caching in templates. You can even build components that take other components as first-class-citizen parameters!

    +
  • +
  • Turtles all the way down: you don't need write custom data binding code in jQuery for every possible user interaction, and you don't need to support a complicated "directive" layer to be able to fit some types of components into the system.

    +
  • +
+

Views in Mithril use a virtual DOM diff implementation, which sidesteps performance problems related to opaque dirty-checking and excessive browser repaint that are present in some frameworks.

+

Another feature - the optional m() utility - allows writing terse templates in a declarative style using CSS shorthands, similar to popular HTML preprocessors from server-side MVC frameworks.

+

And because Mithril views are javascript, the developer has full freedom to abstract common patterns - from bidirectional binding helpers to full blown components - using standard javascript refactoring techniques.

+

Mithril templates are also more collision-proof than other component systems since there's no way to pollute the HTML tag namespace by defining ad-hoc tag names.

+

A more intellectually interesting aspect of the framework is that event handling is encouraged to be done via functional composition (i.e. by using tools like m.withAttr, m.prop and the native .bind() method for currying).

+

If you've been interested in learning or using Functional Programming in the real world, Mithril provides very pragmatic opportunities to get into it.

+
+

Learn More

+

Mithril provides a few more facilities that are not demonstrated in this page. The following topics are good places to start a deeper dive.

+ +

Advanced Topics

+ +

Misc

+ + +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/how-to-read-signatures.html b/archive/v0.1.4/how-to-read-signatures.html new file mode 100644 index 00000000..8060d94f --- /dev/null +++ b/archive/v0.1.4/how-to-read-signatures.html @@ -0,0 +1,154 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

How to Read Signatures

+

Rather than providing concrete classes like other frameworks, Mithril provides methods that operate on plain old javascript objects (POJOs) that match given signatures.

+

A signature is a description of its static type. For functions, it shows what are the parameters of the function, its return value and their expected types. For objects and arrays, it shows the expected data structure and the expected types of their members.

+

Method signatures in this documentation follow a syntax similar to Java syntax, with some extra additions:

+
ReturnType methodName(ParameterType1 param1, ParameterType2 param2)
+

Optional Parameters

+

Square brackets denote optional parameters. In the example below, param2 and param3 can both be omitted, but passing a value to param2 is required if also passing a value to param3:

+
String test(String arg1 [, String arg2 [, String arg3]])
+
//examples of valid function calls
+test("first");
+test("first", "second");
+test("first", "second", "third");
+

Type Placeholders

+

The word void is used as a type when a function does not return a value (i.e. undefined):

+
void test()
+
console.log(test()); // undefined
+

The word any is used as a type if there are no type restrictions on a parameter:

+
void test(any value)
+
//examples of valid function calls
+test("hello");
+test(1);
+test(["hello", "world"]);
+

Arrays

+

Arrays use Generics syntax to denote the expected type of array members:

+
void test(Array<String> values)
+
//example of a valid function call
+test(["first", "second"]);
+

Objects as Key-Value Maps

+

Objects also use Generics syntax when they are meant to be used as a key-value map. Keys are always strings and, in key-value maps, can have any name.

+
void test(Object<Number> values)
+
//example of a valid function call
+test({first: 1, second: 2});
+

Objects as Class Interfaces

+

Objects that require specific keys are denoted using curly brace syntax:

+
void test(Object {String first, Number second} value)
+
//example of a valid function call
+test({first: "first", second: 2});
+

Type Aliasing

+

Some types are aliases of more complex types. For example, in the example below, we created an alias called ComplexType for the type from the previous example

+
void test(ComplexType value)
+
+where:
+    ComplexType :: Object {String first, Number second}
+
+//example of a valid function call
+test({first: "first", second: 2})
+

Mixin Types

+

Curly brace syntax can also appear on other base types to denote that the value contains static members. For example, in the example below, a value of type ComplexType is a string, but it also has a boolean property called flag:

+
ComplexType :: String { Boolean flag }
+
//an example
+var a = aComplexTypeValue
+typeof a == "string" // true
+"flag" in a // true
+a.flag = true
+

In the following example, a value of type ComplexType is a function, with a property called label

+
ComplexType :: void test() { String label }
+
//an example
+var a = aComplexTypeValue
+typeof a == "function" // true
+"label" in a // true
+a.label = "first"
+

Polimorphic Types

+

When multiple (but not all) types are accepted, the pipe | is used to delimit the list of valid types

+
void test(Children children, Value value)
+
+where:
+    Children :: Array<String text | Number number>
+    Value :: String | Number
+
//examples of valid function calls
+test(["test", 2], "second")
+test([1, 2, 3], "second")
+test([1, "test", 3], 2)
+

Pipe syntax within Object curly brace syntax means that for a specific key name has specific type requirements.

+

In the example below, the value parameter should be a key-value map. This map may contain a key called config, whose value should be a function.

+
void test(Object { any | void config(DOMElement) } value)
+
//example of a valid function call
+test({ first: "first", config: function(element) { /*do stuff*/ } })
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/index.html b/archive/v0.1.4/index.html new file mode 100644 index 00000000..5687c2b7 --- /dev/null +++ b/archive/v0.1.4/index.html @@ -0,0 +1,197 @@ + + + + Mithril + + + + + +
+ +
+ +
+
+
+

Mithril

+ +

A Javascript Framework for Building Brilliant Applications

+ + Guide + Download v0.1.4 +
+
+ +
+
+
+

Light-weight

+
    +
  • Only 3kb gzipped, no dependencies
  • +
  • Small API, small learning curve
  • +
+
+ +
+

Robust

+
    +
  • Safe-by-default templates
  • +
  • Hierarchical MVC via components
  • +
+
+ +
+

Fast

+
    +
  • Virtual DOM diffing and compilable templates
  • +
  • Intelligent auto-redrawing system
  • +
+
+
+
+ +
+
+
+

Sample code

+ +
//namespace
+var app = {};
+
+//model
+app.PageList = function() {
+	return m.request({method: "GET", url: "pages.json"});
+};
+
+//controller
+app.controller = function() {
+	this.pages = app.PageList();
+};
+
+//view
+app.view = function(ctrl) {
+	return ctrl.pages().map(function(page) {
+		return m("a", {href: page.url}, page.title);
+	});
+};
+
+//initialize
+m.module(document.getElementById("example"), app);
+ +
+
+

Output

+ + + + +
+
+ +
+ +
+
+

Performance

+

To run the execution time tests below, click on their respective links, run the profiler from your desired browser's developer tools and measure the running time of a page refresh. (Lower is better)

+
+
+

Loading

+ + + + + +
Mithril 0.28ms
jQuery 13.11ms
Backbone 18.54ms
Angular 7.49ms
+
+
+

Rendering

+ + + + + +
Mithril 9.44ms (uncompiled)
jQuery 40.27ms
Backbone 23.05ms
Angular 118.63ms
+
+
+
+
+ +
+
+
+
+

Safety

+

Mithril templates are safe by default, i.e. you can't unintentionally create security holes.

+

To run the tests for each framework, click on the respective links. If you see an alert box, ensuring security with that framework is more work for you.

+
+
+

Test Summary

+ Mithril (pass) ✓
+ jQuery (fail) ✗
+ Backbone (fail) ✗
+ Angular (pass) ✓
+
+
+
+
+ +
+
+
+

Guide

+

Build a simple app, learn the ropes

+

Read Guide

+
+ +
+

API

+

Docs and code samples for your reference

+

Read Docs

+
+
+
+
+
+
+ Released under the MIT license
+ © 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/installation.html b/archive/v0.1.4/installation.html new file mode 100644 index 00000000..2ab54d0b --- /dev/null +++ b/archive/v0.1.4/installation.html @@ -0,0 +1,102 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Installation

+

Mithril is available from a variety of sources:

+
+

Direct download

+

You can download a zip of the latest version version here.

+

Links to older versions can be found in the change log

+

In order to use Mithril, extract it from the zip file and point a script tag to the .js file:

+
<script src="mithril.min.js"></script>
+
+

CDNs (Content Delivery Networks)

+

You can also find Mithril in cdnjs and jsdelivr

+

Content delivery networks allow the library to be cached across different websites that use the same version of the framework, and help reduce latency by serving the files from a server that is physically near the user's location.

+

CdnJs

+
<script src="//cdnjs.cloudflare.com/ajax/libs/mithril/0.1.4/mithril.min.js"></script>
+

JsDelivr

+
<script src="//cdn.jsdelivr.net/mithril/0.1.4/mithril.min.js"></script>
+
+

NPM

+

NPM is the default package manager for NodeJS. If you're using NodeJS already or planning on using Grunt to create a build system, you can use NPM to conveniently keep up-to-date with Mithril versions.

+

Assuming you have NodeJS installed, you can download Mithril by typing this:

+
npm install mithril
+

Then, to use Mithril, point a script tag to the downloaded file:

+
<script src="/node_modules/mithril/mithril.min.js"></script>
+
+

Bower

+

Bower is a package manager for NodeJS. If you're using NodeJS already or planning on using Grunt to create a build system, you can use Bower to conveniently keep up-to-date with Mithril versions.

+

Assuming you have NodeJS installed, you can install Bower by typing this in the command line:

+
npm install -g bower
+

And you can download Mithril by typing this:

+
bower install mithril
+

Then, to use Mithril, point a script tag to the downloaded file:

+
<script src="/bower_components/mithril/mithril.min.js"></script>
+
+

Component

+

Component is another package manager for NodeJS. If you're using NodeJS already or planning on using Grunt to create a build system, you can use Component to conveniently keep up-to-date with Mithril versions.

+

Assuming you have NodeJS installed, you can install Bower by typing this in the command line:

+
npm install -g component
+

And you can download Mithril by typing this:

+
component install lhorie/mithril
+

Then, to use Mithril, point a script tag to the downloaded file:

+
<script src="/components/lhorie/mithril/master/mithril.min.js"></script>
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/integration.html b/archive/v0.1.4/integration.html new file mode 100644 index 00000000..868fe7cb --- /dev/null +++ b/archive/v0.1.4/integration.html @@ -0,0 +1,148 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Integrating with Other Libraries

+

Integration with third party libraries can be achieved via the config attribute of virtual elements.

+

It's recommended that you encapsulate integration code in a component.

+

The example below shows a simple component that integrates with the select2 library.

+
//Select2 component (assumes both jQuery and Select2 are included in the page)
+
+/** @namespace */
+var select2 = {};
+
+/**
+select2 config factory. The params in this doc refer to properties of the `ctrl` argument
+@param {Object} data - the data with which to populate the <option> list
+@param {number} value - the id of the item in `data` that we want to select
+@param {function(Object id)} onchange - the event handler to call when the selection changes.
+    `id` is the the same as `value`
+*/
+select2.config = function(ctrl) {
+    return function(element, isInitialized) {
+        var el = $(element);
+
+        if (!isInitialized) {
+            //set up select2 (only if not initialized already)
+            el.select2()
+                //this event handler updates the controller when the view changes
+                .on("change", function(e) {
+                    //integrate with the auto-redrawing system...
+                    m.startComputation();
+
+                    //...so that Mithril autoredraws the view after calling the controller callback
+                    if (typeof ctrl.onchange == "function") ctrl.onchange(el.select2("val"));
+
+                    m.endComputation();
+                    //end integration
+                });
+        }
+
+        //update the view with the latest controller value
+        el.select2("val", ctrl.value);
+    }
+}
+
+//this view implements select2's `<select>` progressive enhancement mode
+select2.view = function(ctrl) {
+    return m("select", {config: select2.config(ctrl)}, [
+        ctrl.data.map(function(item) {
+            return m("option", {value: item.id}, item.name)
+        })
+    ]);
+};
+
+//end component
+
+
+
+//usage
+var dashboard = {};
+
+dashboard.controller = function() {
+    //list of users to show
+    this.data = [{id: 1, name: "John"}, {id: 2, name: "Mary"}, {id: 3, name: "Jane"}];
+
+    //select Mary
+    this.currentUser = this.data[1];
+
+    this.changeUser = function(id) {
+        console.log(id)
+    };
+}
+
+dashboard.view = function(ctrl) {
+    return m("div", [
+        m("label", "User:"),
+        select2.view({data: ctrl.data, value: ctrl.currentUser.id, onchange: ctrl.changeUser})
+    ]);
+}
+
+m.module(document.body, dashboard);
+

select2.config is a factory that creates a config function based on a given controller. We declare this outside of the select2.view function to avoid cluttering the template.

+

The config function created by our factory only runs the initialization code if it hasn't already. This if statement is important, because this function may be called multiple times by Mithril's auto-redrawing system and we don't want to re-initialize select2 at every redraw.

+

The initialization code defines a change event handler. Because this handler is not created using Mithril's templating engine (i.e. we're not defining an attribute in a virtual element), we must manually integrate it to the auto-redrawing system.

+

This can be done by simply calling m.startComputation at the beginning, and m.endComputation at the end of the function. You must add a pair of these calls for each asynchronous execution thread, unless the thread is already integrated.

+

For example, if you were to call a web service using m.request, you would not need to add more calls to m.startComputation / m.endComputation (you would still need the first pair in the event handler, though).

+

On the other hand, if you were to call a web service using jQuery, then you would be responsible for adding a m.startComputation call before the jQuery ajax call, and for adding a m.endComputation call at the end of the completion callback, in addition to the calls within the change event handler. Refer to the auto-redrawing guide for an example.

+

One important note about the config method is that you should avoid calling m.redraw, m.startComputation and m.endComputation in the config function's execution thread. (An execution thread is basically any amount of code that runs before other asynchronous threads start to run)

+

While Mithril technically does support this use case, relying on multiple redraw passes degrades performance and makes it possible to code yourself into an infinite execution loop situation, which is extremely difficult to debug.

+

The dashboard module in the example shows how a developer would consume the select2 component.

+

You should always document integration components so that others can find out what attribute parameters can be used to initialize the component.

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/lib/prism/prism.css b/archive/v0.1.4/lib/prism/prism.css new file mode 100644 index 00000000..1e61e11d --- /dev/null +++ b/archive/v0.1.4/lib/prism/prism.css @@ -0,0 +1,126 @@ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.builtin { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { + color: #a67f59; + background: hsla(0,0%,100%,.5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + + +.token.regex, +.token.important { + color: #e90; +} + +.token.important { + font-weight: bold; +} + +.token.entity { + cursor: help; +} + diff --git a/archive/v0.1.4/lib/prism/prism.js b/archive/v0.1.4/lib/prism/prism.js new file mode 100644 index 00000000..b7f84870 --- /dev/null +++ b/archive/v0.1.4/lib/prism/prism.js @@ -0,0 +1,9 @@ +/** + * Prism: Lightweight, robust, elegant syntax highlighting + * MIT license http://www.opensource.org/licenses/mit-license.php/ + * @author Lea Verou http://lea.verou.me + */(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.util.clone(e[i]));return r;case"Array":return e.slice()}return e}},languages:{extend:function(e,n){var r=t.util.clone(t.languages[e]);for(var i in n)r[i]=n[i];return r},insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);t.util.type(e)==="Object"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent;if(!f)return;f=f.replace(/&/g,"&").replace(/e.length)break e;if(p instanceof i)continue;a.lastIndex=0;var d=a.exec(p);if(d){l&&(c=d[1].length);var v=d.index-1+c,d=d[0].slice(c),m=d.length,g=v+m,y=p.slice(0,v+1),b=p.slice(g+1),w=[h,1];y&&w.push(y);var E=new i(u,f?t.tokenize(d,f):d);w.push(E);b&&w.push(b);Array.prototype.splice.apply(s,w)}}}return s},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e,r,i){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]")return e.map(function(t){return n.stringify(t,r,e)}).join("");var s={type:e.type,content:n.stringify(e.content,r,i),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:i};s.type=="comment"&&(s.attributes.spellcheck="true");t.hooks.run("wrap",s);var o="";for(var u in s.attributes)o+=u+'="'+(s.attributes[u]||"")+'"';return"<"+s.tag+' class="'+s.classes.join(" ")+'" '+o+">"+s.content+""};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})();; +Prism.languages.markup={comment:/<!--[\w\W]*?-->/g,prolog:/<\?.+?\?>/,doctype:/<!DOCTYPE.+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|\w+))?\s*)*\/?>/gi,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,inside:{punctuation:/=|>|"/g}},punctuation:/\/?>/g,"attr-name":{pattern:/[\w:-]+/g,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/gi};Prism.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))});; +Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/ig,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,"function":{pattern:/[a-z0-9_]+\(/ig,inside:{punctuation:/\(/}}, number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|(&){1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g}; +; +Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|get|set|new|with|typeof|try|throw|catch|finally|null|break|continue)\b/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g});Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}});Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<|<)script[\w\W]*?(>|>)[\w\W]*?(<|<)\/script(>|>)/ig,inside:{tag:{pattern:/(<|<)script[\w\W]*?(>|>)|(<|<)\/script(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});; diff --git a/archive/v0.1.4/mithril-tests.js b/archive/v0.1.4/mithril-tests.js new file mode 100644 index 00000000..200111a3 --- /dev/null +++ b/archive/v0.1.4/mithril-tests.js @@ -0,0 +1,895 @@ +new function(window) { + var selectorCache = {} + var type = {}.toString + var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.+?)\2)?\]/ + + Mithril = m = function() { + var args = arguments + var hasAttrs = type.call(args[1]) == "[object Object]" + var attrs = hasAttrs ? args[1] : {} + var classAttrName = "class" in attrs ? "class" : "className" + var cell = selectorCache[args[0]] + if (cell === undefined) { + selectorCache[args[0]] = cell = {tag: "div", attrs: {}} + var match, classes = [] + while (match = parser.exec(args[0])) { + if (match[1] == "") 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] || true + } + } + if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ") + } + cell = clone(cell) + cell.attrs = clone(cell.attrs) + cell.children = hasAttrs ? args[2] : args[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(parent, data, cached) { + if (data === null || data === undefined) { + if (cached) clear(cached.nodes) + return + } + if (data.subtree === "retain") return + + var cachedType = type.call(cached), dataType = type.call(data) + if (cachedType != dataType) { + if (cached !== null && cached !== undefined) clear(cached.nodes) + cached = new data.constructor + cached.nodes = [] + } + + if (dataType == "[object Array]") { + var nodes = [], intact = cached.length === data.length + for (var i = 0; i < data.length; i++) { + var item = build(parent, data[i], cached[i]) + if (item === undefined) continue + if (!item.nodes.intact) intact = false + cached[i] = item + } + if (!intact) { + for (var i = 0; i < data.length; i++) if (cached[i] !== undefined) nodes = nodes.concat(cached[i].nodes) + for (var i = nodes.length, node; node = cached.nodes[i]; i++) if (node.parentNode !== null) node.parentNode.removeChild(node) + for (var i = cached.nodes.length, node; node = nodes[i]; i++) if (node.parentNode === null) parent.appendChild(node) + cached.length = data.length + cached.nodes = nodes + } + } + else if (dataType == "[object Object]") { + if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join()) clear(cached.nodes) + if (typeof data.tag != "string") return + + var node, isNew = cached.nodes.length === 0 + if (isNew) { + node = window.document.createElement(data.tag) + cached = {tag: data.tag, attrs: setAttributes(node, data.attrs, {}), children: build(node, data.children, cached.children), nodes: [node]} + parent.appendChild(node) + } + else { + node = cached.nodes[0] + setAttributes(node, data.attrs, cached.attrs) + cached.children = build(node, data.children, cached.children) + cached.nodes.intact = true + if (node.parentNode !== parent) parent.appendChild(node) + } + if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew) + } + else { + var node + if (cached.nodes.length === 0) { + if (data.$trusted) { + var lastChild = parent.lastChild + parent.insertAdjacentHTML("beforeend", data) + node = lastChild ? lastChild.nextSibling : parent.firstChild + } + else { + node = window.document.createTextNode(data) + parent.appendChild(node) + } + cached = "string number boolean".indexOf(typeof data) > -1 ? new data.constructor(data) : data + cached.nodes = [node] + } + else if (cached.valueOf() !== data.valueOf()) { + if (data.$trusted) { + var current = cached.nodes[0], nodes = [current] + if (current) { + while (current = current.nextSibling) nodes.push(current) + clear(nodes) + var lastChild = parent.lastChild + parent.insertAdjacentHTML("beforeend", data) + node = lastChild ? lastChild.nextSibling : parent.firstChild + } + else parent.innerHTML = data + } + else { + node = cached.nodes[0] + parent.appendChild(node) + node.nodeValue = data + } + cached = new data.constructor(data) + cached.nodes = [node] + } + else cached.nodes.intact = true + } + + return cached + } + function setAttributes(node, dataAttrs, cachedAttrs) { + for (var attrName in dataAttrs) { + var dataAttr = dataAttrs[attrName] + var cachedAttr = cachedAttrs[attrName] + if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr) || node === window.document.activeElement) { + if (attrName === "config") continue + else if (typeof dataAttr == "function" && attrName.indexOf("on") == 0) { + if (String(dataAttr) !== String(cachedAttr)) node[attrName] = autoredraw(dataAttr, node) + } + else if (attrName === "style") { + for (var rule in dataAttr) { + if (cachedAttr === undefined || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule] + } + } + else if (attrName in node) node[attrName] = dataAttr + else node.setAttribute(attrName, dataAttr) + cachedAttrs[attrName] = dataAttr + } + } + return cachedAttrs + } + function clear(nodes) { + for (var i = 0; i < nodes.length; i++) nodes[i].parentNode.removeChild(nodes[i]) + nodes.length = 0 + } + function clone(object) { + var result = {} + for (var prop in object) result[prop] = object[prop] + return result + } + function autoredraw(callback, object) { + return function(e) { + m.startComputation() + var output = callback.call(object, e) + m.endComputation() + return output + } + } + + var html + var documentNode = { + insertAdjacentHTML: function(_, data) { + window.document.write(data) + window.document.close() + }, + appendChild: function(node) { + if (html === undefined) html = window.document.createElement("html") + if (node.nodeName == "HTML") html = node + else html.appendChild(node) + if (window.document.documentElement !== html) { + window.document.replaceChild(html, window.document.documentElement) + } + } + } + var nodeCache = [], cellCache = {} + m.render = function(root, cell) { + var index = nodeCache.indexOf(root) + var id = index < 0 ? nodeCache.push(root) - 1 : index + var node = root == window.document || root == window.document.documentElement ? documentNode : root + cellCache[id] = build(node, cell, cellCache[id]) + } + + m.trust = function(value) { + value = new String(value) + value.$trusted = true + return value + } + + var currentRoot, currentModule = {view: function() {}}, currentController = {}, now = 0, lastRedraw = 0, lastRedrawId = 0 + m.module = function(root, module) { + m.startComputation() + currentRoot = root + currentModule = module + currentController = new module.controller + m.endComputation() + } + m.redraw = function() { + m.render(currentRoot, currentModule.view(currentController)) + lastRedraw = now + } + function redraw() { + now = window.performance && window.performance.now ? window.performance.now() : new window.Date().getTime() + if (now - lastRedraw > 16) m.redraw() + else { + var cancel = window.cancelAnimationFrame || window.clearTimeout + var defer = window.requestAnimationFrame || window.setTimeout + cancel(lastRedrawId) + lastRedrawId = defer(m.redraw, 0) + } + } + + var pendingRequests = 0, computePostRedrawHook = null + m.startComputation = function() {pendingRequests++} + m.endComputation = function() { + pendingRequests = Math.max(pendingRequests - 1, 0) + if (pendingRequests == 0) { + redraw() + if (computePostRedrawHook) { + computePostRedrawHook() + computePostRedrawHook = null + } + } + } + + m.withAttr = function(prop, withAttrCallback) { + return function(e) {withAttrCallback(prop in e.currentTarget ? e.currentTarget[prop] : e.currentTarget.getAttribute(prop))} + } + + //routing + var modes = {pathname: "", hash: "#", search: "?"} + var redirect = function() {}, routeParams = {} + m.route = function() { + if (arguments.length == 3) { + var root = arguments[0], defaultRoute = arguments[1], router = arguments[2] + redirect = function(source) { + var path = source.slice(modes[m.route.mode].length) + if (!routeByValue(root, router, path)) { + m.route(defaultRoute, true) + } + } + var listener = m.route.mode == "hash" ? "onhashchange" : "onpopstate" + window[listener] = function() { + redirect(window.location[m.route.mode]) + } + computePostRedrawHook = scrollToHash + window[listener]() + } + else if (arguments[0].addEventListener) { + var element = arguments[0] + var isInitialized = arguments[1] + if (!isInitialized) { + element.removeEventListener("click", routeUnobtrusive) + element.addEventListener("click", routeUnobtrusive) + } + } + else if (typeof arguments[0] == "string") { + var route = arguments[0] + var shouldReplaceHistoryEntry = arguments[1] === true + if (window.history.pushState) { + computePostRedrawHook = function() { + window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, window.document.title, modes[m.route.mode] + route) + scrollToHash() + } + redirect(modes[m.route.mode] + route) + } + else window.location[m.route.mode] = route + } + } + m.route.param = function(key) {return routeParams[key]} + m.route.mode = "search" + function routeByValue(root, router, path) { + routeParams = {} + for (var route in router) { + if (route == path) return !void m.module(root, router[route]) + + var matcher = new RegExp("^" + route.replace(/:[^\/]+/g, "([^\\/]+)") + "$") + if (matcher.test(path)) { + return !void path.replace(matcher, function() { + var keys = route.match(/:[^\/]+/g) + var values = [].slice.call(arguments, 1, -2) + for (var i = 0; i < keys.length; i++) routeParams[keys[i].slice(1)] = values[i] + m.module(root, router[route]) + }) + } + } + } + function routeUnobtrusive(e) { + e.preventDefault() + m.route(e.currentTarget.getAttribute("href")) + } + function scrollToHash() { + if (m.route.mode != "hash" && window.location.hash) window.location.hash = window.location.hash + } + + //model + m.prop = function(store) { + return function() { + if (arguments.length) store = arguments[0] + return store + } + } + + m.deferred = function() { + var resolvers = [], rejecters = [] + var object = { + resolve: function(value) { + for (var i = 0; i < resolvers.length; i++) resolvers[i](value) + }, + reject: function(value) { + for (var i = 0; i < rejecters.length; i++) rejecters[i](value) + }, + promise: m.prop() + } + object.promise.resolvers = resolvers + object.promise.then = function(success, error) { + var next = m.deferred() + if (!success) success = identity + if (!error) error = identity + function push(list, method, callback) { + list.push(function(value) { + try { + var result = callback(value) + if (result && typeof result.then == "function") result.then(next[method], error) + else next[method](result !== undefined ? result : value) + } + catch (e) { + if (e instanceof Error && e.constructor !== Error) throw e + else next.reject(e) + } + }) + } + push(resolvers, "resolve", success) + push(rejecters, "reject", error) + return next.promise + } + return object + } + m.sync = function(args) { + var method = "resolve" + function synchronizer(resolved) { + return function(value) { + results.push(value) + if (!resolved) method = "reject" + if (results.length == args.length) { + deferred.promise(results) + deferred[method](results) + } + return value + } + } + + var deferred = m.deferred() + var results = [] + for (var i = 0; i < args.length; i++) { + args[i].then(synchronizer(true), synchronizer(false)) + } + return deferred.promise + } + function identity(value) {return value} + + function ajax(options) { + var xhr = window.XDomainRequest ? new window.XDomainRequest : new window.XMLHttpRequest + xhr.open(options.method, options.url, true, options.user, options.password) + xhr.onload = typeof options.onload == "function" ? options.onload : function() {} + xhr.onerror = typeof options.onerror == "function" ? options.onerror : function() {} + if (typeof options.config == "function") options.config(xhr, options) + xhr.send(options.data) + return xhr + } + function querystring(object, prefix) { + var str = [] + for(var prop in object) { + var key = prefix ? prefix + "[" + prop + "]" : prop, value = object[prop] + str.push(typeof value == "object" ? querystring(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value)) + } + return str.join("&") + } + function bindData(xhrOptions, data, serialize) { + if (data && Object.keys(data).length > 0) { + if (xhrOptions.method == "GET") { + xhrOptions.url = xhrOptions.url + (xhrOptions.url.indexOf("?") < 0 ? "?" : "&") + querystring(data) + } + else xhrOptions.data = serialize(data) + } + return xhrOptions + } + function parameterizeUrl(url, data) { + var tokens = url.match(/:[a-z]\w+/gi) + if (tokens && data) { + for (var i = 0; i < tokens.length; i++) { + var key = tokens[i].slice(1) + url = url.replace(tokens[i], data[key]) + delete data[key] + } + } + return url + } + + m.request = function(xhrOptions) { + m.startComputation() + var deferred = m.deferred() + var serialize = xhrOptions.serialize || JSON.stringify + var deserialize = xhrOptions.deserialize || JSON.parse + xhrOptions.url = parameterizeUrl(xhrOptions.url, xhrOptions.data) + xhrOptions = bindData(xhrOptions, xhrOptions.data, serialize) + xhrOptions.onload = xhrOptions.onerror = function(e) { + var unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity + var response = unwrap(deserialize(e.target.responseText)) + if (response instanceof 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) + deferred.promise(response) + deferred[e.type == "load" ? "resolve" : "reject"](response) + m.endComputation() + } + ajax(xhrOptions) + deferred.promise.then = propBinder(deferred.promise) + return deferred.promise + } + function propBinder(promise) { + var bind = promise.then + return function(success, error) { + var next = bind(function(value) {return next(success(value))}, function(value) {return next(error(value))}) + next.then = propBinder(next) + return next + } + } + + if (typeof module != "undefined" && module !== null) module.exports = m + if (typeof define == "function" && define.amd) define(function() {return m}) + + //testing API + m.deps = function(mock) {return window = mock} +}(this) +function test(condition) { + try {if (!condition()) throw new Error} + catch (e) {console.error(e);test.failures.push(condition)} + test.total++ +} +test.total = 0 +test.failures = [] +test.print = function(print) { + for (var i = 0; i < test.failures.length; i++) { + print(test.failures[i].toString()) + } + print("tests: " + test.total + "\nfailures: " + test.failures.length) + + if (test.failures.length > 0) { + throw new Error(test.failures.length + " tests did not pass") + } +} + +var mock = {} +mock.window = new function() { + var window = {} + window.document = {} + window.document.childNodes = [] + window.document.createElement = function(tag) { + return { + childNodes: [], + nodeName: tag.toUpperCase(), + appendChild: window.document.appendChild, + removeChild: window.document.removeChild, + replaceChild: window.document.replaceChild, + setAttribute: function(name, value) { + this[name] = value.toString() + }, + getAttribute: function(name, value) { + return this[name] + } + } + } + window.document.createTextNode = function(text) { + return {nodeValue: text.toString()} + } + window.document.documentElement = null + window.document.replaceChild = function(newChild, oldChild) { + var index = this.childNodes.indexOf(oldChild) + if (index > -1) this.childNodes.splice(index, 1, newChild) + else this.childNodes.push(newChild) + newChild.parentNode = this + oldChild.parentNode = null + } + window.document.appendChild = function(child) { + this.childNodes.push(child) + child.parentNode = this + } + window.document.removeChild = function(child) { + var index = this.childNodes.indexOf(child) + this.childNodes.splice(index, 1) + child.parentNode = null + } + window.performance = new function () { + var timestamp = 50 + this.$elapse = function(amount) {timestamp += amount} + this.now = function() {return timestamp} + } + window.cancelAnimationFrame = function() {} + window.requestAnimationFrame = function(callback) {window.requestAnimationFrame.$callback = callback} + window.requestAnimationFrame.$resolve = function() { + if (window.requestAnimationFrame.$callback) window.requestAnimationFrame.$callback() + window.requestAnimationFrame.$callback = null + window.performance.$elapse(20) + } + window.XMLHttpRequest = new function() { + var request = function() { + this.open = function(method, url) { + this.method = method + this.url = url + } + this.send = function() { + this.responseText = JSON.stringify(this) + request.$events.push({type: "load", target: this}) + } + } + request.$events = [] + return request + } + window.location = {search: "", pathname: "", hash: ""}, + window.history = {} + window.history.pushState = function(data, title, url) { + window.location.pathname = window.location.search = window.location.hash = url + }, + window.history.replaceState = function(data, title, url) { + window.location.pathname = window.location.search = window.location.hash = url + } + return window +} +function testMithril(mock) { + m.deps(mock) + + //m + test(function() {return m("div").tag === "div"}) + test(function() {return m(".foo").tag === "div"}) + test(function() {return m(".foo").attrs.className === "foo"}) + test(function() {return m("[title=bar]").tag === "div"}) + test(function() {return m("[title=bar]").attrs.title === "bar"}) + test(function() {return m("[title=\'bar\']").attrs.title === "bar"}) + test(function() {return m("[title=\"bar\"]").attrs.title === "bar"}) + test(function() {return m("div", "test").children === "test"}) + test(function() {return m("div", ["test"]).children[0] === "test"}) + test(function() {return m("div", {title: "bar"}, "test").attrs.title === "bar"}) + test(function() {return m("div", {title: "bar"}, "test").children === "test"}) + test(function() {return m("div", {title: "bar"}, ["test"]).children[0] === "test"}) + test(function() {return m("div", {title: "bar"}, m("div")).children.tag === "div"}) + test(function() {return m("div", {title: "bar"}, [m("div")]).children[0].tag === "div"}) + test(function() {return m("div", ["a", "b"]).children.length === 2}) + test(function() {return m("div", [m("div")]).children[0].tag === "div"}) + test(function() {return m("div", m("div")).attrs.tag === "div"}) //yes, this is expected behavior: see method signature + test(function() {return m("div", [undefined]).tag === "div"}) + test(function() {return m("div", [{foo: "bar"}])}) //as long as it doesn't throw errors, it's fine + + //m.module + test(function() { + mock.performance.$elapse(50) + + var root1 = mock.document.createElement("div") + m.module(root1, { + controller: function() {this.value = "test1"}, + view: function(ctrl) {return ctrl.value} + }) + + var root2 = mock.document.createElement("div") + m.module(root2, { + controller: function() {this.value = "test2"}, + view: function(ctrl) {return ctrl.value} + }) + + mock.requestAnimationFrame.$resolve() + + return root1.childNodes[0].nodeValue === "test1" && root2.childNodes[0].nodeValue === "test2" + }) + + //m.withAttr + test(function() { + var value + var handler = m.withAttr("test", function(data) {value = data}) + handler({currentTarget: {test: "foo"}}) + return value === "foo" + }) + + //m.trust + test(function() {return m.trust("test").valueOf() === "test"}) + + //m.render + test(function() { + var root = mock.document.createElement("div") + m.render(root, "test") + return root.childNodes[0].nodeValue === "test" + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("div", {id: "a"})) + var elementBefore = root.childNodes[0] + m.render(root, m("div", {id: "b"})) + var elementAfter = root.childNodes[0] + return elementBefore === elementAfter + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("#a")) + var elementBefore = root.childNodes[0] + m.render(root, m("#b")) + var elementAfter = root.childNodes[0] + return elementBefore === elementAfter + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("div", {id: "a"})) + var elementBefore = root.childNodes[0] + m.render(root, m("div", {title: "b"})) + var elementAfter = root.childNodes[0] + return elementBefore !== elementAfter + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("#a")) + var elementBefore = root.childNodes[0] + m.render(root, m("[title=b]")) + var elementAfter = root.childNodes[0] + return elementBefore !== elementAfter + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("#a")) + var elementBefore = root.childNodes[0] + m.render(root, "test") + var elementAfter = root.childNodes[0] + return elementBefore !== elementAfter + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("div", [undefined])) + return root.childNodes[0].childNodes.length === 0 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("div.classname", [m("a", {href: "/first"})])) + m.render(root, m("div", [m("a", {href: "/second"})])) + return root.childNodes[0].childNodes.length == 1 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [m("li"), undefined])) + return root.childNodes[0].childNodes.length === 1 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li"), m("li")])) + m.render(root, m("ul", [m("li"), undefined])) + return root.childNodes[0].childNodes.length === 1 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [undefined])) + return root.childNodes[0].childNodes.length === 0 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [{}])) + return root.childNodes[0].childNodes.length === 0 + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li", [m("a")])])) + m.render(root, m("ul", [{subtree: "retain"}])) + return root.childNodes[0].childNodes[0].childNodes[0].nodeName === "A" + }) + + //m.redraw + test(function() { + var controller + var root = mock.document.createElement("div") + m.module(root, { + controller: function() {controller = this}, + view: function(ctrl) {return ctrl.value} + }) + controller.value = "foo" + m.redraw() + return root.childNodes[0].nodeValue === "foo" + }) + + //m.route + test(function() { + mock.performance.$elapse(50) + mock.location.search = "?" + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/test1", { + "/test1": {controller: function() {}, view: function() {return "foo"}} + }) + return mock.location.search == "?/test1" && root.childNodes[0].nodeValue === "foo" + }) + test(function() { + mock.performance.$elapse(50) + mock.location.pathname = "/" + + var root = mock.document.createElement("div") + m.route.mode = "pathname" + m.route(root, "/test2", { + "/test2": {controller: function() {}, view: function() {return "foo"}} + }) + return mock.location.pathname == "/test2" && root.childNodes[0].nodeValue === "foo" + }) + test(function() { + mock.performance.$elapse(50) + mock.location.hash = "#" + + var root = mock.document.createElement("div") + m.route.mode = "hash" + m.route(root, "/test3", { + "/test3": {controller: function() {}, view: function() {return "foo"}} + }) + return mock.location.hash == "#/test3" && root.childNodes[0].nodeValue === "foo" + }) + test(function() { + mock.performance.$elapse(50) + mock.location.search = "?" + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/test4/foo", { + "/test4/:test": {controller: function() {}, view: function() {return m.route.param("test")}} + }) + return mock.location.search == "?/test4/foo" && root.childNodes[0].nodeValue === "foo" + }) + test(function() { + mock.performance.$elapse(50) + mock.location.search = "?" + + var module = {controller: function() {}, view: function() {return m.route.param("test")}} + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/test5/foo", { + "/": module, + "/test5/:test": module + }) + var paramValueBefore = m.route.param("test") + m.route("/") + var paramValueAfter = m.route.param("test") + + return mock.location.search == "?/" && paramValueBefore === "foo" && paramValueAfter === undefined + }) + test(function() { + mock.performance.$elapse(50) + mock.location.search = "?" + + var module = {controller: function() {}, view: function() {return m.route.param("a1")}} + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/test6/foo", { + "/": module, + "/test6/:a1": module + }) + var paramValueBefore = m.route.param("a1") + m.route("/") + var paramValueAfter = m.route.param("a1") + return mock.location.search == "?/" && paramValueBefore === "foo" && paramValueAfter === undefined + }) + + //m.prop + test(function() { + var prop = m.prop("test") + return prop() === "test" + }) + test(function() { + var prop = m.prop("test") + prop("foo") + return prop() == "foo" + }) + + //m.request + test(function() { + var prop = m.request({method: "GET", url: "test"}) + var e = mock.XMLHttpRequest.$events.pop() + e.target.onload(e) + return prop().method === "GET" && prop().url === "test" + }) + test(function() { + var prop = m.request({method: "GET", url: "test"}).then(function(value) {return "foo"}) + var e = mock.XMLHttpRequest.$events.pop() + e.target.onload(e) + return prop() === "foo" + }) + test(function() { + var prop = m.request({method: "POST", url: "http://domain.com:80", data: {}}).then(function(value) {return value}) + var e = mock.XMLHttpRequest.$events.pop() + e.target.onload(e) + console.log(prop().url) + return prop().url === "http://domain.com:80" + }) + test(function() { + var prop = m.request({method: "POST", url: "http://domain.com:80/:test1", data: {test1: "foo"}}).then(function(value) {return value}) + var e = mock.XMLHttpRequest.$events.pop() + e.target.onload(e) + console.log(prop().url) + return prop().url === "http://domain.com:80/foo" + }) + + //m.deferred + test(function() { + var value + var deferred = m.deferred() + deferred.promise.then(function(data) {value = data}) + deferred.resolve("test") + return value === "test" + }) + test(function() { + var value + var deferred = m.deferred() + deferred.promise.then(function(data) {return "foo"}).then(function(data) {value = data}) + deferred.resolve("test") + return value === "foo" + }) + test(function() { + var value + var deferred = m.deferred() + deferred.promise.then(null, function(data) {value = data}) + deferred.reject("test") + return value === "test" + }) + test(function() { + var value + var deferred = m.deferred() + deferred.promise.then(null, function(data) {return "foo"}).then(null, function(data) {value = data}) + deferred.reject("test") + return value === "foo" + }) + test(function() { + var value1, value2 + var deferred = m.deferred() + deferred.promise.then(function(data) {throw new Error}).then(function(data) {value1 = 1}, function(data) {value2 = data}) + deferred.resolve("test") + return value1 === undefined && value2 instanceof Error + }) + test(function() { + var deferred1 = m.deferred() + var deferred2 = m.deferred() + var value1, value2 + deferred1.promise.then(function(data) { + value1 = data + return deferred2.promise + }).then(function(data) { + value2 = data + }) + deferred1.resolve(1) + deferred2.resolve(2) + return value1 === 1 && value2 === 2 + }) + + //m.sync + test(function() { + var value + var deferred1 = m.deferred() + var deferred2 = m.deferred() + m.sync([deferred1.promise, deferred2.promise]).then(function(data) {value = data}) + deferred1.resolve("test") + deferred2.resolve("foo") + return value[0] === "test" && value[1] === "foo" + }) + + //m.startComputation/m.endComputation + test(function() { + mock.performance.$elapse(50) + + var controller + var root = mock.document.createElement("div") + m.module(root, { + controller: function() {controller = this}, + view: function(ctrl) {return ctrl.value} + }) + + mock.performance.$elapse(50) + + m.startComputation() + controller.value = "foo" + m.endComputation() + return root.childNodes[0].nodeValue === "foo" + }) +} + +//mocks +testMithril(mock.window) + +test.print(console.log) \ No newline at end of file diff --git a/archive/v0.1.4/mithril.computation.html b/archive/v0.1.4/mithril.computation.html new file mode 100644 index 00000000..bfb75560 --- /dev/null +++ b/archive/v0.1.4/mithril.computation.html @@ -0,0 +1,160 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.startComputation / m.endComputation

+

If you need to do custom asynchronous calls without using Mithril's API, and find that your views are not redrawing, or that you're being forced to call m.redraw manually, you should consider using m.startComputation / m.endComputation so that Mithril can intelligently auto-redraw once your custom code finishes running.

+

In order to integrate an asynchronous code to Mithril's autoredrawing system, you should call m.startComputation BEFORE making an asynchronous call, and m.endComputation after the asynchronous callback completes.

+
//this service waits 1 second, logs "hello" and then notifies the view that
+//it may start redrawing (if no other asynchronous operations are pending)
+var doStuff = function() {
+    m.startComputation(); //call `startComputation` before the asynchronous `setTimeout`
+
+    setTimeout(function() {
+        console.log("hello");
+
+        m.endComputation(); //call `endComputation` at the end of the callback
+    }, 1000);
+};
+

To integrate synchronous code, call m.startComputation at the beginning of the method, and m.endComputation at the end.

+
window.onfocus = function() {
+    m.startComputation(); //call before everything else in the event handler
+
+    doStuff();
+
+    m.endComputation(); //call after everything else in the event handler
+}
+

For each m.startComputation call a library makes, it MUST also make one and ONLY one corresponding m.endComputation call.

+

You should not use these methods if your code is intended to run repeatedly (e.g. by using setInterval). If you want to repeatedly redraw the view without necessarily waiting for user input, you should manually call m.redraw within the repeatable context.

+
+

Integrating multiple execution threads

+

When integrating with third party libraries, you might find that you need to call asynchronous methods from outside of Mithril's API.

+

In order to integrate non-trivial asynchronous code to Mithril's auto-redrawing system, you need to ensure all execution threads call m.startComputation / m.endComputation.

+

An execution thread is basically any amount of code that runs before other asynchronous threads start to run.

+

Integrating multiple execution threads can be done in a two different ways: in a layered fashion or in comprehensive fashion

+

Layered integration

+

Layered integration is recommended for modular code where many different APIs may be put together at the application level.

+

Below is an example where various methods implemented with a third party library can be integrated in layered fashion: any of the methods can be used in isolation or in combination.

+

Notice how doBoth repeatedly calls m.startComputation since that method calls both doSomething and doAnother. This is perfectly valid: there are three asynchronous computations pending after the jQuery.when method is called, and therefore, three pairs of m.startComputation / m.endComputation in play.

+
var doSomething = function(callback) {
+    m.startComputation(); //call `startComputation` before the asynchronous AJAX request
+
+    return jQuery.ajax("/something").done(function() {
+        if (callback) callback();
+
+        m.endComputation(); //call `endComputation` at the end of the callback
+    });
+};
+var doAnother = function(callback) {
+    m.startComputation(); //call `startComputation` before the asynchronous AJAX request
+
+    return jQuery.ajax("/another").done(function() {
+        if (callback) callback();
+        m.endComputation(); //call `endComputation` at the end of the callback
+    });
+};
+var doBoth = function(callback) {
+    m.startComputation(); //call `startComputation` before the asynchronous synchronization method
+
+    jQuery.when(doSomething(), doAnother()).then(function() {
+        if (callback) callback();
+
+        m.endComputation(); //call `endComputation` at the end of the callback
+    })
+};
+

Comprehensive integration

+

Comprehensive integration is recommended if integrating a monolithic series of asynchronous operations. In contrast to layered integration, it minimizes the number of m.startComputation / m.endComputation to avoid clutter.

+

The example below shows a convoluted series of AJAX requests implemented with a third party library.

+
var doSomething = function(callback) {
+    m.startComputation(); //call `startComputation` before everything else
+
+    jQuery.ajax("/something").done(function() {
+        doStuff();
+        jQuery.ajax("/another").done(function() {
+            doMoreStuff();
+            jQuery.ajax("/more").done(function() {
+                if (callback) callback();
+
+                m.endComputation(); //call `endComputation` at the end of everything
+            });
+        });
+    });
+};
+
+

Signature

+

How to read signatures

+
void startComputation()
+
void endComputation()
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/mithril.deferred.html b/archive/v0.1.4/mithril.deferred.html new file mode 100644 index 00000000..e65c4099 --- /dev/null +++ b/archive/v0.1.4/mithril.deferred.html @@ -0,0 +1,163 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.deferred

+

This is a low-level method in Mithril. It's a modified version of the Thenable API.

+

A deferred is an asynchrony monad. It exposes a promise property which can bind callbacks to build a computation tree.

+

The deferred object can then apply a value by calling either resolve or reject, which then dispatches the value to be processed to the computation tree.

+

Each computation function takes a value as a parameter and is expected to return another value, which in turns is forwarded along to the next computation function (or functions) in the tree.

+
+

Usage

+
//standalone usage
+var greetAsync = function() {
+    var deferred = m.deferred();
+    setTimeout(function() {
+        deferred.resolve("hello");
+    }, 1000);
+    return deferred.promise;
+};
+
+greetAsync()
+    .then(function(value) {return value + " world"})
+    .then(function(value) {console.log(value)}); //logs "hello world" after 1 second
+
+

Differences from Promises/A+

+

For the most part, Mithril promises behave as you'd expect a Promise/A+ promise to behave, but with a few key differences:

+

Mithril promises forward a value downstream if a resolution callback returns undefined. This allows simpler debugging of promise chains:

+
var data = m.request({method: "GET", url: "/data"})
+    .then(console.log) //Mithril promises let us debug like this
+    .then(doStuff)
+
+var data = m.request({method: "GET", url: "/data"})
+    .then(function(value) { // Promises/A+ would require us to declare an anonymous function
+        console.log(value) // here's the debugging snippet
+        return value // and we need to remember to return the value as well
+    })
+    .then(doStuff) // or else `doStuff` will break
+

Another subtle difference is that the Promises/A+ require a callback to run in a different execution context than its respective then method. This requirement exists to support an obscure edge cases and incurs a significant performance hit on each link of a promise chain. To be more specific, the performance hit can come either in the form of a 4ms minimum delay (if the implementation uses setTimeout), or from having to load a bunch of hacky polyfill code for a feature that is not being considered for addition by some browser vendors.

+

To illustrate the difference between Mithril and A+ promises, consider the code below:

+
var deferred = m.deferred()
+
+deferred.promise.then(function() {
+    console.log(1)
+})
+
+deferred.resolve("value")
+
+console.log(2)
+

In the example above, A+ promises are required to log 2 before logging 1, whereas Mithril logs 1 before 2. Typically resolve/reject are called asynchronously after the then method is called, so normally this difference does not matter.

+
+

Signature

+

How to read signatures

+
Deferred deferred()
+
+where:
+    Deferred :: Object { Promise promise, void resolve(any value), void reject(any value) }
+    Promise :: GetterSetter { Promise then(any successCallback(any value), any errorCallback(any value)) }
+    GetterSetter :: any getterSetter([any value])
+
    +
  • GetterSetter { Promise then([any successCallback(any value) [, any errorCallback(any value)]]) } promise

    +

    A promise has a method called then which takes two computation callbacks as parameters.

    +

    The then method returns another promise whose computations (if any) receive their inputs from the parent promise's computation.

    +

    A promise is also a getter-setter (see m.prop). After a call to either resolve or reject, it holds the result of the parent's computation (or the resolve/reject value, if the promise has no parent promises)

    +
      +
    • Promise then([any successCallback(any value) [, any errorCallback(any value)]])

      +

      This method accepts two callbacks which process a value passed to the resolve and reject methods, respectively, and pass the processed value to the returned promise

      +
        +
      • any successCallback(any value) (optional)

        +

        The successCallback is called if resolve is called in the root deferred.

        +

        The default value (if this parameter is falsy) is the identity function function(value) {return value}

        +

        If this function returns undefined, then it passes the value argument to the next step in the thennable queue, if any

        +
      • +
      • any errorCallback(any value) (optional)

        +

        The errorCallback is called if reject is called in the root deferred.

        +

        The default value (if this parameter is falsy) is the identity function function(value) {return value}

        +

        If this function returns undefined, then it passes the value argument to the next step in the thennable queue, if any

        +
      • +
      • returns Promise promise

        +
      • +
      +
    • +
    +
  • +
  • void resolve(any value)

    +

    This method passes a value to the successCallback of the deferred object's child promise

    +
  • +
  • void reject(any value)

    +

    This method passes a value to the errorCallback of the deferred object's child promise

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/mithril.html b/archive/v0.1.4/mithril.html new file mode 100644 index 00000000..d4d6274d --- /dev/null +++ b/archive/v0.1.4/mithril.html @@ -0,0 +1,284 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m

+

This is a convenience method to compose virtual elements that can be rendered via m.render().

+

You are encouraged to use CSS selectors to define virtual elements. See "Signature" section for details.

+
+

Usage

+

You can use simple tag selectors to make templates resemble HTML:

+
m("br"); //yields a virtual element that represents <br>
+
+m("div", "Hello"); //yields <div>Hello</div>
+
+m("div", {class: "container"}, "Hello"); //yields <div class="container">Hello</div>
+

Note that the output value from m() is not an actual DOM element. In order to turn the virtual element into a real DOM element, you must call m.render().

+
m.render(document.body, m("br")); //puts a <br> in <body>
+

You can also use more complex CSS selectors:

+
m(".container"); //yields <div class="container"></div>
+
+m("#layout"); //yields <div id="layout"></div>
+
+m("a[name=top]"); //yields <a name="top"></a>
+
+m("[contenteditable]"); //yields <div contenteditable></div>
+
+m("a#google.external[href='http://google.com']", "Google"); //yields <a id="google" class="external" href="http://google.com">Google</a>
+

Each m() call creates a virtual DOM element, that is, a javascript object that represents a DOM element, and which is eventually converted into one.

+

You can, of course, nest virtual elements:

+
m("ul", [
+    m("li", "item 1"),
+    m("li", "item 2"),
+]);
+
+/*
+yields
+<ul>
+    <li>item 1</li>
+    <li>item 2</li>
+</ul>
+*/
+

Be aware that when nesting virtual elements, the child elements must be in an Array.

+
+

The CSS selector syntax (e.g. a#google.external[href='http://google.com']) is meant to be used for declaring static attributes in the element, i.e. attribute values that don't change dynamically when the user interacts with the app.

+

The attributes argument (i.e. the second parameter in the m("div", {class: "container"}, "Hello") example) is meant to be used for attributes whose values we want to dynamically populate.

+

For example, let's say that you're generating a link from an entry that comes from a web service:

+
//assume the variable `link` came from a web service
+var link = {url: "http://google.com", title: "Google"}
+
+m("a", {href: link.url}, link.title); //yields <a href="http://google.com">Google</a>
+

Here's a less trivial example:

+
var links = [
+    {title: "item 1", url: "/item1"},
+    {title: "item 2", url: "/item2"}
+    {title: "item 3", url: "/item3"}
+];
+
+m.render(document.body, [
+    m("ul.nav", [
+        m("li", links.map(function(link) {
+            return m("a", {href: link.url}, link.title)
+        })
+    ])
+]);
+

yields:

+
<body>
+    <ul class="nav">
+        <li>
+            <a href="/item1">item 1</a>
+            <a href="/item2">item 2</a>
+            <a href="/item3">item 3</a>
+        </li>
+    </ul>
+</body>
+

As you can see, flow control is done with vanilla Javascript. This allows the developer to abstract away any aspect of the template at will.

+
+

Note that you can use both javascript property names and HTML attribute names to set values in the attributes argument, but you should pass a value of appropriate type. If an attribute has the same name in Javascript and in HTML, then Mithril assumes you're setting the Javascript property.

+
m("div", {class: "widget"}); //yields <div class="widget"></div>
+
+m("div", {className: "widget"}); //yields <div class="widget"></div>
+
+m("input", {readonly: true}); //yields <input readonly />
+
+m("button", {onclick: alert}); //yields <button></button>, which alerts its event argument when clicked
+
+

Note that you can use JSON syntax if the attribute name you are setting has non-alphanumeric characters:

+
m("div", {"data-index": 1}); //yields <div data-index="1"></div>
+

You can set inline styles like this:

+
m("div", {style: {border: "1px solid red"}}); //yields <div style="border:1px solid red;"></div>
+

Note that in order to keep the framework lean, Mithril does not auto-append units like px or % to any values. Typically, you should not even be using inline styles to begin with (unless you are dynamically changing them).

+
+

You can define a non-HTML-standard attribute called config. This special parameter allows you to call methods on the DOM element after it gets created.

+

This is useful, for example, if you declare a canvas element and want to use the Javascript API to draw:

+
function draw(element, isInitialized) {
+    //don't redraw if we did once already
+    if (isInitialized) return;
+
+    var ctx = element.getContext("2d");
+    /* draws stuff */
+}
+
+var view = [
+    m("canvas", {config: draw})
+]
+
+//this creates the canvas element, and therefore, `isInitialized` is false
+m.render(document.body, view);
+
+//here, isInitialized is `true`
+m.render(document.body, view);
+

One common way of using config is in conjunction with m.route, which is an unobtrusive extension to links that allow Mithril's routing system to work transparently regardless of which routing mode is used.

+
//this link can use any of Mithril's routing system modes
+//(i.e. it can use either the hash, the querystring or the pathname as the router implementation)
+//without needing to hard-code any syntax (`#` or `?`) in the `href` attribute.
+m("a[href='/dashboard']", {config: m.route}, "Dashboard");
+

The config mechanism can also be used to put focus on form inputs, and call methods that would not be possible to execute via the regular attribute syntax.

+

It is only meant to be used to call methods on DOM elements that cannot be called otherwise.

+

It is NOT a "free out-of-jail card". You should not use this method to modify element properties that could be modified via the attributes argument, nor values outside of the DOM element in question.

+

Also note that the config callback only runs after a rendering lifecycle is done. Therefore, you should not use config to modify controller and model values, if you expect these changes to render immediately. Changes to controller and model values in this fashion will only render on the next m.render or m.module call.

+

You can use this mechanism to attach custom event listeners to controller methods (for example, when integrating with third party libraries), but you are responsible for making sure the integration with Mithril's autoredrawing system is in place. See the integration guide for more information.

+
+

Signature

+

How to read signatures

+
VirtualElement m(String selector [, Attributes attributes] [, Children children])
+
+where:
+    VirtualElement :: Object { String tag, Attributes attributes, Children children }
+    Attributes :: Object<any | void config(DOMElement element, Boolean isInitialized)>
+    Children :: String text | Array<String text | VirtualElement virtualElement | SubtreeDirective directive | Children children>
+    SubtreeDirective :: Object { String subtree }
+
    +
  • String selector

    +

    This string should be a CSS rule that represents a DOM element.

    +

    Only tag, id, class and attribute selectors are supported.

    +

    If the tag selector is omitted, it defaults to div.

    +

    Note that if the same attribute is defined in the both selector and attributes parameters, the value in attributes is used.

    +

    For developer convenience, Mithril makes an exception for the class attribute: if there are classes defined in both parameters, they are concatenated as a space separated list. It does not, however, de-dupe classes if the same class is declared twice.

    +

    Examples:

    +

    "div"

    +

    "#container"

    +

    ".active"

    +

    "[title='Application']"

    +

    "div#container.active[title='Application']"

    +

    ".active#container"

    +
  • +
  • Attributes attributes (optional)

    +

    This key-value map should define a list of HTML attributes and their respective values.

    +

    You can use both HTML and Javascript attribute names. For example, both class and className are valid.

    +

    Values' types should match the expected type for the respective attribute.

    +

    For example, the value for className should be a string.

    +

    When a attribute name expects different types for the value in HTML and Javascript, the Javascript type should be used.

    +

    For example, the value for the onclick attribute should be a function.

    +

    Similar, setting the value of attribute readonly to false is equivalent to removing the attribute in HTML.

    +

    It's also possible to set values to Javascript-only properties, such as hash in a <a> element.

    +

    Note that if the same attribute is defined in the both selector and attributes parameters, the value in attributes is used.

    +

    For developer convenience, Mithril makes an exception for the class attribute: if there are classes defined in both parameters, they are concatenated as a space separated list. It does not, however, de-dupe classes if the same class is declared twice.

    +

    Examples:

    +

    { title: "Application" }

    +

    { onclick: function(e) { /*do stuff*/ } }

    +

    { style: {border: "1px solid red"} }

    +
  • +
  • The config attribute

    +

    void config(DOMElement element, Boolean isInitialized) (optional)

    +

    You can define a non-HTML-standard attribute called config. This special parameter allows you to call methods on the DOM element after it gets created.

    +

    This is useful, for example, if you declare a canvas element and want to use the Javascript API to draw:

    +
    function draw(element, isInitialized) {
    +   //don't redraw if we did once already
    +   if (isInitialized) return;
    +
    +   var ctx = element.getContext("2d");
    +   /* draws stuff */
    +}
    +
    +var view = [
    +   m("canvas", {config: draw})
    +]
    +
    +//this creates the canvas element, and therefore, `isInitialized` is false
    +m.render(document.body, view);
    +
    +//here, isInitialized is `true`
    +m.render(document.body, view);
    +

    One common way of using config is in conjunction with m.route, which is an unobtrusive extension to links that allow Mithril's routing system to work transparently regardless of which routing mode is used.

    +
    //this link can use any of Mithril's routing system modes
    +//(i.e. it can use either the hash, the querystring or the pathname as the router implementation)
    +//without needing to hard-code any syntax (`#` or `?`) in the `href` attribute.
    +m("a[href='/dashboard']", {config: m.route}, "Dashboard");
    +

    The config mechanism can also be used to put focus on form inputs, and call methods that would not be possible to execute via the regular attribute syntax.

    +

    It is only meant to be used to call methods on DOM elements that cannot be called otherwise.

    +

    It is NOT a "free out-of-jail card". You should not use this method to modify element properties that could be modified via the attributes argument, nor values outside of the DOM element in question.

    +

    Also note that the config callback only runs after a rendering lifecycle is done. Therefore, you should not use config to modify controller and model values, if you expect these changes to render immediately. Changes to controller and model values in this fashion will only render on the next m.render or m.module call.

    +

    You can use this mechanism to attach custom event listeners to controller methods (for example, when integrating with third party libraries), but you are responsible for making sure the integration with Mithril's autoredrawing system is in place. See the integration guide for more information.

    +
      +
    • DOMElement element
    • +
    +

    The DOM element that corresponds to virtual element defined by the m() call.

    +
      +
    • Boolean isInitialized
    • +
    +

    Whether this is the first time we are running this function on this element. This flag is false the first time it runs on an element, and true on redraws that happen after the element has been created.

    +
  • +
  • Children children (optional)

    +

    If this argument is a string, it will be rendered as a text node. To render a string as HTML, see m.trust

    +

    If it's a VirtualElement, it will be rendered as a DOM Element.

    +

    If it's a list, its contents will recursively be rendered as appropriate and appended as children of the element being created.

    +

    If it's a SubtreeDirective with the value "retain", it will retain the existing DOM tree in place, if any. See subtree directives for more information.

    +
  • +
  • returns VirtualElement

    +

    The returned VirtualElement is a javascript data structure that represents the DOM element to be rendered by m.render

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/mithril.js b/archive/v0.1.4/mithril.js new file mode 100644 index 00000000..86cd8aa6 --- /dev/null +++ b/archive/v0.1.4/mithril.js @@ -0,0 +1,438 @@ +new function(window) { + var selectorCache = {} + var type = {}.toString + var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.+?)\2)?\]/ + + Mithril = m = function() { + var args = arguments + var hasAttrs = type.call(args[1]) == "[object Object]" + var attrs = hasAttrs ? args[1] : {} + var classAttrName = "class" in attrs ? "class" : "className" + var cell = selectorCache[args[0]] + if (cell === undefined) { + selectorCache[args[0]] = cell = {tag: "div", attrs: {}} + var match, classes = [] + while (match = parser.exec(args[0])) { + if (match[1] == "") 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] || true + } + } + if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ") + } + cell = clone(cell) + cell.attrs = clone(cell.attrs) + cell.children = hasAttrs ? args[2] : args[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(parent, data, cached) { + if (data === null || data === undefined) { + if (cached) clear(cached.nodes) + return + } + if (data.subtree === "retain") return + + var cachedType = type.call(cached), dataType = type.call(data) + if (cachedType != dataType) { + if (cached !== null && cached !== undefined) clear(cached.nodes) + cached = new data.constructor + cached.nodes = [] + } + + if (dataType == "[object Array]") { + var nodes = [], intact = cached.length === data.length + for (var i = 0; i < data.length; i++) { + var item = build(parent, data[i], cached[i]) + if (item === undefined) continue + if (!item.nodes.intact) intact = false + cached[i] = item + } + if (!intact) { + for (var i = 0; i < data.length; i++) if (cached[i] !== undefined) nodes = nodes.concat(cached[i].nodes) + for (var i = nodes.length, node; node = cached.nodes[i]; i++) if (node.parentNode !== null) node.parentNode.removeChild(node) + for (var i = cached.nodes.length, node; node = nodes[i]; i++) if (node.parentNode === null) parent.appendChild(node) + cached.length = data.length + cached.nodes = nodes + } + } + else if (dataType == "[object Object]") { + if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join()) clear(cached.nodes) + if (typeof data.tag != "string") return + + var node, isNew = cached.nodes.length === 0 + if (isNew) { + node = window.document.createElement(data.tag) + cached = {tag: data.tag, attrs: setAttributes(node, data.attrs, {}), children: build(node, data.children, cached.children), nodes: [node]} + parent.appendChild(node) + } + else { + node = cached.nodes[0] + setAttributes(node, data.attrs, cached.attrs) + cached.children = build(node, data.children, cached.children) + cached.nodes.intact = true + if (node.parentNode !== parent) parent.appendChild(node) + } + if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew) + } + else { + var node + if (cached.nodes.length === 0) { + if (data.$trusted) { + var lastChild = parent.lastChild + parent.insertAdjacentHTML("beforeend", data) + node = lastChild ? lastChild.nextSibling : parent.firstChild + } + else { + node = window.document.createTextNode(data) + parent.appendChild(node) + } + cached = "string number boolean".indexOf(typeof data) > -1 ? new data.constructor(data) : data + cached.nodes = [node] + } + else if (cached.valueOf() !== data.valueOf()) { + if (data.$trusted) { + var current = cached.nodes[0], nodes = [current] + if (current) { + while (current = current.nextSibling) nodes.push(current) + clear(nodes) + var lastChild = parent.lastChild + parent.insertAdjacentHTML("beforeend", data) + node = lastChild ? lastChild.nextSibling : parent.firstChild + } + else parent.innerHTML = data + } + else { + node = cached.nodes[0] + parent.appendChild(node) + node.nodeValue = data + } + cached = new data.constructor(data) + cached.nodes = [node] + } + else cached.nodes.intact = true + } + + return cached + } + function setAttributes(node, dataAttrs, cachedAttrs) { + for (var attrName in dataAttrs) { + var dataAttr = dataAttrs[attrName] + var cachedAttr = cachedAttrs[attrName] + if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr) || node === window.document.activeElement) { + if (attrName === "config") continue + else if (typeof dataAttr == "function" && attrName.indexOf("on") == 0) { + if (String(dataAttr) !== String(cachedAttr)) node[attrName] = autoredraw(dataAttr, node) + } + else if (attrName === "style") { + for (var rule in dataAttr) { + if (cachedAttr === undefined || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule] + } + } + else if (attrName in node) node[attrName] = dataAttr + else node.setAttribute(attrName, dataAttr) + cachedAttrs[attrName] = dataAttr + } + } + return cachedAttrs + } + function clear(nodes) { + for (var i = 0; i < nodes.length; i++) nodes[i].parentNode.removeChild(nodes[i]) + nodes.length = 0 + } + function clone(object) { + var result = {} + for (var prop in object) result[prop] = object[prop] + return result + } + function autoredraw(callback, object) { + return function(e) { + m.startComputation() + var output = callback.call(object, e) + m.endComputation() + return output + } + } + + var html + var documentNode = { + insertAdjacentHTML: function(_, data) { + window.document.write(data) + window.document.close() + }, + appendChild: function(node) { + if (html === undefined) html = window.document.createElement("html") + if (node.nodeName == "HTML") html = node + else html.appendChild(node) + if (window.document.documentElement !== html) { + window.document.replaceChild(html, window.document.documentElement) + } + } + } + var nodeCache = [], cellCache = {} + m.render = function(root, cell) { + var index = nodeCache.indexOf(root) + var id = index < 0 ? nodeCache.push(root) - 1 : index + var node = root == window.document || root == window.document.documentElement ? documentNode : root + cellCache[id] = build(node, cell, cellCache[id]) + } + + m.trust = function(value) { + value = new String(value) + value.$trusted = true + return value + } + + var currentRoot, currentModule = {view: function() {}}, currentController = {}, now = 0, lastRedraw = 0, lastRedrawId = 0 + m.module = function(root, module) { + m.startComputation() + currentRoot = root + currentModule = module + currentController = new module.controller + m.endComputation() + } + m.redraw = function() { + m.render(currentRoot, currentModule.view(currentController)) + lastRedraw = now + } + function redraw() { + now = window.performance && window.performance.now ? window.performance.now() : new window.Date().getTime() + if (now - lastRedraw > 16) m.redraw() + else { + var cancel = window.cancelAnimationFrame || window.clearTimeout + var defer = window.requestAnimationFrame || window.setTimeout + cancel(lastRedrawId) + lastRedrawId = defer(m.redraw, 0) + } + } + + var pendingRequests = 0, computePostRedrawHook = null + m.startComputation = function() {pendingRequests++} + m.endComputation = function() { + pendingRequests = Math.max(pendingRequests - 1, 0) + if (pendingRequests == 0) { + redraw() + if (computePostRedrawHook) { + computePostRedrawHook() + computePostRedrawHook = null + } + } + } + + m.withAttr = function(prop, withAttrCallback) { + return function(e) {withAttrCallback(prop in e.currentTarget ? e.currentTarget[prop] : e.currentTarget.getAttribute(prop))} + } + + //routing + var modes = {pathname: "", hash: "#", search: "?"} + var redirect = function() {}, routeParams = {} + m.route = function() { + if (arguments.length == 3) { + var root = arguments[0], defaultRoute = arguments[1], router = arguments[2] + redirect = function(source) { + var path = source.slice(modes[m.route.mode].length) + if (!routeByValue(root, router, path)) { + m.route(defaultRoute, true) + } + } + var listener = m.route.mode == "hash" ? "onhashchange" : "onpopstate" + window[listener] = function() { + redirect(window.location[m.route.mode]) + } + computePostRedrawHook = scrollToHash + window[listener]() + } + else if (arguments[0].addEventListener) { + var element = arguments[0] + var isInitialized = arguments[1] + if (!isInitialized) { + element.removeEventListener("click", routeUnobtrusive) + element.addEventListener("click", routeUnobtrusive) + } + } + else if (typeof arguments[0] == "string") { + var route = arguments[0] + var shouldReplaceHistoryEntry = arguments[1] === true + if (window.history.pushState) { + computePostRedrawHook = function() { + window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, window.document.title, modes[m.route.mode] + route) + scrollToHash() + } + redirect(modes[m.route.mode] + route) + } + else window.location[m.route.mode] = route + } + } + m.route.param = function(key) {return routeParams[key]} + m.route.mode = "search" + function routeByValue(root, router, path) { + routeParams = {} + for (var route in router) { + if (route == path) return !void m.module(root, router[route]) + + var matcher = new RegExp("^" + route.replace(/:[^\/]+/g, "([^\\/]+)") + "$") + if (matcher.test(path)) { + return !void path.replace(matcher, function() { + var keys = route.match(/:[^\/]+/g) + var values = [].slice.call(arguments, 1, -2) + for (var i = 0; i < keys.length; i++) routeParams[keys[i].slice(1)] = values[i] + m.module(root, router[route]) + }) + } + } + } + function routeUnobtrusive(e) { + e.preventDefault() + m.route(e.currentTarget.getAttribute("href")) + } + function scrollToHash() { + if (m.route.mode != "hash" && window.location.hash) window.location.hash = window.location.hash + } + + //model + m.prop = function(store) { + return function() { + if (arguments.length) store = arguments[0] + return store + } + } + + m.deferred = function() { + var resolvers = [], rejecters = [] + var object = { + resolve: function(value) { + for (var i = 0; i < resolvers.length; i++) resolvers[i](value) + }, + reject: function(value) { + for (var i = 0; i < rejecters.length; i++) rejecters[i](value) + }, + promise: m.prop() + } + object.promise.resolvers = resolvers + object.promise.then = function(success, error) { + var next = m.deferred() + if (!success) success = identity + if (!error) error = identity + function push(list, method, callback) { + list.push(function(value) { + try { + var result = callback(value) + if (result && typeof result.then == "function") result.then(next[method], error) + else next[method](result !== undefined ? result : value) + } + catch (e) { + if (e instanceof Error && e.constructor !== Error) throw e + else next.reject(e) + } + }) + } + push(resolvers, "resolve", success) + push(rejecters, "reject", error) + return next.promise + } + return object + } + m.sync = function(args) { + var method = "resolve" + function synchronizer(resolved) { + return function(value) { + results.push(value) + if (!resolved) method = "reject" + if (results.length == args.length) { + deferred.promise(results) + deferred[method](results) + } + return value + } + } + + var deferred = m.deferred() + var results = [] + for (var i = 0; i < args.length; i++) { + args[i].then(synchronizer(true), synchronizer(false)) + } + return deferred.promise + } + function identity(value) {return value} + + function ajax(options) { + var xhr = window.XDomainRequest ? new window.XDomainRequest : new window.XMLHttpRequest + xhr.open(options.method, options.url, true, options.user, options.password) + xhr.onload = typeof options.onload == "function" ? options.onload : function() {} + xhr.onerror = typeof options.onerror == "function" ? options.onerror : function() {} + if (typeof options.config == "function") options.config(xhr, options) + xhr.send(options.data) + return xhr + } + function querystring(object, prefix) { + var str = [] + for(var prop in object) { + var key = prefix ? prefix + "[" + prop + "]" : prop, value = object[prop] + str.push(typeof value == "object" ? querystring(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value)) + } + return str.join("&") + } + function bindData(xhrOptions, data, serialize) { + if (data && Object.keys(data).length > 0) { + if (xhrOptions.method == "GET") { + xhrOptions.url = xhrOptions.url + (xhrOptions.url.indexOf("?") < 0 ? "?" : "&") + querystring(data) + } + else xhrOptions.data = serialize(data) + } + return xhrOptions + } + function parameterizeUrl(url, data) { + var tokens = url.match(/:[a-z]\w+/gi) + if (tokens && data) { + for (var i = 0; i < tokens.length; i++) { + var key = tokens[i].slice(1) + url = url.replace(tokens[i], data[key]) + delete data[key] + } + } + return url + } + + m.request = function(xhrOptions) { + m.startComputation() + var deferred = m.deferred() + var serialize = xhrOptions.serialize || JSON.stringify + var deserialize = xhrOptions.deserialize || JSON.parse + xhrOptions.url = parameterizeUrl(xhrOptions.url, xhrOptions.data) + xhrOptions = bindData(xhrOptions, xhrOptions.data, serialize) + xhrOptions.onload = xhrOptions.onerror = function(e) { + var unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity + var response = unwrap(deserialize(e.target.responseText)) + if (response instanceof 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) + deferred.promise(response) + deferred[e.type == "load" ? "resolve" : "reject"](response) + m.endComputation() + } + ajax(xhrOptions) + deferred.promise.then = propBinder(deferred.promise) + return deferred.promise + } + function propBinder(promise) { + var bind = promise.then + return function(success, error) { + var next = bind(function(value) {return next(success(value))}, function(value) {return next(error(value))}) + next.then = propBinder(next) + return next + } + } + + if (typeof module != "undefined" && module !== null) module.exports = m + if (typeof define == "function" && define.amd) define(function() {return m}) + + //testing API + m.deps = function(mock) {return window = mock} +}(this) \ No newline at end of file diff --git a/archive/v0.1.4/mithril.min.js b/archive/v0.1.4/mithril.min.js new file mode 100644 index 00000000..ea3fb882 --- /dev/null +++ b/archive/v0.1.4/mithril.min.js @@ -0,0 +1,8 @@ +/* +Mithril v0.1.4 +http://github.com/lhorie/mithril.js +(c) Leo Horie +License: MIT +*/ +!new function(a){function b(e,f,g){if(null===f||void 0===f)return void(g&&d(g.nodes));if("retain"!==f.subtree){var h=s.call(g),i=s.call(f);if(h!=i&&(null!==g&&void 0!==g&&d(g.nodes),g=new f.constructor,g.nodes=[]),"[object Array]"==i){for(var j=[],k=g.length===f.length,l=0;l-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e],h=d[e];if(!(e in d)||h!==g||b===a.document.activeElement){if("config"===e)continue;if("function"==typeof g&&0==e.indexOf("on"))String(g)!==String(h)&&(b[e]=f(g,b));else if("style"===e)for(var i in g)(void 0===h||h[i]!==g[i])&&(b.style[i]=g[i]);else e in b?b[e]=g:b.setAttribute(e,g);d[e]=g}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:[a-z]\w+/gi);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;ciDD({(xXc1cjU}?P89)*QQ4oZJAc~?Wwj+^93?wFD z;3RVI=kNn~=O^&a3$OhaegQB1{r`UwNKmT2Uf9)9$TOdRo({Wyum1hN{P`#O=kv{f zI@tKn|NFoGw}1cBPx9}-VQko+_on?pe%K%7FJ@c+^mEMyHm!R!CGCw?HugGRH}|dI$&MC-f$#gB`}?bLzrC?7?c6k;FQ%gn8Oe5^KIJDr z8n@$FF1N>)4AWtMlzGC^{A|&hPvbcE?JAsZ^!!;q3I~I1H|O<}c1Jh$p7{NzPxU(X zaj0Eq?bQWd*H^H?cr=?&7twq?_1x|L*?G>(oQ+!-aWvm3OsC=PdB*qqU^bp+<^Btn zc$a=RKZr-&c~5Y0O>f|D?+yN(boU0En+EdGS8ALM&b=-_xxFP^L%tO*7q0KIT9?~TAj>CQ@p zSeOOHM)^rNjYso$@Z{;!bT2=RhvTa_==BHf>?pUFKGvPJI{YGiEVN1=^z><*4=0m& z)Lx#oq-sLMC0{Nc9^%0)-VoVH*i%<7IsPz_?d0cS_X!yBxxcsR{AGMQVr?XuC zV%#5Pb5H#AWOscsmzG8^gNXD;T_kaSJBi1g4ZXyP@197+$Q%12(~poJ?`KDTm~W4x z#SlW~qiGz@dueteG)T&K*RQnc(Af!L znA2g0r-*B(--Yn&1Ay{moKwcD?vKPYd{1gMGM)TCqRX>++%~0~_;G#^&gOxTZ!bo@ zX7O}hXkUbwu=&x4+S_cV6(hVc0+{hS5YeCPPx7PqX5Q?#2Jn10&UgA#cZ7-G4>voTr>Tk&+GH69P*aFhX_c6?LsWD}{%{pFXP{qO7&hwfrVjqtuO zIMIS`{wf?SVopGsu|dn;52^7rEdrRZujfL_uVz>FzWQI&c<<#d@{`4^$NXM9dx5<| z%K!h+Q}{B9r@{`ulaM$it&a(c#EGnb6x96=9V41%Zoh3 zC$VT)o6$yO;?(t{v-sTW`E5QzyC+$lZs+dr@jE{DriBei={v@}f0gLFG9_bTj`;g= zj`qAiTEyz@5*V0r@__e3(1koECYkXllgl;L7UBB1(Y1SMO|}J4eka@YTDZ?dLXf=M zL2S1rDD?%UZZ2y+13-F!f7U-24EX3-dEH0PhU`2=wzaPpb$46&Sv)UbQTwgMJjT^` zkraUedOFIsSA=bAWZ?W~3n$t7bC|e@7EUreYn`Xu&6R#XQuQ#)Q8OV>ReN zLUPHG!@Rhz^jfc-5~prfnI5op5>H{kFdRi#$h9uge7*nONLGwCkyV&Kz-8p}-FW_? zKa8_EFZku)FFUVtLuk~VhSyo+wj+bU$Q6Uas6SNb52xWUzQ1Q2n=_Ro#*4WZ0ns%6 zx`=1%x0V0CIhT=c5C| z^N$&g_u~8<9AMnqEzxp65oi*cu9Q0R+j*!H^-{-UxQXiWvjO3)@j0A!)oahZ9q*TC zIj@7%cK#eCQrkgle>;oLI&M>TCwK10boUd-9UqlvePva|m&_Ub@7lS!mg}^Wd zy5yt9RHCjA+#k<{?<*zC^rmqK8?X}O3nVPl3uiro6L*AASY>Q5CjKKR&&xa(0u4%! zAOdUs)Tx&Oq*IP?b=e>bSA}6BKA#T8L&Ed-CPnJD9uMxC0Dr2zJtEC;D-+d;@?+w@ zY%4#E=e=>;YvmWyfk!|=12@fQ63%AV<7u1tCqEty#$nsfB-~tC$(EbjPa3<)8c6e? zTjS|;JpH4+I;oqrwZ8j@_n8TQ`c(2IiCMA{wuFmj*aGlrhx3r`ifm*>Vr-0XXXi^h zrF!BFSnOk1H#29*{$}Ppv)e}8LKI{#GB=#DbYz8gXMdFKcyVqw9!2o`_0O_I_qRvnv;F!uQ-RjuKK~T<}<4O3Wr{dU1N9so^ho=U=Wd1$|Zh z2oB^Dr`AR}+biX4CwW(l&u*_YbT0(H#J0!`RwyRjYsZ6l9&bn|p-&2+uPZkb=I6b5 zl*sT>(n2wU!r(T+^KulXJ38Y21j~n&HCicHI~#F+?qTUv+IoloHH+lqP2|`q_SKv5 zlb?^x>3eg3E4#m&{p-Dkg?-R=(eUv?8CZ=$Yop{!~D9rqj>Qb_;fP%;x#uin-RTT`B@6_!=8xPm3Qrb`#wWj*+X^o zso?f{+Q(l-sBS939gJthC=XsLsZX5%#ggjJ6$a!AW&DHOIMbUC2hhaJNL~QVTN?C> z?Cf9pt=+4&vH|8dby9Mq%d?4VxhysB^k1{W?A71ciQo2aq}FpQ;f;hqMB>wxmT0CR zw2S?lrKy7;L<)b2_is#oqTDY#yF|x+luixq@7KCZGB}v`uXm%pTY?G9N!M$8(uAtR zyymwn2v$}k)ELSo1o)qDnF~vj9vYADyn_Gjsvlpkc!H07@QPwy0)N{(;NKFaGzno+ zH;D`TQE=?t`60))I7HmNI>q^PJQ(1^zYmWE2a6x2~makqV)_Rz~ zrZ6h;*XoczN*r3*P_=M9e%p`M`)47_w?4EU;)y3PtdIO}6M!8d^1GRgCqdk9=1&=K zMjRi}+|N9Cl@(kP>}VM7!oc(lR`EH~ml$(ri_CttR^!+-$qbTZSQrfXs*4>>-M0)} zLJOL+M`!M44M5M6OZIx?)NLY_`+GM}y!3=LLak2hTlDI$MX&p$x`J=i3rF3Um|;Ac zj3;n@9%uGKO>|dLeI*sxu7dyv%l?HY8?Z@9PP{^B`#c}E+a(Fa-}WgRQH3y?5PL0& z08P57E>@R3AIoYb(6!B399%!T%y@llz;{QdW>>%@rNw2^pT5|DxlG*-Fhp2O*mllI z-=A!g8~1>JJiS$u(-b=GXUk~`Sgtr5>{D3RX^S}T&j+#hh_#!S zIgi8x@q5g4W+bjl@``nK_?MWPL`EnrhpB*LWypR*MOMosm!W>9BA;RNWICR#@LKIk zMJ`F+l2FMBBFdeLXT*JD73HFK+#&Lh+pA-Z?x9%zmi$CMaolV?xQbVH6Kj~no)HE! zT9q>*EoVgM1bh$PG?rI{)ixkr-_~;A?a4MNSr7|8c%V7#^T=WdL+Zp?lMdJv@~03@ zXPDpBs6?kLM68tMRAobxZV6OLKc%#{q!~cXr?(o-5HAD39{J-BQXi6Sj^DRWwnJHl zQrWJ2T`>t)8}G-vU_jatFU4kg1 z8wWg)J&TL&U?37sQSwNV7*sLx5yv)-wloAu1YBv80a*rGG9YZo04|E@C`fK7?4R9^ zq7}u2KMB2Ba1h4p8&xt$F*X^B9Tjt4OQA?EkrMF0Jq#s9D}=9|+)H}qwT00!KCVMG z#Z|>3e+74Y4O=ZuNK`@nAvtkR>JOn*AA~2_Ru+@$@w-FyFG)I*8&#bwPhZ3IduIw| zh1jk2>m}VMCGUrW{vEZX-343$MZlTI^XT_1P9U+&ki1TFe3om99Qu-O zT2D9?CRx5TG#Ot(>(Z;3{6z`@1cXsu%A}HXj;^QSq`8Qqcs9fMSl(7wBR*6Tw1&RO?dBOY6dI&_mf!A9VD zXKewuQPeQ?m6w9n*&|$*{NGJv>0H)54rs*k4$x95kvLTjJ7A~rM5d?a!(lsVuWZUvO@_Q*iRw;f zD{ijzD)d_q57{~HE4TOa*4CdkX5+;)ifiN#h}ZG^w|?>vEic%^aPs@wbNB!JAOA4_ zFFQZ|B>(=~N6+0X{+79lr!z`mnce3g(x()@n@JydG0X6}r&rE0yJwkv{>vBnH(A5{ zxtEa;OsB6Y#gDJq(pvDM#Md53X^2GgiNdG{nbRa^P%5vC=?fiS_m;g}{sBp&BLb&m zg8?5o&`qQ)B@^~WOv7x9jL5(9c*uvPSY8@(7oXJwE}hh)ExlBRMvoUrey!F+Lmo_J zjXaq0l^0V!{n@)%uul>!nrknb^wVzwvNL%^q?x?U(Of>t(f&T=WH9vtM{o54N5^hV zUflEmeIB9Z-oIp#)lg}Pr_)95iHY{|^@z5RS>k(zRhgIc1b0a{Q;TRCgJlh!wdWoB zmVL>@f?!Om^K_<^yZP8l%sPb_jt1B|zOKHfzKe=5BdGF37Vw7-tx%%MjI0wb<4o6H zC>Wx4%zK5bS$mIQUVVNQ6pk zAyC(sC@jMRCNN6q)trFhEzBLLDU)vvW}~KG7tCVewdQzzt%6MSc_IG`}od`@PA}Ri)DOK_nCkuHYl+!?yo~!>4(X5fy%8-P!cGTw$)5npbx}+ zwK&AriRSyUDv|5e^xchFsjsXS1-wC9wMNF$9r>#qe6V}A5sdlsFpNz6`#7-?N8_>B zC8??Kp{l}fQZY$2gOv$GSCx24WrI~oawnFmKh-c@Oo_aUy_Y93S)v}A#=xcNG#_W~ z48eZ+!x~!sS!L9pTfnO3MgLxDEm>#PtT$c^+V9Oo99afkf*c~%>G(@q(^zD{u z3C3kp5{}bO1>^Ko^7&d=yll95dD%~ZNWx<#EMTUQjEU2wt06L97bMcMK#&HZpmrw$ z}OlI*R?c%fcqLwTp@RSJc#Men3gxG4T6 z6V<9*_Q4AZC4)n^hI(N&@T#o%U2bVMWc7(2=EmPMQolRk$aj^Rtcl9BgF^xB(|Z~C zwHOF!pA^m+aIV7>>>M1)P*c`cbW!yf_=7?5gNy_8aiQGsE{lc2=cGldGcA>)l*;zt^&oTA|vI3{8n1aS)r{S78HW1;%>yN_lmM6 zP5)}BaO%nRQ`+$2ld$c>so)*x(bbOvP^~UNG?b!(U2qW?aI&oc{mO+>OYcPl5MLE#&7@eCX;3*8iBdS|AXq0lbSjb(+#D^hdsS3w+_~$F zigtMVQXQ}TbJq=&IrXtfd*M_N2)4={!K$u^H&g7NLMkDW-UW5g10{90=na>GwW)%2 zC9-f}q^NQ@RIQKfs-g7)!M$#JG1r;uLg~R6dUSl0nxERvSu!sYVPh$s3~mm+u7L5I zKrfd<;(igLJ29{51QY>>uFC37@y1S0s8=fl&x;`&gCo-$2Q!@FP7Fx|PoEpKidC=^ zu!Q@LK})0#vmwm1+pBtRT@@}eewZ={s)+k(O^!X@(ml0H`?wW$m40e+yhNIdHHV zG3#o20R|hS1VE<4LLo-rAeZUZO6)44#b4pxC{V()SPZ5Z#)1$FFe3ryS@BBoSdJk? zB{i${CIVBM9Et%@Myv4Age#!J?@cjH6;l;i{YD|Gn4n`9g(Wg>keU%Y?WyIfBR5U# zV3Dq zaK%V7R*Ms1zJ=~z0};{_p~Y)6=x*YBBI(V?Hf{Dn{g{E#yp;2T8-SR?6WC?-TP46LfY%D;n<9J_ zrl+Nd0d(uFptKTZ#+)8jZ%xnd0`M&jaN->B*hw3M5X0|TV}|&VLY2`oQQMDDq4ZwW z%t+ysdt1qe^8gOD!%ifWoOlyJP@baMG%IBtP}hZ+vS(`HMW{ok$~rj0jWzXJrw788 zK3)qZ#2RY|$LKWIttHWsh^BCQu9skp@oFtS7oBb}uKQ?fV50Id))RNk4>lFO2A2o( z1CiE3UHu^Ok+cs5k`VnBX+_CF=TvRYiAH3{fYm6Jwn_pVmm|zARv}LBWyBPvCTAdc z*!=Qy4YE2flua+pl%7;%!LhQqSyjTD8ds4|A&SDw0V35B0+x3U-^rF!*aScjEd`xY z^C~b2ucB@u?`q|ab?SnkPq0u3^jAVn;kJxBZX=I!urj#4!o=iCZga6&DiCq8nT_be zawQdFXmq2oLh!ouK~GovD890ULy{;OWk}sh6U$f?TMMp6N)@n%B4BYguAFs@08?FJ z)?mxrzR=7e^G=qWXiSXHNBm6I>cXvNMFu*m7o|Xi1oFT+v&l_ma`QsvvHGj7YDmCU zLVfFngM!{(DtmqQVMfY@S2!0zr3B1ePt?mAV5^NZpstTg!jE{e?1^8MusprO#t=WX zc@?&TK>ce;xg-W7cyp*CUen+&lxYiuN)?U|2szLQb5Uv}n>TjfZONOdO~14Lwm<{L z46)}n_@d-3Sae%_Y5i9vxp74FE>OsM!S5<@vBA(1{J2=*trRv72>FEs3YH}&2oh`H z7<)(Dh9(pqF|{e20bu|q1QCQ(p_|Itng~)z;KO(cT^7w$b+ChR2QYiCWVCqvSWi$m z>Pp;%TmaUP!%meA0tloLe(OCAi@3$x2gsycYF@4;7@3qc87`xrPQ4EgUaNZ6pQ$6Q z)l_5g#TaNaho-|9YDub>haz<+LJm#9dT4`m(g}oykK`soP`hWSYfg9cimGZ)88+3B z8pTe>>&idQLr(vYPW3_+Z5Q?^iTXvD1+sT$iAuOp5#>Whg{j1Aa)$*(C9IJIr)E(I zQc81Uv?}ST9NH@qC8RdSvi-6+()6P~E=%A;hznx1>7y}Jy#9EsRv4CE9xAkolBL;V zs|$7Ei$vXmicS*Gfk;bGH|4GE*cH#p67OJ~W#+j`RsBa}FUp`-Lo@1yw*@sZh@e;= zH3E&Q@jULY6Dsu&$+$k);J6sehEt1=$Y!_>4+8|X=+G2RHKLF7XWVJF<(bd}BG_@ZI zETx^pTLtX|2xTo0a`>^rR(f94NykE^f7f!6>Px{+T%NGzg<9FBC4dzYTvVxs*bD0f zn6Iiz$1mkL$iw;w6g~ z_KE$)VgQ)h4RMx28HKxUWi~ZaCpSN$iajvl0zoG<0LVB zzgnn33J8?vgkZ+NcDQm2lh1M0wB< zUTNj;b?{X;)YWc9mV!elNq3Q)@Twx)K5XkvB$#R;*4DDBroz#n_@u%nrd`}biFB|E z?UXlWQ0g#9Qq(v2{s%E(=)0T1oF~@x#7&qjQ{t=m0aTP!$|pA|UsQ=9Rase%H9r;7pBhxodoPo5zY(-`Lz> zP(X(BNElO~QNOYPY#|OYXere0<_0-BD{4&fkwJ)ZQ#6^VqQxRJ&Kw%;6>K|#E#kfO zI*_TDRX)Wcam|T4!vTriWyze7kP$6i?5Y8Y zF(|Gl%CC+zoER4+7b0a}zYc37Nuow9f=mh$HbRTa;6S}`myi~NF)3~-6H1T}liF0= z@Yh1|O%cu{gtu}=LuqylH^s_pl84Q?)b*ikdau7gPWu?@i&V_iAZyZAQ9l^G3|^}2 z)-4uj^i=MIWLWgmp(;gcPh`IM%hokW^@Db4j2y<_|v31naWN z+_r*DhQ!XkB@5zlFN)N4z;RK8LIKKvUFK@T#F`CjJwkr04E(!p>P=DY zX_CiD;!Ak~kid}NNwF!gAEUV(5{ho@81wta{7|Q5X=SLai$|Ki8XTHIP~ep#KrFZ` zAyBL}O34Wla>-`17IiE)_}8b0YBZ57Ati(pqPR>`6UG_}hby7VtzeiWS*EX%CgNys zAjz&5dT+#Clu(swy78HYmE@!*uFET+9`RzVzvSt62jr)KS-;c@$Ei@sJ+w8qG^j~3 zR~WMfE0w=NKt+ea>mtSlBHR=S`smAHn1ACT7%P#uq&z!RiAE|V98%r4*z*`q>sHBI zic+Urv_C~oVCFN!-35S4f~ycTU1s$IX8TVRZm4U?$Uklt1C%E;%I zDBM&>W2L-!92Mj%HCT!3d-aCg>rRt>odReq7`N8*Oo971wb5q z{31V`T4>g#$lZakXMob$pB*f|MN-JxOWfqF5#90UN969Fya5|ENfE3)+<9Sl__N zlnc?MXqBGoSV?>$d^*{21V&f{(XVa_ERStU%B3=+cJmzofa`meni7~T^@jvZ3V$Xf zmMLij#ha3;6qhyC;qZz^@upzaoyKe9s4?@sDpTPYv&og>1u?m|W;<`xo*ff93N+;7 zMBzI{(}E_5iLoP!1NjT9$2BcfYOo*G_|y|;Qu`oO5y9>p)cMsr!VeZWm{-5>=T(HU|R2va4LrWeYO)$L7g9LCdD^q##DmcD+rkZhdu(hr^3Oj1q7nxB2{vV(E^B;9qud;i6DzC0B4Cv ztm-BKT(`)3N*xs;fS1EtLaJIUi@QbTferB|JhGp17nO*Y2e(SJP;WJ@wuf(5537-j zi9jA0SXw=&#w{o4i;SU4?LZhOztk!9<=#B#$DRtLXcvg|ii?l9qpbsj=`_rTbGs>?A3IQX6w6LOs`X943n;{F488N+A|34-F>R-!(zvF+meE<9J()&M|1LhL| literal 0 HcmV?d00001 diff --git a/archive/v0.1.4/mithril.module.html b/archive/v0.1.4/mithril.module.html new file mode 100644 index 00000000..68edf892 --- /dev/null +++ b/archive/v0.1.4/mithril.module.html @@ -0,0 +1,176 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.module

+

A module is an Object with two keys: controller and view. Each of those should point to a Javascript class constructor function.

+

'm.module' activates a module by instantiating its controller, then instantiating its view and rendering it into a root DOM element.

+

Conceptually, the easiest way to think of a module is as a logical namespace with which to organize applications. For example, an app might have a dashboard module, a userEditForm module, an autocompleter module, a date formatting module, etc

+

In the context of single page applications (SPA), a module can often be thought of as the code for a single "page", i.e. a visual state that is bookmarkable. Module can, however, also represent parts of pages.

+

Note that a module might have external dependencies and that the dependencies aren't considered part of the module.

+

In more complex applications, modules can be nested in a hierarchical MVC pattern. Nested reusable modules that have views are called Components.

+

Modules and namespaces are often used interchangeably, but namespaces that do not implement the module interface (that is, objects that do not have a property called controller and a property called view) cannot be activated with m.module. For example, a namespace for date formatting utilities could be labeled a "module" (in the generic sense of the word) but it would not contain a view class, and therefore attempting to initialize it via m.module would result in undefined behavior.

+
+

Usage

+

You can make anonymous modules out of existing classes

+
//controller class
+var dashboardController = function() {
+    this.greeting = "Hello";
+};
+
+//view class
+var dashboardView = function(ctrl) {
+    return m("h1", ctrl.greeting);
+};
+
+//initialize an anonymous module
+m.module(document.body, {controller: dashboardController, view: dashboardView});
+

Typically, however, modules and namespaces are used interchangeably.

+
//`dashboard` is both a namespace and a module
+var dashboard = {}
+
+//controller class
+dashboard.controller = function() {
+    this.greeting = "Hello";
+};
+
+//view class
+dashboard.view = function(ctrl) {
+    return m("h1", ctrl.greeting);
+};
+
+//initialize it
+m.module(document.body, dashboard);
+

The example below shows a component module called user being included in a parent module dashboard.

+
//this is a sample module
+var dashboard = {
+    controller: function() {
+        this.greeting = "Hello";
+
+        this.user = new user.controller();
+    },
+    view: function(controller) {
+        return [
+            m("h1", controller.greeting),
+
+            new user.view(controller.user)
+        ];
+    }
+};
+
+//this module is being included as a component
+var user = {
+    //model
+    User: function(name) {
+        this.name = name;
+    },
+    //controller
+    controller: function() {
+        this.user = new user.User("John Doe");
+    },
+    //view
+    view: function(controller) {
+        return m("div", controller.user.name);
+    }
+};
+
+//activate the dashboard module
+m.module(document.body, dashboard);
+

yields:

+
<body>
+    <h1>Hello</h1>
+    <div>John Doe</div>
+</body>
+
+

Signature

+

How to read signatures

+
void module(DOMElement rootElement, Module module)
+
+where:
+    Module :: Object { void controller(), void view(Object controllerInstance) }
+
    +
  • DOMElement rootElement

    +

    A DOM element which will contain the view's template.

    +
  • +
  • Module module

    +

    A module is supposed to be an Object with two keys: controller and view. Each of those should point to a Javascript class constructor function

    +

    The controller class is instantiated immediately upon calling m.module.

    +

    Once the controller code finishes executing (and this may include waiting for AJAX requests to complete), the view class is instantiated, and the instance of the controller is passed as an argument to the view's constructor.

    +

    Note that controllers can manually instantiate child controllers (since they are simply Javascript constructors), and likewise, views can instantiate child views and manually pass the child controller instances down the the child view constructors.

    +

    This "turtles all the way down" approach is the heart of Mithril's component system.

    +

    Components are nothing more than decoupled classes that can be dynamically brought together as required. This permits the swapping of implementations at a routing level (for example, if implementing widgetized versions of existing components) and class dependency hierarchies can be structurally organized to provide uniform interfaces (for unit tests, for example).

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/mithril.prop.html b/archive/v0.1.4/mithril.prop.html new file mode 100644 index 00000000..0ee29000 --- /dev/null +++ b/archive/v0.1.4/mithril.prop.html @@ -0,0 +1,141 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.prop

+

This is a getter-setter factory utility. It returns a function that stores information

+
+

Usage

+
//define a getter-setter with initial value `John`
+var name = m.prop("John");
+
+//read the value
+var a = name(); //a == "John"
+
+//set the value to `Mary`
+name("Mary"); //Mary
+
+//read the value
+var b = name(); //b == "Mary"
+

It can be used in conjunction with m.withAttr to implement data binding in the view-to-model direction and to provide uniform data access for model entity properties.

+
//a contrived example of bi-directional data binding
+var user = {
+    model: function(name) {
+        this.name = m.prop(name);
+    },
+    controller: function() {
+        this.user = new user.model("John Doe");
+    },
+    view: function(controller) {
+        m.render("body", [
+            m("input", {onchange: m.withAttr("value", controller.user.name), value: controller.user.name()})
+        ]);
+    }
+};
+

In the example above, the usage of m.prop allows the developer to change the implementation of the user name getter/setter without the need for code changes in the controller and view.

+

m.prop can also be used in conjunction with m.request and m.deferred to bind data on completion of an asynchronous operation.

+
var users = m.prop([]);
+var error = m.prop("");
+
+m.request({method: "GET", url: "/users"})
+    .then(users, error); //on success, `users` will be populated, otherwise `error` will be populated
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of User instances
+//i.e. users()[0].name() == "John"
+
+

Signature

+

How to read signatures

+
GetterSetter prop([any initialValue])
+
+where:
+    GetterSetter :: any getterSetter([any value])
+
    +
  • any initialValue (optional)

    +

    An initialization value. If not provided, the value of the getter-setter's internal store defaults to undefined.

    +
  • +
  • returns any getterSetter([any value])

    +

    A getter-setter method.

    +
      +
    • any value (optional)

      +

      If provided, it updates the getter-setter's internal store to the provided value.

      +

      If not provided, return the current internally stored value.

      +
    • +
    • returns any value

      +

      This method always returns the value of the internal store, regardless of whether it was updated or not.

      +
    • +
    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/mithril.redraw.html b/archive/v0.1.4/mithril.redraw.html new file mode 100644 index 00000000..0cbcfc47 --- /dev/null +++ b/archive/v0.1.4/mithril.redraw.html @@ -0,0 +1,90 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.redraw

+

Redraws the view for the currently active module. Use m.module() to activate a module.

+

This method is called internally by Mithril's auto-redrawing system and is only documented for completeness; you should avoid calling it manually unless you explicitly want a multi-pass redraw cycle.

+

A multi-pass redraw cycle is usually only useful if you need non-trivial UI metrics measurements. A multi-pass cycle may span multiple browser repaints and therefore could cause flash of unbehaviored content (FOUC) and performance degradation.

+

By default, if you're using either m.route or m.module, m.redraw() is called automatically by Mithril's auto-redrawing system once the controller finishes executing.

+

m.redraw is also called automatically on event handlers defined in virtual elements.

+

If there are pending m.request calls in either a controller constructor or event handler, the auto-redrawing system waits for all the AJAX requests to complete before calling m.redraw.

+

This method may also be called manually from within a controller if more granular updates to the view are needed, however doing so is generally not recommended, as it may degrade performance. Model classes should never call this method.

+

If you are developing an asynchronous model-level service and finding that Mithril is not redrawing the view after your code runs, you should use m.startComputation and m.endComputation to integrate with Mithril's auto-redrawing system instead.

+
+

Signature

+

How to read signatures

+
void redraw()
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/mithril.render.html b/archive/v0.1.4/mithril.render.html new file mode 100644 index 00000000..92eedc17 --- /dev/null +++ b/archive/v0.1.4/mithril.render.html @@ -0,0 +1,158 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.render

+

This method generates a DOM tree inside of a given HTML element.

+

If the method is run more than once with the same root element, it diffs the new tree against the existing one and intelligently modifies only the portions that have changed.

+

Note that, unlike many templating engines, this "smart diff" feature does not affect things like cursor placement in inputs and focus, and is therefore safe to call during user interactions.

+
+

Usage

+

Assuming a document has an empty <body> element, the code below:

+
var links = [
+    {title: "item 1", url: "/item1"}
+];
+
+m.render(document.body, [
+    m("ul.nav", [
+        m("li", links.map(function(link) {
+            return m("a", {href: link.url, config: m.route}, link.title)
+        })
+    ])
+]);
+

yields:

+
<body>
+    <ul class="nav">
+        <li>
+            <a href="/item1">item 1</a>
+        </li>
+    </ul>
+</body>
+
+

Subtree Directives

+

m.render accepts a special low level SubtreeDirective object as a node in a virtual DOM tree: if a tree contains a node that looks exactly like the object below, Mithril will abort the diff algorithm for that node. This allows you to implement optimizations that avoid creating virtual DOM trees in favor of their cached counterparts, if you know they have not changed between redraws. Note that using this feature is discouraged if you don't have visible performance problems.

+
{subtree: "retain"}
+

This mechanism is only intended to be used as a last resort optimization tool. If you do use it, you are responsible for determining what constitutes a scenario where the virtual DOM tree is changed/unchanged.

+

The example below shows how to use a SubtreeDirective object to create a static header that doesn't incur diff costs once it has been rendered. This means that we are avoiding the creation of the header subtree (and therefore skipping the diff algorithm) altogether, but it also means that dynamic variables will NOT be updated within the header.

+
var app = {}
+
+//here's an example plugin that determines whether data has changes.
+//in this case, it simply assume data has changed the first time, and never changes after that.
+app.bindOnce = new function() {
+    var cache = {}
+    function(view) {
+        if (!cache[view.toString()]) {
+            cache[view.toString()] = true
+            return view()
+        }
+        else return {subtree: "retain"}
+    }
+}
+
+//here's the view
+app.view = function(ctrl) {
+    m(".layout", [
+        app.bindOnce(function() {
+            //this only runs once in order to boost performance
+            //dynamic variables are not updated here
+            return m("header", [
+                m("h1", "this never changes")
+            ])
+        }),
+        //dynamic variables here still update on every redraw
+        m("main", "rest of app goes here")
+    ])
+}
+
+

Signature

+

How to read signatures

+
void render(DOMElement rootElement, Children children)
+
+where:
+    Children :: String text | Array<String text | VirtualElement virtualElement | SubtreeDirective directive | Children children>
+    VirtualElement :: Object { String tag, Attributes attributes, Children children }
+    Attributes :: Object<Any | void config(DOMElement element)>
+    SubtreeDirective :: Object { String subtree }
+
    +
  • DOMElement rootElement

    +

    A DOM element which will contain the template represented by children.

    +
  • +
  • Children children

    +

    If this argument is a string, it will be rendered as a text node. To render a string as HTML, see m.trust

    +

    If it's a VirtualElement, it will be rendered as a DOM Element.

    +

    If it's a list, its contents will recursively be rendered as appropriate and appended as children of the root element.

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/mithril.request.html b/archive/v0.1.4/mithril.request.html new file mode 100644 index 00000000..728c8f68 --- /dev/null +++ b/archive/v0.1.4/mithril.request.html @@ -0,0 +1,347 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.request

+

This is a high-level utility for working with web services, which allows writing asynchronous code relatively procedurally.

+

By default, it assumes server responses are in JSON format and optionally instantiates a class with the response data.

+

It provides a number of useful features out of the box:

+
    +
  • The ability to get an early reference to a container that will hold the asynchronous response
  • +
  • The ability to queue operations to be performed after the asynchronous request completes
  • +
  • The ability to "cast" the response to a class of your choice
  • +
  • The ability to unwrap data in a response that includes metadata properties
  • +
+
+

Basic usage

+

The basic usage pattern for m.request returns an m.prop getter-setter, which is populated when the AJAX request completes.

+

The returned getter-setter can be thought of as a box: you can pass this reference around cheaply, and you can "unwrap" its value when needed.

+
var users = m.request({method: "GET", url: "/user"});
+
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}]
+

Note that this getter-setter holds an undefined value until the AJAX request completes. Attempting to unwrap its value early will likely result in errors.

+

The returned getter-setter also implements the promise interface (also known as a thennable): this is the mechanism you should always use to queue operations to be performed on the data from the web service.

+

The simplest use case of this feature is to implement functional value assignment via m.prop (i.e. the same thing as above). You can bind a pre-existing getter-setter by passing it in as a parameter to a .then method:

+
var users = m.prop([]); //default value
+
+m.request({method: "GET", url: "/user"}).then(users)
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}]
+

This syntax allows you to bind intermediate results before piping them down for further processing, for example:

+
var users = m.prop([]); //default value
+var doSomething = function() { /*...*/ }
+
+m.request({method: "GET", url: "/user"}).then(users).then(doSomething)
+

While both basic assignment syntax and thennable syntax can be used to the same effect, typically it's recommended that you use the assignment syntax whenever possible, as it's easier to read.

+

The thennable mechanism is intended to be used in 3 ways:

+
    +
  • in the model layer: to process web service data in transformative ways (e.g. filtering a list based on a parameter that the web service doesn't support)
  • +
  • in the controller layer: to bind redirection code upon a condition
  • +
  • in the controller layer: to bind error messages
  • +
+

Processing web service data

+

This step is meant to be done in the model layer. Doing it in the controller level is also possible, but philosophically not recommended, because by tying logic to a controller, the code becomes harder to reuse due to unrelated controller dependencies.

+

In the example below, the listEven method returns a getter-setter that resolves to a list of users containing only users whose id is even.

+
//model
+var User = {}
+
+User.listEven = function() {
+    return m.request({method: "GET", url: "/user"}).then(function(list) {
+        return list.filter(function(user) {return user.id % 2 == 0});
+    });
+}
+
+//controller
+var controller = function() {
+    this.users = User.listEven()
+}
+

Bind redirection code

+

This step is meant to be done in the controller layer. Doing it in the model level is also possible, but philosophically not recommended, because by tying redirection to the model, the code becomes harder to reuse due to overly tight coupling.

+

In the example below, we use the previously defined listEven model method and queue a controller-level function that redirects to another page if the user list is empty.

+
//controller
+var controller = function() {
+    this.users = User.listEven().then(function(users) {
+        if (users.length == 0) m.route("/add");
+    })
+}
+

Binding errors

+

Mithril thennables take two functions as optional parameters: the first parameter is called if the web service request completes successfully. The second one is called if it completes with an error.

+

Error binding is meant to be done in the controller layer. Doing it in the model level is also possible, but generally leads to more code in order to connect all the dots.

+

In the example below, we bind an error getter-setter to our previous controller so that the error variable gets populated if the server throws an error.

+
//controller
+var controller = function() {
+    this.error = m.prop("")
+
+    this.users = User.listEven().then(function(users) {
+        if (users.length == 0) m.route("/add");
+    }, this.error)
+}
+

If the controller doesn't already have a success callback to run after a request resolves, you can still bind errors like this:

+
//controller
+var controller = function() {
+    this.error = m.prop("")
+
+    this.users = User.listEven().then(null, this.error)
+}
+
+

Queuing Operations

+

As you saw, you can chain operations that act on the response data. Typically this is required in three situations:

+
    +
  • in model-level methods if client-side processing is needed to make the data useful for a controller or view.
  • +
  • in the controller, to redirect after a model service resolves.
  • +
  • in the controller, to bind error messages
  • +
+

In the example below, we take advantage of queuing to debug the ajax response data prior to doing further processing on the user list

+
var users = m.request({method: "GET", url: "/user"})
+    .then(console.log);
+    .then(function(users) {
+        //add one more user to the response
+        return users.concat({name: "Jane"})
+    })
+
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}, {name: "Jane"}]
+
+

Casting the Response Data to a Class

+

It's possible to auto-cast a JSON response to a class. This is useful when we want to control access to certain properties in an object, as opposed to exposing all the fields in POJOs (plain old javascript objects) for arbitrary processing.

+

In the example below, User.list returns a list of User instances.

+
var User = function(data) {
+    this.name = m.prop(data.name);
+}
+
+User.list = function() {
+    return m.request({method: "GET", url: "/user", type: User});
+}
+
+var users = User.list();
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), `users` will contain a list of User instances
+//i.e. users()[0].name() == "John"
+
+

Unwrapping Response Data

+

Often, web services return the relevant data wrapped in objects that contain metadata.

+

Mithril allows you to unwrap the relevant data, by providing two callback hooks: unwrapSuccess and unwrapError.

+

These hooks allow you to unwrap different parts of the response data depending on whether it succeed or failed.

+
var users = m.request({
+    method: "GET",
+    url: "/user",
+    unwrapSuccess: function(response) {
+        return response.data;
+    },
+    unwrapError: function(response) {
+        return response.error;
+    }
+});
+
+//assuming the response is: `{data: [{name: "John"}, {name: "Mary"}], count: 2}`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}]
+
+

Using Different Data Transfer Formats

+

By default, m.request uses JSON to send and receive data to web services. You can override this by providing serialize and deserialize options:

+
var users = m.request({
+    method: "GET",
+    url: "/user",
+    serialize: mySerializer,
+    deserialize: myDeserializer
+});
+

One typical way to override this is to receive as-is responses. The example below shows how to receive a plain string from a txt file.

+
var file = m.request({
+    method: "GET",
+    url: "myfile.txt",
+    deserialize: function(value) {return value;}
+});
+
+

Signature

+

How to read signatures

+
Promise request(XHROptions options)
+
+where:
+    Promise :: GetterSetter { Promise then(any successCallback(any value), any errorCallback(any value)) }
+    GetterSetter :: any getterSetter([any value])
+    XHROptions :: Object {
+        String method,
+        String url,
+        [String user,]
+        [String password,]
+        [Object<any> data,]
+        [Response unwrapSuccess(Response data),]
+        [Response unwrapError(Response data),]
+        [String serialize(any dataToSerialize),]
+        [any deserialize(String dataToDeserialize),]
+        [void type(Object<any> data),]
+        [void config(XMLHttpRequest xhr, XHROptions options)]
+    }
+    Response :: Object<any> | Array<any>
+
    +
  • XHROptions options

    +

    A map of options for the XMLHttpRequest

    +
      +
    • String method

      +

      The HTTP method. Must be either "GET", "POST", "PUT", "DELETE", "HEAD" or "OPTIONS"

      +
    • +
    • String url

      +

      The URL to request. If the URL is not in the same domain as the application, the target server must be configured to accept cross-domain requests from the application's domain, i.e. its responses must include the header Access-Control-Allow-Origin: *.

      +
    • +
    • String user (optional)

      +

      A user for HTTP authentication. Defaults to undefined

      +
    • +
    • String password (optional)

      +

      A password for HTTP authentication. Defaults to undefined

      +
    • +
    • String password (optional)

      +

      A password for HTTP authentication. Defaults to undefined

      +
    • +
    • Object data (optional)

      +

      Data to be sent. It's automatically placed in the appropriate section of the request with the appropriate serialization based on method

      +
    • +
    • Response unwrapSuccess(Response data) (optional)

      +

      A preprocessor function to extract the data from a success response in case the response contains metadata wrapping the data.

      +

      The default value (if this parameter is falsy) is the identity function function(value) {return value}

      +

      For example, if the response is {data: [{name: "John"}, {name: "Mary"}]} and the unwrap function is function(response) {return response.data}, then the response will be considered to be [{name: "John"}, {name: "Mary"}] when processing the type parameter

      +
        +
      • Object | Array data

        +

        The data to unwrap

        +
      • +
      • returns Object | Array unwrappedData

        +

        The unwrapped data

        +
      • +
      +
    • +
    • String unwrapError(Response data) (optional)

      +

      A preprocessor function to extract the data from an error response in case the response contains metadata wrapping the data.

      +

      The default value (if this parameter is falsy) is the identity function function(value) {return value}

      +
        +
      • Object | Array data

        +

        The data to unwrap

        +
      • +
      • returns Object | Array unwrappedData

        +

        The unwrapped data

        +
      • +
      +
    • +
    • String serialize(any dataToSerialize) (optional)

      +

      Method to use to serialize the request data

      +

      The default value (if this parameter is falsy) is JSON.stringify

      +
        +
      • any dataToSerialize

        +

        Data to be serialized

        +
      • +
      • returns String serializedData

        +
      • +
      +
    • +
    • any deserialize(String dataToDeserialize) (optional)

      +

      Method to use to deserialize the response data

      +

      The default value (if this parameter is falsy) is JSON.parse

      +
        +
      • String dataToDeserialize

        +

        Data to be deserialized

        +
      • +
      • returns any deserializedData

        +
      • +
      +
    • +
    • void type(Object data) (optional)

      +

      The response object (or the child items if this object is an Array) will be passed as a parameter to the class constructor defined by type

      +

      If this parameter is falsy, the deserialized data will not be wrapped.

      +

      For example, if type is the following class:

      +
      var User = function(data) {
      +  this.name = m.prop(data.name);
      +}
      +

      And the data is [{name: "John"}, {name: "Mary"}], then the response will contain an array of two User instances.

      +
    • +
    • void config(XMLHttpRequest xhr, XHROptions options) (optional)

      +

      An initialization function that runs after open and before send. Useful for adding request headers and when using XHR2 features, such as the XMLHttpRequest's upload property.

      +
        +
      • XMLHttpRequest xhr

        +

        The XMLHttpRequest instance.

        +
      • +
      • XHROptions options

        +

        The options parameter that was passed into m.request call

        +
      • +
      +
    • +
    +
  • +
  • returns Promise promise

    +

    returns a promise that can bind callbacks which get called on completion of the AJAX request.

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/mithril.route.html b/archive/v0.1.4/mithril.route.html new file mode 100644 index 00000000..0e919bf8 --- /dev/null +++ b/archive/v0.1.4/mithril.route.html @@ -0,0 +1,222 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.route

+

Routing is a system that allows creating Single-Page-Applications (SPA), i.e. applications that can go from a page to another without causing a full browser refresh.

+

It enables seamless navigability while preserving the ability to bookmark each page individually, and the ability to navigate the application via the browser's history mechanism.

+

This method overloads 3 different units of functionality:

+
    +
  • m.route(rootElement, defaultRoute, routes) - defines the available URLs in an application, and their respective modules

    +
  • +
  • m.route(path) - redirects to another route

    +
  • +
  • m.route(element) - an extension to link elements that unobtrusively abstracts away the routing mode

    +
  • +
+

Routing is single-page-application (SPA) friendly, and can be implemented using either location.hash, HTML5 URL rewriting or location.querystring. See m.route.mode for the caveats of each implementation.

+
+

+

Defining routes

+

Usage

+

To define a list of routes, you need to specify a host DOM element, a default route and a key-value map of possible routes and respective modules to be rendered.

+

The example below defines 3 routes, to be rendered in <body>. home, login and dashboard are modules. We'll see how to define a module in a bit.

+
m.route(document.body, "/", {
+    "/": home,
+    "/login": login,
+    "/dashboard": dashboard,
+});
+

Routes can take arguments, by prefixing words with a colon :

+

The example below shows a route that takes an userID parameter

+
//a sample module
+var dashboard = {
+    controller: function() {
+        this.id = m.route.param("userID");
+    },
+    view: function(controller) {
+        m.render("body", controller.id);
+    }
+}
+
+//define a route
+m.route(document.body, "/dashboard/johndoe", {
+    "/dashboard/:userID": dashboard
+});
+
+//setup routes to start w/ the `#` symbol
+m.route.mode = "hash";
+

This redirects to the URL http://server/#/dashboard/johndoe and yields:

+
<body>johndoe</body>
+

Above, dashboard is a module. It contains a controller and a view properties. When the URL matches a route, the respective module's controller is instantiated and passed as a parameter to the view.

+

In this case, since there's only route, the app redirects to the default route "/dashboard/johndoe".

+

The string johndoe is bound to the :userID parameter, which can be retrived programmatically in the controller via m.route.param("userID").

+

The m.route.mode defines which part of the URL to use for routing.

+
+

Signature

+

How to read signatures

+
void route(DOMElement rootElement, String defaultRoute, Object<Module> routes) { String mode, String param(String key) }
+
+where:
+    Module :: Object { void controller(), void view(Object controllerInstance) }
+
    +
  • DOMElement root

    +

    A DOM element which will contain the view's template.

    +
  • +
  • String defaultRoute

    +

    The route to redirect to if the current URL does not match any of the defined routes

    +
  • +
  • Object routes

    +

    A key-value map of possible routes and their respective modules. Keys are expected to be absolute pathnames, but can include dynamic parameters. Dynamic parameters are words preceded by a colon :

    +

    {'/path/to/page/': pageModule} - a route with a basic pathname

    +

    {'/path/to/page/:id': pageModule} - a route with a pathname that contains a dynamic parameter called id. This route would be selected if the URL was /path/to/page/1, /path/to/page/test, etc

    +

    {'/user/:userId/book/:bookId': userBookModule} - a route with a pathname that contains two parameters

    +

    Dynamic parameters are wild cards that allow selecting a module based on a URL pattern. The values that replace the dynamic parameters in a URL are available via m.route.param()

    +

    Note that the URL component used to resolve routes is dependent on m.route.mode. By default, the querystring is considered the URL component to test against the routes collection

    +

    If the current page URL matches a route, its respective module is activated. See m.module for information on modules.

    +
  • +
  • +

    m.route.mode

    +

    String mode

    +

    The m.route.mode property defines which URL portion is used to implement the routing mechanism. Its value can be set to either "search", "hash" or "pathname". Default value is "search"

    +
      +
    • search mode uses the querystring. This allows named anchors (i.e. <a href="#top">Back to top</a>, <a name="top"></a>) to work on the page, but routing changes causes page refreshes in IE8, due to its lack of support for history.pushState.

      +

      Example URL: http://server/?/path/to/page

      +
    • +
    • hash mode uses the hash. It's the only mode in which routing changes do not cause page refreshes in any browser. However, this mode does not support named anchors.

      +

      Example URL: http://server/#/path/to/page

      +
    • +
    • pathname mode allows routing URLs that contains no special characters, however this mode requires server-side setup in order to support bookmarking and page refreshes. It also causes page refreshes in IE8.

      +

      Example URL: http://server/path/to/page

      +

      The simplest server-side setup possible to support pathname mode is to serve the same content regardless of what URL is requested. In Apache, this URL rewriting can be achieved using ModRewrite.

      +
    • +
    +
  • +
  • +

    m.route.param

    +

    String param(String key)

    +

    Route parameters are dynamic values that can be extracted from the URL based on the signature of the currently active route.

    +

    A route without parameters looks like this:

    +

    "/path/to/page/"

    +

    A route with parameters might look like this:

    +

    "/path/to/page/:id" - here id is the name of the route parameter

    +

    If the currently active route is /dashboard/:userID and the current URL is /dashboard/johndoe, then calling m.route.param("userID") returns "johndoe"

    +
      +
    • String key

      +

      The name of a route parameter

      +
    • +
    • returns String value

      +

      The value that maps to the parameter specified by key

      +
    • +
    +
  • +
+
+

+

Redirecting

+

Usage

+

You can programmatically redirect to another page. Given the example in the "Defining Routes" section:

+
m.route("/dashboard/marysue");
+

redirects to http://server/#/dashboard/marysue

+
+

Signature

+

How to read signatures

+
void route(String path)
+
    +
  • String path

    +

    The route to redirect to. Note that to redirect to a different page outside of the scope of Mithril's routing, you should use window.location

    +
  • +
+
+

+

Mode abstraction

+

Usage

+

This method is meant to be used with a virtual element's config attribute. For example:

+
//Note that the '#' is not required in `href`, thanks to the `config` setting.
+m("a[href='/dashboard/alicesmith']", {config: m.route});
+

This makes the href behave correctly regardless of which m.route.mode is selected. It's a good practice to always use the idiom above, instead of hardcoding ? or # in the href attribute.

+

See m() for more information on virtual elements.

+
+

Signature

+

How to read signatures

+
void route(DOMElement element, Boolean isInitialized)
+
    +
  • DOMElement element

    +

    an anchor element <a> with an href attribute that points to a route

    +
  • +
  • Boolean isInitialized

    +

    the method does not run if this flag is set to true. This is to make the method compatible with virtual DOM elements' config attribute (see m())

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/mithril.sync.html b/archive/v0.1.4/mithril.sync.html new file mode 100644 index 00000000..d502d779 --- /dev/null +++ b/archive/v0.1.4/mithril.sync.html @@ -0,0 +1,112 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.sync

+

This method takes a list of promises and returns a promise that resolves when all promises in the input list have resolved. See m.deferred for more information on promises.

+
+

Usage

+
var greetAsync = function(delay) {
+    var deferred = m.deferred();
+    setTimeout(function() {
+        deferred.resolve("hello");
+    }, delay);
+    return deferred.promise;
+};
+
+m.sync([
+    greetAsync(1000),
+    greetAsync(1500)
+]).then(function(args) {
+    console.log(args); // ["hello", "hello"]
+});
+
+

Signature

+

How to read signatures

+
Promise sync(Array<Promise> promises)
+
+where:
+    Promise :: GetterSetter { Promise then(any successCallback(any value), any errorCallback(any value)) }
+    GetterSetter :: any getterSetter([any value])
+
    +
  • Array promises

    +

    A list of promises to synchronize

    +
  • +
  • return Promise promise

    +

    The promise of the deferred object that is resolved when all input promises have been resolved

    +

    The callbacks for this promise receive as a parameter an Array containing the values of all the input promises

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/mithril.trust.html b/archive/v0.1.4/mithril.trust.html new file mode 100644 index 00000000..d49d5ca6 --- /dev/null +++ b/archive/v0.1.4/mithril.trust.html @@ -0,0 +1,121 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.trust

+

If you're writing a template for a view, use m() instead.

+

This method flags a string as trusted HTML.

+

Trusted HTML is allowed to render arbitrary, potentially invalid markup, as well as run arbitrary javascript, and therefore the developer is responsible for either:

+
    +
  • sanitizing the markup contained in the string, or

    +
  • +
  • acknowledging that the string is authorized to run any code that may be contained within it.

    +
  • +
+

Note that browsers ignore <script> tags that have been inserted into the DOM via innerHTML. They do this because once the element is ready (and thus, has an accessible innerHTML property), their rendering engines cannot backtrack to the parsing-stage if the script calls something like document.write("</body>").

+

For this reason, m.trust will not auto-run <script> tags from trusted strings.

+

Browsers do, however, allow scripts to be run asynchronously via a number of execution points, such as the onload or onerror attributes in <img> and <iframe>.

+

IE also allows running of javascript via CSS behaviors in <link>/<style> tags and style attributes.

+

It's worth noting that the execution points listed above are commonly used for security attacks in combination with malformed markup, e.g. strings with mismatched attribute quotes like " onload="alert(1).

+

Mithril templates are defended against these attacks by default, except when markup is injected via m.trust.

+

It is the developer's responsibility to ensure the input to m.trust cannot be maliciously modified by user-entered data.

+
+

Usage

+
//assume this content comes from the server
+var content = "<h1>Error: invalid user</h1>";
+
+m.render("body", [
+    m("div", m.trust(content))
+]);
+

yields:

+
<body>
+    <div>
+        <h1>Error: invalid user</h1>
+    </div>
+</body>
+
+

Signature

+

How to read signatures

+
String trust(String html)
+
    +
  • String html

    +

    A string containing HTML markup

    +
  • +
  • returns String trustedHtml

    +

    The returned string is a String object instance (as opposed to a string primitive) containing the same html content, and exposing a flag property for internal use within Mithril. Do not create or manipulate trust flags manually.

    +

    Also note that concatenating or splitting a trusted string removes the trust flag. If doing such operations, the final string needs to be flagged as trusted.

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/mithril.withAttr.html b/archive/v0.1.4/mithril.withAttr.html new file mode 100644 index 00000000..80039808 --- /dev/null +++ b/archive/v0.1.4/mithril.withAttr.html @@ -0,0 +1,126 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

m.withAttr

+

This is an event handler factory. It returns a method that can be bound to a DOM element's event listener.

+

Typically, it's used in conjunction with m.prop to implement data binding in the view-to-model direction.

+

This method is provided to decouple the browser's event model from the controller/logic model.

+

You should use this method and implement similar ones when extracting values from a browser's Event object, instead of hard-coding the extraction code into controllers (or model methods).

+
+

Usage

+
//standalone usage
+document.body.onclick = m.withAttr("title", function(value) {
+    //alerts the title of the body element when it's clicked
+    alert(value);
+})
+

A contrived example of bi-directional data binding

+
var user = {
+    model: function(name) {
+        this.name = m.prop(name);
+    },
+    controller: function() {
+        this.user = new user.model("John Doe");
+    },
+    view: function(controller) {
+        m.render("body", [
+            m("input", {onchange: m.withAttr("value", controller.user.name), value: controller.user.name()})
+        ]);
+    }
+};
+
+

Signature

+

How to read signatures

+
EventHandler withAttr(String property, void callback(any value))
+
+where:
+    EventHandler :: void handler(Event e)
+
    +
  • String property

    +

    Defines the property of the DOM element whose value will be passed to the callback.

    +
  • +
  • void callback(any value)

    +

    This function will be called with the value of the defined property as an argument.

    +
      +
    • any value

      +

      This is the value of the defined DOM element's property.

      +
    • +
    +
  • +
  • returns EventHandler handler

    +

    This handler method can be assigned to properties like onclick, or passed as callbacks to addEventListener.

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/mithril.xhr.html b/archive/v0.1.4/mithril.xhr.html new file mode 100644 index 00000000..b843d168 --- /dev/null +++ b/archive/v0.1.4/mithril.xhr.html @@ -0,0 +1,77 @@ + + + + Mithril + + + + + +
+ +
+
+
+ +
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/package.json b/archive/v0.1.4/package.json new file mode 100644 index 00000000..be8cd283 --- /dev/null +++ b/archive/v0.1.4/package.json @@ -0,0 +1,11 @@ +{ + "name": "mithril", + "description": "A Javascript Framework for building brilliant applications", + "keywords": ["mvc", "framework"], + "version": "0.1.4", + "author": "Leo Horie ", + "repository": {"type": "git", "url": "https://github.com/lhorie/mithril"}, + "main": "mithril.min.js", + "licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}], + "files": ["mithril.min.js", "mithril.min.map", "mithril.js"] +} \ No newline at end of file diff --git a/archive/v0.1.4/pages.json b/archive/v0.1.4/pages.json new file mode 100644 index 00000000..788db1c5 --- /dev/null +++ b/archive/v0.1.4/pages.json @@ -0,0 +1,4 @@ +[ +{"title": "Getting Started", "url": "getting-started.html"}, +{"title": "Documentation", "url": "mithril.html"} +] \ No newline at end of file diff --git a/archive/v0.1.4/practices.html b/archive/v0.1.4/practices.html new file mode 100644 index 00000000..3eccb669 --- /dev/null +++ b/archive/v0.1.4/practices.html @@ -0,0 +1,119 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

How Should Code Be Organized

+

While Mithril doesn't dictate how to organize your code, it does provide some recommendations for structuring it.

+

As a rule of thumb, controllers should not change model entity properties on an individual basis.

+

Data manipulation should be done in model classes, such that controllers never have entities lying around in temporarily invalid states.

+

Mithril's design strongly encourages all entity logic to be handled in atomic model layer methods (in the sense of entity state stability).

+

In fact, unavoidable abstraction leaks (such as network-bound asynchrony) are laid out in such a way as to make idiomatic code organization elegant, and conversely, to make it so that the abstraction leak problems themselves discourage attempts to misplace entity logic in the controller.

+

This design decision comes from experience with DRY and "bus factor" of large, highly relational model layers.

+

This is in stark contrast to the ActiveRecord pattern of other frameworks, where model entities are largely object representations of database entities and these entities are manipulated in controllers in an ad-hoc field-by-field fashion, and the "committed" via a save method.

+

Because Mithril encourages all entity logic to be done in the model layer, it's idiomatic to create modules with model-level classes that deal specifically with relationship between entities, when there isn't already a model entity that can logically hold the relational business logic.

+

Models are also responsible for centralizing tasks such as filtering of entity lists and validation routines, so that access to these methods is available across the application.

+

DOM manipulation should be done in the view via m() and config. Controllers may explicitly call m.redraw, but, if possible, it's preferable to abstract this into a service which integrates w/ Mithril's auto-redrawing system (see. m.startComputation / m.endComputation)

+
+

File Separation

+

The examples in this site usually conflate different MVC layers together for the sake of readability, but normally it's recommended that each layer on a module be in different files. For example:

+
//app.model.js
+var app = {};
+
+app.PageList = function() {
+    return m.request({method: "GET", url: "pages.json"});
+};
+
//app.controller.js
+app.controller = function() {
+    this.pages = new app.PageList();
+};
+
//app.view.js
+app.view = function(ctrl) {
+    return ctrl.pages().map(function(page) {
+        return m("a", {href: page.url}, page.title);
+    });
+};
+

You can use task automation tools such as GruntJS to concatenate the files back together for production.

+

Typically, when separating MVC layers, it's common that the namespace declaration be in the model layer, since this is usually the most used dependency for the other layers.

+

You may choose to declare the namespace in a separate file or have the build system generate it on demand, instead.

+

You should avoid grouping classes by the MVC layer they belong to, i.e. don't create 3 files called model.js, controllers.js and views.js.

+

That organization pattern needlessly ties unrelated aspects of the application together and dilutes the clarity of modules.

+
+

Global Namespace Hygiene

+

For developer convenience, Mithril uses the global m variable as a namespace, much like jQuery uses $.

+

If you want to ensure global namespace hygiene, you can wrap your code in "islands" like this:

+
new function(m) {
+
+    //your code goes here
+
+}(Mithril);
+

If you are creating components to be used by 3rd parties, it's recommended that you always use this idiom.

+

In the unlikely case that you have another global variable called m in your page, you should consider renaming it to make it more descriptive, or use the idiom below to keep it intact.

+
<script>_temp = m</script>
+<script src="mithril.js"></script>
+<script>m = _temp</script>
+
+

Usage of m.redraw

+

m.redraw is a method that allows you to render a template outside the scope of Mithril's auto-redrawing system.

+

Calling of 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 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.

+

If you call this method more often than that, Mithril may ignore calls or defer them to the next browser repaint cycle.

+

If calls are more expensive than a repaint window, the browser may drop frames, resulting in choppy animations. It's your responsibility to make sure single iterations of animation rendering code don't take longer than 16ms (for a frequency of 60 frames-per-second).

+

In addition, note that template performance, both in Mithril templates as well as in general, is dependent on markup complexity. You are responsible for ensuring that templates aren't too big to render efficiently.

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/refactoring.html b/archive/v0.1.4/refactoring.html new file mode 100644 index 00000000..ffa5f4aa --- /dev/null +++ b/archive/v0.1.4/refactoring.html @@ -0,0 +1,64 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Refactoring

+

Below are some common refactoring patterns:

+

Porting legacy code to Mithril

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/roadmap.html b/archive/v0.1.4/roadmap.html new file mode 100644 index 00000000..555df026 --- /dev/null +++ b/archive/v0.1.4/roadmap.html @@ -0,0 +1,104 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Roadmap

+

Things that would be useful to have (though likely not as part of Mithril core)

+

Utilities

+
    +
  • Formatters / parsers
      +
    • i18n
    • +
    • Date
        +
      • Absolute (e.g. Jan 1, 1970 12:00 AM)
      • +
      • Relative (e.g. 10 days ago)
      • +
      +
    • +
    • Number (e.g. 1,234.5)
    • +
    • Currency (e.g. $1,000.00)
    • +
    • Word wrap
    • +
    +
  • +
  • Dependency management
  • +
  • Functional / relational tools
  • +
  • Animation
  • +
+

Components

+
    +
  • Autocompleter
  • +
  • Date/time picker
  • +
  • Swipe-to-show panel
  • +
  • Tree
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/routing.html b/archive/v0.1.4/routing.html new file mode 100644 index 00000000..40d1904e --- /dev/null +++ b/archive/v0.1.4/routing.html @@ -0,0 +1,128 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Routing

+

Routing is a system that allows creating Single-Page-Applications (SPA), i.e. applications that can go from a page to another without causing a full browser refresh.

+

It enables seamless navigability while preserving the ability to bookmark each page individually, and the ability to navigate the application via the browser's history mechanism.

+

Mithril provides utilities to handle three different aspect of routing:

+
    +
  • defining a list of routes
  • +
  • programmatically redirecting between routes
  • +
  • making links in templates routed transparently and unobtrusively
  • +
+
+

Defining routes

+

To define a list of routes, you need to specify a host DOM element, a default route and a key-value map of possible routes and respective modules to be rendered.

+

The example below defines 3 routes, to be rendered in <body>. home, login and dashboard are modules. We'll see how to define a module in a bit.

+
m.route(document.body, "/", {
+    "/": home,
+    "/login": login,
+    "/dashboard": dashboard,
+});
+

Routes can take arguments, by prefixing words with a colon :

+

The example below shows a route that takes an userID parameter

+
//a sample module
+var dashboard = {
+    controller: function() {
+        this.id = m.route.param("userID");
+    },
+    view: function(controller) {
+        m.render("body", controller.id);
+    }
+}
+
+//define a route
+m.route(document.body, "/dashboard/johndoe", {
+    "/dashboard/:userID": dashboard
+});
+
+//setup routes to start w/ the `#` symbol
+m.route.mode = "hash";
+

This redirects to the URL http://server/#/dashboard/johndoe and yields:

+
<body>johndoe</body>
+

Above, dashboard is a module. It contains a controller and a view properties. When the URL matches a route, the respective module's controller is instantiated and passed as a parameter to the view.

+

In this case, since there's only route, the app redirects to the default route "/dashboard/johndoe" and, under the hood, it calls m.module(document.body, dashboard).

+

The string johndoe is bound to the :userID parameter, which can be retrived programmatically in the controller via m.route.param("userID").

+

The m.route.mode property defines which URL portion is used to implement the routing mechanism. Its value can be set to either "search", "hash" or "pathname". The default value is "search"

+
    +
  • search mode uses the querystring. This allows named anchors (i.e. <a href="#top">Back to top</a>, <a name="top"></a>) to work on the page, but routing changes causes page refreshes in IE8, due to its lack of support for history.pushState.

    +

    Example URL: http://server/?/path/to/page

    +
  • +
  • hash mode uses the hash. It's the only mode in which routing changes do not cause page refreshes in any browser. However, this mode does not support named anchors and browser history lists.

    +

    Example URL: http://server/#/path/to/page

    +
  • +
  • pathname mode allows routing URLs that contains no special characters, however this mode requires server-side setup in order to support bookmarking and page refreshes. It also causes page refreshes in IE8.

    +

    Example URL: http://server/path/to/page

    +

    The simplest server-side setup possible to support pathname mode is to serve the same content regardless of what URL is requested. In Apache, this URL rewriting can be achieved using ModRewrite.

    +
  • +
+
+

Redirecting

+

You can programmatically redirec to another page. Given the example in the "Defining Routes" section:

+
m.route("/dashboard/marysue");
+

redirects to http://server/#/dashboard/marysue

+
+

Mode abstraction

+

This method is meant to be used with a virtual element's config attribute. For example:

+
//Note that the '#' is not required in `href`, thanks to the `config` setting.
+m("a[href='/dashboard/alicesmith']", {config: m.route});
+

This makes the href behave correctly regardless of which m.route.mode is selected. It's a good practice to always use the idiom above, instead of hardcoding ? or # in the href attribute.

+

See m() for more information on virtual elements.

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/style.css b/archive/v0.1.4/style.css new file mode 100644 index 00000000..d24d0aaa --- /dev/null +++ b/archive/v0.1.4/style.css @@ -0,0 +1,91 @@ +.container {margin:auto;max-width:1000px;padding:0 20px;position:relative;} +.container:after,.row:after {content:"";display:table;clear:both;} +.container,.row,[class*='col('] {-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;} +[class*='col('] {float:left;} +[class*='(3,'] {width:25%;} +[class*='(4,'] {width:33.33333%;} +[class*='(6,'] {width:50%;} +[class*='(8,'] {width:66.66667%;} +[class*='(9,'] {width:75%;} +@media (max-width:1000px) { +[class*=',3,'] {width:25%;} +[class*=',4,'] {width:33.33333%;} +[class*=',6,'] {width:50%;} +[class*=',8,'] {width:66.66667%;} +[class*=',9,'] {width:75%;} +} +@media (max-width:750px) { +[class*=',6)'] {width:50%;} +[class*=',12)'] {width:100%;} +} + +html {background:#999;color:#222;font:14px Helvetica;} +html,body {margin:0;padding:0;} +header,footer {background:#999;} +nav {text-align:right;} +nav a:first-child,nav a:first-child:visited {color:#fff;font-size:27px;float:left;line-height:1.3em;padding:0;text-decoration:none;} +nav a {color:#fff;display:inline-block;padding:10px;} +nav a:visited {color:#ddd;} +footer {text-align:center;padding:10px 0;} +footer,footer a,footer a:visited {color:#fff;} +h1,h2 {font-family:Palatino;margin:0 0 10px;} +h1 {font-size:3em;text-shadow:0.024em 0.024em #777, -0.024em -0.024em #fff;} +h1 span {animation:logo 2s;display:inline-block;-webkit-animation:logo 2s;} +h2 {color:#888;font-style:italic;} +h3 {margin:10px 0;} +p {margin:15px 0;} +ul {margin:15px 0;padding:0 0 0 1em;} +li {margin:0 0 10px;} +a {color:#161;} +a:visited {color:#383;} +a:hover {text-decoration:none;} +pre {background:#ffe;border:1px solid #ddd;overflow:auto;margin:0 0 15px;padding:5px 10px;white-space:pre;-webkit-overflow-scrolling:touch;} +pre[class*="language-"],code {background:#ffe;font:12px/15px Lucida Console,Monaco,monospace;} +hr {border-top:1px solid #ccc;border-width:1px 0 0;margin:20px 0;} +table {margin:0 0 10px;width:100%;} +.cta {padding:30px 0 20px;text-align:center;} +.cta { +background: +linear-gradient(27deg, #e5e5e5 5px, rgba(255,255,255,0) 5px) 0 5px, +linear-gradient(207deg, #e5e5e5 5px, rgba(255,255,255,0) 5px) 10px 0px, +linear-gradient(27deg, #f2f2f2 5px, rgba(255,255,255,0) 5px) 0px 10px, +linear-gradient(207deg, #f2f2f2 5px, rgba(255,255,255,0) 5px) 10px 5px, +linear-gradient(90deg, #ebebeb 10px, rgba(255,255,255,0) 10px), +linear-gradient(#ededed 25%, #eaeaea 25%, #eaeaea 50%, rgba(255,255,255,0) 50%, rgba(255,255,255,0) 75%, #f4f4f4 75%, #f4f4f4); +background-color: #e3e3e3; +background-size: 20px 20px; +} +.logo {color:#d3d3d3;font-family:Georgia;font-style:italic;} +.logo span {font-family:Arial;font-style:normal;} +.logo :before {content:"\25CB";position:absolute;margin:-0.17em 0 0 -0.10em;} +.logo :after {content:"\25CB";position:absolute;margin:-0.17em 0 0 -0.5em;} +.button,.button:visited {background:#5a5;border-radius:5px;box-shadow:1px 1px #777, -1px -1px #fff;color:#fff;display:inline-block;font:normal bold 16px Helvetica;margin:0 10px 10px;padding:10px 30px;text-decoration:none;} +.features {background:#fff;padding:30px 0 0;} +.feature {margin:0 0 30px;padding:0 20px 0 0;} +.sample {background:#f5f5f5;padding:30px 0 10px;} +.example {background:#ffe;border:1px solid #ddd;display:block;font:Courier New;margin-bottom:20px;padding:5px 10px;} +.example span {color:#383;font-weight:bold;} +.example small {color:#888;font-size:1em;} +.more {background:#ddd;padding:30px 0;} +.output a,.more a {display:block;margin:0 0 10px;} +.output a:after,.more a:after {content:" \bb";} +.performance {background:#fff;padding:30px 0;} +.performance td:first-child {text-align:right;width:1%;} +.bar {background:red;height:4px;float:left;margin:0.5em 1em 0 0;} +.security {background:#f5f5f5;padding:30px 0;} +.success {color:#383;} +.error {color:#f00;} +.content {background:#f5f5f5;padding:30px 0;} + +@media (min-width:750px) { +.sample pre {margin-right:20px;} +} + +@keyframes logo { + from {opacity:0;transform:scale(2) rotate(359deg);} + to {opacity:1;transform:scale(1) rotate(0deg);} +} +@-webkit-keyframes logo { + from {opacity:0;-webkit-transform:scale(2) rotate(359deg);} + to {opacity:1;-webkit-transform:scale(1) rotate(0deg);} +} \ No newline at end of file diff --git a/archive/v0.1.4/tools.html b/archive/v0.1.4/tools.html new file mode 100644 index 00000000..f56681ee --- /dev/null +++ b/archive/v0.1.4/tools.html @@ -0,0 +1,95 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Tools

+

HTML-to-Mithril Template Converter

+

If you already have your HTML written and want to convert it into a Mithril template, you can use the tool below for one-off manual conversion.

+

Template Converter

+
+

Automatic HTML-to-Mithril Template Converter

+

There's a tool called MSX by Jonathan Buchanan that allows you to write templates using HTML syntax, and then automatically compile them to Javascript when files change.

+

It is useful for teams where styling and functionality are done by different people, and for those who prefer to maintain templates in HTML syntax.

+

The tool allows you to write code like this:

+
todo.view = function(ctrl) {
+    return <html>
+        <body>
+            <input onchange={m.withAttr("value", ctrl.description)} value={ctrl.description()}/>
+            <button onclick={ctrl.add.bind(ctrl, ctrl.description)}>Add</button>
+        </body>
+    </html>
+};
+

Note, however, that since the code above is not valid Javascript, this syntax can only be used with a preprocessor build tool such as the provided Gulp.js script.

+
+

Mithril Template Compiler

+

You can pre-compile Mithril templates to make them run faster. For more information see this page:

+

Compiling Templates

+
+

Internet Explorer Compatibility

+

Mithril relies on some Ecmascript 5 features, namely: Array::indexOf and Object::keys, as well as the JSON object.

+

You can use polyfill libraries to support these features in IE7.

+ +

Mithril also has a dependency on XMLHttpRequest. If you wish to support IE6, you'll need a shim for it. IE7 and lower do not support cross-domain AJAX requests.

+

In addition, note that most m.route modes rely on history.pushState in order to allow moving from one page to another without a browser refresh. IE9 and lower do not support this feature and will gracefully degrade to page refreshes instead.

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.4/tools/template-compiler.sjs b/archive/v0.1.4/tools/template-compiler.sjs new file mode 100644 index 00000000..4d5924c0 --- /dev/null +++ b/archive/v0.1.4/tools/template-compiler.sjs @@ -0,0 +1,64 @@ +/* +Compiles Mithril templates + +Requires sweet.js (https://github.com/mozilla/sweet.js) +Installation: npm install -g sweet.js +Usage: sjs --module /mithril.compile.sjs --output .js .js +*/ + +macro m { + case { _ ($selector) } => { + return #{m($selector, {}, [])}; + } + case { _ ($selector, $partial) } => { + var partialSyntax = #{$partial}; + var partial = unwrapSyntax(partialSyntax); + return partial.value == "{}" ? #{m($selector, $partial, [])} : #{m($selector, {}, partial)}; + } + case { _ ($selector, $dynAttrs, $children) } => { + var selectorSyntax = #{$selector}; + var selector = unwrapSyntax(selectorSyntax); + + var dynAttrsSyntax = #{$dynAttrs}; + var dynAttrs = unwrapSyntax(dynAttrsSyntax); + + var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g; + var attrParser = /\[(.+?)=("|'|)(.+?)\2\]/; + var _match = null; + var classes = []; + var cell = {tag: "div", attrs: {}, children: []}; + + while (_match = parser.exec(selector)) { + if (_match[1] == "") 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]; + } + } + if (classes.length > 0) cell.attrs["class"] = classes.join(" "); + + var tag = makeValue(cell.tag, #{here}); + var attrsBody = Object.keys(cell.attrs).reduce(function(memo, attrName) { + return memo.concat([ + makeValue(attrName, #{here}), + makePunc(":", #{here}), + makeValue(cell.attrs[attrName], #{here}), + makePunc(",", #{here}) + ]); + }, []).concat(dynAttrs.inner); + var attrs = [makeDelim("{}", attrsBody, #{here})]; + var children = cell.children.map(function(child) { + return makeValue(child, #{here}); + }) + letstx $tag = [tag], $attrs = attrs; + + return #{ ({tag: $tag, attrs: $attrs , children: $children}) }; + } + case { _ } => { + return #{Mithril}; + } +} + +export m; \ No newline at end of file diff --git a/archive/v0.1.4/tools/template-converter.html b/archive/v0.1.4/tools/template-converter.html new file mode 100644 index 00000000..e3ec7de5 --- /dev/null +++ b/archive/v0.1.4/tools/template-converter.html @@ -0,0 +1,9 @@ +

If you already have your HTML written and want to convert it into a Mithril template, paste the HTML below and press the "Convert" button.

+ +
+ + + + \ No newline at end of file diff --git a/archive/v0.1.4/tools/template-converter.js b/archive/v0.1.4/tools/template-converter.js new file mode 100644 index 00000000..5501df8e --- /dev/null +++ b/archive/v0.1.4/tools/template-converter.js @@ -0,0 +1,89 @@ +var templateConverter = {}; + +templateConverter.DOMFragment = function(markup) { + if (markup.indexOf(" -1) return [new DOMParser().parseFromString(markup, "text/html").childNodes[1]] + var container = document.createElement("div"); + container.insertAdjacentHTML("beforeend", markup); + return container.childNodes; +} +templateConverter.VirtualFragment = function recurse(domFragment) { + var virtualFragment = []; + for (var i = 0, el; el = domFragment[i]; i++) { + if (el.nodeType == 3) { + virtualFragment.push(el.nodeValue); + } + else { + var attrs = {}; + for (var j = 0, attr; attr = el.attributes[j]; j++) { + attrs[attr.name] = attr.value; + } + + virtualFragment.push({tag: el.tagName.toLowerCase(), attrs: attrs, children: recurse(el.childNodes)}); + } + } + return virtualFragment; +} +templateConverter.Template = function recurse() { + if (Object.prototype.toString.call(arguments[0]) == "[object String]") { + return new recurse(new templateConverter.VirtualFragment(new templateConverter.DOMFragment(arguments[0]))); + } + + var virtualFragment = arguments[0], level = arguments[1] + if (!level) level = 1; + + var tab = "\n" + new Array(level + 1).join("\t"); + var virtuals = []; + for (var i = 0, el; el = virtualFragment[i]; i++) { + if (typeof el == "string") { + if (el.match(/\t| {2,}/g) && el.trim().length == 0) virtuals.indented = true; + else virtuals.push('"' + el.replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n") + '"'); + } + else { + var virtual = ""; + if (el.tag != "div") virtual += el.tag; + if (el.attrs["class"]) { + virtual += "." + el.attrs["class"].replace(/\t+/g, " ").split(" ").join("."); + delete el.attrs["class"]; + } + var attrNames = Object.keys(el.attrs).sort() + for (var j = 0, attrName; attrName = attrNames[j]; j++) { + if (attrName != "style") virtual += "[" + attrName + "='" + el.attrs[attrName].replace(/'/g, "\\'") + "']"; + } + virtual = '"' + virtual + '"'; + + var style = "" + if (el.attrs.style) { + virtual += ", {style: " + ("{\"" + el.attrs.style.replace(/:/g, "\": \"").replace(/;/g, "\", \"") + "}").replace(/, "}|"}/, "}") + "}" + } + + if (el.children.length > 0) { + virtual += ", " + recurse(el.children, level + 1); + } + virtual = "m(" + virtual + ")"; + virtuals.push(virtual); + } + } + if (!virtuals.indented) tab = ""; + + var isInline = virtuals.length == 1 && virtuals[0].charAt(0) == '"'; + var template = isInline ? virtuals.join(", ") : "[" + tab + virtuals.join("," + tab) + tab.slice(0, -1) + "]"; + return new String(template); +} + +templateConverter.controller = function() { + this.source = m.prop(""); + this.output = m.prop(""); + + this.convert = function() { + return this.output(new templateConverter.Template(this.source())); + }; + +}; + +templateConverter.view = function(ctrl) { + return [ + m("textarea", {autofocus: true, style: {width:"100%", height: "40%"}, onchange: m.withAttr("value", ctrl.source)}, ctrl.source()), + m("button", {onclick: ctrl.convert.bind(ctrl)}, "Convert"), + m("textarea", {style: {width:"100%", height: "40%"}}, ctrl.output()) + ]; +}; \ No newline at end of file diff --git a/archive/v0.1.4/web-services.html b/archive/v0.1.4/web-services.html new file mode 100644 index 00000000..aa1b0141 --- /dev/null +++ b/archive/v0.1.4/web-services.html @@ -0,0 +1,213 @@ + + + + Mithril + + + + + +
+ +
+
+
+
+
+ +
+

Web Services

+

Mithril provides a high-level utility for working with web services, which allows writing asynchronous code relatively procedurally.

+

It provides a number of useful features out of the box:

+
    +
  • The ability to get an early reference to a container that will hold the asynchronous response
  • +
  • The ability to queue operations to be performed after the asynchronous request completes
  • +
  • The ability to "cast" the response to a class of your choice
  • +
  • The ability to unwrap data in a response that includes metadata properties
  • +
+
+

Basic usage

+

The basic usage pattern for m.request returns an m.prop getter-setter, which is populated when the AJAX request completes.

+

The returned getter-setter can be thought of as a box: you can pass this reference around cheaply, and you can "unwrap" its value when needed.

+
var users = m.request({method: "GET", url: "/user"});
+
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}]
+

Note that this getter-setter holds an undefined value until the AJAX request completes. Attempting to unwrap its value early will likely result in errors.

+

The returned getter-setter also implements the promise interface (also known as a thennable): this is the mechanism you should always use to queue operations to be performed on the data from the web service.

+

The simplest use case of this feature is to implement functional value assignment via m.prop (i.e. the same thing as above). You can bind a pre-existing getter-setter by passing it in as a parameter to a .then method:

+
var users = m.prop([]); //default value
+
+m.request({method: "GET", url: "/user"}).then(users)
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}]
+

This syntax allows you to bind intermediate results before piping them down for further processing, for example:

+
var users = m.prop([]); //default value
+var doSomething = function() { /*...*/ }
+
+m.request({method: "GET", url: "/user"}).then(users).then(doSomething)
+

While both basic assignment syntax and thennable syntax can be used to the same effect, typically it's recommended that you use the assignment syntax in the first example whenever possible, as it's easier to read.

+

The thennable mechanism is intended to be used in 3 ways:

+
    +
  • in the model layer: to process web service data in transformative ways (e.g. filtering a list based on a parameter that the web service doesn't support)
  • +
  • in the controller layer: to bind redirection code upon a condition
  • +
  • in the controller layer: to bind error messages
  • +
+

Processing web service data

+

This step is meant to be done in the model layer. Doing it in the controller level is also possible, but philosophically not recommended, because by tying logic to a controller, the code becomes harder to reuse due to unrelated controller dependencies.

+

In the example below, the listEven method returns a getter-setter that resolves to a list of users containing only users whose id is even.

+
//model
+var User = {}
+
+User.listEven = function() {
+    return m.request({method: "GET", url: "/user"}).then(function(list) {
+        return list.filter(function(user) {return user.id % 2 == 0});
+    });
+}
+
+//controller
+var controller = function() {
+    this.users = User.listEven()
+}
+

Bind redirection code

+

This step is meant to be done in the controller layer. Doing it in the model level is also possible, but philosophically not recommended, because by tying redirection to the model, the code becomes harder to reuse due to overly tight coupling.

+

In the example below, we use the previously defined listEven model method and queue a controller-level function that redirects to another page if the user list is empty.

+
//controller
+var controller = function() {
+    this.users = User.listEven().then(function(users) {
+        if (users.length == 0) m.route("/add");
+    })
+}
+

Binding errors

+

Mithril thennables take two functions as optional parameters: the first parameter is called if the web service request completes successfully. The second one is called if it completes with an error.

+

Error binding is meant to be done in the controller layer. Doing it in the model level is also possible, but generally leads to more code in order to connect all the dots.

+

In the example below, we bind an error getter-setter to our previous controller so that the error variable gets populated if the server throws an error.

+
//controller
+var controller = function() {
+    this.error = m.prop("")
+
+    this.users = User.listEven().then(function(users) {
+        if (users.length == 0) m.route("/add");
+    }, this.error)
+}
+

If the controller doesn't already have a success callback to run after a request resolves, you can still bind errors like this:

+
//controller
+var controller = function() {
+    this.error = m.prop("")
+
+    this.users = User.listEven().then(null, this.error)
+}
+
+

Queuing Operations

+

As you saw, you can chain operations that act on the response data. Typically this is required in three situations:

+
    +
  • in model-level methods if client-side processing is needed to make the data useful for a controller or view.
  • +
  • in the controller, to redirect after a model service resolves.
  • +
  • in the controller, to bind error messages
  • +
+

In the example below, we take advantage of queuing to debug the ajax response data prior to doing further processing on the user list

+
var users = m.request({method: "GET", url: "/user"})
+    .then(console.log);
+    .then(function(users) {
+        //add one more user to the response
+        return users.concat({name: "Jane"})
+    })
+
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}, {name: "Jane"}]
+
+

Casting the Response Data to a Class

+

It's possible to auto-cast a JSON response to a class. This is useful when we want to control access to certain properties in an object, as opposed to exposing all the fields in POJOs (plain old javascript objects) for arbitrary processing.

+

In the example below, User.list returns a list of User instances.

+
var User = function(data) {
+    this.name = m.prop(data.name);
+}
+
+User.list = function() {
+    return m.request({method: "GET", url: "/user", type: User});
+}
+
+var users = User.list();
+//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
+//then when resolved (e.g. in a view), `users` will contain a list of User instances
+//i.e. users()[0].name() == "John"
+
+

Unwrapping Response Data

+

Often, web services return the relevant data wrapped in objects that contain metadata.

+

Mithril allows you to unwrap the relevant data, by providing two callback hooks: unwrapSuccess and unwrapError.

+

These hooks allow you to unwrap different parts of the response data depending on whether it succeed or failed.

+
var users = m.request({
+    method: "GET",
+    url: "/user",
+    unwrapSuccess: function(response) {
+        return response.data;
+    },
+    unwrapError: function(response) {
+        return response.error;
+    }
+});
+
+//assuming the response is: `{data: [{name: "John"}, {name: "Mary"}], count: 2}`
+//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
+//i.e. users() //[{name: "John"}, {name: "Mary"}]
+
+

Using Different Data Transfer Formats

+

By default, m.request uses JSON to send and receive data to web services. You can override this by providing serialize and deserialize options:

+
var users = m.request({
+    method: "GET",
+    url: "/user",
+    serialize: mySerializer,
+    deserialize: myDeserializer
+});
+

One typical way to override this is to receive as-is responses. The example below shows how to receive a plain string from a txt file.

+
var file = m.request({
+    method: "GET",
+    url: "myfile.txt",
+    deserialize: function(value) {return value;}
+});
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + \ No newline at end of file diff --git a/docs/change-log.md b/docs/change-log.md index 9ddc7952..bceb5feb 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -1,5 +1,12 @@ ## Change Log +[v0.1.4](/mithril/archive/v0.1.3) - maintenance + +### Bug Fixes: + +- URLs with port numbers are now handled correctly [#40](https://github.com/lhorie/mithril.js/issues/40) +- NPM package now contains unminified version for map files [#39](https://github.com/lhorie/mithril.js/issues/39) + [v0.1.3](/mithril/archive/v0.1.3) - maintenance ### News: diff --git a/mithril.js b/mithril.js index 79b2179d..86cd8aa6 100644 --- a/mithril.js +++ b/mithril.js @@ -388,7 +388,7 @@ new function(window) { return xhrOptions } function parameterizeUrl(url, data) { - var tokens = url.match(/:[a-zA-Z]+/g) + var tokens = url.match(/:[a-z]\w+/gi) if (tokens && data) { for (var i = 0; i < tokens.length; i++) { var key = tokens[i].slice(1) diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 3ef3c946..902a2f7d 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -158,6 +158,7 @@ function testMithril(mock) { //m.route test(function() { mock.performance.$elapse(50) + mock.location.search = "?" var root = mock.document.createElement("div") m.route.mode = "search" @@ -168,6 +169,7 @@ function testMithril(mock) { }) test(function() { mock.performance.$elapse(50) + mock.location.pathname = "/" var root = mock.document.createElement("div") m.route.mode = "pathname" @@ -178,6 +180,7 @@ function testMithril(mock) { }) test(function() { mock.performance.$elapse(50) + mock.location.hash = "#" var root = mock.document.createElement("div") m.route.mode = "hash" @@ -188,6 +191,7 @@ function testMithril(mock) { }) test(function() { mock.performance.$elapse(50) + mock.location.search = "?" var root = mock.document.createElement("div") m.route.mode = "search" @@ -198,6 +202,7 @@ function testMithril(mock) { }) test(function() { mock.performance.$elapse(50) + mock.location.search = "?" var module = {controller: function() {}, view: function() {return m.route.param("test")}} @@ -213,6 +218,23 @@ function testMithril(mock) { return mock.location.search == "?/" && paramValueBefore === "foo" && paramValueAfter === undefined }) + test(function() { + mock.performance.$elapse(50) + mock.location.search = "?" + + var module = {controller: function() {}, view: function() {return m.route.param("a1")}} + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/test6/foo", { + "/": module, + "/test6/:a1": module + }) + var paramValueBefore = m.route.param("a1") + m.route("/") + var paramValueAfter = m.route.param("a1") + return mock.location.search == "?/" && paramValueBefore === "foo" && paramValueAfter === undefined + }) //m.prop test(function() { @@ -238,6 +260,20 @@ function testMithril(mock) { e.target.onload(e) return prop() === "foo" }) + test(function() { + var prop = m.request({method: "POST", url: "http://domain.com:80", data: {}}).then(function(value) {return value}) + var e = mock.XMLHttpRequest.$events.pop() + e.target.onload(e) + console.log(prop().url) + return prop().url === "http://domain.com:80" + }) + test(function() { + var prop = m.request({method: "POST", url: "http://domain.com:80/:test1", data: {test1: "foo"}}).then(function(value) {return value}) + var e = mock.XMLHttpRequest.$events.pop() + e.target.onload(e) + console.log(prop().url) + return prop().url === "http://domain.com:80/foo" + }) //m.deferred test(function() { From 3f8c630702ea7c708b01d7b0574c0e62e3ccf459 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 8 Apr 2014 15:33:54 -0400 Subject: [PATCH 30/38] add example for m.request config --- archive/v0.1.4/mithril.min.zip | Bin 20825 -> 20825 bytes archive/v0.1.4/mithril.request.html | 8 ++++++++ docs/mithril.request.md | 14 ++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/archive/v0.1.4/mithril.min.zip b/archive/v0.1.4/mithril.min.zip index 3dede029703860560b241a31980c72f2ff8fbc33..74333ec87e18022ade5ec08dc43fd2d405dc2dc5 100644 GIT binary patch delta 45 vcmcb)i1FqkMxFp~W)?065HPCQ$n#c?$*5-YZ@JrsKzj0x04oqB66gv5JLwKV delta 45 vcmcb)i1FqkMxFp~W)?065QwVS$n#c?DXL=gZ@JrsKzj0x04oqB66gv5MTrj4 diff --git a/archive/v0.1.4/mithril.request.html b/archive/v0.1.4/mithril.request.html index 728c8f68..eeeb33ed 100644 --- a/archive/v0.1.4/mithril.request.html +++ b/archive/v0.1.4/mithril.request.html @@ -213,6 +213,14 @@ var users = User.list(); url: "myfile.txt", deserialize: function(value) {return value;} }); +

Configuring the underlying XMLHttpRequest

+

The config option can be used to arbitrarily configure the native XMLHttpRequest instance and to access properties that would not be accessible otherwise.

+

The example below show how to configure a request where the server expects requests to have a Content-Type: application/json header

+
var xhrConfig = function(xhr) {
+    xhr.setRequestHeader("Content-Type", "application/json");
+}
+
+m.request({method: "POST", url: "/foo", config: xhrConfig});

Signature

How to read signatures

diff --git a/docs/mithril.request.md b/docs/mithril.request.md index 7cdfee61..27cac116 100644 --- a/docs/mithril.request.md +++ b/docs/mithril.request.md @@ -226,6 +226,20 @@ var file = m.request({ }); ``` +### Configuring the underlying XMLHttpRequest + +The `config` option can be used to arbitrarily configure the native XMLHttpRequest instance and to access properties that would not be accessible otherwise. + +The example below show how to configure a request where the server expects requests to have a `Content-Type: application/json` header + +```javascript +var xhrConfig = function(xhr) { + xhr.setRequestHeader("Content-Type", "application/json"); +} + +m.request({method: "POST", url: "/foo", config: xhrConfig}); +``` + --- ### Signature From 9dcffab2ac8a8a6b2d7fc86b425abfd1cf3ea528 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 8 Apr 2014 15:48:15 -0400 Subject: [PATCH 31/38] don't touch DOM to check for parenthood --- archive/v0.1.4/mithril-tests.js | 12 ++++++------ archive/v0.1.4/mithril.js | 12 ++++++------ archive/v0.1.4/mithril.min.js | 2 +- archive/v0.1.4/mithril.min.map | 2 +- archive/v0.1.4/mithril.min.zip | Bin 20825 -> 20873 bytes mithril.js | 12 ++++++------ 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/archive/v0.1.4/mithril-tests.js b/archive/v0.1.4/mithril-tests.js index 200111a3..fde7a491 100644 --- a/archive/v0.1.4/mithril-tests.js +++ b/archive/v0.1.4/mithril-tests.js @@ -32,7 +32,7 @@ new function(window) { } return cell } - function build(parent, data, cached) { + function build(parent, data, cached, shouldReattach) { if (data === null || data === undefined) { if (cached) clear(cached.nodes) return @@ -49,7 +49,7 @@ new function(window) { if (dataType == "[object Array]") { var nodes = [], intact = cached.length === data.length for (var i = 0; i < data.length; i++) { - var item = build(parent, data[i], cached[i]) + var item = build(parent, data[i], cached[i], shouldReattach) if (item === undefined) continue if (!item.nodes.intact) intact = false cached[i] = item @@ -69,15 +69,15 @@ new function(window) { var node, isNew = cached.nodes.length === 0 if (isNew) { node = window.document.createElement(data.tag) - cached = {tag: data.tag, attrs: setAttributes(node, data.attrs, {}), children: build(node, data.children, cached.children), nodes: [node]} + cached = {tag: data.tag, attrs: setAttributes(node, data.attrs, {}), children: build(node, data.children, cached.children, true), nodes: [node]} parent.appendChild(node) } else { node = cached.nodes[0] setAttributes(node, data.attrs, cached.attrs) - cached.children = build(node, data.children, cached.children) + cached.children = build(node, data.children, cached.children, false) cached.nodes.intact = true - if (node.parentNode !== parent) parent.appendChild(node) + if (shouldReattach === true) parent.appendChild(node) } if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew) } @@ -180,7 +180,7 @@ new function(window) { var index = nodeCache.indexOf(root) var id = index < 0 ? nodeCache.push(root) - 1 : index var node = root == window.document || root == window.document.documentElement ? documentNode : root - cellCache[id] = build(node, cell, cellCache[id]) + cellCache[id] = build(node, cell, cellCache[id], false) } m.trust = function(value) { diff --git a/archive/v0.1.4/mithril.js b/archive/v0.1.4/mithril.js index 86cd8aa6..7fcb3937 100644 --- a/archive/v0.1.4/mithril.js +++ b/archive/v0.1.4/mithril.js @@ -32,7 +32,7 @@ new function(window) { } return cell } - function build(parent, data, cached) { + function build(parent, data, cached, shouldReattach) { if (data === null || data === undefined) { if (cached) clear(cached.nodes) return @@ -49,7 +49,7 @@ new function(window) { if (dataType == "[object Array]") { var nodes = [], intact = cached.length === data.length for (var i = 0; i < data.length; i++) { - var item = build(parent, data[i], cached[i]) + var item = build(parent, data[i], cached[i], shouldReattach) if (item === undefined) continue if (!item.nodes.intact) intact = false cached[i] = item @@ -69,15 +69,15 @@ new function(window) { var node, isNew = cached.nodes.length === 0 if (isNew) { node = window.document.createElement(data.tag) - cached = {tag: data.tag, attrs: setAttributes(node, data.attrs, {}), children: build(node, data.children, cached.children), nodes: [node]} + cached = {tag: data.tag, attrs: setAttributes(node, data.attrs, {}), children: build(node, data.children, cached.children, true), nodes: [node]} parent.appendChild(node) } else { node = cached.nodes[0] setAttributes(node, data.attrs, cached.attrs) - cached.children = build(node, data.children, cached.children) + cached.children = build(node, data.children, cached.children, false) cached.nodes.intact = true - if (node.parentNode !== parent) parent.appendChild(node) + if (shouldReattach === true) parent.appendChild(node) } if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew) } @@ -180,7 +180,7 @@ new function(window) { var index = nodeCache.indexOf(root) var id = index < 0 ? nodeCache.push(root) - 1 : index var node = root == window.document || root == window.document.documentElement ? documentNode : root - cellCache[id] = build(node, cell, cellCache[id]) + cellCache[id] = build(node, cell, cellCache[id], false) } m.trust = function(value) { diff --git a/archive/v0.1.4/mithril.min.js b/archive/v0.1.4/mithril.min.js index ea3fb882..6c01d404 100644 --- a/archive/v0.1.4/mithril.min.js +++ b/archive/v0.1.4/mithril.min.js @@ -4,5 +4,5 @@ http://github.com/lhorie/mithril.js (c) Leo Horie License: MIT */ -!new function(a){function b(e,f,g){if(null===f||void 0===f)return void(g&&d(g.nodes));if("retain"!==f.subtree){var h=s.call(g),i=s.call(f);if(h!=i&&(null!==g&&void 0!==g&&d(g.nodes),g=new f.constructor,g.nodes=[]),"[object Array]"==i){for(var j=[],k=g.length===f.length,l=0;l-1?new f.constructor(f):f,g.nodes=[n]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var q=g.nodes[0],j=[q];if(q){for(;q=q.nextSibling;)j.push(q);d(j);var p=e.lastChild;e.insertAdjacentHTML("beforeend",f),n=p?p.nextSibling:e.firstChild}else e.innerHTML=f}else n=g.nodes[0],e.appendChild(n),n.nodeValue=f;g=new f.constructor(f),g.nodes=[n]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e],h=d[e];if(!(e in d)||h!==g||b===a.document.activeElement){if("config"===e)continue;if("function"==typeof g&&0==e.indexOf("on"))String(g)!==String(h)&&(b[e]=f(g,b));else if("style"===e)for(var i in g)(void 0===h||h[i]!==g[i])&&(b.style[i]=g[i]);else e in b?b[e]=g:b.setAttribute(e,g);d[e]=g}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:[a-z]\w+/gi);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f])},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c-1?new f.constructor(f):f,g.nodes=[o]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var r=g.nodes[0],k=[r];if(r){for(;r=r.nextSibling;)k.push(r);d(k);var q=e.lastChild;e.insertAdjacentHTML("beforeend",f),o=q?q.nextSibling:e.firstChild}else e.innerHTML=f}else o=g.nodes[0],e.appendChild(o),o.nodeValue=f;g=new f.constructor(f),g.nodes=[o]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e],h=d[e];if(!(e in d)||h!==g||b===a.document.activeElement){if("config"===e)continue;if("function"==typeof g&&0==e.indexOf("on"))String(g)!==String(h)&&(b[e]=f(g,b));else if("style"===e)for(var i in g)(void 0===h||h[i]!==g[i])&&(b.style[i]=g[i]);else e in b?b[e]=g:b.setAttribute(e,g);d[e]=g}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:[a-z]\w+/gi);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f],!1)},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;co)E7Pr2j`>WH`cRbUfjcw&XhKsXnx-V$5zrAi6u&e4 z>1zwz6>V2dUpHN~P8LP8GuRKhD-a+kx-hye5}+`;D2i^{255nf>yp#o)dB z?&sWd@4Jus^ZTA3eBimdo*El}bp-$ZbMpO|{^6&;`S=?nBOkwa^@pRZ{NB_1(-pZB zD46xSZ&zZ~!MY?xDbrz}PHol5a~&@0RK%#6s>sw;a}#gJ5;RZS$_r;XfY>zv-}2!0dM09rpM{ zVg*D5pjUR__@84}=9xDrG0JY`k+XfmE`*b$72j$8!EIImyYTq5hpYpP>Ts?+vNy=E z>fYeX*q@HyecfT+rgUP}I~LU*{EYiOE&ldI=%ypL?zRH9b=b{TcIp*-cf|^nD(y~} zawnVX4sTB|i2k#Q41E7J5z*R%)E@yn=)U$gwq{%OL$oco&w7kXwLo*PQ-A<|LCJnLL(!e z@JrJ_`f8`_?A43+Eg;`4l>2--_sYY#qGU9dRJ1036_Xi?@*siBpqgY^*sQX+5^t(3 zuf%lH$s|Fdq*1OIIv6BFD$^CErLs*$sjBzzAeH8KByaCNddg-|vY8;c#!_(l0M^EF z49wPa19b{XSZfIkzx4nG5jBqCw+KrIu$+&ng<|iZD2uv!48N&Oz^K*L4U_}0oRZ|9 zO1FAjw9lir%^h5)FiUM;({Q8XC#le~I6OsCAf*)36s=8Ji`iaKdB1s(KOAPD5y zFlqe|gq0J^i8@Mp?$YhqZMY?SjQ2{UfCX;4~qZC*|F$^d! zo;YHsp`{_pv`;6g1AfA$7P~@iPjwXla;8nPGrgc4CfKg3C=c&ZNm~4m>}Ky+Sn4bd z)MenS&8sHXdlma&2q2;&4Oxv9pdS`RXo9GPG{6XHExg~)xOa?h(5I_SFnLOeQ>2;wnasB7+W7_|X+w;$TqnTe!|BJtQrZ5Fl`TDOtQM@rP)t zyNS0nG%0jRE7hT!yon4fiDBR#_^FaPJP>K<6gwx5VFqEtp90AOEpeU?=D#>=iL%LV@1(Dx<>74668o{ehFxIhqRCKhVA!^+@0prlVVgbZ&VaR&$3 zAsxQF7?qEOWiZYP9LmBPIyYR5x~AQ^9h4}=Cj;0or*IzF$`p-VoI zwX4z#l3GH98cYtn3}Jo}oq1!49*P|dEAB{v|2o>fB3`AvRhXDCo%b=SlG^LbcaFyW z&3DkttV%%*_liPH?Cv(5bq8ns#dj7aK?dlI2*0N2!#3cya^;(Y6EW!{tJn9wkO3n3H^@CLo_nSqd7>aB0rNqsQQ7YJIqK&)UVLT)-vm;N*L#n=s~ICfBj z9;H0|Oo8i47TGfWS}|JuR|)^5Eo$80e@ncWT&7F_prY+G>;Zk zw-Yc0EK2%N_=@nU)psY`B7;`{$!dQ>g0ev)EXG6?O>zL82MOot5pnC9p6hR|eRp*7 ucn$GpkNEG`uh0*<;eSE=uj{kZ=YHD3&%4*?zoo(F-#nDLap`~Sxc@(uEbNv5 delta 3347 zcmb7HOKcn071h^LQU?j*!WL?$k_<%Uh|l~;q9ltM7;he_Av2;!6fIG-D!>%QuZBa4 zq`tNwbW@~VC4IYY0y~?cC;}8jcSTzuT@+cgKv!+iMO<_lASnVAD9}aky^qw#ae-Eg z(fi!bx#ynuX841TdjI^a_r|i-Kk#Y~{!Kl3FEaM*-@o|e+dVy>d~oB3AAGbjTGE@o zT-I?sMU9oao z2g1%f-D%kIdbP*sQq{LSc~;nPHdzrKT6n)!-X05#>w0x!!c+CRwe5V~b7fK-j zU`deOJ;woPuCJbLqOKm@{7K=ddu>){wFtGR?`Cz?1gN#dmdN*^389_U-jR5s|WBJ#**%$1En*6Yd-2b1vp$Kbr!?UJ+sutBN2n<(R5rJnGvzI!oZSgZKzd(9H; zhqpZykR@o9vJX;r`RvNn=p`avB#OE6t1EYU-S+s<@2<8#zxKvAUc(dir{7;~eH`rR z`B=U(`hNSTqbsjGFu4dB%;Y@FY|bA1fKfQc16&7StK5|T)Fy*#G*4r+^g|q58Av1c zg}_N1A10^amfDrc$y?tk88}IhO_H8gmU2-DI|F0jY+e3%QX5hkGV;^O*|AOxECk$u zmOWz~Q*TEwo}H0n-jSj0&iI~pcBnvOQ=WL&hE!}*{>3{NatMH3L*{t~ixqyefDr67 zMo(R+r2#oGx+zVj`H27v9D!-uRbi@XCeYJXJ!sL6wI@GhBcX;eYZ@H`K%7)-7aA=N z7g^EZ{NcL@!>Y;8+1saw%F|>CpeqSaF>PeAnK9Lf4uDw$Sx5<%gJw^w)C5rk1(-m* zRlo>*S)etj1@YsI;@TE2<*4F>>WsB#K!;-+hm8thJEs4nbw-8dlQ%P>v>2UKn=HfAF`vy!=k!8WEAPndr(3qt~ zAfYH7|Hy`;JgS1_x#>hyw5~2YvPi)m&$}2*w1he;XFZ|&hm@aU2ax5!fLI`-d|iI# zzrK#go@dt17!koml@aeIp2EW(eTZ6$tFmhX9dZqIsCNa*D2;W_Q7!S##=NuxH-^;& z4wf6J-ub_4e{`|-=v_eta{GvKW)21P$2>HO#QRJ1wnVx;;{mwoXwy1U;y;$(3yw^j z696szdg`I1^=iV02`J)O@!6*AVXu zxTY%JMESzC0f`cg7uN88>#oVfycq~8gDDf`R914HHVN3v)@I~y=f4_m;aL-Fd+lG} zeYH1mOs|zhAO>DG^eX{xs9+3hPr?fhP@LbMP;Vk5|Fw`?a?v^(Ts$J@G{it?5Ce|k zDtSkz4chFGPh=Gh5~LMEgkny?osmBYPrMnT`+f`4S_nU$m7j$h*SF|gH)9}KxMf?I zm0Qhr>7AniuZx$m@EerW?wl)t09QbwPJ6BHp1jIuhG7lR*|RdnabxF_0*bcO$ zX57b+`=V>AcIRWLj_fkJV`LTOkNoLdrA0MS;;hnJIoVy5?;Df1D`eiire_FiB8Oh9 z>%f^gC)&@A=l!>#`>3X!CH$9x?+Unt$T;Pvd(f`Qzb|@+9o6q0dBc1@JhwI zp_fCa#fd=sz59>)h7XY_o;r|UE?=i_tg~M+@> Date: Tue, 8 Apr 2014 21:33:00 -0400 Subject: [PATCH 32/38] fix disappearing link text --- archive/v0.1.4/mithril-tests.js | 10 +++++++--- archive/v0.1.4/mithril.js | 2 +- archive/v0.1.4/mithril.min.js | 2 +- archive/v0.1.4/mithril.min.map | 2 +- archive/v0.1.4/mithril.min.zip | Bin 20873 -> 20871 bytes mithril.js | 2 +- tests/mithril-tests.js | 8 ++++++-- 7 files changed, 17 insertions(+), 9 deletions(-) diff --git a/archive/v0.1.4/mithril-tests.js b/archive/v0.1.4/mithril-tests.js index fde7a491..09bc7c69 100644 --- a/archive/v0.1.4/mithril-tests.js +++ b/archive/v0.1.4/mithril-tests.js @@ -126,6 +126,7 @@ new function(window) { var dataAttr = dataAttrs[attrName] var cachedAttr = cachedAttrs[attrName] if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr) || node === window.document.activeElement) { + cachedAttrs[attrName] = dataAttr if (attrName === "config") continue else if (typeof dataAttr == "function" && attrName.indexOf("on") == 0) { if (String(dataAttr) !== String(cachedAttr)) node[attrName] = autoredraw(dataAttr, node) @@ -137,7 +138,6 @@ new function(window) { } else if (attrName in node) node[attrName] = dataAttr else node.setAttribute(attrName, dataAttr) - cachedAttrs[attrName] = dataAttr } } return cachedAttrs @@ -673,6 +673,12 @@ function testMithril(mock) { m.render(root, m("ul", [{subtree: "retain"}])) return root.childNodes[0].childNodes[0].childNodes[0].nodeName === "A" }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("a", {config: m.route}, "test")) + m.render(root, m("a", {config: m.route}, "test")) + return root.childNodes[0].childNodes[0].nodeValue === "test" + }) //m.redraw test(function() { @@ -796,14 +802,12 @@ function testMithril(mock) { var prop = m.request({method: "POST", url: "http://domain.com:80", data: {}}).then(function(value) {return value}) var e = mock.XMLHttpRequest.$events.pop() e.target.onload(e) - console.log(prop().url) return prop().url === "http://domain.com:80" }) test(function() { var prop = m.request({method: "POST", url: "http://domain.com:80/:test1", data: {test1: "foo"}}).then(function(value) {return value}) var e = mock.XMLHttpRequest.$events.pop() e.target.onload(e) - console.log(prop().url) return prop().url === "http://domain.com:80/foo" }) diff --git a/archive/v0.1.4/mithril.js b/archive/v0.1.4/mithril.js index 7fcb3937..8cc2547a 100644 --- a/archive/v0.1.4/mithril.js +++ b/archive/v0.1.4/mithril.js @@ -126,6 +126,7 @@ new function(window) { var dataAttr = dataAttrs[attrName] var cachedAttr = cachedAttrs[attrName] if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr) || node === window.document.activeElement) { + cachedAttrs[attrName] = dataAttr if (attrName === "config") continue else if (typeof dataAttr == "function" && attrName.indexOf("on") == 0) { if (String(dataAttr) !== String(cachedAttr)) node[attrName] = autoredraw(dataAttr, node) @@ -137,7 +138,6 @@ new function(window) { } else if (attrName in node) node[attrName] = dataAttr else node.setAttribute(attrName, dataAttr) - cachedAttrs[attrName] = dataAttr } } return cachedAttrs diff --git a/archive/v0.1.4/mithril.min.js b/archive/v0.1.4/mithril.min.js index 6c01d404..fbe0e105 100644 --- a/archive/v0.1.4/mithril.min.js +++ b/archive/v0.1.4/mithril.min.js @@ -4,5 +4,5 @@ http://github.com/lhorie/mithril.js (c) Leo Horie License: MIT */ -!new function(a){function b(e,f,g,h){if(null===f||void 0===f)return void(g&&d(g.nodes));if("retain"!==f.subtree){var i=s.call(g),j=s.call(f);if(i!=j&&(null!==g&&void 0!==g&&d(g.nodes),g=new f.constructor,g.nodes=[]),"[object Array]"==j){for(var k=[],l=g.length===f.length,m=0;m-1?new f.constructor(f):f,g.nodes=[o]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var r=g.nodes[0],k=[r];if(r){for(;r=r.nextSibling;)k.push(r);d(k);var q=e.lastChild;e.insertAdjacentHTML("beforeend",f),o=q?q.nextSibling:e.firstChild}else e.innerHTML=f}else o=g.nodes[0],e.appendChild(o),o.nodeValue=f;g=new f.constructor(f),g.nodes=[o]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e],h=d[e];if(!(e in d)||h!==g||b===a.document.activeElement){if("config"===e)continue;if("function"==typeof g&&0==e.indexOf("on"))String(g)!==String(h)&&(b[e]=f(g,b));else if("style"===e)for(var i in g)(void 0===h||h[i]!==g[i])&&(b.style[i]=g[i]);else e in b?b[e]=g:b.setAttribute(e,g);d[e]=g}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:[a-z]\w+/gi);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f],!1)},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c-1?new f.constructor(f):f,g.nodes=[o]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var r=g.nodes[0],k=[r];if(r){for(;r=r.nextSibling;)k.push(r);d(k);var q=e.lastChild;e.insertAdjacentHTML("beforeend",f),o=q?q.nextSibling:e.firstChild}else e.innerHTML=f}else o=g.nodes[0],e.appendChild(o),o.nodeValue=f;g=new f.constructor(f),g.nodes=[o]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e],h=d[e];if(!(e in d)||h!==g||b===a.document.activeElement){if(d[e]=g,"config"===e)continue;if("function"==typeof g&&0==e.indexOf("on"))String(g)!==String(h)&&(b[e]=f(g,b));else if("style"===e)for(var i in g)(void 0===h||h[i]!==g[i])&&(b.style[i]=g[i]);else e in b?b[e]=g:b.setAttribute(e,g)}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:[a-z]\w+/gi);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f],!1)},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c~}X$;>Z%?1<4wF zZEm`0$iQ%OGpBsHu!3ueyN@Ldzvtcq{pb4shll=ot*nR|noB;r~(k^cR delta 154 zcmZo)%-Fe@Q7FKhnT3l11isaFxOlF*vu&f$9QMgu+3&Jjr$nd5+NN)w#E~V&3zGdV za`^CVLk5Q1n>ppnh54OJ+;!X?9W$MD!W|uxJT@;dl3SkKL#NGq ajooEAV0KLA2{b`9a Date: Tue, 8 Apr 2014 22:17:19 -0400 Subject: [PATCH 33/38] add test for issue 29 --- archive/v0.1.4/mithril-tests.js | 22 +++++++++++++++++++++- archive/v0.1.4/mithril.js | 2 +- archive/v0.1.4/mithril.min.js | 2 +- archive/v0.1.4/mithril.min.map | 2 +- archive/v0.1.4/mithril.min.zip | Bin 20871 -> 20822 bytes mithril.js | 2 +- tests/mithril-tests.js | 20 ++++++++++++++++++++ 7 files changed, 45 insertions(+), 5 deletions(-) diff --git a/archive/v0.1.4/mithril-tests.js b/archive/v0.1.4/mithril-tests.js index 09bc7c69..b06b4ce4 100644 --- a/archive/v0.1.4/mithril-tests.js +++ b/archive/v0.1.4/mithril-tests.js @@ -129,7 +129,7 @@ new function(window) { cachedAttrs[attrName] = dataAttr if (attrName === "config") continue else if (typeof dataAttr == "function" && attrName.indexOf("on") == 0) { - if (String(dataAttr) !== String(cachedAttr)) node[attrName] = autoredraw(dataAttr, node) + node[attrName] = autoredraw(dataAttr, node) } else if (attrName === "style") { for (var rule in dataAttr) { @@ -679,6 +679,26 @@ function testMithril(mock) { m.render(root, m("a", {config: m.route}, "test")) return root.childNodes[0].childNodes[0].nodeValue === "test" }) + test(function() { + //see issue #29 + var root = mock.document.createElement("div") + var list = [false, false] + m.render(root, list.reverse().map(function(flag, index) { + return m("input[type=checkbox]", {onclick: m.withAttr("checked", function(value) {list[index] = value}), checked: flag}) + })) + + mock.document.activeElement = root.childNodes[0] + root.childNodes[0].checked = true + root.childNodes[0].onclick({currentTarget: {checked: true}}) + + m.render(root, list.reverse().map(function(flag, index) { + return m("input[type=checkbox]", {onclick: m.withAttr("checked", function(value) {list[index] = value}), checked: flag}) + })) + + mock.document.activeElement = null + + return root.childNodes[0].checked === false && root.childNodes[1].checked === true + }) //m.redraw test(function() { diff --git a/archive/v0.1.4/mithril.js b/archive/v0.1.4/mithril.js index 8cc2547a..28912e30 100644 --- a/archive/v0.1.4/mithril.js +++ b/archive/v0.1.4/mithril.js @@ -129,7 +129,7 @@ new function(window) { cachedAttrs[attrName] = dataAttr if (attrName === "config") continue else if (typeof dataAttr == "function" && attrName.indexOf("on") == 0) { - if (String(dataAttr) !== String(cachedAttr)) node[attrName] = autoredraw(dataAttr, node) + node[attrName] = autoredraw(dataAttr, node) } else if (attrName === "style") { for (var rule in dataAttr) { diff --git a/archive/v0.1.4/mithril.min.js b/archive/v0.1.4/mithril.min.js index fbe0e105..97ce9252 100644 --- a/archive/v0.1.4/mithril.min.js +++ b/archive/v0.1.4/mithril.min.js @@ -4,5 +4,5 @@ http://github.com/lhorie/mithril.js (c) Leo Horie License: MIT */ -!new function(a){function b(e,f,g,h){if(null===f||void 0===f)return void(g&&d(g.nodes));if("retain"!==f.subtree){var i=s.call(g),j=s.call(f);if(i!=j&&(null!==g&&void 0!==g&&d(g.nodes),g=new f.constructor,g.nodes=[]),"[object Array]"==j){for(var k=[],l=g.length===f.length,m=0;m-1?new f.constructor(f):f,g.nodes=[o]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var r=g.nodes[0],k=[r];if(r){for(;r=r.nextSibling;)k.push(r);d(k);var q=e.lastChild;e.insertAdjacentHTML("beforeend",f),o=q?q.nextSibling:e.firstChild}else e.innerHTML=f}else o=g.nodes[0],e.appendChild(o),o.nodeValue=f;g=new f.constructor(f),g.nodes=[o]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e],h=d[e];if(!(e in d)||h!==g||b===a.document.activeElement){if(d[e]=g,"config"===e)continue;if("function"==typeof g&&0==e.indexOf("on"))String(g)!==String(h)&&(b[e]=f(g,b));else if("style"===e)for(var i in g)(void 0===h||h[i]!==g[i])&&(b.style[i]=g[i]);else e in b?b[e]=g:b.setAttribute(e,g)}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:[a-z]\w+/gi);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f],!1)},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c-1?new f.constructor(f):f,g.nodes=[o]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var r=g.nodes[0],k=[r];if(r){for(;r=r.nextSibling;)k.push(r);d(k);var q=e.lastChild;e.insertAdjacentHTML("beforeend",f),o=q?q.nextSibling:e.firstChild}else e.innerHTML=f}else o=g.nodes[0],e.appendChild(o),o.nodeValue=f;g=new f.constructor(f),g.nodes=[o]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e],h=d[e];if(!(e in d)||h!==g||b===a.document.activeElement){if(d[e]=g,"config"===e)continue;if("function"==typeof g&&0==e.indexOf("on"))b[e]=f(g,b);else if("style"===e)for(var i in g)(void 0===h||h[i]!==g[i])&&(b.style[i]=g[i]);else e in b?b[e]=g:b.setAttribute(e,g)}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:[a-z]\w+/gi);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f],!1)},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;cA6O750=;rWL7Mf|}T^V<%}7f*;I`=ht!K+SD+*n9WF(9pXN&prOc3oBn( zy?1l%Gj(I-cr^OgSI&^cV*34e9wml`9vweF{;g+@e|+)i+2=~#oo2g!{Q8xLpL{)G zZ3)9RO)n)3$26-hEh+{2Th0n2FzpSgUQWJN_6*N7!=mAu(n(6AWSVO!Nmo+BXdAo2G<%}2 z*KyJ-W0Jv!Nxhk}jXek*inLy5j{lS%k3`e#L=abz?1j-ro-V%gRn$EoGF__!Jv6RNRX7w+8Omu_UrMsqg{0AIaJ;J?2oP zLP<6}Lz;4*nd$a83ck-lo#urzr7qpLI;I!y13K|`3&@~MiY_6-Qmx&198EKxaz@SH zTsqp|vhXB9^y+#6V=^IHj5l1@PUu`yWDf*;Sr?Sa3j2y^AS7j@3Rh%@F9eGO;J(K& z$j50xKGeD{U0XV2OC6yu&W=U))vejro|&}Nz1d=PpW*}Yo*P>emX4Rg0JR5%= z)F6@!`@WCzcp%TxiG`M z!r#j?U~C5DsrJP~Ov%@jI(Pf(w5CsWhjJz>1Brn(5DjteN|S!7Ii}T~u0L-}^=@B} zG1b=HMS|*jq{5_($^yc@366F+tR)?Fr>!2}{k;`)N0S7wA^z7(P7Hp|o<{L-boP&;-BVPl=4s{w6gXCumq^Qa|HiT}W!N6Fm zV5~F7d9tuCfrvB=PBR7F?P!m{{?`#M!Z~-tqHlMz?FWSu`d&2lvYR(rtd@2DATWDHFXS8QoC5n2GQz%!J$XUdlBt8MFSWLJH~ zN<=|_)TO(terb)|(4EoFXkv2K8_Wy8$(c(EqD(-(mC!>7+9Z3?6fJeucC#QD7>JgK zn3ee)Ljy_hY1_l(;7J1z2~FP$<#sizlx-MjsyMAtJ4$G`>v|g1$ez~LVQ*Kr)IaR8 zMT86pDh!h(5q&nP6e~=L z(3{lO>tooO;5{aq1g^9SHDyZuJ6P9odIm1NRVr@Z3{iF~(0|r4`$RTg54-5ID zb(i2^;<1&gY0u+-mWv#Kab@HbOb9dFutRKeAV9d5M0#A)U5YUP8*KuD72@|@P9nd7 z>VhVSY>0ifQs}wtCYw)i9L~TpQH$?%)z97J)!jS+?^_51vyI9Zp#aLpQ?j2bCQ$?V zVr)2P5w0pd%V?^pFBd>zb`b((q?=!bXNU=IsA+#}a_UqmrIp?;csdR?lRS?d zv>!^yrh3c2GzM!WGY)F%*RDOq=_qp`vg+6VsEwIU4dm1ev2UeCg-j1+{H;i0aV^6= z46hlu+SiTizMdJCdR~pspO3UuZvKsHc#`2IleY8=t;Q+_&p-X-*KamIuD&{-IBL?j z!Jw*_^<=R@FgVZ@Xt!h@9gaDJ4n<3JKVEX1mhC)VtF+Cs0gF^)nrxr}F^MQAll;tc z^E7}v!eLIL0ezgVw$x1E-h&>B!(d+S^7F6M@F8hu?I(xO=}vCeM>jt zlUyS{e+kY;@s5Bwdqe#@81dW8QQM)ZZx{CTM-NXrIj27YPHuDD104$#DOAu&iP~Rg zuv9AxS6)G_5bgH)Dbup}IkDVR-(9#o%r)xtZBBq4SHD|G@psARV6^@6}F)fo+3`}P-P3BL{Oq!>$9skAo8{WI``T6NJTB_O`58Mu z{)wIc1%a|C3!p7mDw|56RzgCcq7oq_mMmtCSb(}fOuIm=P=QSsEcnj3c3>*9aD4AQ z_nz;Z?>pyS|M9)h8}El+EVy5I=D7j-d*K(~HGlW+;46L0_~Mg`cZz$R&1UUh zZT!~UTNhrCn|pMxrmc>jg~Fd)xkOvz`LowQiVX~WboS}!I~Sf+_GjO@ z_4+esgJU0jd3MjNXuhudInCAeHJiU@>3cO}XqK+K2}84Wz2efKoTtxH+Ry^sidgc= z@B?N6r7&`B9$e^@9-;1v1!PkzUf!h!?@=P+u0ct(a?O1kE z2JyS$me8ueu_V70|6%A@UQYZdyk*j1?v(shVpg;*+T-t9WVX>cA5{R?1RmY{uZ5aJ zpaFSvuz-JlSH_Yvb0=zN)Y7_!t{)mPHJ+q%MMN;zAgLx(mbMS0VbA|2?(8lMSNh_n*R(LZl82S0w37+!SZ z^5@eNp_Kg7bToHl(Vl?L^~uuVn0Yv%64R!wRxBIklgc=dY;hp0OYt1+t+)}D{>;_E zGG#v3$L5Q=Nc?2*Ay z)(m2@%&{UHNJ&YnAQZ978~i0Yca4kAr5I6+h@pDm(6;3$dukc-)47q*ksO?V=|att z+I&8IMETLZhWzIIc({{eRUOJ-&PTrlX%NVoDgQSAu)lOHa}RE(wj4@Rk#^cO6+~}q zizDAum;+4G5jfP^v0Cyc4~8{m7ff-l@bJxR5Qo& zGyR5#NQs0lD>)!zhYLp}MUuJmQ>vq)4fRe!8>%!uKS{Yr$#vt-&T6I5_Eia3b)Cs>aFuQ|Mw?sBA(e#KGEaEL651wG zj3CO0cAvy9w!{&mvHBeles+vqxopP5=&0E3?8@((*Jg19+*#;~BvhsyqQ#U~WBTI; zX~w7Miy3z3O!4}rQJ0^XPI{Be4lNM-OB@{f9BA?gyL)VL8zM+CCFe3RzLAuxmZqV} zqWY@To?LoA-zJnCE z=>U_Q01~AD36Ab8XV|ATL45^`L4U6@Rs8a)8FX);D)!h*Zr|bX*!>B|e+iyRK)mNb zzTw30qQ&9@JTVbIU2SZP6aZ!YDcg^ocxYdKEgu;|I{>Z6)2$t|Ld4!2YT|Hcavc#lQ*)c}o=C9}f&RgVjB09boimw#Tk z{(@?P9PV~CGSf(ilMe+1DuzF&uR@>(qOfuJ)g+JUqpkSr0K=y-`fAGKJ zsVf%u5S{^6ZeRWW;cHMVpxa|}|41eSzs3kZ6xmxTQZG#OhB8(dyMh8DzW4YUgP%q8 zzF66pjl!Lw1Jbj7VlgY$vHWo%!9NAhe?`hCg~*MkejMTl;t>DZ?0@`I-Vbg+`&og; F{{aX!6;l8J diff --git a/mithril.js b/mithril.js index 8cc2547a..28912e30 100644 --- a/mithril.js +++ b/mithril.js @@ -129,7 +129,7 @@ new function(window) { cachedAttrs[attrName] = dataAttr if (attrName === "config") continue else if (typeof dataAttr == "function" && attrName.indexOf("on") == 0) { - if (String(dataAttr) !== String(cachedAttr)) node[attrName] = autoredraw(dataAttr, node) + node[attrName] = autoredraw(dataAttr, node) } else if (attrName === "style") { for (var rule in dataAttr) { diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 1f18b8fc..ff6adf39 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -147,6 +147,26 @@ function testMithril(mock) { m.render(root, m("a", {config: m.route}, "test")) return root.childNodes[0].childNodes[0].nodeValue === "test" }) + test(function() { + //see issue #29 + var root = mock.document.createElement("div") + var list = [false, false] + m.render(root, list.reverse().map(function(flag, index) { + return m("input[type=checkbox]", {onclick: m.withAttr("checked", function(value) {list[index] = value}), checked: flag}) + })) + + mock.document.activeElement = root.childNodes[0] + root.childNodes[0].checked = true + root.childNodes[0].onclick({currentTarget: {checked: true}}) + + m.render(root, list.reverse().map(function(flag, index) { + return m("input[type=checkbox]", {onclick: m.withAttr("checked", function(value) {list[index] = value}), checked: flag}) + })) + + mock.document.activeElement = null + + return root.childNodes[0].checked === false && root.childNodes[1].checked === true + }) //m.redraw test(function() { From 72c08a559ad965f8b7bdcc07bf1255664bcd393d Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Wed, 9 Apr 2014 09:10:42 -0400 Subject: [PATCH 34/38] add step to deploy to CDNs --- Gruntfile.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 6922e4c3..47ad452f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -79,6 +79,7 @@ module.exports = function(grunt) { links: {expand: true, flatten: true, src: [tempFolder + "/**/*.html"], dest: currentVersionArchiveFolder + "/"}, index: {src: inputFolder + "/layout/index.html", dest: currentVersionArchiveFolder + "/index.html"}, commonjs: {expand: true, flatten: true, src: [inputFolder + "/layout/*.json"], dest: currentVersionArchiveFolder}, + cdnjs: {src: "deploy/cdnjs-package.json", dest: "../cdnjs/ajax/libs/mithril/package.json"} }, copy: { style: {src: inputFolder + "/layout/style.css", dest: currentVersionArchiveFolder + "/style.css"}, @@ -88,7 +89,13 @@ module.exports = function(grunt) { comparisons: {expand: true, cwd: inputFolder + "/layout/comparisons/", src: "./**", dest: currentVersionArchiveFolder + "/comparisons/"}, unminified: {src: "mithril.js", dest: currentVersionArchiveFolder + "/mithril.js"}, publish: {expand: true, cwd: currentVersionArchiveFolder, src: "./**", dest: outputFolder}, - archive: {expand: true, cwd: currentVersionArchiveFolder, src: "./**", dest: outputFolder + "/archive/v" + version} + 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"]} From 08dd1cd62d28e8475d7560c8b524ccbead8bdda7 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Wed, 9 Apr 2014 09:11:02 -0400 Subject: [PATCH 35/38] add template file for cdnjs deploy --- deploy/cdnjs-package.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 deploy/cdnjs-package.json diff --git a/deploy/cdnjs-package.json b/deploy/cdnjs-package.json new file mode 100644 index 00000000..d431fe27 --- /dev/null +++ b/deploy/cdnjs-package.json @@ -0,0 +1,23 @@ +{ + "name": "mithril", + "npmName": "mithril", + "version": "$version", + "filename": "mithril.min.js", + "description": "A Javascript Framework for building brilliant applications", + "homepage": "http://lhorie.github.io/mithril", + "license": "MIT", + "main": "mithril", + "keywords": [ + "mvc", + "browser" + ], + "author": "Leo Horie (http://lhorie.blogspot.com/)", + "contributors": [ + "Leo Horie (http://lhorie.blogspot.com/)" + ], + "bugs": "https://github.com/lhorie/mithril.js/issues", + "repository": { + "type": "git", + "url": "https://github.com/lhorie/mithril.js.git" + } +} From b972607c8df1d5ac7ea8d005674947f6bad9629a Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Wed, 9 Apr 2014 09:12:14 -0400 Subject: [PATCH 36/38] fix out-of-order issue when mixing new and old elements --- archive/v0.1.4/mithril-tests.js | 35 +++++++++++++++++++++++++------- archive/v0.1.4/mithril.js | 12 +++++------ archive/v0.1.4/mithril.min.js | 2 +- archive/v0.1.4/mithril.min.map | 2 +- archive/v0.1.4/mithril.min.zip | Bin 20822 -> 21025 bytes mithril.js | 12 +++++------ tests/mithril-tests.js | 17 +++++++++++++++- tests/mock.js | 6 ++++++ 8 files changed, 64 insertions(+), 22 deletions(-) diff --git a/archive/v0.1.4/mithril-tests.js b/archive/v0.1.4/mithril-tests.js index b06b4ce4..06ce9c2d 100644 --- a/archive/v0.1.4/mithril-tests.js +++ b/archive/v0.1.4/mithril-tests.js @@ -32,7 +32,7 @@ new function(window) { } return cell } - function build(parent, data, cached, shouldReattach) { + function build(parent, data, cached, shouldReattach, index) { if (data === null || data === undefined) { if (cached) clear(cached.nodes) return @@ -49,7 +49,7 @@ new function(window) { if (dataType == "[object Array]") { var nodes = [], intact = cached.length === data.length for (var i = 0; i < data.length; i++) { - var item = build(parent, data[i], cached[i], shouldReattach) + var item = build(parent, data[i], cached[i], shouldReattach, i) if (item === undefined) continue if (!item.nodes.intact) intact = false cached[i] = item @@ -70,14 +70,14 @@ new function(window) { if (isNew) { node = window.document.createElement(data.tag) cached = {tag: data.tag, attrs: setAttributes(node, data.attrs, {}), children: build(node, data.children, cached.children, true), nodes: [node]} - parent.appendChild(node) + parent.insertBefore(node, parent.childNodes[index]) } else { node = cached.nodes[0] setAttributes(node, data.attrs, cached.attrs) cached.children = build(node, data.children, cached.children, false) cached.nodes.intact = true - if (shouldReattach === true) parent.appendChild(node) + if (shouldReattach === true) parent.insertBefore(node, parent.childNodes[index]) } if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew) } @@ -91,7 +91,7 @@ new function(window) { } else { node = window.document.createTextNode(data) - parent.appendChild(node) + parent.insertBefore(node, parent.childNodes[index]) } cached = "string number boolean".indexOf(typeof data) > -1 ? new data.constructor(data) : data cached.nodes = [node] @@ -110,7 +110,7 @@ new function(window) { } else { node = cached.nodes[0] - parent.appendChild(node) + parent.insertBefore(node, parent.childNodes[index]) node.nodeValue = data } cached = new data.constructor(data) @@ -466,6 +466,12 @@ mock.window = new function() { appendChild: window.document.appendChild, removeChild: window.document.removeChild, replaceChild: window.document.replaceChild, + insertBefore: function(node, reference) { + node.parentNode = this + var index = this.childNodes.indexOf(reference) + if (index < 0) this.childNodes.push(node) + else this.childNodes.splice(index, 0, node) + }, setAttribute: function(name, value) { this[name] = value.toString() }, @@ -674,13 +680,14 @@ function testMithril(mock) { return root.childNodes[0].childNodes[0].childNodes[0].nodeName === "A" }) test(function() { + //https://github.com/lhorie/mithril.js/issues/43 var root = mock.document.createElement("div") m.render(root, m("a", {config: m.route}, "test")) m.render(root, m("a", {config: m.route}, "test")) return root.childNodes[0].childNodes[0].nodeValue === "test" }) test(function() { - //see issue #29 + //https://github.com/lhorie/mithril.js/issues/29 var root = mock.document.createElement("div") var list = [false, false] m.render(root, list.reverse().map(function(flag, index) { @@ -699,6 +706,20 @@ function testMithril(mock) { return root.childNodes[0].checked === false && root.childNodes[1].checked === true }) + test(function() { + //https://github.com/lhorie/mithril.js/issues/44 + var root = mock.document.createElement("div") + m.render(root, m("#foo", [null, m("#bar")])) + m.render(root, m("#foo", ["test", m("#bar")])) + return root.childNodes[0].childNodes[0].nodeValue === "test" + }) + test(function() { + //https://github.com/lhorie/mithril.js/issues/44 + var root = mock.document.createElement("div") + m.render(root, m("#foo", [null, m("#bar")])) + m.render(root, m("#foo", [m("div"), m("#bar")])) + return root.childNodes[0].childNodes[0].nodeName === "DIV" + }) //m.redraw test(function() { diff --git a/archive/v0.1.4/mithril.js b/archive/v0.1.4/mithril.js index 28912e30..5b3e6063 100644 --- a/archive/v0.1.4/mithril.js +++ b/archive/v0.1.4/mithril.js @@ -32,7 +32,7 @@ new function(window) { } return cell } - function build(parent, data, cached, shouldReattach) { + function build(parent, data, cached, shouldReattach, index) { if (data === null || data === undefined) { if (cached) clear(cached.nodes) return @@ -49,7 +49,7 @@ new function(window) { if (dataType == "[object Array]") { var nodes = [], intact = cached.length === data.length for (var i = 0; i < data.length; i++) { - var item = build(parent, data[i], cached[i], shouldReattach) + var item = build(parent, data[i], cached[i], shouldReattach, i) if (item === undefined) continue if (!item.nodes.intact) intact = false cached[i] = item @@ -70,14 +70,14 @@ new function(window) { if (isNew) { node = window.document.createElement(data.tag) cached = {tag: data.tag, attrs: setAttributes(node, data.attrs, {}), children: build(node, data.children, cached.children, true), nodes: [node]} - parent.appendChild(node) + parent.insertBefore(node, parent.childNodes[index]) } else { node = cached.nodes[0] setAttributes(node, data.attrs, cached.attrs) cached.children = build(node, data.children, cached.children, false) cached.nodes.intact = true - if (shouldReattach === true) parent.appendChild(node) + if (shouldReattach === true) parent.insertBefore(node, parent.childNodes[index]) } if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew) } @@ -91,7 +91,7 @@ new function(window) { } else { node = window.document.createTextNode(data) - parent.appendChild(node) + parent.insertBefore(node, parent.childNodes[index]) } cached = "string number boolean".indexOf(typeof data) > -1 ? new data.constructor(data) : data cached.nodes = [node] @@ -110,7 +110,7 @@ new function(window) { } else { node = cached.nodes[0] - parent.appendChild(node) + parent.insertBefore(node, parent.childNodes[index]) node.nodeValue = data } cached = new data.constructor(data) diff --git a/archive/v0.1.4/mithril.min.js b/archive/v0.1.4/mithril.min.js index 97ce9252..2ab9844c 100644 --- a/archive/v0.1.4/mithril.min.js +++ b/archive/v0.1.4/mithril.min.js @@ -4,5 +4,5 @@ http://github.com/lhorie/mithril.js (c) Leo Horie License: MIT */ -!new function(a){function b(e,f,g,h){if(null===f||void 0===f)return void(g&&d(g.nodes));if("retain"!==f.subtree){var i=s.call(g),j=s.call(f);if(i!=j&&(null!==g&&void 0!==g&&d(g.nodes),g=new f.constructor,g.nodes=[]),"[object Array]"==j){for(var k=[],l=g.length===f.length,m=0;m-1?new f.constructor(f):f,g.nodes=[o]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var r=g.nodes[0],k=[r];if(r){for(;r=r.nextSibling;)k.push(r);d(k);var q=e.lastChild;e.insertAdjacentHTML("beforeend",f),o=q?q.nextSibling:e.firstChild}else e.innerHTML=f}else o=g.nodes[0],e.appendChild(o),o.nodeValue=f;g=new f.constructor(f),g.nodes=[o]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e],h=d[e];if(!(e in d)||h!==g||b===a.document.activeElement){if(d[e]=g,"config"===e)continue;if("function"==typeof g&&0==e.indexOf("on"))b[e]=f(g,b);else if("style"===e)for(var i in g)(void 0===h||h[i]!==g[i])&&(b.style[i]=g[i]);else e in b?b[e]=g:b.setAttribute(e,g)}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:[a-z]\w+/gi);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f],!1)},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;c-1?new f.constructor(f):f,g.nodes=[p]}else if(g.valueOf()!==f.valueOf()){if(f.$trusted){var t=g.nodes[0],l=[t];if(t){for(;t=t.nextSibling;)l.push(t);d(l);var r=e.lastChild;e.insertAdjacentHTML("beforeend",f),p=r?r.nextSibling:e.firstChild}else e.innerHTML=f}else p=g.nodes[0],e.insertBefore(p,e.childNodes[i]),p.nodeValue=f;g=new f.constructor(f),g.nodes=[p]}else g.nodes.intact=!0}return g}}function c(b,c,d){for(var e in c){var g=c[e],h=d[e];if(!(e in d)||h!==g||b===a.document.activeElement){if(d[e]=g,"config"===e)continue;if("function"==typeof g&&0==e.indexOf("on"))b[e]=f(g,b);else if("style"===e)for(var i in g)(void 0===h||h[i]!==g[i])&&(b.style[i]=g[i]);else e in b?b[e]=g:b.setAttribute(e,g)}}return d}function d(a){for(var b=0;b16)m.redraw();else{var b=a.cancelAnimationFrame||a.clearTimeout,c=a.requestAnimationFrame||a.setTimeout;b(E),E=c(m.redraw,0)}}function h(a,b,c){J={};for(var d in b){if(d==c)return!void m.module(a,b[d]);var e=new RegExp("^"+d.replace(/:[^\/]+/g,"([^\\/]+)")+"$");if(e.test(c))return!void c.replace(e,function(){for(var c=d.match(/:[^\/]+/g),e=[].slice.call(arguments,1,-2),f=0;f0&&("GET"==a.method?a.url=a.url+(a.url.indexOf("?")<0?"?":"&")+n(b):a.data=c(b)),a}function p(a,b){var c=a.match(/:[a-z]\w+/gi);if(c&&b)for(var d=0;d0&&(f.attrs[d]=h.join(" "))}f=e(f),f.attrs=e(f.attrs),f.children=b?a[2]:a[1];for(var j in c)f.attrs[j]=j==d?(f.attrs[j]||"")+" "+c[j]:c[j];return f};var v,w={insertAdjacentHTML:function(b,c){a.document.write(c),a.document.close()},appendChild:function(b){void 0===v&&(v=a.document.createElement("html")),"HTML"==b.nodeName?v=b:v.appendChild(b),a.document.documentElement!==v&&a.document.replaceChild(v,a.document.documentElement)}},x=[],y={};m.render=function(c,d){var e=x.indexOf(c),f=0>e?x.push(c)-1:e,g=c==a.document||c==a.document.documentElement?w:c;y[f]=b(g,d,y[f],!1)},m.trust=function(a){return a=new String(a),a.$trusted=!0,a};var z,A={view:function(){}},B={},C=0,D=0,E=0;m.module=function(a,b){m.startComputation(),z=a,A=b,B=new b.controller,m.endComputation()},m.redraw=function(){m.render(z,A.view(B)),D=C};var F=0,G=null;m.startComputation=function(){F++},m.endComputation=function(){F=Math.max(F-1,0),0==F&&(g(),G&&(G(),G=null))},m.withAttr=function(a,b){return function(c){b(a in c.currentTarget?c.currentTarget[a]:c.currentTarget.getAttribute(a))}};var H={pathname:"",hash:"#",search:"?"},I=function(){},J={};m.route=function(){if(3==arguments.length){var b=arguments[0],c=arguments[1],d=arguments[2];I=function(a){var e=a.slice(H[m.route.mode].length);h(b,d,e)||m.route(c,!0)};var e="hash"==m.route.mode?"onhashchange":"onpopstate";a[e]=function(){I(a.location[m.route.mode])},G=j,a[e]()}else if(arguments[0].addEventListener){var f=arguments[0],g=arguments[1];g||(f.removeEventListener("click",i),f.addEventListener("click",i))}else if("string"==typeof arguments[0]){var k=arguments[0],l=arguments[1]===!0;a.history.pushState?(G=function(){a.history[l?"replaceState":"pushState"](null,a.document.title,H[m.route.mode]+k),j()},I(H[m.route.mode]+k)):a.location[m.route.mode]=k}},m.route.param=function(a){return J[a]},m.route.mode="search",m.prop=function(a){return function(){return arguments.length&&(a=arguments[0]),a}},m.deferred=function(){var a=[],b=[],c={resolve:function(b){for(var c=0;czl8mULx#mMp$};Wj{#;VjdbOe{nx+jGp(%ITlEhIzP~!io|J7MSvnT+5){4?Y%%x=_zQ^LoW&11VL^s&~N5nT9z9)J=6zk z|JTen-^@QVyZ-Q9-@W&IFDzP5?0aet{hLYsFg|wZJB6=K?b-9y-4|Zpt5xs*{L1NC ztQ{;CTz9nW*H-(_EB@K)%4lsUwkn_ZUG1&5u}CExF# z?VHgATGsu9Z~MXLO@Sn zh>}qX7MjgceOXkh?(!&PJs`5ro90Q3x$>WTPx@BnGf%vFys53n!Vva|w)IP+P5Isv zQ^%UxO0dxBwA$J$cl=tBgai2bTUfri?}+^NzNezin|HJl7;cwZ9is#uN~29o@Ls~! zzEQpD_n#l#AtQWKD?&hQ-)SF-nLhRS!}8v~ONW};(7OLl$!(X|^-cNtzJRYKPwbBr zo3YlVR}0C9+-kjiz7$-kw%Q%$g3k;#V};;yqu5;o>%n5HROpnJaDecQuK2$Z zzkGXt8d$&B9~GTGS2th=g0^#$BD$l3XtopUJd9-4?*^OQb_MIR%cCx$6Jg7!_cLnw z?vrETUBrANlUh@L`Q$lY^Zw9*KfdrR-M!zRsJ$N_+p{M=CcilK&i-nBxwLuzjnjvH zM<3>OSR3AP$^CnSOZ)Ht`^C@p-Y|8&YzQr>i)I|ZdO>q^eQAy!!$wNWU@)(ly4f_e zydF={PCcE%jBeAs?pQdGj2K!<*Vhd#r|UIiffg)B#{5IQB|4DF(o8PF^Fp(*dKJ&k zb{xkRQfb;#OyXH2Nd2{|G!ZqDr2ZOXX#>k{ogxyyOj8-!n_}!%9vFpdT%&ma%j!w_ zk^gMuI*$t^Ew@b1RuO8%h7dGh!AYGTSJbMEo;@+)8Z@wZPnv(N66<0SPLZE)(VntV zz#mbGUz4w$^&e2`it>ZAQ!jY}$tuVIM_r%;j^#Nz2qEBN)mWlInjzRZ+UrzUxl@!Q zfvJI-8d!2UaP@%FZpl9crl;T(lF}yM=y?(;LP_ifBMv(<^N_hMgTXqM3~K%!b7bKp z05~lr2mYgxU|}&iL_|z02ja^cM%k#`{{n~&$ihxB6Zb$`(5>5!2I@29VTy0KX^q*qW~)GYJa|ht z>q1+H3+loq8?2p6s1s|@P$bgEjBQ2hmi$d{Vx(^=BS@3mi<$s5R-IC@wJPMH(BWQ_ z*>)NNV#J1lAqC;O(AJ<8SP`HFo&+tc9^w2a&tac6X-Bc6q;!Q=(vxl+@l8^8$3_lo zdTE1mR!79yCDqcA`sF;L=daT<-%1fl zC}5QA^h_SV=5>m~l*iERoM8Ohyv^DY6o)zDYMO~ic?*aq3Qbx-;E+PromI<7S>);+ zAen|-2?u*2Cdvkx@ViA8i(0(Jf;l~j1;#SP67&CePxN$i23{jr&Jo-0L;FI*Of5CksAPnE&n ziptN%P7J^VxOgQh4~?H5z^$X(){xWVr_ZQsV{^P)o=92;x5~rTy4)H+1?*C%)ly6{$E3Pvk|2%c-n44gfYY7$h zFqw;aMa;;7=`*1QAI*>RUA4F)NJ1IMI-yo~m{EvnGa#=||2W)9DqFb+Ks|sZ_U0pV zRIK`Q@>nz(o>m*8NlFp$3RymrWXr#9E+FqlzZ}?30*Sa9maorz`tppj1m>6sDN4Cj zJ`V)2DnH52x2LElao}7V-OKbGQJ?PiOCa4*B!1|&sm(wbBhGKpQ_G0Tzns4^(xKyK zBr(C#au7H-`lz{Bcj$94VJ_v+Tm9tb=LWjLBrp%I@ z8W(ESc36~h)E!QR*&>?@fm7l3Dmf*;nEl>KHAtzZWor)J6ywG=@36My^V<&yv^4W_g_Et@)U7Q~PREnpD{J};409d97+mgS$c(*?Y z%ehO#Q*Ns77d=^3u)WRejHs$mrZGv*gc#E`*X28xj%jLN8f^ry*?M4=X;%bnhY6= zC)Lel#+0QSyw=&hHbGzz%k>nOU_zE5BgK_vdR{8elg@_0S;>J2*yVU~RTqYB0(BBj zsu^C}*kwmF3~s<3(20Ny8D0b+J}LRI64%(BRiks-DzZrfuLyv!A$IEv%u!-1aOtpj zFX12>iIjX_pZ?Zl3WxRd*1Y^wAG{u72j;gJc@{BGE<>rLEo4k;nnJE$Xq+hLt#q9| zM}|_v*m!i3Sc=MJg>k(FjHH|b~+#oX2tI(H8$;cmr=F=tzT zV!Vi4en1TRYI0O8&aQHfD%J>qSYBluWkZ3i7)ZCk+)(!&DHm2al4^G<;GI;{lf2e3UY~{ zaU5Nw(#`5=ZkJ4`$Fy-DZeo1FuoM4S};wlb~y!b8FhvaDypq zsA6FoY;K7*A4|vC@2G~f){^yPY&gs-6Gp@n2$LP6cUn}GO=I#;$>ReN&cM!?{5*N` zWJKBIrH1G{G2|?J+{xGGo|hNro<4}O4jPeR6sMR!{4|Og%)x|1gdCP{&K-RQ@rU#3 z3c2#vbBP`jF;JuEGMKz>F5oFnK0~}_5d(;vbd(XLG08+wcni2QrKgrqnaF|2A=sEB zZ*c#PFBT9;pE$r)#ptk>IV3P*gSKY2nXYG`nL?3#nc9?}q{eT6VYH3gaj?bFNt?kK zP~z=;XDY>A7 z9yt^CyC$SRXe&?}ruTOCDI{k23OXy8SkTKHYw5{(l3`7-t}E)y(wHEVtMBn;&3~z>HvpI5AH-H zU>d&SqKV4Lq$p$hq|iOc(@z%SvD=NiinSzlGUgmUB)}$%E4H{?Rq4-*MfRYhSjA<> zK2nW#swE~4cw-q^965}8NRh3YJ6vMwk*;>-+TxjmXoD<0xoxs9%`N%%;^DwJe@DxV x(Q7|iGckN;(emwme$UtZe~kQiF*LaQEAgH^2l@YP`aeUbWhg!Ljy6 zvDlLTs;$IAlPee6!^xGw!GW7}q!ybG6ttyhS8ixUdMpo2ZhED~-cVmGRy%htP#daw zi-q-}Qmv6+XxzFLsK)xj1H3m@8@OFsY6beLdkF4x&8|3-4~dmnWoW^xE#w;kno;rx z1}57|c)(imVNZHch5=wFgRX5){6T4TmLvRF8G&Xa2gPlmnyLj03^qAGU_ z0Xd$0`{3ydIJHYyn;*?T8>`&DU8t@XdO>*qv)+8Uu+Zpr_|J|07!2^xp<9PMZB@Q_ z=#j&#mja&r^xLQ8yN4d@@Z?8_PU88+q5gz-xai4*e6B4clgqSp4PWIz$n?Ep6og@*s&&m zaWs6yi>+N+lOG)I0g#^_>AmOh`$t^N2ab-5`Yy{r0w=29AP=i6vrg7y^`Tm!*+`e> ztEF0Ta-cG_y0N~D^~uFR1^xmz+JE10&pzGm)m`)C`-;}-966PVFcG_N}r4kW^cX6gExp=EWwY+R-VJ0*WT(ASzJN$E7rWT$vu zXf{^o@b2uyacstNXwO0d?_!Fszcxn`g3Kp^G|et#o(F2JK1l zQS2dU3k-oJJ9&P;j(M>R`5Evhzj*5LV+uoFo*j%{`r0iU#x(-B$OJ(I2&x#wknS?B zRF-IQmT1Pw%U1@Yoy!cvvE*L|uO3rZTQZ`JMQdsSw#eyvj%*}?3G416S0@LCVp&^`#X@{AFyZS@i42;?T38R>Z z`?wXejs~kyRC2h$-cS{!rF0e4yVxz=ysE9iPBp>&+k4Tg`zRL(&9Kq*k;}PYWY0Ta z40d(SlY;i3A>RykwaUz_QxZ4@g)k(?tO#udmjDDP%kw1kEwM?0 z4@>7)N-cWs8ohH&itv;ILHSQlQ)HRf>7?b`cug?W2Jf=hITd6z1LL9=!%7 z%~^Kk2;b|+bV$BBT<-!$ut$2oTdc_^N1mTmbGAB^;|h>v&!^~yC9CWN&;>bR9=kyA zED2K{VN-&1r!gTCBjm{XQbc|*($lGUwkt13dd?y#kulh9+ZT~K@M~o}dqoFOF}7YFy$S zkGl?}cBqr8-!n&*sXZtEIi3iG)rR2&g}ZV-Yb3$y-)}A=uTOl|*`P8Yu5QT8`A?r8 zRq8+|6N#SE;vlyiKmY}DJM0&!P!cj7L|6(9OE^jVyX)-=2UIqaGufN_r!=|7JSk8& zBJ#D#*~>LLZic6}Sy~oCNfQXg6&V(1ju^b?`!idxXmH85M@b=a8{A+;3Q;ni)T6; z7RAflwtVa2<2{?qqfNfykkw73Mq7__#VLE;hT6YxtF)A#{QfdNoo~j4bPY%Xq%!fRrP4mBqgx? z6((~35)Pv5Th+J;qsW^^xUp4qxV>zj^vW^L*wN6LCzPWVuU@;iEu)4k%}0< zDj=16Ct;m*LwVUi5&)Lwi6_-t{Uq+S3yk3fEl=fj0!OpX7MMf(^Ka#fP7cCM+}u^&8TvCW^9=q zFjb=HLc-t}rh;gaqk6*}w#3w5E=n;Yj#P(-N+HsnMy zW)8E&h%w?z1P2ab7!za4nYS;~ZhQ!{rs~rNUW3u5(d6$eXIuU;dFHXEQr}Nl(P?%V zE;^~8{5;tk8fQw<%B|o;_p`8gR$66xnj3En;f*)Jo&k;wXj0Qg<3>! zkUx2f#`(E^`Sr|*#KNc*IyT5?ol38nz6Vo zv_0t-t-rtB(S6m<^p_ z-p1thnN!c9D*|En++XIP!}kOf&QcKT`6wr5GiN2Q5ArQY;RWUZpz`81`KHs;i9|^2W%(DUzg6TuH(gF~aH;t7=a%_n zS_Lj-5+!I{iO6+z2pe29w`P~VH)<}a*dkI znk&R@%_#&_fr5DKuEuNfcUMk_)hATj;n*IIA_+MFTG9%BKB -1 ? new data.constructor(data) : data cached.nodes = [node] @@ -110,7 +110,7 @@ new function(window) { } else { node = cached.nodes[0] - parent.appendChild(node) + parent.insertBefore(node, parent.childNodes[index]) node.nodeValue = data } cached = new data.constructor(data) diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index ff6adf39..39225477 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -142,13 +142,14 @@ function testMithril(mock) { return root.childNodes[0].childNodes[0].childNodes[0].nodeName === "A" }) test(function() { + //https://github.com/lhorie/mithril.js/issues/43 var root = mock.document.createElement("div") m.render(root, m("a", {config: m.route}, "test")) m.render(root, m("a", {config: m.route}, "test")) return root.childNodes[0].childNodes[0].nodeValue === "test" }) test(function() { - //see issue #29 + //https://github.com/lhorie/mithril.js/issues/29 var root = mock.document.createElement("div") var list = [false, false] m.render(root, list.reverse().map(function(flag, index) { @@ -167,6 +168,20 @@ function testMithril(mock) { return root.childNodes[0].checked === false && root.childNodes[1].checked === true }) + test(function() { + //https://github.com/lhorie/mithril.js/issues/44 + var root = mock.document.createElement("div") + m.render(root, m("#foo", [null, m("#bar")])) + m.render(root, m("#foo", ["test", m("#bar")])) + return root.childNodes[0].childNodes[0].nodeValue === "test" + }) + test(function() { + //https://github.com/lhorie/mithril.js/issues/44 + var root = mock.document.createElement("div") + m.render(root, m("#foo", [null, m("#bar")])) + m.render(root, m("#foo", [m("div"), m("#bar")])) + return root.childNodes[0].childNodes[0].nodeName === "DIV" + }) //m.redraw test(function() { diff --git a/tests/mock.js b/tests/mock.js index dfbe99c1..c8b6c6fd 100644 --- a/tests/mock.js +++ b/tests/mock.js @@ -10,6 +10,12 @@ mock.window = new function() { appendChild: window.document.appendChild, removeChild: window.document.removeChild, replaceChild: window.document.replaceChild, + insertBefore: function(node, reference) { + node.parentNode = this + var index = this.childNodes.indexOf(reference) + if (index < 0) this.childNodes.push(node) + else this.childNodes.splice(index, 0, node) + }, setAttribute: function(name, value) { this[name] = value.toString() }, From 3eb4bfc6a600d28feeed5d1a5a36025b2c0f1da1 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Wed, 9 Apr 2014 09:18:59 -0400 Subject: [PATCH 37/38] add test for textnode->element and element->textnode --- archive/v0.1.4/mithril-tests.js | 14 ++++++++++++++ archive/v0.1.4/mithril.min.zip | Bin 21025 -> 21025 bytes tests/mithril-tests.js | 14 ++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/archive/v0.1.4/mithril-tests.js b/archive/v0.1.4/mithril-tests.js index 06ce9c2d..a056c081 100644 --- a/archive/v0.1.4/mithril-tests.js +++ b/archive/v0.1.4/mithril-tests.js @@ -720,6 +720,20 @@ function testMithril(mock) { m.render(root, m("#foo", [m("div"), m("#bar")])) return root.childNodes[0].childNodes[0].nodeName === "DIV" }) + test(function() { + //https://github.com/lhorie/mithril.js/issues/44 + var root = mock.document.createElement("div") + m.render(root, m("#foo", ["test", m("#bar")])) + m.render(root, m("#foo", [m("div"), m("#bar")])) + return root.childNodes[0].childNodes[0].nodeName === "DIV" + }) + test(function() { + //https://github.com/lhorie/mithril.js/issues/44 + var root = mock.document.createElement("div") + m.render(root, m("#foo", [m("div"), m("#bar")])) + m.render(root, m("#foo", ["test", m("#bar")])) + return root.childNodes[0].childNodes[0].nodeValue === "test" + }) //m.redraw test(function() { diff --git a/archive/v0.1.4/mithril.min.zip b/archive/v0.1.4/mithril.min.zip index 46d8d463f32a9d4340120ecc274d604eb69269d4..ef45d77139eaa7c749be37d12e6d821b5bd2d915 100644 GIT binary patch delta 45 vcmZ3ugmK{#MxFp~W)?065b*Qb$RjV$ Date: Wed, 9 Apr 2014 13:31:49 -0400 Subject: [PATCH 38/38] update change log --- archive/v0.1.4/change-log.html | 9 ++++++++- archive/v0.1.4/mithril.min.zip | Bin 21025 -> 21025 bytes docs/change-log.md | 9 ++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/archive/v0.1.4/change-log.html b/archive/v0.1.4/change-log.html index 916149b8..63e86976 100644 --- a/archive/v0.1.4/change-log.html +++ b/archive/v0.1.4/change-log.html @@ -61,11 +61,18 @@

Change Log

-

v0.1.4 - maintenance

+

v0.1.4 - maintenance

+

News:

+
    +
  • added regression tests for reported bugs
  • +

Bug Fixes:

  • URLs with port numbers are now handled correctly #40
  • NPM package now contains unminified version for map files #39
  • +
  • fixed ordering issue when mixing newly created virtual elements with elements from cache #44
  • +
  • fixed caching bug in links w/ config option attached #43
  • +
  • fixed attribute update bug when an element has both oninput and onkeydown handlers #36

v0.1.3 - maintenance

News:

diff --git a/archive/v0.1.4/mithril.min.zip b/archive/v0.1.4/mithril.min.zip index ef45d77139eaa7c749be37d12e6d821b5bd2d915..4c5fea6dceba5e31a8f853ee86ee125bd65be361 100644 GIT binary patch delta 45 vcmZ3ugmK{#MxFp~W)?065O|rrkw;#h>1Fn2UHL*IAU%0OpcRO^6X*&6E#nUR delta 45 vcmZ3ugmK{#MxFp~W)?065b*Qb$RjV$