diff --git a/docs/api.md b/docs/api.md index 9b301bdb..77c2b0d8 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,22 +1,174 @@ # API -- [m](hyperscript.md) -- [m.render](render.md) -- [m.mount](mount.md) -- [m.route](route.md) - - [m.route.set](route.md#routeset) - - [m.route.get](route.md#routeget) - - [m.route.prefix](route.md#routeprefix) - - [m.route.link](route.md#routelink) -- [m.request](request.md) -- [m.jsonp](jsonp.md) -- [m.parseQueryString](parseQueryString.md) -- [m.buildQueryString](buildQueryString.md) -- [m.withAttr](withAttr.md) -- [m.trust](trust.md) -- [m.fragment](fragment.md) -- [m.redraw](redraw.md) -- [m.version](version.md) +### Cheatsheet + +Here are examples for the most commonly used methods. If a method is not listed below, it's meant for advanced usage. + +#### m(selector, attrs, children) - [docs](hyperscript.md) + +```javascript +m("div.class#id", {title: "title"}, ["children"]) +``` + +--- + +#### m.mount(element, component) - [docs](mount.md) + +```javascript +var state = { + count: 0, + inc: function() {state.count++} +} + +var Counter = { + view: function() { + return m("div", {onclick: state.inc}, state.count) + } +} + +m.mount(document.body, Counter) +``` + +--- + +#### m.route(root, defaultRoute, routes) - [docs](route.md) + +```javascript +var Home = { + view: function() { + return "Welcome" + } +} + +m.route(document.body, "/home", { + "/home": Home, // defines `http://localhost/#!/home` +}) +``` + +#### m.route.set(path) - [docs](route.md#routeset) + +```javascript +m.route.set("/home") +``` + +#### m.route.get() - [docs](route.md#routeget) + +```javascript +var currentRoute = m.route.get() +``` + +#### m.route.prefix(prefix) - [docs](route.md#routeprefix) + +Call this before `m.route()` + +```javascript +m.route.prefix("#!") +``` + +#### m.route.link() - [docs](route.md#routelink) + +```javascript +m("a[href='/Home']", {oncreate: m.route.link}, "Go to home page") +``` + +--- + +#### m.request(options) - [docs](request.md) + +```javascript +m.request({ + method: "PUT", + url: "/api/v1/users/:id", + data: {id: 1, name: "test"} +}) +.then(function(result) { + console.log(result) +}) +``` + +--- + +#### m.jsonp(options) - [docs](jsonp.md) + +```javascript +m.jsonp({ + url: "/api/v1/users/:id", + data: {id: 1}, + callbackKey: "callback", +}) +.then(function(result) { + console.log(result) +}) +``` + +--- + +#### m.parseQueryString(querystring) - [docs](parseQueryString.md) + +```javascript +var object = m.parseQueryString("a=1&b=2") +// {a: "1", b: "2"} +``` + +--- + +#### m.buildQueryString(object) - [docs](buildQueryString.md) + +```javascript +var querystring = m.buildQueryString({a: "1", b: "2"}) +// "a=1&b=2" +``` + +--- + +#### m.withAttr(attrName, callback) - [docs](withAttr.md) + +```javascript +var state: { + value = "" + setValue: function(v) {value = v} +} + +var Component = { + view: function() { + return m("input", { + oninput: m.withAttr("value", state.setValue), + value: state.value, + }) + } +} + +m.mount(document.body, Component) +``` + +--- + +#### m.trust(htmlString) - [docs](trust.md) + +```javascript +m.render(document.body, m.trust("

Hello

")) +``` + +--- + +#### m.redraw() - [docs](redraw.md) + +```javascript +var count = 0 +function inc() { + setInterval(function() { + count++ + m.redraw() + }, 1000) +} + +var Counter = { + oninit: inc, + view: function() { + return m("div", count) + } +} + +m.mount(document.body, Counter) +``` -- [Promise](promise.md) -- [Stream](stream.md) \ No newline at end of file diff --git a/docs/buildQueryString.md b/docs/buildQueryString.md index 0588da00..03f723fa 100644 --- a/docs/buildQueryString.md +++ b/docs/buildQueryString.md @@ -1,10 +1,22 @@ # buildQueryString(object) +- [Description](#description) - [Signature](#signature) - [How it works](#how-it-works) --- +### Description + +Turns an object into a string of form `a=1&b=2` + +```javascript +var querystring = m.buildQueryString({a: "1", b: "2"}) +// "a=1&b=2" +``` + +--- + ### Signature `querystring = m.buildQueryString(object)` diff --git a/docs/examples.md b/docs/examples.md index d52df077..140ad3f6 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -1,8 +1,11 @@ # Examples +Here are some examples of Mithril in action + - [Animation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/animation/mosaic.html) - [DBMonster](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/mithril/index.html) - [Markdown Editor](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/editor/index.html) - SVG: [Clock](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/clock.html), [Ring](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/ring.html), [Tiger](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/tiger.html) - [ThreadItJS](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/threaditjs/index.html) -- [TodoMVC](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/todomvc/index.html) \ No newline at end of file +- [TodoMVC](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/todomvc/index.html) + diff --git a/docs/fragment.md b/docs/fragment.md index 4b180a91..cd1ff53a 100644 --- a/docs/fragment.md +++ b/docs/fragment.md @@ -1,10 +1,17 @@ # fragment(attrs, children) +- [Description](#description) - [Signature](#signature) - [How it works](#how-it-works) --- +### Description + +Allows attaching lifecycle methods to a fragment [vnode](vnodes.md) + +--- + ### Signature Generates a fragment [vnode](vnodes.md) diff --git a/docs/generate.js b/docs/generate.js new file mode 100644 index 00000000..797ffbfd --- /dev/null +++ b/docs/generate.js @@ -0,0 +1,52 @@ +var fs = require("fs") +var path = require("path") +var marked = require("marked") +var layout = fs.readFileSync("./docs/layout.html", "utf-8") +var version = JSON.parse(fs.readFileSync("./package.json", "utf-8")).version +try {fs.mkdirSync("docs/archive/" + version)} catch (e) {} +try {fs.mkdirSync("docs/archive/" + version + "/lib")} catch (e) {} +try {fs.mkdirSync("docs/archive/" + version + "/lib/prism")} catch (e) {} + +var guides = fs.readFileSync("docs/guides.md", "utf-8") +var methods = fs.readFileSync("docs/methods.md", "utf-8") + +generate("docs") + +function generate(pathname) { + if (fs.lstatSync(pathname).isDirectory()) { + fs.readdirSync(pathname).forEach(function(filename) { + generate(pathname + "/" + filename) + }) + } + else if (!pathname.match(/tutorials|archive/)) { + if (pathname.match(/\.md$/)) { + var outputFilename = pathname.replace(/\.md$/, ".html") + var markdown = fs.readFileSync(pathname, "utf-8") + var fixed = markdown + .replace(/(`[^`]+?)<(.*`)/gim, "$1<$2") // fix generic syntax + .replace(/`((?:\S| -> |, )+)(\|)(\S+)`/gim, function(match, a, b, c) { // fix pipes in code tags + return "" + (a + b + c).replace(/\|/g, "|") + "" + }) + .replace(/(^# .+?(?:\r?\n){2,}?)(?:(-(?:.|\r|\n)+?)((?:\r?\n){2,})|)/m, function(match, title, nav, space) { // inject menu + var file = path.basename(pathname) + var link = new RegExp("([ \t]*)(- )(\\[.+?\\]\\(" + file + "\\))") + var replace = function(match, space, li, link) { + return space + li + "**" + link + "**" + (nav ? "\n" + nav.replace(/(^|\n)/g, "$1\t" + space) : "") + } + var modified = guides.match(link) ? guides.replace(link, replace) : methods.replace(link, replace) + return title + modified + "\n\n" + }) + .replace(/\.md/gim, ".html") // fix links + var html = layout + .replace(/\[body\]/, marked(fixed)) + .replace(/
([^<]+?)<\/h5>/gim, function(match, id, text) { // fix anchors + return "
" + text + "
" + }) + fs.writeFileSync("docs/archive/" + version + "/" + outputFilename.replace(/^docs\//, ""), html, "utf-8") + } + else { + fs.writeFileSync("docs/archive/" + version + "/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, "utf-8"), "utf-8") + } + } +} + diff --git a/docs/guides.md b/docs/guides.md new file mode 100644 index 00000000..f7a7f0b9 --- /dev/null +++ b/docs/guides.md @@ -0,0 +1,16 @@ +- Tutorials + - [Installation](installation.md) + - [Introduction](introduction.md) + - [Tutorial](tutorial.md) + - [Testing](testing.md) + - [Examples](examples.md) +- Key concepts + - [Vnodes](vnodes.md) + - [Components](components.md) + - [Lifecycle methods](lifecycle-methods.md) + - [Keys](keys.md) +- Migration + - [Migrating from 0.2.x](migration.md) +- Meta + - [Chat room](https://gitter.im/lhorie/mithril.js) + - [Credits](credits.md) \ No newline at end of file diff --git a/docs/hyperscript.md b/docs/hyperscript.md index 44190ec4..9d608dcd 100644 --- a/docs/hyperscript.md +++ b/docs/hyperscript.md @@ -1,5 +1,6 @@ # m(selector, attributes, children) +- [Description](#description) - [Signature](#signature) - [How it works](#how-it-works) - [Flexibility](#flexibility) @@ -17,6 +18,24 @@ --- +### Description + +Represents an HTML element in a Mithril view + +```javascript +m("div", {class: "foo"}, "hello") +// represents
hello
+``` + +You can also [use HTML syntax](https://babeljs.io/repl/#?code=%2F**%20%40jsx%20m%20*%2F%0A%3Ch1%3EMy%20first%20app%3C%2Fh1%3E) via a Babel plugin. + +```markup +/** jsx m */ +
hello
+``` + +--- + ### Signature `vnode = m(selector, attributes, children)` diff --git a/docs/introduction.md b/docs/introduction.md index 88e5e2a8..52c12158 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -12,13 +12,13 @@ ### What is Mithril? -Mithril is a framework for developing Javascript-based Single Page Applications. It's designed to be fast, small and economical. +Mithril is a framework for building Single Page Applications. It's small but batteries-included. --- ### Getting started -The easiest way to try out Mithril is to include it from a CDN, and follow this tutorial. It'll only take 10 minutes. +The easiest way to try out Mithril is to include it from a CDN, and follow this tutorial. It'll cover the majority of the API surface but it'll only take 10 minutes. Let's create an HTML file to follow along: @@ -84,6 +84,8 @@ m("main", [ ]) ``` +Note: If you would rather use `` syntax, [you can do so by using Babel](https://babeljs.io/repl/#?code=%2F**%20%40jsx%20m%20*%2F%0A%3Ch1%3EMy%20first%20app%3C%2Fh1%3E). + --- ### Components @@ -180,7 +182,7 @@ Basically, XHR is just a way to talk to a server. Let's change our click counter to make it save data on a server. For the server, we'll use [REM](http://rem-rest-api.herokuapp.com), a mock REST API designed for toy apps like this tutorial. -First we create a function that calls `m.request`. +First we create a function that calls `m.request`. The `url` specifies an endpoint that represents a resource, the `method` specifies the type of action we're taking (typically the `PUT` method [upserts](https://en.wiktionary.org/wiki/upsert)), `data` is the payload that we're sending to the endpoint and `useCredentials` means to enable cookies (a requirement for the REM API to work) ```javascript var count = 0 diff --git a/docs/jsonp.md b/docs/jsonp.md index cf760338..f0ee9b38 100644 --- a/docs/jsonp.md +++ b/docs/jsonp.md @@ -1,11 +1,29 @@ # jsonp(options) +- [Description](#description) - [Signature](#signature) - [How it works](#how-it-works) - [Typical usage](#typical-usage) --- +### Description + +Makes JSON-P requests. Typically, it's useful to interact with servers that allow JSON-P but that don't have CORS enabled. + +```javascript +m.jsonp({ + url: "/api/v1/users/:id", + data: {id: 1}, + callbackKey: "callback", +}) +.then(function(result) { + console.log(result) +}) +``` + +--- + ### Signature `promise = m.jsonp(options)` diff --git a/docs/layout.html b/docs/layout.html new file mode 100644 index 00000000..6e96bb1b --- /dev/null +++ b/docs/layout.html @@ -0,0 +1,29 @@ + + + + Mithril.js + + + + + +
+
+

Mithril

+ +
+
+
+
+ [body] +
+ License: MIT. © Leo Horie. +
+
+ + +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/docs/methods.md b/docs/methods.md new file mode 100644 index 00000000..9b19b4cc --- /dev/null +++ b/docs/methods.md @@ -0,0 +1,20 @@ +- Core + - [m](hyperscript.md) + - [m.render](render.md) + - [m.mount](mount.md) + - [m.route](route.md) + - [m.request](request.md) + - [m.jsonp](jsonp.md) + - [m.parseQueryString](parseQueryString.md) + - [m.buildQueryString](buildQueryString.md) + - [m.withAttr](withAttr.md) + - [m.trust](trust.md) + - [m.fragment](fragment.md) + - [m.redraw](redraw.md) + - [m.version](version.md) + - [Promise](promise.md) +- Optional + - [Stream](stream.md) +- Tooling + - [Bundler](bundler.md) + - [Ospec](ospec.md) diff --git a/docs/mount.md b/docs/mount.md index 857c0018..a188668b 100644 --- a/docs/mount.md +++ b/docs/mount.md @@ -1,5 +1,6 @@ # mount(root, component) +- [Description](#description) - [Signature](#signature) - [How it works](#how-it-works) - [Performance considerations](#performance-considerations) @@ -7,6 +8,27 @@ --- +### Description + +Activates a component, enabling it to autoredraw on user events + +```javascript +var state = { + count: 0, + inc: function() {state.count++} +} + +var Counter = { + view: function() { + return m("div", {onclick: state.inc}, state.count) + } +} + +m.mount(document.body, Counter) +``` + +--- + ### Signature `m.mount(element, component)` diff --git a/docs/parseQueryString.md b/docs/parseQueryString.md index c27f01ed..e3d23dcb 100644 --- a/docs/parseQueryString.md +++ b/docs/parseQueryString.md @@ -1,10 +1,22 @@ # parseQueryString(string) +- [Description](#description) - [Signature](#signature) - [How it works](#how-it-works) --- +### Description + +Turns a string of the form `?a=1&b=2` to an object + +```javascript +var object = m.parseQueryString("a=1&b=2") +// {a: "1", b: "2"} +``` + +--- + ### Signature `object = m.parseQueryString(string)` diff --git a/docs/promise.md b/docs/promise.md index 7a8a71d5..119d3a51 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -1,5 +1,6 @@ # Promise(executor) +- [Description](#description) - [Signature](#signature) - [Static members](#static-members) - [Promise.resolve](#promiseresolve) @@ -14,7 +15,16 @@ - [Promise absorption](#promise-absorption) - [Error handling](#error-handling) - [Shorthands](#shorthands) -- [Waiting for multiple promises](#waiting-for-multiple-promises) +- [Multiple promises](#multiple-promises) +- [Why not callbacks](#why-not-callbacks) + +--- + +### Description + +A [ES6 Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) polyfill. + +A Promise is a mechanism for working with asynchronous computations. --- @@ -262,7 +272,7 @@ promise --- -### Waiting for multiple promises +### Multiple promises In some occasions, you may need to make HTTP requests in parallel, and run code after all requests complete. This can be accomplished by `Promise.all` @@ -287,3 +297,13 @@ Promise.all([ In the example above, there are two user searches happening in parallel. Once they both complete, we take the names of all the users and alert them. This example also illustrates another benefit of smaller functions: we reused the `getUserNames` function we had created above. + +--- + +### Why not callbacks + +Callbacks are another mechanism for working with asynchrounous computations, and are indeed more adequate to use if an asynchronous computation may occur more than one time (for example, an `onscroll` event handler). + +However, for asynchronous computations that only occur once in response to an action, promises can be refactored more effectively, reducing code smells known as pyramids of doom (deeply nested series of callbacks with unmanaged state being used across several closure levels). + +In addition, promises can considerably reduce boilerplate related to error handling. \ No newline at end of file diff --git a/docs/redraw.md b/docs/redraw.md index 3be49fa6..ebc17283 100644 --- a/docs/redraw.md +++ b/docs/redraw.md @@ -1,10 +1,21 @@ # redraw() +- [Description](#description) - [Signature](#signature) - [How it works](#how-it-works) --- +### Description + +Updates the DOM after a change in the application data layer. + +You DON'T need to call it if data is modified within the execution context of an event handler defined in a Mithril view, or after request completion when using `m.request`/`m.jsonp`. + +You DO need to call it in `setTimeout`/`setInterval`/`requestAnimationFrame` callbacks, or callbacks from 3rd party libraries. + +--- + ### Signature `m.redraw()` diff --git a/docs/render.md b/docs/render.md index f3cf38fd..3218c761 100644 --- a/docs/render.md +++ b/docs/render.md @@ -1,5 +1,6 @@ # render(element, vnodes) +- [Description](#description) - [Signature](#signature) - [How it works](#how-it-works) - [Why Virtual DOM](#why-virtual-dom) @@ -8,6 +9,17 @@ --- +### Description + +Renders a template to the DOM + +```javascript +m.render(document.body, "hello") +// hello +``` + +--- + ### Signature `m.render(element, vnodes)` diff --git a/docs/request.md b/docs/request.md index d97432cb..cbd887a6 100644 --- a/docs/request.md +++ b/docs/request.md @@ -1,5 +1,6 @@ # request(options) +- [Description](#description) - [Signature](#signature) - [How it works](#how-it-works) - [Typical usage](#typical-usage) @@ -12,7 +13,25 @@ - [Non-JSON responses](#non-json-responses) - [Retrieving response details](#retrieving-response-details) - [Why JSON instead of HTML](#why-json-instead-of-html) -- [Why XMLHttpRequest instead of fetch](#why-xmlhttprequest-instead-of-fetch) +- [Why XHR instead of fetch](#why-xhr-instead-of-fetch) +- [Avoid anti-patterns](#avoid-anti-patterns) + +--- + +### Description + +Makes XHR (aka AJAX) requests, and returns a [promise](promise.md) + +```javascript +m.request({ + method: "PUT", + url: "/api/v1/users/:id", + data: {id: 1, name: "test"} +}) +.then(function(result) { + console.log(result) +}) +``` --- @@ -422,7 +441,7 @@ Data services may be organized in many different ways depending on the nature of --- -### Why XMLHttpRequest instead of fetch +### Why XHR instead of fetch [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is a newer Web API for fetching resources from servers, similar to `XMLHttpRequest`. @@ -431,11 +450,13 @@ Mithril's `m.request` uses `XMLHttpRequest` instead of `fetch()` for a number of - `fetch` is not fully standardized yet, and may be subject to specification changes. - `XMLHttpRequest` calls can be aborted before they resolve (e.g. to avoid race conditions in for instant search UIs). - `XMLHttpRequest` provides hooks for progress listeners for long running requests (e.g. file uploads). -- `XMLHttpRequest` is supported by all browsers, whereas `fetch()` is not supported by Internet Explorer and Safari. +- `XMLHttpRequest` is supported by all browsers, whereas `fetch()` is not supported by Internet Explorer, Safari and Android (non-Chromium). -Currently, due to lack of browser support, `fetch()` typically requires a [polyfill](https://github.com/github/fetch), which is over 11kb uncompressed - nearly three times larger than Mithril's `m.request`. +Currently, due to lack of browser support, `fetch()` typically requires a [polyfill](https://github.com/github/fetch), which is over 11kb uncompressed - nearly three times larger than Mithril's XHR module. -Despite being much smaller, `m.request` supports many important and not-so-trivial-to-implement features like [URL interpolation](#dynamic-urls), querystring serialization and [JSON-P requests](jsonp.md). The `fetch` polyfill does not support any of those. +Despite being much smaller, Mithril's XHR module supports many important and not-so-trivial-to-implement features like [URL interpolation](#dynamic-urls), querystring serialization and [JSON-P requests](jsonp.md), in addition to its ability to integrate seamlessly to Mithril's autoredrawing subsystem. The `fetch` polyfill does not support any of those, and requires extra libraries and boilerplates to achieve the same level of functionality. + +In addition, Mithril's XHR module is optimized for JSON-based endpoints and makes that most common case appropriately terse - i.e. `m.request(url)` - whereas `fetch` requires an additional explicit step to parse the response data as JSON: `fetch(url).then(function(response) {return response.json()})` The `fetch()` API does have a few technical advantages over `XMLHttpRequest` in a few uncommon cases: @@ -443,3 +464,24 @@ The `fetch()` API does have a few technical advantages over `XMLHttpRequest` in - it integrates to the [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), which provides an extra layer of control over how and when network requests happen. This API also allows access to push notifications and background synchronization features. In typical scenarios, streaming won't provide noticeable performance benefits because it's generally not advisable to download megabytes of data to begin with. Also, the memory gains from repeatedly reusing small buffers may be offset or nullified if they result in excessive browser repaints. For those reasons, choosing `fetch()` streaming instead of `m.request` is only recommended for extremely resource intensive applications. + +--- + +### Avoid anti-patterns + +#### Promises are not the response data + +The `m.request` method returns a [Promise](promise.md), not the response data itself. It cannot return that data directly because an HTTP request may take a long time to complete (due to network latency), and if Javascript waited for it, it would freeze the application until the data was available. + +```javascript +// AVOID +var users = m.request("/api/v1/users") +console.log("list of users:", users) +// `users` is NOT a list of users, it's a promise + +// PREFER +m.request("/api/v1/users").then(function(users) { + console.log("list of users:", users) +}) +``` + diff --git a/docs/route.md b/docs/route.md index 50b59bc4..b16d1336 100644 --- a/docs/route.md +++ b/docs/route.md @@ -1,5 +1,6 @@ # route(root, defaultRoute, routes) +- [Description](#description) - [Signature](#signature) - [Static members](#static-members) - [route.set](#routeset) @@ -21,6 +22,24 @@ --- +### Description + +Navigate between "pages" within an application + +```javascript +var Home = { + view: function() { + return "Welcome" + } +} + +m.route(document.body, "/home", { + "/home": Home, // defines `http://localhost/#!/home` +}) +``` + +--- + ### Signature `m.route(root, defaultRoute, routes)` diff --git a/docs/stream.md b/docs/stream.md index 12dd85ed..7b6110f8 100644 --- a/docs/stream.md +++ b/docs/stream.md @@ -1,5 +1,6 @@ # stream() +- [Description](#description) - [Signature](#signature) - [Static members](#static-members) - [stream.combine](#streamcombine) @@ -25,6 +26,16 @@ --- +### Description + +A Stream is a reactive data structure, similar to cells in spreadsheet applications. + +For example, in a spreadsheet, if `A1 = B1 + C1`, then changing the value of `B1` or `C1` automatically changes the value of `A1`. + +Similarly, you can make a stream depend on other streams so that changing the value of one automatically updates the other. This is useful when you have very expensive computations and want to only run them when necessary, as opposed to, say, on every redraw. + +--- + ### Signature Creates a stream diff --git a/docs/style.css b/docs/style.css new file mode 100644 index 00000000..faef030c --- /dev/null +++ b/docs/style.css @@ -0,0 +1,45 @@ +body,table,h5 {font:normal 16px 'Open Sans';} +header,main {margin:auto;max-width:1000px;} +header section {position:absolute;width:250px;} +nav a {border-left:1px solid #ddd;padding:0 10px;} +nav a:first-child {border:0;padding-left:0;} +main {margin-bottom:100px;} +main section {margin-left:270px;} +h1 {margin:0 0 15px;} +h5 {font-style:italic;} +pre,code {background:#eee;font-family:monospace;} +pre {border-left:3px solid #1e5799;overflow:auto;padding:10px 20px;} +code {border:1px solid #ddd;display:inline-block;margin:0 0 1px;padding:3px;white-space:pre;} +pre code {border:0;margin:0;padding:0;} +table {border-collapse:collapse;width:100%;} +tbody tr:nth-child(odd) {background:#fafafa;} +thead tr,tbody tr:nth-child(even) {background:#f3f3f3;} +th {text-align:left;} +th,td {padding:3px 10px;vertical-align:top;} +a {color:#1e5799;display:inline-block;text-decoration:none;} +a:hover {text-decoration:underline;} +hr {border:0;border-bottom:1px solid #ddd;margin:30px 0;} + +#signature + p code {padding:3px 10px;} +h1 + ul {margin:40px 0 0 -270px;padding:0;position:absolute;width:250px;} +h1 + ul + hr {display:none;} +h1 + ul li {border-bottom:1px solid #eee;list-style:none;margin:0;padding:0;} +h1 + ul li:last-child {border-bottom:0;} +h1 + ul ul {margin:0 0 10px;padding:0 0 0 15px;} +h1 + ul ul li {border:0;} + +@media (max-width: 767px) { + main section {margin:0;} + h1 + ul + hr {display:block;} + header section,h1 + ul {margin:0 0 20px;position:static;width:auto;} +} +@media (max-width: 1024px) { + #signature + p + table,#signature + p + table tbody,#signature + p + table tr,#signature + p + table th,#signature + p + table td {display:block;} + #signature + p + table thead {display:none;} + #signature + p + table td:before {display:inline-block;font-style:italic;padding:0 10px 0 0;width:100px;} + #signature + p + table tr:not(:last-child) td:nth-child(1):before {content:"Argument:";} + #signature + p + table td:nth-child(2):before {content:"Type:";} + #signature + p + table td:nth-child(3):before {content:"Required:";} + #signature + p + table td:nth-child(4):before {content:"Description:";} + #signature + p + table tr:last-child td:nth-child(3) {display:none;} +} diff --git a/docs/trust.md b/docs/trust.md index 53da8ed7..d960e743 100644 --- a/docs/trust.md +++ b/docs/trust.md @@ -1,5 +1,6 @@ # trust(html) +- [Description](#description) - [Signature](#signature) - [How it works](#how-it-works) - [Security considerations](#security-considerations) @@ -8,9 +9,15 @@ --- -### Signature +### Description -Generates a trusted HTML [vnode](vnodes.md) +Turns an HTML string into unescaped HTML. **Do not use `m.trust` on unsanitized user input.** + +Always try to use an [alternative method](#avoid-trusting-html) first, before considering using `m.trust`. + +--- + +### Signature `vnode = m.trust(html)` diff --git a/docs/withAttr.md b/docs/withAttr.md index 23ab5a1c..9fd7a7df 100644 --- a/docs/withAttr.md +++ b/docs/withAttr.md @@ -1,18 +1,39 @@ # withAttr(attrName, callback) +- [Description](#description) - [Signature](#signature) -- [How to use](#how-to-use) +- [How it works](#how-it-works) - [Predictable event target](#predictable-event-target) - [Attributes and properties](#attributes-and-properties) --- +### Description + +Returns an event handler that runs `callback` with the value of the specified DOM attribute + +```javascript +var state: { + value = "" + setValue: function(v) {value = v} +} + +var Component = { + view: function() { + return m("input", { + oninput: m.withAttr("value", state.setValue), + value: state.value, + }) + } +} + +m.mount(document.body, Component) +``` + +--- + ### Signature -Creates an event handler. The event handler takes the value of a DOM element's property and calls a function with it as the argument. - -This helper function is provided to help decouple the browser's event model from application code. - `m.withAttr(attrName, callback, thisArg?)` Argument | Type | Required | Description @@ -26,7 +47,11 @@ Argument | Type | Required | Description --- -### How to use +### How it works + +The `m.withAttr` method creates an event handler. The event handler takes the value of a DOM element's property and calls a function with it as the argument. + +This helper function is provided to help decouple the browser's event model from application code. ```javascript // standalone usage diff --git a/package.json b/package.json index c34192fe..a046b2d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mithril", - "version": "1.0.0-rc.5", + "version": "1.0.0-rc.6", "description": "A framework for building brilliant applications", "author": "Leo Horie", "license": "MIT", @@ -12,13 +12,15 @@ "build-browser": "node bundler/cli browser.js -o mithril.js", "build-min": "node bundler/cli browser.js -o mithril.min.js -m", "lintdocs": "node docs/lint", + "gendocs": "node docs/generate", "lint": "eslint .", "test": "node ospec/bin/ospec", "cover": "istanbul cover --print both ospec/bin/ospec" }, "devDependencies": { "eslint": "^2.10.2", - "istanbul": "^0.4.3" + "istanbul": "^0.4.3", + "marked": "^0.3.6" }, "publishConfig": { "tag": "rewrite"