Comparing version 2.0.0-rc.4 to 2.0.0-rc.5
@@ -26,3 +26,3 @@ "use strict" | ||
} | ||
routeService.defineRoutes(routes, function(payload, params, path) { | ||
routeService.defineRoutes(routes, function(payload, params, path, route) { | ||
var update = lastUpdate = function(routeResolver, comp) { | ||
@@ -38,9 +38,9 @@ if (update !== lastUpdate) return | ||
if (payload.onmatch) { | ||
Promise.resolve(payload.onmatch(params, path)).then(function(resolved) { | ||
Promise.resolve(payload.onmatch(params, path, route)).then(function(resolved) { | ||
update(payload, resolved) | ||
}, bail) | ||
}, function () { bail(path) }) | ||
} | ||
else update(payload, "div") | ||
} | ||
}, bail) | ||
}, bail, defaultRoute) | ||
} | ||
@@ -47,0 +47,0 @@ route.set = function(path, data, options) { |
@@ -324,3 +324,3 @@ "use strict" | ||
var resolver = { | ||
onmatch: function(args, requestedPath) { | ||
onmatch: function(args, requestedPath, route) { | ||
matchCount++ | ||
@@ -330,2 +330,3 @@ | ||
o(requestedPath).equals("/abc") | ||
o(route).equals("/:id") | ||
o(this).equals(resolver) | ||
@@ -367,3 +368,3 @@ return Component | ||
var resolver = { | ||
onmatch: function(args, requestedPath) { | ||
onmatch: function(args, requestedPath, route) { | ||
matchCount++ | ||
@@ -373,2 +374,3 @@ | ||
o(requestedPath).equals("/abc") | ||
o(route).equals("/:id") | ||
o(this).equals(resolver) | ||
@@ -405,3 +407,3 @@ return Promise.resolve(Component) | ||
var resolver = { | ||
onmatch: function(args, requestedPath) { | ||
onmatch: function(args, requestedPath, route) { | ||
matchCount++ | ||
@@ -411,2 +413,3 @@ | ||
o(requestedPath).equals("/abc") | ||
o(route).equals("/:id") | ||
o(this).equals(resolver) | ||
@@ -443,3 +446,3 @@ return Promise.resolve() | ||
var resolver = { | ||
onmatch: function(args, requestedPath) { | ||
onmatch: function(args, requestedPath, route) { | ||
matchCount++ | ||
@@ -449,2 +452,3 @@ | ||
o(requestedPath).equals("/abc") | ||
o(route).equals("/:id") | ||
o(this).equals(resolver) | ||
@@ -519,3 +523,3 @@ return Promise.resolve([]) | ||
"/:id" : { | ||
onmatch: function(args, requestedPath) { | ||
onmatch: function(args, requestedPath, route) { | ||
matchCount++ | ||
@@ -525,2 +529,3 @@ | ||
o(requestedPath).equals("/abc") | ||
o(route).equals("/:id") | ||
@@ -998,3 +1003,4 @@ return Component | ||
"/a": {view: view}, | ||
"/b": {onmatch: onmatch} | ||
"/b": {onmatch: onmatch}, | ||
"/": {view: function() {}} | ||
}) | ||
@@ -1001,0 +1007,0 @@ |
@@ -12,3 +12,3 @@ # Animations | ||
Animations are often used to make applications come alive. Nowadays, browsers have good support for CSS animations, and there are [various](https://greensock.com/gsap) [libraries](http://velocityjs.org/) that provide fast Javascript-based animations. There's also an upcoming [Web API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API) and a [polyfill](https://github.com/web-animations/web-animations-js) if you like living on the bleeding edge. | ||
Animations are often used to make applications come alive. Nowadays, browsers have good support for CSS animations, and there are [various](https://greensock.com/gsap) [libraries](http://velocityjs.org/) that provide fast JavaScript-based animations. There's also an upcoming [Web API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API) and a [polyfill](https://github.com/web-animations/web-animations-js) if you like living on the bleeding edge. | ||
@@ -15,0 +15,0 @@ Mithril does not provide any animation APIs per se, since these other options are more than sufficient to achieve rich, complex animations. Mithril does, however, offer hooks to make life easier in some specific cases where it's traditionally difficult to make animations work. |
@@ -13,7 +13,11 @@ # Change log | ||
- [Migrating from v0.2.x](#migrating-from-v02x) | ||
- [Older docs](http://mithril.js.org/archive/v0.2.5/index.html) | ||
- [ospec change-log](../ospec/change-log.md) | ||
- [v1.x docs](http://mithril.js.org/archive/v1.1.6/index.html) | ||
- [v0.2 docs](http://mithril.js.org/archive/v0.2.5/index.html) | ||
- [`ospec` change log](https://github.com/MithrilJS/mithril.js/blob/master/ospec/change-log.md) | ||
- [`mithril/stream` change log](https://github.com/MithrilJS/mithril.js/blob/master/stream/change-log.md) | ||
--- | ||
### Upcoming... | ||
### v2.0.0-rc | ||
@@ -31,9 +35,11 @@ | ||
- hyperscript: when an attribute is defined on both the first and second argument (as a CSS selector and an `attrs` field, respectively), the latter takes precedence, except for `class` attributes that are still added together. [#2172](https://github.com/MithrilJS/mithril.js/issues/2172) ([#2174](https://github.com/MithrilJS/mithril.js/pull/2174)) | ||
- stream: when a stream conditionally returns HALT, dependant stream will also end ([#2200](https://github.com/MithrilJS/mithril.js/pull/2200)) | ||
- render: remove some redundancy within the component initialization code ([#2213](https://github.com/MithrilJS/mithril.js/pull/2213)) | ||
- render: Align custom elements to work like normal elements, minus all the HTML-specific magic. ([#2221](https://github.com/MithrilJS/mithril.js/pull/2221)) | ||
- render: simplify component removal ([#2214](https://github.com/MithrilJS/mithril.js/pull/2214)) | ||
- cast className using toString ([#2309](https://github.com/MithrilJS/mithril.js/pull/2309)) | ||
- render: call attrs' hooks first, with express exception of `onbeforeupdate` to allow attrs to block components from even diffing ([#2297](https://github.com/MithrilJS/mithril.js/pull/2297)) | ||
- API: `m.withAttr` removed. ([#2317](https://github.com/MithrilJS/mithril.js/pull/2317)) | ||
- request: `data` has now been split to `params` and `body` and `useBody` has been removed in favor of just using `body`. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) | ||
- route, request: Interpolated arguments are URL-escaped (and for declared routes, URL-unescaped) automatically. If you want to use a raw route parameter, use a variadic parameter like in `/asset/:path.../view`. This was previously only available in `m.route` route definitions, but it's now usable in both that and where paths are accepted. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) | ||
- route, request: Interpolated arguments are *not* appended to the query string. This means `m.request({url: "/api/user/:id/get", params: {id: user.id}})` would result in a request like `GET /api/user/1/get`, not one like `GET /api/user/1/get?id=1`. If you really need it in both places, pass the same value via two separate parameters with the non-query-string parameter renamed, like in `m.request({url: "/api/user/:urlID/get", params: {id: user.id, urlID: user.id}})`. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) | ||
- route, request: `m.route.set`, `m.request`, and `m.jsonp` all use the same path template syntax now, and vary only in how they receive their parameters. Furthermore, declared routes in `m.route` shares the same syntax and semantics, but acts in reverse as if via pattern matching. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) | ||
- request: `options.responseType` now defaults to `"json"` if `extract` is absent, and `deserialize` receives the parsed response, not the raw string. If you want the old behavior, [use `responseType: "text"`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType). ([#2335](https://github.com/MithrilJS/mithril.js/pull/2335)) | ||
@@ -54,3 +60,2 @@ #### News | ||
- docs: Emphasize Closure Components for stateful components, use them for all stateful component examples. | ||
- stream: Add `stream.lift` as a user-friendly alternative to `merge -> map` or `combine` [#1944](https://github.com/MithrilJS/mithril.js/issues/1944) | ||
- API: ES module bundles are now available for `mithril` and `mithril/stream` ([#2194](https://github.com/MithrilJS/mithril.js/pull/2194) [@porsager](https://github.com/porsager)). | ||
@@ -61,2 +66,6 @@ - All of the `m.*` properties from `mithril` are re-exported as named exports in addition to being attached to `m`. | ||
- fragments: allow same attrs/children overloading logic as hyperscript ([#2328](https://github.com/MithrilJS/mithril.js/pull/2328)) | ||
- route: Declared routes may check against path names with query strings. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) | ||
- route: Declared routes in `m.route` now support `-` and `.` as delimiters for path segments. This means you can have a route like `"/edit/:file.:ext"`. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) | ||
- Previously, this was possible to do in `m.route.set`, `m.request`, and `m.jsonp`, but it was wholly untested for and also undocumented. | ||
- API: `m.buildPathname` and `m.parsePathname` added. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) | ||
@@ -82,2 +91,8 @@ #### Bug fixes | ||
- request: don't modify params, call `extract`/`serialize`/`deserialize` with correct `this` value ([#2288](https://github.com/MithrilJS/mithril.js/pull/2288)) | ||
- render: simplify component removal ([#2214](https://github.com/MithrilJS/mithril.js/pull/2214)) | ||
- render: remove some redundancy within the component initialization code ([#2213](https://github.com/MithrilJS/mithril.js/pull/2213)) | ||
- API: `mithril` loads `mithril/index.js`, not the bundle, so users of `mithril/hyperscript`, `mithril/render`, and similar see the same Mithril instance as those just using `mithril` itself. | ||
- `https://unpkg.com/mithril` is configured to receive the *minified* bundle, not the development bundle. | ||
- The raw bundle itself remains accessible at `mithril.js`, and is *not* browser-wrapped. | ||
- Note: this *will* increase overhead with bundlers like Webpack, Rollup, and Browserify. | ||
@@ -84,0 +99,0 @@ --- |
@@ -17,3 +17,3 @@ # Components | ||
Any Javascript object that has a `view` method is a Mithril component. Components can be consumed via the [`m()`](hyperscript.md) utility: | ||
Any JavaScript object that has a `view` method is a Mithril component. Components can be consumed via the [`m()`](hyperscript.md) utility: | ||
@@ -121,3 +121,3 @@ ```javascript | ||
In the above examples, each component is defined as a POJO (Plain Old Javascript Object), which is used by Mithril internally as the prototype for that component's instances. It's possible to use component state with a POJO (as we'll discuss below), but it's not the cleanest or simplest approach. For that we'll use a **_closure component_**, which is simply a wrapper function which _returns_ a POJO component instance, which in turn carries its own, closed-over scope. | ||
In the above examples, each component is defined as a POJO (Plain Old JavaScript Object), which is used by Mithril internally as the prototype for that component's instances. It's possible to use component state with a POJO (as we'll discuss below), but it's not the cleanest or simplest approach. For that we'll use a **_closure component_**, which is simply a wrapper function which _returns_ a POJO component instance, which in turn carries its own, closed-over scope. | ||
@@ -252,3 +252,3 @@ With a closure component, state can simply be maintained by variables that are declared within the outer function: | ||
Be aware that when using ES5 functions, the value of `this` in nested anonymous functions is not the component instance. There are two recommended ways to get around this Javascript limitation, use ES6 arrow functions, or if ES6 is not available, use `vnode.state`. | ||
Be aware that when using ES5 functions, the value of `this` in nested anonymous functions is not the component instance. There are two recommended ways to get around this JavaScript limitation, use ES6 arrow functions, or if ES6 is not available, use `vnode.state`. | ||
@@ -255,0 +255,0 @@ --- |
@@ -8,3 +8,3 @@ # ES6 | ||
Mithril is written in ES5, and is fully compatible with ES6 as well. ES6 is a recent update to Javascript that introduces new syntax sugar for various common cases. It's not yet fully supported by all major browsers and it's not a requirement for writing an application, but it may be pleasing to use depending on your team's preferences. | ||
Mithril is written in ES5, and is fully compatible with ES6 as well. ES6 is a recent update to JavaScript that introduces new syntax sugar for various common cases. It's not yet fully supported by all major browsers and it's not a requirement for writing an application, but it may be pleasing to use depending on your team's preferences. | ||
@@ -11,0 +11,0 @@ In some limited environments, it's possible to use a significant subset of ES6 directly without extra tooling (for example, in internal applications that do not support IE). However, for the vast majority of use cases, a compiler toolchain like [Babel](https://babeljs.io) is required to compile ES6 features down to ES5. |
@@ -70,4 +70,4 @@ # fragment(attrs, children) | ||
However, Javascript arrays cannot be keyed or hold lifecycle methods. One option would be to create a wrapper element to host the key or lifecycle method, but sometimes it is not desirable to have an extra element (for example in complex table structures). In those cases, a fragment vnode can be used instead. | ||
However, JavaScript arrays cannot be keyed or hold lifecycle methods. One option would be to create a wrapper element to host the key or lifecycle method, but sometimes it is not desirable to have an extra element (for example in complex table structures). In those cases, a fragment vnode can be used instead. | ||
There are a few benefits that come from using `m.fragment` instead of handwriting a vnode object structure: m.fragment creates [monomorphic objects](vnodes.md#monomorphic-class), which have better performance characteristics than creating objects dynamically. In addition, using `m.fragment` makes your intentions clear to other developers, and it makes it less likely that you'll mistakenly set attributes on the vnode object itself rather than on its `attrs` map. |
@@ -41,3 +41,3 @@ # Framework comparison | ||
- They both organize views via components | ||
- They both use Javascript as a flow control mechanism within views | ||
- They both use JavaScript as a flow control mechanism within views | ||
@@ -54,3 +54,3 @@ The most obvious difference between React and Mithril is in their scope. React is a view library, so a typical React-based application relies on third-party libraries for routing, XHR and state management. Using a library oriented approach allows developers to customize their stack to precisely match their needs. The not-so-nice way of saying that is that React-based architectures can vary wildly from project to project, and that those projects are that much more likely to cross the 1MB size line. | ||
Here's a comparison of library load times, i.e. the time it takes to parse and run the Javascript code for each framework, by adding a `console.time()` call on the first line and a `console.timeEnd()` call on the last of a script that is composed solely of framework code. For your reading convenience, here are best-of-20 results with logging code manually added to bundled scripts, running from the filesystem, in Chrome on a modest 2010 PC desktop: | ||
Here's a comparison of library load times, i.e. the time it takes to parse and run the JavaScript code for each framework, by adding a `console.time()` call on the first line and a `console.timeEnd()` call on the last of a script that is composed solely of framework code. For your reading convenience, here are best-of-20 results with logging code manually added to bundled scripts, running from the filesystem, in Chrome on a modest 2010 PC desktop: | ||
@@ -79,3 +79,3 @@ React | Mithril | ||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [React implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/react/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Sample results are shown below: | ||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and JavaScript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [React implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/react/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Sample results are shown below: | ||
@@ -131,3 +131,3 @@ React | Mithril | ||
The most obvious difference between Angular and Mithril is in their complexity. This can be seen most easily in how views are implemented. Mithril views are plain Javascript, and flow control is done with Javascript built-in mechanisms such as ternary operators or `Array.prototype.map`. Angular, on the other hand, implements a directive system to extend HTML views so that it's possible to evaluate Javascript-like expressions within HTML attributes and interpolations. Angular actually ships with a parser and a compiler written in Javascript to achieve that. If that doesn't seem complex enough, there's actually two compilation modes (a default mode that generates Javascript functions dynamically for performance, and [a slower mode](https://docs.angularjs.org/api/ng/directive/ngCsp) for dealing with Content Security Policy restrictions). | ||
The most obvious difference between Angular and Mithril is in their complexity. This can be seen most easily in how views are implemented. Mithril views are plain JavaScript, and flow control is done with JavaScript built-in mechanisms such as ternary operators or `Array.prototype.map`. Angular, on the other hand, implements a directive system to extend HTML views so that it's possible to evaluate JavaScript-like expressions within HTML attributes and interpolations. Angular actually ships with a parser and a compiler written in JavaScript to achieve that. If that doesn't seem complex enough, there's actually two compilation modes (a default mode that generates JavaScript functions dynamically for performance, and [a slower mode](https://docs.angularjs.org/api/ng/directive/ngCsp) for dealing with Content Security Policy restrictions). | ||
@@ -146,3 +146,3 @@ #### Performance | ||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare an [Angular implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/angular/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: | ||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and JavaScript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare an [Angular implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/angular/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: | ||
@@ -163,3 +163,3 @@ Angular | Mithril | ||
With that being said, Angular has a lot more concepts to learn than Mithril. It offers Angular-specific APIs for many things that often can be trivially implemented (e.g. pluralization is essentially a switch statement, "required" validation is simply an equality check, etc). Angular templates also have several layers of abstractions to emulate what Javascript does natively in Mithril - Angular's `ng-if`/`ngIf` is a *directive*, which uses a custom *parser* and *compiler* to evaluate an expression string and emulate lexical scoping... and so on. Mithril tends to be a lot more transparent, and therefore easier to reason about. | ||
With that being said, Angular has a lot more concepts to learn than Mithril. It offers Angular-specific APIs for many things that often can be trivially implemented (e.g. pluralization is essentially a switch statement, "required" validation is simply an equality check, etc). Angular templates also have several layers of abstractions to emulate what JavaScript does natively in Mithril - Angular's `ng-if`/`ngIf` is a *directive*, which uses a custom *parser* and *compiler* to evaluate an expression string and emulate lexical scoping... and so on. Mithril tends to be a lot more transparent, and therefore easier to reason about. | ||
@@ -192,3 +192,3 @@ #### Documentation | ||
Here's a comparison of library load times, i.e. the time it takes to parse and run the Javascript code for each framework, by adding a `console.time()` call on the first line and a `console.timeEnd()` call on the last of a script that is composed solely of framework code. For your reading convenience, here are best-of-20 results with logging code manually added to bundled scripts, running from the filesystem, in Chrome on a modest 2010 PC desktop: | ||
Here's a comparison of library load times, i.e. the time it takes to parse and run the JavaScript code for each framework, by adding a `console.time()` call on the first line and a `console.timeEnd()` call on the last of a script that is composed solely of framework code. For your reading convenience, here are best-of-20 results with logging code manually added to bundled scripts, running from the filesystem, in Chrome on a modest 2010 PC desktop: | ||
@@ -203,3 +203,3 @@ Vue | Mithril | ||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [Vue implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/vue/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: | ||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and JavaScript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [Vue implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/vue/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: | ||
@@ -214,3 +214,3 @@ Vue | Mithril | ||
Mithril has far less concepts and typically organizes applications in terms of components and a data layer. All component creation styles in Mithril output the same vnode structure using native Javascript features only. The direct consequence of leaning on the language is less tooling and a simpler project setup. | ||
Mithril has far less concepts and typically organizes applications in terms of components and a data layer. All component creation styles in Mithril output the same vnode structure using native JavaScript features only. The direct consequence of leaning on the language is less tooling and a simpler project setup. | ||
@@ -217,0 +217,0 @@ #### Documentation |
@@ -58,3 +58,3 @@ # m(selector, attributes, children) | ||
Mithril provides a hyperscript function `m()`, which allows expressing any HTML structure using javascript syntax. It accepts a `selector` string (required), an `attrs` object (optional) and a `children` array (optional). | ||
Mithril provides a hyperscript function `m()`, which allows expressing any HTML structure using JavaScript syntax. It accepts a `selector` string (required), an `attrs` object (optional) and a `children` array (optional). | ||
@@ -68,3 +68,3 @@ ```javascript | ||
The `m()` function does not actually return a DOM element. Instead it returns a [virtual DOM node](vnodes.md), or *vnode*, which is a javascript object that represents the DOM element to be created. | ||
The `m()` function does not actually return a DOM element. Instead it returns a [virtual DOM node](vnodes.md), or *vnode*, which is a JavaScript object that represents the DOM element to be created. | ||
@@ -172,5 +172,5 @@ ```javascript | ||
Mithril uses both the Javascript API and the DOM API (`setAttribute`) to resolve attributes. This means you can use both syntaxes to refer to attributes. | ||
Mithril uses both the JavaScript API and the DOM API (`setAttribute`) to resolve attributes. This means you can use both syntaxes to refer to attributes. | ||
For example, in the Javascript API, the `readonly` attribute is called `element.readOnly` (notice the uppercase). In Mithril, all of the following are supported: | ||
For example, in the JavaScript API, the `readonly` attribute is called `element.readOnly` (notice the uppercase). In Mithril, all of the following are supported: | ||
@@ -328,3 +328,3 @@ ```javascript | ||
A component is any Javascript object that contains a `view` method. To consume a component, pass the component as the first argument to `m()` instead of passing a CSS selector string. You can pass arguments to the component by defining attributes and children, as shown in the example below. | ||
A component is any JavaScript object that contains a `view` method. To consume a component, pass the component as the first argument to `m()` instead of passing a CSS selector string. You can pass arguments to the component by defining attributes and children, as shown in the example below. | ||
@@ -420,3 +420,3 @@ ```javascript | ||
Since nested vnodes are just plain Javascript expressions, you can simply use Javascript facilities to manipulate them | ||
Since nested vnodes are just plain JavaScript expressions, you can simply use JavaScript facilities to manipulate them | ||
@@ -462,3 +462,3 @@ #### Dynamic text | ||
You cannot use Javascript statements such as `if` or `for` within Javascript expressions. It's preferable to avoid using those statements altogether and instead, use the constructs above exclusively in order to keep the structure of the templates linear and declarative, and to avoid deoptimizations. | ||
You cannot use JavaScript statements such as `if` or `for` within JavaScript expressions. It's preferable to avoid using those statements altogether and instead, use the constructs above exclusively in order to keep the structure of the templates linear and declarative, and to avoid deoptimizations. | ||
@@ -529,3 +529,3 @@ --- | ||
Javascript statements often require changing the naturally nested structure of an HTML tree, making the code more verbose and harder to understand. Constructing an virtual DOM tree procedurally can also potentially trigger expensive deoptimizations (such as an entire template being recreated from scratch) | ||
JavaScript statements often require changing the naturally nested structure of an HTML tree, making the code more verbose and harder to understand. Constructing an virtual DOM tree procedurally can also potentially trigger expensive deoptimizations (such as an entire template being recreated from scratch) | ||
@@ -546,3 +546,3 @@ ```javascript | ||
Instead, prefer using Javascript expressions such as the ternary operator and Array methods. | ||
Instead, prefer using JavaScript expressions such as the ternary operator and Array methods. | ||
@@ -549,0 +549,0 @@ ```javascript |
@@ -15,3 +15,3 @@ # Introduction | ||
Mithril is a modern client-side Javascript framework for building Single Page Applications. | ||
Mithril is a modern client-side JavaScript framework for building Single Page Applications. | ||
It's small (< 8kb gzip), fast and provides routing and XHR utilities out of the box. | ||
@@ -50,3 +50,3 @@ | ||
*Looking for the v1 docs? [Click here](archive/v1.1.6).* | ||
*Looking for the v1 docs? [Click here](https://mithril.js.org/archive/v1.1.6/index.html).* | ||
@@ -63,3 +63,3 @@ --- | ||
<body> | ||
<script src="https://unpkg.com/mithril/mithril.js"></script> | ||
<script src="https://unpkg.com/mithril@next/mithril.js"></script> | ||
<script> | ||
@@ -66,0 +66,0 @@ var root = document.body |
@@ -10,6 +10,6 @@ # Installation | ||
If you're new to Javascript or just want a very simple setup to get your feet wet, you can get Mithril from a [CDN](https://en.wikipedia.org/wiki/Content_delivery_network): | ||
If you're new to JavaScript or just want a very simple setup to get your feet wet, you can get Mithril from a [CDN](https://en.wikipedia.org/wiki/Content_delivery_network): | ||
```markup | ||
<script src="https://unpkg.com/mithril/mithril.js"></script> | ||
<script src="https://unpkg.com/mithril@next/mithril.js"></script> | ||
``` | ||
@@ -75,3 +75,3 @@ | ||
NPM (Node package manager) is the default package manager that is bundled w/ Node.js. It is widely used as the package manager for both client-side and server-side libraries in the Javascript ecosystem. Download and install [Node.js](https://nodejs.org); NPM will be automatically installed as well. | ||
NPM (Node package manager) is the default package manager that is bundled w/ Node.js. It is widely used as the package manager for both client-side and server-side libraries in the JavaScript ecosystem. Download and install [Node.js](https://nodejs.org); NPM will be automatically installed as well. | ||
@@ -104,5 +104,5 @@ To use Mithril via NPM, go to your project folder, and run `npm init --yes` from the command line. This will create a file called `package.json`. | ||
CommonJS is a de-facto standard for modularizing Javascript code, and it's used by Node.js, as well as tools like [Browserify](http://browserify.org/) and [Webpack](https://webpack.js.org/). It's a robust, battle-tested precursor to ES6 modules. Although the syntax for ES6 modules is specified in Ecmascript 6, the actual module loading mechanism is not. If you wish to use ES6 modules despite the non-standardized status of module loading, you can use tools like [Rollup](http://rollupjs.org/), [Babel](https://babeljs.io/) or [Traceur](https://github.com/google/traceur-compiler). | ||
CommonJS is a de-facto standard for modularizing JavaScript code, and it's used by Node.js, as well as tools like [Browserify](http://browserify.org/) and [Webpack](https://webpack.js.org/). It's a robust, battle-tested precursor to ES6 modules. Although the syntax for ES6 modules is specified in Ecmascript 6, the actual module loading mechanism is not. If you wish to use ES6 modules despite the non-standardized status of module loading, you can use tools like [Rollup](http://rollupjs.org/), [Babel](https://babeljs.io/) or [Traceur](https://github.com/google/traceur-compiler). | ||
Most browser today do not natively support modularization systems (CommonJS or ES6), so modularized code must be bundled into a single Javascript file before running in a client-side application. | ||
Most browser today do not natively support modularization systems (CommonJS or ES6), so modularized code must be bundled into a single JavaScript file before running in a client-side application. | ||
@@ -253,3 +253,3 @@ A popular way for creating a bundle is to setup an NPM script for [Webpack](https://webpack.js.org/). To install Webpack, run this from the command line: | ||
<body> | ||
<script src="https://cdn.rawgit.com/MithrilJS/mithril.js/master/mithril.js"></script> | ||
<script src="https://unpkg.com/mithril@next/mithril.js"></script> | ||
<script src="index.js"></script> | ||
@@ -256,0 +256,0 @@ </body> |
# 3rd Party Integration | ||
Integration with third party libraries or vanilla javascript code can be achieved via [lifecycle methods](lifecycle-methods.md). | ||
Integration with third party libraries or vanilla JavaScript code can be achieved via [lifecycle methods](lifecycle-methods.md). | ||
@@ -69,3 +69,3 @@ ## noUiSlider Example | ||
## Bootstrap FullCalandar Example | ||
## Bootstrap FullCalendar Example | ||
@@ -76,3 +76,3 @@ ```javascript | ||
oncreate: function (vnode) { | ||
console.log('FullCalndar::oncreate') | ||
console.log('FullCalendar::oncreate') | ||
$(vnode.dom).fullCalendar({ | ||
@@ -79,0 +79,0 @@ // put your initial options and callbacks here |
@@ -29,8 +29,8 @@ # jsonp(options) | ||
`promise = m.jsonp([url,] options)` | ||
`promise = m.jsonp(options)` | ||
Argument | Type | Required | Description | ||
---------------------- | --------------------------------- | -------- | --- | ||
`url` | `String` | No | If present, it's equivalent to having the option `{url: url}`. Values passed to the `options` argument override options set via this shorthand. | ||
`options.url` | `String` | Yes | The URL to send the request to. The URL may be either absolute or relative, and it may contain [interpolations](#dynamic-urls). | ||
`options` | `Object` | Yes | The request options to pass. | ||
`options.url` | `String` | Yes | The [path name](paths.md) to send the request to, optionally interpolated with values from `options.data`. | ||
`options.data` | `any` | No | The data to be interpolated into the URL and serialized into the querystring. | ||
@@ -43,2 +43,12 @@ `options.type` | `any = Function(any)` | No | A constructor to be applied to each object in the response. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function). | ||
`promise = m.jsonp(url, options)` | ||
Argument | Type | Required | Description | ||
----------- | --------- | -------- | --- | ||
`url` | `String` | Yes | The [path name](paths.md) to send the request to. `options.url` overrides this when present. | ||
`options` | `Object` | No | The request options to pass. | ||
**returns** | `Promise` | | A promise that resolves to the response data, after it has been piped through the `type` method | ||
This second form is mostly equivalent to `m.jsonp(Object.assign({url: url}, options))`, just it does not depend on the ES6 global `Object.assign` internally. | ||
[How to read signatures](signatures.md) | ||
@@ -54,3 +64,3 @@ | ||
JSON-P has several limitations: it can only use GET requests, it implicitly trusts that the third party server won't serve malicious code and it requires polluting the global Javascript scope. Nonetheless, it is sometimes the only available way to retrieve data from a service (for example, if the service doesn't support [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)). | ||
JSON-P has several limitations: it can only use GET requests, it implicitly trusts that the third party server won't serve malicious code and it requires polluting the global JavaScript scope. Nonetheless, it is sometimes the only available way to retrieve data from a service (for example, if the service doesn't support [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)). | ||
@@ -93,2 +103,1 @@ --- | ||
``` | ||
@@ -13,3 +13,3 @@ # JSX | ||
JSX is a syntax extension that enables you to write HTML tags interspersed with Javascript. It's not part of any Javascript standards and it's not required for building applications, but it may be more pleasing to use depending on your team's preferences. | ||
JSX is a syntax extension that enables you to write HTML tags interspersed with JavaScript. It's not part of any JavaScript standards and it's not required for building applications, but it may be more pleasing to use depending on your team's preferences. | ||
@@ -37,3 +37,3 @@ ```jsx | ||
When using JSX, it's possible to interpolate Javascript expressions within JSX tags by using curly braces: | ||
When using JSX, it's possible to interpolate JavaScript expressions within JSX tags by using curly braces: | ||
@@ -191,3 +191,3 @@ ```jsx | ||
JSX is useful for teams where HTML is primarily written by someone without Javascript experience, but it requires a significant amount of tooling to maintain (whereas plain HTML can, for the most part, simply be opened in a browser) | ||
JSX is useful for teams where HTML is primarily written by someone without JavaScript experience, but it requires a significant amount of tooling to maintain (whereas plain HTML can, for the most part, simply be opened in a browser) | ||
@@ -199,3 +199,3 @@ Hyperscript is the compiled representation of JSX. It's designed to be readable and can also be used as-is, instead of JSX (as is done in most of the documentation). Hyperscript tends to be terser than JSX for a couple of reasons: | ||
In addition, since hyperscript is plain Javascript, it's often more natural to indent than JSX: | ||
In addition, since hyperscript is plain JavaScript, it's often more natural to indent than JSX: | ||
@@ -246,5 +246,5 @@ ```jsx | ||
In non-trivial applications, it's possible for components to have more control flow and component configuration code than markup, making a Javascript-first approach more readable than an HTML-first approach. | ||
In non-trivial applications, it's possible for components to have more control flow and component configuration code than markup, making a JavaScript-first approach more readable than an HTML-first approach. | ||
Needless to say, since hyperscript is pure Javascript, there's no need to run a compilation step to produce runnable code. | ||
Needless to say, since hyperscript is pure JavaScript, there's no need to run a compilation step to produce runnable code. | ||
@@ -251,0 +251,0 @@ --- |
@@ -68,7 +68,7 @@ # mount(root, component) | ||
It may seem wasteful to generate a vnode tree on every redraw, but as it turns out, creating and comparing Javascript data structures is surprisingly cheap compared to reading and modifying the DOM. | ||
It may seem wasteful to generate a vnode tree on every redraw, but as it turns out, creating and comparing JavaScript data structures is surprisingly cheap compared to reading and modifying the DOM. | ||
Touching the DOM can be extremely expensive for a couple of reasons. Alternating reads and writes can adversely affect performance by causing several browser repaints to occur in quick succession, whereas comparing virtual dom trees allows writes to be batched into a single repaint. Also, the performance characteristics of various DOM operations vary between implementations and can be difficult to learn and optimize for all browsers. For example, in some implementations, reading `childNodes.length` has a complexity of O(n); in some, reading `parentNode` causes a repaint, etc. | ||
In contrast, traversing a javascript data structure has a much more predictable and sane performance profile, and in addition, a vnode tree is implemented in such a way that enables modern javascript engines to apply aggressive optimizations such as hidden classes for even better performance. | ||
In contrast, traversing a JavaScript data structure has a much more predictable and sane performance profile, and in addition, a vnode tree is implemented in such a way that enables modern JavaScript engines to apply aggressive optimizations such as hidden classes for even better performance. | ||
@@ -75,0 +75,0 @@ --- |
@@ -15,2 +15,3 @@ - Getting Started | ||
- [3rd Party Integration](integrating-libs.md) | ||
- [Path Handling](paths.md) | ||
- Key concepts | ||
@@ -17,0 +18,0 @@ - [Vnodes](vnodes.md) |
@@ -22,3 +22,3 @@ # parseQueryString(string) | ||
`object = m.parseQueryString(string)` | ||
`object = m.parseQueryString(string, object)` | ||
@@ -28,3 +28,4 @@ Argument | Type | Required | Description | ||
`string` | `String` | Yes | A querystring | ||
**returns** | `Object` | | A key-value map | ||
`object` | `Object` | No | An existing key-value map to merge values into, potentially from a previous `m.parseQueryString` call | ||
**returns** | `Object` | | A key-value map, `object` if provided | ||
@@ -73,2 +74,2 @@ [How to read signatures](signatures.md) | ||
// data is {a: ["hello", "world"]} | ||
``` | ||
``` |
@@ -163,3 +163,3 @@ # Promise(executor) | ||
Asynchronous APIs are those which typically take a long time to run, and therefore would take too long to return a value using the `return` statement of a function. Instead, they do their work in the background, allowing other Javascript code to run in the meantime. When they are done, they call a function with their results. | ||
Asynchronous APIs are those which typically take a long time to run, and therefore would take too long to return a value using the `return` statement of a function. Instead, they do their work in the background, allowing other JavaScript code to run in the meantime. When they are done, they call a function with their results. | ||
@@ -312,2 +312,2 @@ The `m.request` function takes time to run because it makes an HTTP request to a remote server and has to wait for a response, which may take several milliseconds due to network latency. | ||
In addition, promises can considerably reduce boilerplate related to error handling. | ||
In addition, promises can considerably reduce boilerplate related to error handling. |
@@ -47,7 +47,7 @@ # render(element, vnodes) | ||
It may seem wasteful to generate a vnode tree on every redraw, but as it turns out, creating and comparing Javascript data structures is surprisingly cheap compared to reading and modifying the DOM. | ||
It may seem wasteful to generate a vnode tree on every redraw, but as it turns out, creating and comparing JavaScript data structures is surprisingly cheap compared to reading and modifying the DOM. | ||
Touching the DOM can be extremely expensive for a couple of reasons. Alternating reads and writes can adversely affect performance by causing several browser repaints to occur in quick succession, whereas comparing virtual dom trees allows writes to be batched into a single repaint. Also, the performance characteristics of various DOM operations vary between implementations and can be difficult to learn and optimize for all browsers. For example, in some implementations, reading `childNodes.length` has a complexity of O(n); in some, reading `parentNode` causes a repaint, etc. | ||
In contrast, traversing a javascript data structure has a much more predictable and sane performance profile, and in addition, a vnode tree is implemented in such a way that enables modern javascript engines to apply aggressive optimizations such as hidden classes for even better performance. | ||
In contrast, traversing a JavaScript data structure has a much more predictable and sane performance profile, and in addition, a vnode tree is implemented in such a way that enables modern JavaScript engines to apply aggressive optimizations such as hidden classes for even better performance. | ||
@@ -54,0 +54,0 @@ --- |
@@ -41,9 +41,9 @@ # request(options) | ||
`promise = m.request([url,] options)` | ||
`promise = m.request(options)` | ||
Argument | Type | Required | Description | ||
------------------------- | --------------------------------- | -------- | --- | ||
`url` | `String` | No | If present, it's equivalent to having the options `{method: "GET", url: url}`. Values passed to the `options` argument override options set via this shorthand. | ||
`options` | `Object` | Yes | The request options to pass. | ||
`options.method` | `String` | No | The HTTP method to use. This value should be one of the following: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD` or `OPTIONS`. Defaults to `GET`. | ||
`options.url` | `String` | Yes | The URL to send the request to. The URL may be either absolute or relative, and it may contain [interpolations](#dynamic-urls). | ||
`options.url` | `String` | Yes | The [path name](paths.md) to send the request to, optionally interpolated with values from `options.data`. | ||
`options.data` | `any` | No | The data to be interpolated into the URL and serialized into the querystring (for GET requests) or body (for other types of requests). | ||
@@ -55,3 +55,3 @@ `options.async` | `Boolean` | No | Whether the request should be asynchronous. Defaults to `true`. | ||
`options.timeout` | `Number` | No | The amount of milliseconds a request can take before automatically being [terminated](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/timeout). Defaults to `undefined`. | ||
`options.responseType` | `String` | No | The expected [type](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType) of the response. Defaults to `undefined`. | ||
`options.responseType` | `String` | No | The expected [type](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType) of the response. Defaults to `""` if `extract` is defined, `"json"` if missing. If `responseType: "json"`, it internally performs `JSON.parse(responseText)`. | ||
`options.config` | `xhr = Function(xhr)` | No | Exposes the underlying XMLHttpRequest object for low-level configuration. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function). | ||
@@ -61,4 +61,4 @@ `options.headers` | `Object` | No | Headers to append to the request before sending it (applied right before `options.config`). | ||
`options.serialize` | `string = Function(any)` | No | A serialization method to be applied to `data`. Defaults to `JSON.stringify`, or if `options.data` is an instance of [`FormData`](https://developer.mozilla.org/en/docs/Web/API/FormData), defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function) (i.e. `function(value) {return value}`). | ||
`options.deserialize` | `any = Function(string)` | No | A deserialization method to be applied to the `xhr.responseText`. Defaults to a small wrapper around `JSON.parse` that returns `null` for empty responses. If `extract` is defined, `deserialize` will be skipped. | ||
`options.extract` | `any = Function(xhr, options)` | No | A hook to specify how the XMLHttpRequest response should be read. Useful for processing response data, reading headers and cookies. By default this is a function that returns `xhr.responseText`, which is in turn passed to `deserialize`. If a custom `extract` callback is provided, the `xhr` parameter is the XMLHttpRequest instance used for the request, and `options` is the object that was passed to the `m.request` call. Additionally, `deserialize` will be skipped and the value returned from the extract callback will be left as-is when the promise resolves. Furthermore, when an extract callback is provided, exceptions are *not* thrown when the server response status code indicates an error. | ||
`options.deserialize` | `any = Function(any)` | No | A deserialization method to be applied to the `xhr.response` or normalized `xhr.responseText`. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function). If `extract` is defined, `deserialize` will be skipped. | ||
`options.extract` | `any = Function(xhr, options)` | No | A hook to specify how the XMLHttpRequest response should be read. Useful for processing response data, reading headers and cookies. By default this is a function that returns `options.deserialize(parsedResponse)`, throwing an exception when the server response status code indicates an error or when the response is syntactically invalid. If a custom `extract` callback is provided, the `xhr` parameter is the XMLHttpRequest instance used for the request, and `options` is the object that was passed to the `m.request` call. Additionally, `deserialize` will be skipped and the value returned from the extract callback will be left as-is when the promise resolves. | ||
`options.useBody` | `Boolean` | No | Force the use of the HTTP body section for `data` in `GET` requests when set to `true`, or the use of querystring for other HTTP methods when set to `false`. Defaults to `false` for `GET` requests and `true` for other methods. | ||
@@ -68,2 +68,12 @@ `options.background` | `Boolean` | No | If `false`, redraws mounted components upon completion of the request. If `true`, it does not. Defaults to `false`. | ||
`promise = m.request(url, options)` | ||
Argument | Type | Required | Description | ||
----------- | --------- | -------- | --- | ||
`url` | `String` | Yes | The [path name](paths.md) to send the request to. `options.url` overrides this when present. | ||
`options` | `Object` | No | The request options to pass. | ||
**returns** | `Promise` | | A promise that resolves to the response data, after it has been piped through the `extract`, `deserialize` and `type` methods | ||
This second form is mostly equivalent to `m.request(Object.assign({url: url}, options))`, just it does not depend on the ES6 global `Object.assign` internally. | ||
[How to read signatures](signatures.md) | ||
@@ -89,3 +99,3 @@ | ||
By default, `m.request` assumes the response is in JSON format and parses it into a Javascript object (or array). | ||
By default, `m.request` assumes the response is in JSON format and parses it into a JavaScript object (or array). | ||
@@ -132,3 +142,3 @@ If the HTTP response status code indicates an error, the returned Promise will be rejected. Supplying an extract callback will prevent the promise rejection. | ||
When `m.route` is called at the bottom, the `Todos` component is initialized. `oninit` is called, which calls `m.request`. This retrieves an array of objects from the server asynchronously. "Asynchronously" means that Javascript continues running other code while it waits for the response from server. In this case, it means `fetch` returns, and the component is rendered using the original empty array as `Data.todos.list`. Once the request to the server completes, the array of objects `items` is assigned to `Data.todos.list` and the component is rendered again, yielding a list of `<div>`s containing the titles of each todo. | ||
When `m.route` is called at the bottom, the `Todos` component is initialized. `oninit` is called, which calls `m.request`. This retrieves an array of objects from the server asynchronously. "Asynchronously" means that JavaScript continues running other code while it waits for the response from server. In this case, it means `fetch` returns, and the component is rendered using the original empty array as `Data.todos.list`. Once the request to the server completes, the array of objects `items` is assigned to `Data.todos.list` and the component is rendered again, yielding a list of `<div>`s containing the titles of each todo. | ||
@@ -473,3 +483,3 @@ --- | ||
By contrast, Mithril is framework designed for thick client applications, which typically download templates and data separately and combine them in the browser via Javascript. Doing the templating heavy-lifting in the browser can bring benefits like reducing operational costs by freeing server resources. Separating templates from data also allow template code to be cached more effectively and enables better code reusability across different types of clients (e.g. desktop, mobile). Another benefit is that Mithril enables a [retained mode](https://en.wikipedia.org/wiki/Retained_mode) UI development paradigm, which greatly simplifies development and maintenance of complex user interactions. | ||
By contrast, Mithril is framework designed for thick client applications, which typically download templates and data separately and combine them in the browser via JavaScript. Doing the templating heavy-lifting in the browser can bring benefits like reducing operational costs by freeing server resources. Separating templates from data also allow template code to be cached more effectively and enables better code reusability across different types of clients (e.g. desktop, mobile). Another benefit is that Mithril enables a [retained mode](https://en.wikipedia.org/wiki/Retained_mode) UI development paradigm, which greatly simplifies development and maintenance of complex user interactions. | ||
@@ -514,3 +524,3 @@ By default, `m.request` expects response data to be in JSON format. In a typical Mithril application, that JSON data is then usually consumed by a view. | ||
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. | ||
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. | ||
@@ -517,0 +527,0 @@ ```javascript |
@@ -74,3 +74,3 @@ # route(root, defaultRoute, routes) | ||
----------------- | --------- | -------- | --- | ||
`path` | `String` | Yes | The path to route to, without a prefix. The path may include slots for routing parameters | ||
`path` | `String` | Yes | The [path name](paths.md) to route to, without a prefix. The path may include parameters, interpolated with values from `data`. | ||
`data` | `Object` | No | Routing parameters. If `path` has routing parameter slots, the properties of this object are interpolated into the path string | ||
@@ -160,3 +160,3 @@ `options.replace` | `Boolean` | No | Whether to create a new history entry or to replace the current one. Defaults to false | ||
Note that in the `onmatch` function of a RouteResolver, the new route hasn't yet been fully resolved, and `m.route.params()` will return the parameters of the previous route, if any. `onmatch` receives the parameters of the new route as an argument. | ||
Note that in the `onmatch` function of a RouteResolver, the new route hasn't yet been fully resolved, and `m.route.param()` will return the parameters of the previous route, if any. `onmatch` receives the parameters of the new route as an argument. | ||
@@ -181,3 +181,3 @@ #### RouteResolver | ||
`routeResolver.onmatch(args, requestedPath)` | ||
`routeResolver.onmatch(args, requestedPath, route)` | ||
@@ -188,2 +188,3 @@ Argument | Type | Description | ||
`requestedPath` | `String` | The router path requested by the last routing action, including interpolated routing parameter values, but without the prefix. When `onmatch` is called, the resolution for this path is not complete and `m.route.get()` still returns the previous path. | ||
`route` | `String` | The router path requested by the last routing action, excluding interpolated routing parameter values | ||
**returns** | `Component|Promise<Component>|undefined` | Returns a component or a promise that resolves to a component | ||
@@ -215,3 +216,3 @@ | ||
Routing without page refreshes is made partially possible by the [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState()_method) API. Using this API, it's possible to programmatically change the URL displayed by the browser after a page has loaded, but it's the application developer's responsibility to ensure that navigating to any given URL from a cold state (e.g. a new tab) will render the appropriate markup. | ||
Routing without page refreshes is made partially possible by the [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState%28%29_method) API. Using this API, it's possible to programmatically change the URL displayed by the browser after a page has loaded, but it's the application developer's responsibility to ensure that navigating to any given URL from a cold state (e.g. a new tab) will render the appropriate markup. | ||
@@ -300,3 +301,3 @@ #### Routing strategies | ||
Sometimes we want to have a variable id or similar data appear in a route, but we don't want to explicitly specify a separate route for every possible id. In order to achieve that, Mithril supports parameterized routes: | ||
Sometimes we want to have a variable id or similar data appear in a route, but we don't want to explicitly specify a separate route for every possible id. In order to achieve that, Mithril supports [parameterized routes](paths.md#path-parameters): | ||
@@ -355,3 +356,3 @@ ```javascript | ||
For isomorphic / universal javascript app, an url param and a variadic route combined is very useful to display custom 404 error page. | ||
For isomorphic / universal JavaScript app, an url param and a variadic route combined is very useful to display custom 404 error page. | ||
@@ -439,3 +440,3 @@ In a case of 404 Not Found error, the server send back the custom page to client. When Mithril is loaded, it will redirect client to the default route because it can't know that route. | ||
"/": { | ||
onmatch: function(args, requestedPath) { | ||
onmatch: function(args, requestedPath, route) { | ||
return Home | ||
@@ -594,3 +595,3 @@ }, | ||
}).then(function(data) { | ||
localStorage.setItem("auth-token": data.token) | ||
localStorage.setItem("auth-token", data.token) | ||
m.route.set("/secret") | ||
@@ -597,0 +598,0 @@ }) |
@@ -23,7 +23,7 @@ # Simple application | ||
The `<!doctype html>` line indicates this is an HTML 5 document. The first `charset` meta tag indicates the encoding of the document and the `viewport` meta tag dictates how mobile browsers should scale the page. The `title` tag contains the text to be displayed on the browser tab for this application, and the `script` tag indicates what is the path to the Javascript file that controls the application. | ||
The `<!doctype html>` line indicates this is an HTML 5 document. The first `charset` meta tag indicates the encoding of the document and the `viewport` meta tag dictates how mobile browsers should scale the page. The `title` tag contains the text to be displayed on the browser tab for this application, and the `script` tag indicates what is the path to the JavaScript file that controls the application. | ||
We could create the entire application in a single Javascript file, but doing so would make it difficult to navigate the codebase later on. Instead, let's split the code into *modules*, and assemble these modules into a *bundle* `bin/app.js`. | ||
We could create the entire application in a single JavaScript file, but doing so would make it difficult to navigate the codebase later on. Instead, let's split the code into *modules*, and assemble these modules into a *bundle* `bin/app.js`. | ||
There are many ways to setup a bundler tool, but most are distributed via NPM. In fact, most modern Javascript libraries and tools are distributed that way, including Mithril. NPM stands for Node.js Package Manager. To download NPM, [install Node.js](https://nodejs.org/en/); NPM is installed automatically with it. Once you have Node.js and NPM installed, open the command line and run this command: | ||
There are many ways to setup a bundler tool, but most are distributed via NPM. In fact, most modern JavaScript libraries and tools are distributed that way, including Mithril. NPM stands for Node.js Package Manager. To download NPM, [install Node.js](https://nodejs.org/en/); NPM is installed automatically with it. Once you have Node.js and NPM installed, open the command line and run this command: | ||
@@ -105,3 +105,3 @@ ```bash | ||
The `m.request` call returns a Promise that resolves to the data from the endpoint. By default, Mithril assumes a HTTP response body are in JSON format and automatically parses it into a Javascript object or array. The `.then` callback runs when the XHR request completes. In this case, the callback assigns the `result.data` array to `User.list`. | ||
The `m.request` call returns a Promise that resolves to the data from the endpoint. By default, Mithril assumes a HTTP response body are in JSON format and automatically parses it into a JavaScript object or array. The `.then` callback runs when the XHR request completes. In this case, the callback assigns the `result.data` array to `User.list`. | ||
@@ -138,3 +138,3 @@ Notice we also have a `return` statement in `loadList`. This is a general good practice when working with Promises, which allows us to register more callbacks to run after the completion of the XHR request. | ||
By default, Mithril views are described using [hyperscript](hyperscript.md). Hyperscript offers a terse syntax that can be indented more naturally than HTML for complex tags, and in addition, since its syntax is simply Javascript, it's possible to leverage a lot of Javascript tooling ecosystem: for example [Babel](es6.md), [JSX](jsx.md) (inline-HTML syntax extension), [eslint](http://eslint.org/) (linting), [uglifyjs](https://github.com/mishoo/UglifyJS2) (minification), [istanbul](https://github.com/gotwarlost/istanbul) (code coverage), [flow](https://flowtype.org/) (static type analysis), etc. | ||
By default, Mithril views are described using [hyperscript](hyperscript.md). Hyperscript offers a terse syntax that can be indented more naturally than HTML for complex tags, and in addition, since its syntax is simply JavaScript, it's possible to leverage a lot of JavaScript tooling ecosystem: for example [Babel](es6.md), [JSX](jsx.md) (inline-HTML syntax extension), [eslint](http://eslint.org/) (linting), [uglifyjs](https://github.com/mishoo/UglifyJS2) (minification), [istanbul](https://github.com/gotwarlost/istanbul) (code coverage), [flow](https://flowtype.org/) (static type analysis), etc. | ||
@@ -173,3 +173,3 @@ Let's use Mithril hyperscript to create a list of items. Hyperscript is the most idiomatic way of writing Mithril views, but [JSX is another popular alternative that you could explore](jsx.md) once you're more comfortable with the basics: | ||
Since `User.list` is a Javascript array, and since hyperscript views are just Javascript, we can loop through the array using the `.map` method. This creates an array of vnodes that represents a list of `div`s, each containing the name of a user. | ||
Since `User.list` is a JavaScript array, and since hyperscript views are just JavaScript, we can loop through the array using the `.map` method. This creates an array of vnodes that represents a list of `div`s, each containing the name of a user. | ||
@@ -195,3 +195,3 @@ The problem, of course, is that we never called the `User.loadList` function. Therefore, `User.list` is still an empty array, and thus this view would render a blank page. Since we want `User.loadList` to be called when we render this component, we can take advantage of component [lifecycle methods](lifecycle-methods.md): | ||
Also notice we **didn't** do `oninit: User.loadList()` (with parentheses at the end). The difference is that `oninit: User.loadList()` calls the function once and immediately, but `oninit: User.loadList` only calls that function when the component renders. This is an important difference and a common pitfall for developers new to javascript: calling the function immediately means that the XHR request will fire as soon as the source code is evaluated, even if the component never renders. Also, if the component is ever recreated (through navigating back and forth through the application), the function won't be called again as expected. | ||
Also notice we **didn't** do `oninit: User.loadList()` (with parentheses at the end). The difference is that `oninit: User.loadList()` calls the function once and immediately, but `oninit: User.loadList` only calls that function when the component renders. This is an important difference and a common pitfall for developers new to JavaScript: calling the function immediately means that the XHR request will fire as soon as the source code is evaluated, even if the component never renders. Also, if the component is ever recreated (through navigating back and forth through the application), the function won't be called again as expected. | ||
@@ -256,3 +256,3 @@ --- | ||
Routing means binding a screen to a unique URL, to create the ability to go from one "page" to another. Mithril is designed for Single Page Applications, so these "pages" aren't necessarily different HTML files in the traditional sense of the word. Instead, routing in Single Page Applications retains the same HTML file throughout its lifetime, but changes the state of the application via Javascript. Client side routing has the benefit of avoiding flashes of blank screen between page transitions, and can reduce the amount of data being sent down from the server when used in conjunction with an web service oriented architecture (i.e. an application that downloads data as JSON instead of downloading pre-rendered chunks of verbose HTML). | ||
Routing means binding a screen to a unique URL, to create the ability to go from one "page" to another. Mithril is designed for Single Page Applications, so these "pages" aren't necessarily different HTML files in the traditional sense of the word. Instead, routing in Single Page Applications retains the same HTML file throughout its lifetime, but changes the state of the application via JavaScript. Client side routing has the benefit of avoiding flashes of blank screen between page transitions, and can reduce the amount of data being sent down from the server when used in conjunction with an web service oriented architecture (i.e. an application that downloads data as JSON instead of downloading pre-rendered chunks of verbose HTML). | ||
@@ -259,0 +259,0 @@ We can add routing by changing the `m.mount` call to a `m.route` call: |
@@ -49,6 +49,6 @@ # stream() | ||
```markup | ||
<script src="https://unpkg.com/mithril@next/stream"></script> | ||
<script src="https://unpkg.com/mithril@next/stream/stream.js"></script> | ||
``` | ||
When loaded directly with a `<script>` tag (rather than required), the stream library will be exposed as `window.m.stream`. If `window.m` is already defined (e.g. because you also use the main Mithril script), it will attach itself to the existing object. Otherwise it creates a new `window.m`. If you want to use streams in conjunction with Mithril as raw script tags, you should include Mithril in your page before `mithril/stream`, because `mithril` will otherwise overwrite the `window.m` object defined by `mithril-stream`. This is not a concern when the libraries are consumed as CommonJS modules (using `require(...)`). | ||
When loaded directly with a `<script>` tag (rather than required), the stream library will be exposed as `window.m.stream`. If `window.m` is already defined (e.g. because you also use the main Mithril script), it will attach itself to the existing object. Otherwise it creates a new `window.m`. If you want to use streams in conjunction with Mithril as raw script tags, you should include Mithril in your page before `mithril/stream`, because `mithril` will otherwise overwrite the `window.m` object defined by `mithril/stream`. This is not a concern when the libraries are consumed as CommonJS modules (using `require(...)`). | ||
@@ -55,0 +55,0 @@ --- |
@@ -31,3 +31,3 @@ # Testing | ||
To run the test, use the command `npm test`. Ospec considers any Javascript file inside of a `tests` folder (anywhere in the project) to be a test. | ||
To run the test, use the command `npm test`. Ospec considers any JavaScript file inside of a `tests` folder (anywhere in the project) to be a test. | ||
@@ -34,0 +34,0 @@ ``` |
@@ -66,3 +66,3 @@ # trust(html) | ||
You **must sanitize the input** of `m.trust` to ensure there's no user-generated malicious code in the HTML string. If you don't sanitize an HTML string and mark it as a trusted string, any asynchronous javascript call points within the HTML string will be triggered and run with the authorization level of the user viewing the page. | ||
You **must sanitize the input** of `m.trust` to ensure there's no user-generated malicious code in the HTML string. If you don't sanitize an HTML string and mark it as a trusted string, any asynchronous JavaScript call points within the HTML string will be triggered and run with the authorization level of the user viewing the page. | ||
@@ -77,3 +77,3 @@ There are many ways in which an HTML string may contain executable code. The most common ways to inject security attacks are to add an `onload` or `onerror` attributes in `<img>` or `<iframe>` tags, and to use unbalanced quotes such as `" onerror="alert(1)` to inject executable contexts in unsanitized string interpolations. | ||
// An attack using javascript-related attributes | ||
// An attack using JavaScript-related attributes | ||
data.description = "<img onload='alert(1)'>" | ||
@@ -90,3 +90,3 @@ | ||
// An attack that does not use javascript | ||
// An attack that does not use JavaScript | ||
data.description = "<a href='http://evil.com/login-page-that-steals-passwords.html'>Click here to read more</a>" | ||
@@ -101,3 +101,3 @@ ``` | ||
Even though there are many obscure ways to make an HTML string run Javascript, `<script>` tags are one thing that does not run when it appears in an HTML string. | ||
Even though there are many obscure ways to make an HTML string run JavaScript, `<script>` tags are one thing that does not run when it appears in an HTML string. | ||
@@ -188,2 +188,2 @@ For historical reasons, browsers ignore `<script>` tags that are inserted into the DOM via innerHTML. They do this because once the element is ready (and thus, has an accessible innerHTML property), the rendering engines cannot backtrack to the parsing-stage if the script calls something like document.write("</body>"). | ||
To avoid encoding issues, you should set the file encoding to UTF-8 on the Javascript file, as well as add the `<meta charset="utf-8">` meta tag in the host HTML file. | ||
To avoid encoding issues, you should set the file encoding to UTF-8 on the JavaScript file, as well as add the `<meta charset="utf-8">` meta tag in the host HTML file. |
@@ -14,3 +14,3 @@ # Virtual DOM nodes | ||
A virtual DOM tree is a Javascript data structure that describes a DOM tree. It consists of nested virtual DOM nodes, also known as *vnodes*. | ||
A virtual DOM tree is a JavaScript data structure that describes a DOM tree. It consists of nested virtual DOM nodes, also known as *vnodes*. | ||
@@ -21,5 +21,5 @@ The first time a virtual DOM tree is rendered, it is used as a blueprint to create a DOM tree that matches its structure. | ||
It may seem wasteful to recreate vnodes so frequently, but as it turns out, modern Javascript engines can create hundreds of thousands of objects in less than a millisecond. On the other hand, modifying the DOM is several orders of magnitude more expensive than creating vnodes. | ||
It may seem wasteful to recreate vnodes so frequently, but as it turns out, modern JavaScript engines can create hundreds of thousands of objects in less than a millisecond. On the other hand, modifying the DOM is several orders of magnitude more expensive than creating vnodes. | ||
For that reason, Mithril uses a sophisticated and highly optimized virtual DOM diffing algorithm to minimize the amount of DOM updates. Mithril *also* generates carefully crafted vnode data structures that are compiled by Javascript engines for near-native data structure access performance. In addition, Mithril aggressively optimizes the function that creates vnodes as well. | ||
For that reason, Mithril uses a sophisticated and highly optimized virtual DOM diffing algorithm to minimize the amount of DOM updates. Mithril *also* generates carefully crafted vnode data structures that are compiled by JavaScript engines for near-native data structure access performance. In addition, Mithril aggressively optimizes the function that creates vnodes as well. | ||
@@ -38,3 +38,3 @@ The reason Mithril goes to such great lengths to support a rendering model that recreates the entire virtual DOM tree on every render is to provide a declarative [immediate mode](https://en.wikipedia.org/wiki/Immediate_mode_(computer_graphics%29) API, a style of rendering that makes it drastically easier to manage UI complexity. | ||
Virtual DOM nodes, or *vnodes*, are javascript objects that represent DOM elements (or parts of the DOM). Mithril's virtual DOM engine consumes a tree of vnodes to produce a DOM tree. | ||
Virtual DOM nodes, or *vnodes*, are JavaScript objects that represent DOM elements (or parts of the DOM). Mithril's virtual DOM engine consumes a tree of vnodes to produce a DOM tree. | ||
@@ -68,3 +68,3 @@ Vnodes are created via the [`m()`](hyperscript.md) hyperscript utility: | ||
Virtual DOM nodes, or *vnodes*, are Javascript objects that represent an element (or parts of the DOM) and have the following properties: | ||
Virtual DOM nodes, or *vnodes*, are JavaScript objects that represent an element (or parts of the DOM) and have the following properties: | ||
@@ -98,3 +98,3 @@ Property | Type | Description | ||
Trusted HTML | `{tag: "<", children: "<br>"}` | Represents a list of DOM elements from an HTML string. | ||
Component | `{tag: ExampleComponent}` | If `tag` is a Javascript object with a `view` method, the vnode represents the DOM generated by rendering the component. | ||
Component | `{tag: ExampleComponent}` | If `tag` is a JavaScript object with a `view` method, the vnode represents the DOM generated by rendering the component. | ||
@@ -109,5 +109,5 @@ Everything in a virtual DOM tree is a vnode, including text. The `m()` utility automatically normalizes its `children` argument and turns strings into text vnodes and nested arrays into fragment vnodes. | ||
The `mithril/render/vnode` module is used by Mithril to generate all vnodes. This ensures modern Javascript engines can optimize virtual dom diffing by always compiling vnodes to the same hidden class. | ||
The `mithril/render/vnode` module is used by Mithril to generate all vnodes. This ensures modern JavaScript engines can optimize virtual dom diffing by always compiling vnodes to the same hidden class. | ||
When creating libraries that emit vnodes, you should use this module instead of writing naked Javascript objects in order to ensure a high level of rendering performance. | ||
When creating libraries that emit vnodes, you should use this module instead of writing naked JavaScript objects in order to ensure a high level of rendering performance. | ||
@@ -114,0 +114,0 @@ --- |
@@ -22,2 +22,4 @@ "use strict" | ||
m.buildQueryString = require("./querystring/build") | ||
m.parsePathname = require("./pathname/parse") | ||
m.buildPathname = require("./pathname/build") | ||
m.version = "bleeding-edge" | ||
@@ -24,0 +26,0 @@ m.vnode = require("./render/vnode") |
@@ -1,1 +0,1 @@ | ||
!function(){"use strict";function e(e,t,n,r,o,i){return{tag:e,key:t,attrs:n,children:r,text:o,dom:i,domSize:void 0,state:void 0,events:void 0,instance:void 0}}e.normalize=function(t){return Array.isArray(t)?e("[",void 0,void 0,e.normalizeChildren(t),void 0,void 0):null!=t&&"object"!=typeof t?e("#",void 0,void 0,!1===t?"":t,void 0,void 0):t},e.normalizeChildren=function(t){for(var n=[],r=0;r<t.length;r++)n[r]=e.normalize(t[r]);return n};var t=function(){var t,n=arguments[this],r=this+1;if(null==n?n={}:("object"!=typeof n||null!=n.tag||Array.isArray(n))&&(n={},r=this),arguments.length===r+1)t=arguments[r],Array.isArray(t)||(t=[t]);else for(t=[];r<arguments.length;)t.push(arguments[r++]);return e("",n.key,n,t)},n=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,r={},o={}.hasOwnProperty;function i(e){for(var t in e)if(o.call(e,t))return!1;return!0}function l(l){if(null==l||"string"!=typeof l&&"function"!=typeof l&&"function"!=typeof l.view)throw Error("The selector must be either a string or a component.");var a=t.apply(1,arguments);return"string"==typeof l&&(a.children=e.normalizeChildren(a.children),"["!==l)?function(t,n){var r=n.attrs,l=e.normalizeChildren(n.children),a=o.call(r,"class"),u=a?r.class:r.className;if(n.tag=t.tag,n.attrs=null,n.children=void 0,!i(t.attrs)&&!i(r)){var f={};for(var s in r)o.call(r,s)&&(f[s]=r[s]);r=f}for(var s in t.attrs)o.call(t.attrs,s)&&"className"!==s&&!o.call(r,s)&&(r[s]=t.attrs[s]);for(var s in null==u&&null==t.attrs.className||(r.className=null!=u?null!=t.attrs.className?String(t.attrs.className)+" "+String(u):u:null!=t.attrs.className?t.attrs.className:null),a&&(r.class=null),r)if(o.call(r,s)&&"key"!==s){n.attrs=r;break}return Array.isArray(l)&&1===l.length&&null!=l[0]&&"#"===l[0].tag?n.text=l[0].children:n.children=l,n}(r[l]||function(e){for(var t,o="div",i=[],l={};t=n.exec(e);){var a=t[1],u=t[2];if(""===a&&""!==u)o=u;else if("#"===a)l.id=u;else if("."===a)i.push(u);else if("["===t[3][0]){var f=t[6];f&&(f=f.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===t[4]?i.push(f):l[t[4]]=""===f?f:f||!0}}return i.length>0&&(l.className=i.join(" ")),r[e]={tag:o,attrs:l}}(l),a):(a.tag=l,a)}l.trust=function(t){return null==t&&(t=""),e("<",void 0,void 0,t,void 0,void 0)},l.fragment=function(){var n=t.apply(0,arguments);return n.tag="[",n.children=e.normalizeChildren(n.children),n};var a=function(){return l.apply(this,arguments)};if(a.m=l,a.trust=l.trust,a.fragment=l.fragment,(u=function(e){if(!(this instanceof u))throw new Error("Promise must be called with `new`");if("function"!=typeof e)throw new TypeError("executor must be a function");var t=this,n=[],r=[],o=f(n,!0),i=f(r,!1),l=t._instance={resolvers:n,rejectors:r},a="function"==typeof setImmediate?setImmediate:setTimeout;function f(e,o){return function u(f){var c;try{if(!o||null==f||"object"!=typeof f&&"function"!=typeof f||"function"!=typeof(c=f.then))a(function(){o||0!==e.length||console.error("Possible unhandled promise rejection:",f);for(var t=0;t<e.length;t++)e[t](f);n.length=0,r.length=0,l.state=o,l.retry=function(){u(f)}});else{if(f===t)throw new TypeError("Promise can't be resolved w/ itself");s(c.bind(f))}}catch(e){i(e)}}}function s(e){var t=0;function n(e){return function(n){t++>0||e(n)}}var r=n(i);try{e(n(o),r)}catch(e){r(e)}}s(e)}).prototype.then=function(e,t){var n,r,o=this._instance;function i(e,t,i,l){t.push(function(t){if("function"!=typeof e)i(t);else try{n(e(t))}catch(e){r&&r(e)}}),"function"==typeof o.retry&&l===o.state&&o.retry()}var l=new u(function(e,t){n=e,r=t});return i(e,o.resolvers,n,!0),i(t,o.rejectors,r,!1),l},u.prototype.catch=function(e){return this.then(null,e)},u.prototype.finally=function(e){return this.then(function(t){return u.resolve(e()).then(function(){return t})},function(t){return u.resolve(e()).then(function(){return u.reject(t)})})},u.resolve=function(e){return e instanceof u?e:new u(function(t){t(e)})},u.reject=function(e){return new u(function(t,n){n(e)})},u.all=function(e){return new u(function(t,n){var r=e.length,o=0,i=[];if(0===e.length)t([]);else for(var l=0;l<e.length;l++)!function(l){function a(e){o++,i[l]=e,o===r&&t(i)}null==e[l]||"object"!=typeof e[l]&&"function"!=typeof e[l]||"function"!=typeof e[l].then?a(e[l]):e[l].then(a,n)}(l)})},u.race=function(e){return new u(function(t,n){for(var r=0;r<e.length;r++)e[r].then(t,n)})},"undefined"!=typeof window){void 0===window.Promise?window.Promise=u:window.Promise.prototype.finally||(window.Promise.prototype.finally=u.prototype.finally);var u=window.Promise}else if("undefined"!=typeof global){void 0===global.Promise?global.Promise=u:global.Promise.prototype.finally||(global.Promise.prototype.finally=u.prototype.finally);u=global.Promise}var f=function(e){if("[object Object]"!==Object.prototype.toString.call(e))return"";var t=[];for(var n in e)r(n,e[n]);return t.join("&");function r(e,n){if(Array.isArray(n))for(var o=0;o<n.length;o++)r(e+"["+o+"]",n[o]);else if("[object Object]"===Object.prototype.toString.call(n))for(var o in n)r(e+"["+o+"]",n[o]);else t.push(encodeURIComponent(e)+(null!=n&&""!==n?"="+encodeURIComponent(n):""))}},s=function(e,t){var n,r=0;function o(e){return function(r,o){"string"!=typeof r?(o=r,r=r.url):null==o&&(o={});var i=new t(function(t,n){e(r,o,function(e){if("function"==typeof o.type)if(Array.isArray(e))for(var n=0;n<e.length;n++)e[n]=new o.type(e[n]);else e=new o.type(e);t(e)},n)});if(!0===o.background)return i;var l=0;function a(){0==--l&&"function"==typeof n&&n()}return function e(t){var n=t.then;return t.then=function(){l++;var r=n.apply(t,arguments);return r.then(a,function(e){if(a(),0===l)throw e}),e(r)},t}(i)}}function i(e,t){for(var n in e.headers)if({}.hasOwnProperty.call(e.headers,n)&&t.test(n))return!0;return!1}function l(e,t,n){if(null==t)return e;if(e=e.replace(/:([^\/]+)/gi,function(e,n){return null!=t[n]?t[n]:e}),n&&null!=t){var r=f(t);r&&(e+=(e.indexOf("?")<0?"?":"&")+r)}return e}return{request:o(function(t,n,r,o){var a=null!=n.method?n.method.toUpperCase():"GET",u="GET"!==a&&"TRACE"!==a&&("boolean"!=typeof n.useBody||n.useBody),f=n.data,s=!(null!=n.serialize&&n.serialize!==JSON.serialize||f instanceof e.FormData);u&&("function"==typeof n.serialize?f=n.serialize(f):f instanceof e.FormData||(f=JSON.stringify(f)));var c=new e.XMLHttpRequest,d=!1,v=c.abort;for(var h in c.abort=function(){d=!0,v.call(c)},c.open(a,l(t,n.data,!u),"boolean"!=typeof n.async||n.async,"string"==typeof n.user?n.user:void 0,"string"==typeof n.password?n.password:void 0),s&&u&&!i(n,/^content-type0$/i)&&c.setRequestHeader("Content-Type","application/json; charset=utf-8"),"function"==typeof n.deserialize||i(n,/^accept$/i)||c.setRequestHeader("Accept","application/json, text/*"),n.withCredentials&&(c.withCredentials=n.withCredentials),n.timeout&&(c.timeout=n.timeout),n.responseType&&(c.responseType=n.responseType),n.headers)({}).hasOwnProperty.call(n.headers,h)&&c.setRequestHeader(h,n.headers[h]);"function"==typeof n.config&&(c=n.config(c,n)||c),c.onreadystatechange=function(){if(!d&&4===c.readyState)try{var e=c.status>=200&&c.status<300||304===c.status||/^file:\/\//i.test(t),i=c.responseText;if("function"==typeof n.extract)i=n.extract(c,n),e=!0;else if("function"==typeof n.deserialize)i=n.deserialize(i);else try{i=i?JSON.parse(i):null}catch(e){throw new Error("Invalid JSON: "+i)}if(e)r(i);else{var l=new Error(c.responseText);l.code=c.status,l.response=i,o(l)}}catch(e){o(e)}},u&&null!=f?c.send(f):c.send()}),jsonp:o(function(t,n,o,i){var a=n.callbackName||"_mithril_"+Math.round(1e16*Math.random())+"_"+r++,u=e.document.createElement("script");e[a]=function(t){u.parentNode.removeChild(u),o(t),delete e[a]},u.onerror=function(){u.parentNode.removeChild(u),i(new Error("JSONP request failed")),delete e[a]},t=l(t,n.data,!0),u.src=t+(t.indexOf("?")<0?"?":"&")+encodeURIComponent(n.callbackKey||"callback")+"="+encodeURIComponent(a),e.document.documentElement.appendChild(u)}),setCompletionCallback:function(e){n=e}}}(window,u),c=function(t){var n,r=t.document,o={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"};function i(e){return e.attrs&&e.attrs.xmlns||o[e.tag]}function l(e,t){if(e.state!==t)throw new Error("`vnode.state` must not be modified")}function a(e){var t=e.state;try{return this.apply(t,arguments)}finally{l(e,t)}}function u(){try{return r.activeElement}catch(e){return null}}function f(e,t,n,r,o,i,l){for(var a=n;a<r;a++){var u=t[a];null!=u&&s(e,u,o,l,i)}}function s(t,n,o,l,u){var c=n.tag;if("string"==typeof c)switch(n.state={},null!=n.attrs&&$(n.attrs,n,o),c){case"#":!function(e,t,n){t.dom=r.createTextNode(t.children),g(e,t.dom,n)}(t,n,u);break;case"<":d(t,n,l,u);break;case"[":!function(e,t,n,o,i){var l=r.createDocumentFragment();if(null!=t.children){var a=t.children;f(l,a,0,a.length,n,null,o)}t.dom=l.firstChild,t.domSize=l.childNodes.length,g(e,l,i)}(t,n,o,l,u);break;default:!function(t,n,o,l,a){var u=n.tag,s=n.attrs,c=s&&s.is,d=(l=i(n)||l)?c?r.createElementNS(l,u,{is:c}):r.createElementNS(l,u):c?r.createElement(u,{is:c}):r.createElement(u);n.dom=d,null!=s&&function(e,t,n){for(var r in t)k(e,r,null,t[r],n)}(n,s,l);if(g(t,d,a),null!=s&&null!=s.contenteditable)w(n);else if(null!=n.text&&(""!==n.text?d.textContent=n.text:n.children=[e("#",void 0,void 0,n.text,void 0,void 0)]),null!=n.children){var v=n.children;f(d,v,0,v.length,o,null,l),"select"===n.tag&&null!=s&&function(e,t){if("value"in t)if(null===t.value)-1!==e.dom.selectedIndex&&(e.dom.value=null);else{var n=""+t.value;e.dom.value===n&&-1!==e.dom.selectedIndex||(e.dom.value=n)}"selectedIndex"in t&&k(e,"selectedIndex",null,t.selectedIndex,void 0)}(n,s)}}(t,n,o,l,u)}else!function(t,n,r,o,i){(function(t,n){var r;if("function"==typeof t.tag.view){if(t.state=Object.create(t.tag),null!=(r=t.state.view).$$reentrantLock$$)return;r.$$reentrantLock$$=!0}else{if(t.state=void 0,null!=(r=t.tag).$$reentrantLock$$)return;r.$$reentrantLock$$=!0,t.state=null!=t.tag.prototype&&"function"==typeof t.tag.prototype.view?new t.tag(t):t.tag(t)}if($(t.state,t,n),null!=t.attrs&&$(t.attrs,t,n),t.instance=e.normalize(a.call(t.state.view,t)),t.instance===t)throw Error("A view cannot return the vnode it received as argument");r.$$reentrantLock$$=null})(n,r),null!=n.instance?(s(t,n.instance,r,o,i),n.dom=n.instance.dom,n.domSize=null!=n.dom?n.instance.domSize:0):n.domSize=0}(t,n,o,l,u)}var c={caption:"table",thead:"table",tbody:"table",tfoot:"table",tr:"tbody",th:"tr",td:"tr",colgroup:"table",col:"colgroup"};function d(e,t,n,o){var i=t.children.match(/^\s*?<(\w+)/im)||[],l=r.createElement(c[i[1]]||"div");"http://www.w3.org/2000/svg"===n?(l.innerHTML='<svg xmlns="http://www.w3.org/2000/svg">'+t.children+"</svg>",l=l.firstChild):l.innerHTML=t.children,t.dom=l.firstChild,t.domSize=l.childNodes.length;for(var a,u=r.createDocumentFragment();a=l.firstChild;)u.appendChild(a);g(e,u,o)}function v(e,t,n,r,o,i){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)f(e,n,0,n.length,r,o,i);else if(null==n||0===n.length)b(t,0,t.length);else{for(var l=0,a=0,u=null,c=null;a<t.length;a++)if(null!=t[a]){u=null!=t[a].key;break}for(;l<n.length;l++)if(null!=n[l]){c=null!=n[l].key;break}if(null===c&&null==u)return;if(u!==c)b(t,a,t.length),f(e,n,l,n.length,r,o,i);else if(c){for(var d,v,w,k,S,C=t.length-1,z=n.length-1;C>=a&&z>=l;)if(w=t[C],k=n[z],null==w)C--;else if(null==k)z--;else{if(w.key!==k.key)break;w!==k&&h(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,z--}for(;C>=a&&z>=l;)if(d=t[a],v=n[l],null==d)a++;else if(null==v)l++;else{if(d.key!==v.key)break;a++,l++,d!==v&&h(e,d,v,r,y(t,a,o),i)}for(;C>=a&&z>=l;){if(null==d)a++;else if(null==v)l++;else if(null==w)C--;else if(null==k)z--;else{if(l===z)break;if(d.key!==k.key||w.key!==v.key)break;S=y(t,a,o),g(e,m(w),S),w!==v&&h(e,w,v,r,S,i),++l<=--z&&g(e,m(d),o),d!==k&&h(e,d,k,r,o,i),null!=k.dom&&(o=k.dom),a++,C--}w=t[C],k=n[z],d=t[a],v=n[l]}for(;C>=a&&z>=l;){if(null==w)C--;else if(null==k)z--;else{if(w.key!==k.key)break;w!==k&&h(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,z--}w=t[C],k=n[z]}if(l>z)b(t,a,C+1);else if(a>C)f(e,n,l,z+1,r,o,i);else{var E,A,j=o,N=z-l+1,P=new Array(N),O=0,$=0,T=2147483647,I=0;for($=0;$<N;$++)P[$]=-1;for($=z;$>=l;$--)if(null==E&&(E=p(t,a,C+1)),null!=(k=n[$])){var R=E[k.key];null!=R&&(T=R<T?R:-1,P[$-l]=R,w=t[R],t[R]=null,w!==k&&h(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),I++)}if(o=j,I!==C-a+1&&b(t,a,C+1),0===I)f(e,n,l,z+1,r,o,i);else if(-1===T)for(O=(A=function(e){var t,n,r=e.slice(),o=[];o.push(0);for(var i=0,l=e.length;i<l;++i)if(-1!==e[i]){var a=o[o.length-1];if(e[a]<e[i])r[i]=a,o.push(i);else{for(t=0,n=o.length-1;t<n;){var u=(t+n)/2|0;e[o[u]]<e[i]?t=u+1:n=u}e[i]<e[o[t]]&&(t>0&&(r[i]=o[t-1]),o[t]=i)}}t=o.length,n=o[t-1];for(;t-- >0;)o[t]=n,n=r[n];return o}(P)).length-1,$=z;$>=l;$--)v=n[$],-1===P[$-l]?s(e,v,r,i,o):A[O]===$-l?O--:g(e,m(v),o),null!=v.dom&&(o=n[$].dom);else for($=z;$>=l;$--)v=n[$],-1===P[$-l]&&s(e,v,r,i,o),null!=v.dom&&(o=n[$].dom)}}else{var L=t.length<n.length?t.length:n.length;for(l=l<a?l:a;l<L;l++)(d=t[l])===(v=n[l])||null==d&&null==v||(null==d?s(e,v,r,i,y(t,l+1,o)):null==v?x(d):h(e,d,v,r,y(t,l+1,o),i));t.length>L&&b(t,l,t.length),n.length>L&&f(e,n,l,n.length,r,o,i)}}}function h(t,n,r,o,l,u){var f=n.tag;if(f===r.tag){if(r.state=n.state,r.events=n.events,function(e,t){do{if(null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate){var n=a.call(e.attrs.onbeforeupdate,e,t);if(void 0!==n&&!n)break}if("string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate){var n=a.call(e.state.onbeforeupdate,e,t);if(void 0!==n&&!n)break}return!1}while(0);return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,!0}(r,n))return;if("string"==typeof f)switch(null!=r.attrs&&T(r.attrs,r,o),f){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children);t.dom=e.dom}(n,r);break;case"<":!function(e,t,n,r,o){t.children!==n.children?(m(t),d(e,n,r,o)):(n.dom=t.dom,n.domSize=t.domSize)}(t,n,r,u,l);break;case"[":!function(e,t,n,r,o,i){v(e,t.children,n.children,r,o,i);var l=0,a=n.children;if(n.dom=null,null!=a){for(var u=0;u<a.length;u++){var f=a[u];null!=f&&null!=f.dom&&(null==n.dom&&(n.dom=f.dom),l+=f.domSize||1)}1!==l&&(n.domSize=l)}}(t,n,r,o,l,u);break;default:!function(t,n,r,o){var l=n.dom=t.dom;o=i(n)||o,"textarea"===n.tag&&(null==n.attrs&&(n.attrs={}),null!=n.text&&(n.attrs.value=n.text,n.text=void 0));(function(e,t,n,r){if(null!=n)for(var o in n)k(e,o,t&&t[o],n[o],r);var i;if(null!=t)for(var o in t)null==(i=t[o])||null!=n&&null!=n[o]||S(e,o,i,r)})(n,t.attrs,n.attrs,o),null!=n.attrs&&null!=n.attrs.contenteditable?w(n):null!=t.text&&null!=n.text&&""!==n.text?t.text.toString()!==n.text.toString()&&(t.dom.firstChild.nodeValue=n.text):(null!=t.text&&(t.children=[e("#",void 0,void 0,t.text,void 0,t.dom.firstChild)]),null!=n.text&&(n.children=[e("#",void 0,void 0,n.text,void 0,void 0)]),v(l,t.children,n.children,r,null,o))}(n,r,o,u)}else!function(t,n,r,o,i,l){if(r.instance=e.normalize(a.call(r.state.view,r)),r.instance===r)throw Error("A view cannot return the vnode it received as argument");T(r.state,r,o),null!=r.attrs&&T(r.attrs,r,o);null!=r.instance?(null==n.instance?s(t,r.instance,o,l,i):h(t,n.instance,r.instance,o,i,l),r.dom=r.instance.dom,r.domSize=r.instance.domSize):null!=n.instance?(x(n.instance),r.dom=void 0,r.domSize=0):(r.dom=n.dom,r.domSize=n.domSize)}(t,n,r,o,l,u)}else x(n),s(t,r,o,u,l)}function p(e,t,n){for(var r=Object.create(null);t<n;t++){var o=e[t];if(null!=o){var i=o.key;null!=i&&(r[i]=t)}}return r}function m(e){var t=e.domSize;if(null!=t||null==e.dom){var n=r.createDocumentFragment();if(t>0){for(var o=e.dom;--t;)n.appendChild(o.nextSibling);n.insertBefore(o,n.firstChild)}return n}return e.dom}function y(e,t,n){for(;t<e.length;t++)if(null!=e[t]&&null!=e[t].dom)return e[t].dom;return n}function g(e,t,n){null!=n?e.insertBefore(t,n):e.appendChild(t)}function w(e){var t=e.children;if(null!=t&&1===t.length&&"<"===t[0].tag){var n=t[0].children;e.dom.innerHTML!==n&&(e.dom.innerHTML=n)}else if(null!=e.text||null!=t&&0!==t.length)throw new Error("Child node of a contenteditable must be trusted")}function b(e,t,n){for(var r=t;r<n;r++){var o=e[r];null!=o&&x(o)}}function x(e){var t,n=1,r=0,o=e.state;"string"!=typeof e.tag&&"function"==typeof e.state.onbeforeremove&&(null!=(t=a.call(e.state.onbeforeremove,e))&&"function"==typeof t.then&&(n++,t.then(i,i)));e.attrs&&"function"==typeof e.attrs.onbeforeremove&&(null!=(t=a.call(e.attrs.onbeforeremove,e))&&"function"==typeof t.then&&(n++,t.then(i,i)));function i(){if(++r===n&&(l(e,o),function e(t){"string"!=typeof t.tag&&"function"==typeof t.state.onremove&&a.call(t.state.onremove,t);t.attrs&&"function"==typeof t.attrs.onremove&&a.call(t.attrs.onremove,t);if("string"!=typeof t.tag)null!=t.instance&&e(t.instance);else{var n=t.children;if(Array.isArray(n))for(var r=0;r<n.length;r++){var o=n[r];null!=o&&e(o)}}}(e),e.dom)){for(var t=e.dom.parentNode,i=e.domSize||1;--i;)t.removeChild(e.dom.nextSibling);t.removeChild(e.dom)}}i()}function k(e,t,n,o,i){if("key"!==t&&"is"!==t&&null!=o&&!C(t)&&(n!==o||function(e,t){return"value"===t||"checked"===t||"selectedIndex"===t||"selected"===t&&e.dom===u()||"option"===e.tag&&e.dom.parentNode===r.activeElement}(e,t)||"object"==typeof o)){if("o"===t[0]&&"n"===t[1])return O(e,t,o);if("xlink:"===t.slice(0,6))e.dom.setAttributeNS("http://www.w3.org/1999/xlink",t.slice(6),o);else if("style"===t)N(e.dom,n,o);else if(z(e,t,i)){if("value"===t){if(("input"===e.tag||"textarea"===e.tag)&&e.dom.value===""+o&&e.dom===u())return;if("select"===e.tag&&null!==n&&e.dom.value===""+o)return;if("option"===e.tag&&null!==n&&e.dom.value===""+o)return}"input"===e.tag&&"type"===t?e.dom.setAttribute(t,o):e.dom[t]=o}else"boolean"==typeof o?o?e.dom.setAttribute(t,""):e.dom.removeAttribute(t):e.dom.setAttribute("className"===t?"class":t,o)}}function S(e,t,n,r){if("key"!==t&&"is"!==t&&null!=n&&!C(t))if("o"!==t[0]||"n"!==t[1]||C(t))if("style"===t)N(e.dom,n,null);else if(!z(e,t,r)||"className"===t||"value"===t&&("option"===e.tag||"select"===e.tag&&-1===e.dom.selectedIndex&&e.dom===u())||"input"===e.tag&&"type"===t){var o=t.indexOf(":");-1!==o&&(t=t.slice(o+1)),!1!==n&&e.dom.removeAttribute("className"===t?"class":t)}else e.dom[t]=null;else O(e,t,void 0)}function C(e){return"oninit"===e||"oncreate"===e||"onupdate"===e||"onremove"===e||"onbeforeremove"===e||"onbeforeupdate"===e}function z(e,t,n){return void 0===n&&(e.tag.indexOf("-")>-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}var E=/[A-Z]/g;function A(e){return"-"+e.toLowerCase()}function j(e){return"-"===e[0]&&"-"===e[1]?e:"cssFloat"===e?"float":e.replace(E,A)}function N(e,t,n){if(t===n);else if(null==n)e.style.cssText="";else if("object"!=typeof n)e.style.cssText=n;else if(null==t||"object"!=typeof t)for(var r in e.style.cssText="",n){null!=(o=n[r])&&e.style.setProperty(j(r),String(o))}else{for(var r in n){var o;null!=(o=n[r])&&(o=String(o))!==String(t[r])&&e.style.setProperty(j(r),o)}for(var r in t)null!=t[r]&&null==n[r]&&e.style.removeProperty(j(r))}}function P(){}function O(e,t,n){if(null!=e.events){if(e.events[t]===n)return;null==n||"function"!=typeof n&&"object"!=typeof n?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}else null==n||"function"!=typeof n&&"object"!=typeof n||(e.events=new P,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}function $(e,t,n){"function"==typeof e.oninit&&a.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(a.bind(e.oncreate,t))}function T(e,t,n){"function"==typeof e.onupdate&&n.push(a.bind(e.onupdate,t))}return P.prototype=Object.create(null),P.prototype.handleEvent=function(e){var t,r=this["on"+e.type];"function"==typeof r?t=r.call(e.currentTarget,e):"function"==typeof r.handleEvent&&r.handleEvent(e),!1===e.redraw?e.redraw=void 0:"function"==typeof n&&n(),!1===t&&(e.preventDefault(),e.stopPropagation())},{render:function(t,n){if(!t)throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var r=[],o=u(),i=t.namespaceURI;null==t.vnodes&&(t.textContent=""),n=e.normalizeChildren(Array.isArray(n)?n:[n]),v(t,t.vnodes,n,r,null,"http://www.w3.org/1999/xhtml"===i?void 0:i),t.vnodes=n,null!=o&&u()!==o&&"function"==typeof o.focus&&o.focus();for(var l=0;l<r.length;l++)r[l]()},setRedraw:function(e){return n=e}}};var d=function(e,t){var n=c(e),r=[],o=!1;function i(e){var t=r.indexOf(e);t>-1&&r.splice(t,2)}function l(){if(o)throw new Error("Nested m.redraw.sync() call");o=!0;for(var e=1;e<r.length;e+=2)try{r[e]()}catch(e){"undefined"!=typeof console&&console.error(e)}o=!1}var a=(t||function(e){var t=null;return function(){null===t&&(t=requestAnimationFrame(function(){t=null,e()}))}})(l);return a.sync=l,n.setRedraw(a),{subscribe:function(e,t){i(e),r.push(e,t)},unsubscribe:i,redraw:a,render:n.render}}(window);s.setCompletionCallback(d.redraw);var v;a.mount=(v=d,function(t,n){if(null===n)return v.render(t,[]),void v.unsubscribe(t);if(null==n.view&&"function"!=typeof n)throw new Error("m.mount(element, component) expects a component, not a vnode");var r=function(){v.render(t,e(n))};v.subscribe(t,r),r()});var h=u,p=function(e){if(""===e||null==e)return{};"?"===e.charAt(0)&&(e=e.slice(1));for(var t=e.split("&"),n={},r={},o=0;o<t.length;o++){var i=t[o].split("="),l=decodeURIComponent(i[0]),a=2===i.length?decodeURIComponent(i[1]):"";"true"===a?a=!0:"false"===a&&(a=!1);var u=l.split(/\]\[?|\[/),f=n;l.indexOf("[")>-1&&u.pop();for(var s=0;s<u.length;s++){var c=u[s],d=u[s+1],v=""==d||!isNaN(parseInt(d,10)),h=s===u.length-1;if(""===c)null==r[l=u.slice(0,s).join()]&&(r[l]=0),c=r[l]++;null==f[c]&&(f[c]=h?a:v?[]:{}),f=f[c]}}return n},m=function(e){var t,n="function"==typeof e.history.pushState,r="function"==typeof setImmediate?setImmediate:setTimeout;function o(t){var n=e.location[t].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);return"pathname"===t&&"/"!==n[0]&&(n="/"+n),n}function i(e,t,n){var r=e.indexOf("?"),o=e.indexOf("#"),i=r>-1?r:o>-1?o:e.length;if(r>-1){var l=o>-1?o:e.length,a=p(e.slice(r+1,l));for(var u in a)t[u]=a[u]}if(o>-1){var f=p(e.slice(o+1));for(var u in f)n[u]=f[u]}return e.slice(0,i)}var l={prefix:"#!",getPath:function(){switch(l.prefix.charAt(0)){case"#":return o("hash").slice(l.prefix.length);case"?":return o("search").slice(l.prefix.length)+o("hash");default:return o("pathname").slice(l.prefix.length)+o("search")+o("hash")}},setPath:function(t,r,o){var a={},u={};if(t=i(t,a,u),null!=r){for(var s in r)a[s]=r[s];t=t.replace(/:([^\/]+)/g,function(e,t){return delete a[t],r[t]})}var c=f(a);c&&(t+="?"+c);var d=f(u);if(d&&(t+="#"+d),n){var v=o?o.state:null,h=o?o.title:null;e.onpopstate(),o&&o.replace?e.history.replaceState(v,h,l.prefix+t):e.history.pushState(v,h,l.prefix+t)}else e.location.href=l.prefix+t}};return l.defineRoutes=function(o,a,u){function f(){var t=l.getPath(),n={},r=i(t,n,n),f=e.history.state;if(null!=f)for(var s in f)n[s]=f[s];for(var c in o){var d=new RegExp("^"+c.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(d.test(r))return void r.replace(d,function(){for(var e=c.match(/:[^\/]+/g)||[],r=[].slice.call(arguments,1,-2),i=0;i<e.length;i++)n[e[i].replace(/:|\./g,"")]=decodeURIComponent(r[i]);a(o[c],n,t,c)})}u(t,n)}var s;n?e.onpopstate=(s=f,function(){null==t&&(t=r(function(){t=null,s()}))}):"#"===l.prefix.charAt(0)&&(e.onhashchange=f),f()},l};a.route=function(t,n){var r,o,i,l,a,u=m(t),f=function(t,f,s){if(null==t)throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined");function c(){null!=r&&n.render(t,r(e(o,i.key,i)))}var d=function(){c(),d=n.redraw};n.subscribe(t,c);var v=function(e){if(e===f)throw new Error("Could not resolve default route "+f);u.setPath(f,null,{replace:!0})};u.defineRoutes(s,function(e,t,n){var u=a=function(e,f){u===a&&(o=null==f||"function"!=typeof f.view&&"function"!=typeof f?"div":f,i=t,l=n,a=null,r=(e.render||function(e){return e}).bind(e),d())};e.view||"function"==typeof e?u({},e):e.onmatch?h.resolve(e.onmatch(t,n)).then(function(t){u(e,t)},v):u(e,"div")},v)};f.set=function(e,t,n){null!=a&&((n=n||{}).replace=!0),a=null,u.setPath(e,t,n)},f.get=function(){return l},f.prefix=function(e){u.prefix=e};var s=function(e,t){t.dom.setAttribute("href",u.prefix+t.attrs.href),t.dom.onclick=function(t){if(!(t.ctrlKey||t.metaKey||t.shiftKey||2===t.which)){t.preventDefault(),t.redraw=!1;var n=this.getAttribute("href");0===n.indexOf(u.prefix)&&(n=n.slice(u.prefix.length)),f.set(n,void 0,e)}}};return f.link=function(e){return null==e.tag?s.bind(s,e):s({},e)},f.param=function(e){return void 0!==i&&void 0!==e?i[e]:i},f}(window,d);var y=c(window);a.render=y.render,a.redraw=d.redraw,a.request=s.request,a.jsonp=s.jsonp,a.parseQueryString=p,a.buildQueryString=f,a.version="2.0.0-rc.4",a.vnode=e,a.PromisePolyfill=u,"undefined"!=typeof module?module.exports=a:window.m=a}(); | ||
!function(){"use strict";function e(e,t,n,r,o,i){return{tag:e,key:t,attrs:n,children:r,text:o,dom:i,domSize:void 0,state:void 0,events:void 0,instance:void 0}}e.normalize=function(t){return Array.isArray(t)?e("[",void 0,void 0,e.normalizeChildren(t),void 0,void 0):null!=t&&"object"!=typeof t?e("#",void 0,void 0,!1===t?"":t,void 0,void 0):t},e.normalizeChildren=function(t){for(var n=[],r=0;r<t.length;r++)n[r]=e.normalize(t[r]);return n};var t=function(){var t,n=arguments[this],r=this+1;if(null==n?n={}:("object"!=typeof n||null!=n.tag||Array.isArray(n))&&(n={},r=this),arguments.length===r+1)t=arguments[r],Array.isArray(t)||(t=[t]);else for(t=[];r<arguments.length;)t.push(arguments[r++]);return e("",n.key,n,t)},n=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,r={},o={}.hasOwnProperty;function i(e){for(var t in e)if(o.call(e,t))return!1;return!0}function l(l){if(null==l||"string"!=typeof l&&"function"!=typeof l&&"function"!=typeof l.view)throw Error("The selector must be either a string or a component.");var a=t.apply(1,arguments);return"string"==typeof l&&(a.children=e.normalizeChildren(a.children),"["!==l)?function(t,n){var r=n.attrs,l=e.normalizeChildren(n.children),a=o.call(r,"class"),u=a?r.class:r.className;if(n.tag=t.tag,n.attrs=null,n.children=void 0,!i(t.attrs)&&!i(r)){var s={};for(var f in r)o.call(r,f)&&(s[f]=r[f]);r=s}for(var f in t.attrs)o.call(t.attrs,f)&&"className"!==f&&!o.call(r,f)&&(r[f]=t.attrs[f]);for(var f in null==u&&null==t.attrs.className||(r.className=null!=u?null!=t.attrs.className?String(t.attrs.className)+" "+String(u):u:null!=t.attrs.className?t.attrs.className:null),a&&(r.class=null),r)if(o.call(r,f)&&"key"!==f){n.attrs=r;break}return Array.isArray(l)&&1===l.length&&null!=l[0]&&"#"===l[0].tag?n.text=l[0].children:n.children=l,n}(r[l]||function(e){for(var t,o="div",i=[],l={};t=n.exec(e);){var a=t[1],u=t[2];if(""===a&&""!==u)o=u;else if("#"===a)l.id=u;else if("."===a)i.push(u);else if("["===t[3][0]){var s=t[6];s&&(s=s.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===t[4]?i.push(s):l[t[4]]=""===s?s:s||!0}}return i.length>0&&(l.className=i.join(" ")),r[e]={tag:o,attrs:l}}(l),a):(a.tag=l,a)}l.trust=function(t){return null==t&&(t=""),e("<",void 0,void 0,t,void 0,void 0)},l.fragment=function(){var n=t.apply(0,arguments);return n.tag="[",n.children=e.normalizeChildren(n.children),n};var a=function(){return l.apply(this,arguments)};if(a.m=l,a.trust=l.trust,a.fragment=l.fragment,(u=function(e){if(!(this instanceof u))throw new Error("Promise must be called with `new`");if("function"!=typeof e)throw new TypeError("executor must be a function");var t=this,n=[],r=[],o=s(n,!0),i=s(r,!1),l=t._instance={resolvers:n,rejectors:r},a="function"==typeof setImmediate?setImmediate:setTimeout;function s(e,o){return function u(s){var c;try{if(!o||null==s||"object"!=typeof s&&"function"!=typeof s||"function"!=typeof(c=s.then))a(function(){o||0!==e.length||console.error("Possible unhandled promise rejection:",s);for(var t=0;t<e.length;t++)e[t](s);n.length=0,r.length=0,l.state=o,l.retry=function(){u(s)}});else{if(s===t)throw new TypeError("Promise can't be resolved w/ itself");f(c.bind(s))}}catch(e){i(e)}}}function f(e){var t=0;function n(e){return function(n){t++>0||e(n)}}var r=n(i);try{e(n(o),r)}catch(e){r(e)}}f(e)}).prototype.then=function(e,t){var n,r,o=this._instance;function i(e,t,i,l){t.push(function(t){if("function"!=typeof e)i(t);else try{n(e(t))}catch(e){r&&r(e)}}),"function"==typeof o.retry&&l===o.state&&o.retry()}var l=new u(function(e,t){n=e,r=t});return i(e,o.resolvers,n,!0),i(t,o.rejectors,r,!1),l},u.prototype.catch=function(e){return this.then(null,e)},u.prototype.finally=function(e){return this.then(function(t){return u.resolve(e()).then(function(){return t})},function(t){return u.resolve(e()).then(function(){return u.reject(t)})})},u.resolve=function(e){return e instanceof u?e:new u(function(t){t(e)})},u.reject=function(e){return new u(function(t,n){n(e)})},u.all=function(e){return new u(function(t,n){var r=e.length,o=0,i=[];if(0===e.length)t([]);else for(var l=0;l<e.length;l++)!function(l){function a(e){o++,i[l]=e,o===r&&t(i)}null==e[l]||"object"!=typeof e[l]&&"function"!=typeof e[l]||"function"!=typeof e[l].then?a(e[l]):e[l].then(a,n)}(l)})},u.race=function(e){return new u(function(t,n){for(var r=0;r<e.length;r++)e[r].then(t,n)})},"undefined"!=typeof window){void 0===window.Promise?window.Promise=u:window.Promise.prototype.finally||(window.Promise.prototype.finally=u.prototype.finally);var u=window.Promise}else if("undefined"!=typeof global){void 0===global.Promise?global.Promise=u:global.Promise.prototype.finally||(global.Promise.prototype.finally=u.prototype.finally);u=global.Promise}var s=function(e){if("[object Object]"!==Object.prototype.toString.call(e))return"";var t=[];for(var n in e)r(n,e[n]);return t.join("&");function r(e,n){if(Array.isArray(n))for(var o=0;o<n.length;o++)r(e+"["+o+"]",n[o]);else if("[object Object]"===Object.prototype.toString.call(n))for(var o in n)r(e+"["+o+"]",n[o]);else t.push(encodeURIComponent(e)+(null!=n&&""!==n?"="+encodeURIComponent(n):""))}},f=Object.assign||function(e,t){Object.keys(t).forEach(function(n){e[n]=t[n]})},c=function(e,t){if(/:([^\/\.-]+)(\.{3})?:/.test(e))throw new SyntaxError("Template parameter names *must* be separated");if(null==t)return e;var n=e.indexOf("?"),r=e.indexOf("#"),o=r<0?e.length:r,i=n<0?o:n,l=e.slice(0,i),a={};f(a,t);var u=l.replace(/:([^\/\.-]+)(\.{3})?/g,function(e,n,r){return delete a[n],null==t[n]?e:r?t[n]:encodeURIComponent(String(t[n]))}),c=u.indexOf("?"),d=u.indexOf("#"),h=d<0?u.length:d,p=c<0?h:c,v=u.slice(0,p);n>=0&&(v+="?"+e.slice(n,o)),c>=0&&(v+=(n<0?"?":"&")+u.slice(c,h));var m=s(a);return m&&(v+=(n<0&&c<0?"?":"&")+m),r>=0&&(v+=e.slice(r)),d>=0&&(v+=(r<0?"":"&")+u.slice(d)),v},d=function(e,t){var n,r=0;function o(e){return function(r,o){"string"!=typeof r?(o=r,r=r.url):null==o&&(o={});var i=new t(function(t,n){e(c(r,o.params),o,function(e){if("function"==typeof o.type)if(Array.isArray(e))for(var n=0;n<e.length;n++)e[n]=new o.type(e[n]);else e=new o.type(e);t(e)},n)});if(!0===o.background)return i;var l=0;function a(){0==--l&&"function"==typeof n&&n()}return function e(t){var n=t.then;return t.then=function(){l++;var r=n.apply(t,arguments);return r.then(a,function(e){if(a(),0===l)throw e}),e(r)},t}(i)}}function i(e,t){for(var n in e.headers)if({}.hasOwnProperty.call(e.headers,n)&&t.test(n))return!0;return!1}return{request:o(function(t,n,r,o){var l=null!=n.method?n.method.toUpperCase():"GET",a=n.body,u=!(null!=n.serialize&&n.serialize!==JSON.serialize||a instanceof e.FormData),s=new e.XMLHttpRequest,f=!1,c=s.abort;for(var d in s.abort=function(){f=!0,c.call(s)},s.open(l,t,!1!==n.async,"string"==typeof n.user?n.user:void 0,"string"==typeof n.password?n.password:void 0),u&&!i(n,/^content-type0$/i)&&s.setRequestHeader("Content-Type","application/json; charset=utf-8"),"function"==typeof n.deserialize||i(n,/^accept$/i)||s.setRequestHeader("Accept","application/json, text/*"),n.withCredentials&&(s.withCredentials=n.withCredentials),n.timeout&&(s.timeout=n.timeout),s.responseType=n.responseType||("function"==typeof n.extract?"":"json"),n.headers)({}).hasOwnProperty.call(n.headers,d)&&s.setRequestHeader(d,n.headers[d]);"function"==typeof n.config&&(s=n.config(s,n)||s),s.onreadystatechange=function(){if(!f&&4===s.readyState)try{var e,i=s.status>=200&&s.status<300||304===s.status||/^file:\/\//i.test(t),l=s.response;if(null==l)try{l=s.responseText,"function"!=typeof n.extract&&"json"===s.responseType&&(l=JSON.parse(l))}catch(e){l=null}if("function"==typeof n.extract?(l=n.extract(s,n),i=!0):"function"==typeof n.deserialize&&(l=n.deserialize(l)),i)r(l);else{try{e=s.responseText}catch(t){e=l}var a=new Error(e);a.code=s.status,a.response=l,o(a)}}catch(e){o(e)}},null==a?s.send():"function"==typeof n.serialize?s.send(n.serialize(a)):a instanceof e.FormData?s.send(a):s.send(JSON.stringify(a))}),jsonp:o(function(t,n,o,i){var l=n.callbackName||"_mithril_"+Math.round(1e16*Math.random())+"_"+r++,a=e.document.createElement("script");e[l]=function(t){delete e[l],a.parentNode.removeChild(a),o(t)},a.onerror=function(){delete e[l],a.parentNode.removeChild(a),i(new Error("JSONP request failed"))},a.src=t+(t.indexOf("?")<0?"?":"&")+encodeURIComponent(n.callbackKey||"callback")+"="+encodeURIComponent(l),e.document.documentElement.appendChild(a)}),setCompletionCallback:function(e){n=e}}}(window,u),h=function(t){var n,r=t.document,o={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"};function i(e){return e.attrs&&e.attrs.xmlns||o[e.tag]}function l(e,t){if(e.state!==t)throw new Error("`vnode.state` must not be modified")}function a(e){var t=e.state;try{return this.apply(t,arguments)}finally{l(e,t)}}function u(){try{return r.activeElement}catch(e){return null}}function s(e,t,n,r,o,i,l){for(var a=n;a<r;a++){var u=t[a];null!=u&&f(e,u,o,l,i)}}function f(t,n,o,l,u){var c=n.tag;if("string"==typeof c)switch(n.state={},null!=n.attrs&&$(n.attrs,n,o),c){case"#":!function(e,t,n){t.dom=r.createTextNode(t.children),g(e,t.dom,n)}(t,n,u);break;case"<":d(t,n,l,u);break;case"[":!function(e,t,n,o,i){var l=r.createDocumentFragment();if(null!=t.children){var a=t.children;s(l,a,0,a.length,n,null,o)}t.dom=l.firstChild,t.domSize=l.childNodes.length,g(e,l,i)}(t,n,o,l,u);break;default:!function(t,n,o,l,a){var u=n.tag,f=n.attrs,c=f&&f.is,d=(l=i(n)||l)?c?r.createElementNS(l,u,{is:c}):r.createElementNS(l,u):c?r.createElement(u,{is:c}):r.createElement(u);n.dom=d,null!=f&&function(e,t,n){for(var r in t)k(e,r,null,t[r],n)}(n,f,l);if(g(t,d,a),null!=f&&null!=f.contenteditable)w(n);else if(null!=n.text&&(""!==n.text?d.textContent=n.text:n.children=[e("#",void 0,void 0,n.text,void 0,void 0)]),null!=n.children){var h=n.children;s(d,h,0,h.length,o,null,l),"select"===n.tag&&null!=f&&function(e,t){if("value"in t)if(null===t.value)-1!==e.dom.selectedIndex&&(e.dom.value=null);else{var n=""+t.value;e.dom.value===n&&-1!==e.dom.selectedIndex||(e.dom.value=n)}"selectedIndex"in t&&k(e,"selectedIndex",null,t.selectedIndex,void 0)}(n,f)}}(t,n,o,l,u)}else!function(t,n,r,o,i){(function(t,n){var r;if("function"==typeof t.tag.view){if(t.state=Object.create(t.tag),null!=(r=t.state.view).$$reentrantLock$$)return;r.$$reentrantLock$$=!0}else{if(t.state=void 0,null!=(r=t.tag).$$reentrantLock$$)return;r.$$reentrantLock$$=!0,t.state=null!=t.tag.prototype&&"function"==typeof t.tag.prototype.view?new t.tag(t):t.tag(t)}if($(t.state,t,n),null!=t.attrs&&$(t.attrs,t,n),t.instance=e.normalize(a.call(t.state.view,t)),t.instance===t)throw Error("A view cannot return the vnode it received as argument");r.$$reentrantLock$$=null})(n,r),null!=n.instance?(f(t,n.instance,r,o,i),n.dom=n.instance.dom,n.domSize=null!=n.dom?n.instance.domSize:0):n.domSize=0}(t,n,o,l,u)}var c={caption:"table",thead:"table",tbody:"table",tfoot:"table",tr:"tbody",th:"tr",td:"tr",colgroup:"table",col:"colgroup"};function d(e,t,n,o){var i=t.children.match(/^\s*?<(\w+)/im)||[],l=r.createElement(c[i[1]]||"div");"http://www.w3.org/2000/svg"===n?(l.innerHTML='<svg xmlns="http://www.w3.org/2000/svg">'+t.children+"</svg>",l=l.firstChild):l.innerHTML=t.children,t.dom=l.firstChild,t.domSize=l.childNodes.length;for(var a,u=r.createDocumentFragment();a=l.firstChild;)u.appendChild(a);g(e,u,o)}function h(e,t,n,r,o,i){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)s(e,n,0,n.length,r,o,i);else if(null==n||0===n.length)b(t,0,t.length);else{for(var l=0,a=0,u=null,c=null;a<t.length;a++)if(null!=t[a]){u=null!=t[a].key;break}for(;l<n.length;l++)if(null!=n[l]){c=null!=n[l].key;break}if(null===c&&null==u)return;if(u!==c)b(t,a,t.length),s(e,n,l,n.length,r,o,i);else if(c){for(var d,h,w,k,S,C=t.length-1,E=n.length-1;C>=a&&E>=l;)if(w=t[C],k=n[E],null==w)C--;else if(null==k)E--;else{if(w.key!==k.key)break;w!==k&&p(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,E--}for(;C>=a&&E>=l;)if(d=t[a],h=n[l],null==d)a++;else if(null==h)l++;else{if(d.key!==h.key)break;a++,l++,d!==h&&p(e,d,h,r,y(t,a,o),i)}for(;C>=a&&E>=l;){if(null==d)a++;else if(null==h)l++;else if(null==w)C--;else if(null==k)E--;else{if(l===E)break;if(d.key!==k.key||w.key!==h.key)break;S=y(t,a,o),g(e,m(w),S),w!==h&&p(e,w,h,r,S,i),++l<=--E&&g(e,m(d),o),d!==k&&p(e,d,k,r,o,i),null!=k.dom&&(o=k.dom),a++,C--}w=t[C],k=n[E],d=t[a],h=n[l]}for(;C>=a&&E>=l;){if(null==w)C--;else if(null==k)E--;else{if(w.key!==k.key)break;w!==k&&p(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,E--}w=t[C],k=n[E]}if(l>E)b(t,a,C+1);else if(a>C)s(e,n,l,E+1,r,o,i);else{var j,z,A=o,O=E-l+1,N=new Array(O),P=0,$=0,I=2147483647,R=0;for($=0;$<O;$++)N[$]=-1;for($=E;$>=l;$--)if(null==j&&(j=v(t,a,C+1)),null!=(k=n[$])){var T=j[k.key];null!=T&&(I=T<I?T:-1,N[$-l]=T,w=t[T],t[T]=null,w!==k&&p(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),R++)}if(o=A,R!==C-a+1&&b(t,a,C+1),0===R)s(e,n,l,E+1,r,o,i);else if(-1===I)for(P=(z=function(e){var t,n,r=e.slice(),o=[];o.push(0);for(var i=0,l=e.length;i<l;++i)if(-1!==e[i]){var a=o[o.length-1];if(e[a]<e[i])r[i]=a,o.push(i);else{for(t=0,n=o.length-1;t<n;){var u=(t+n)/2|0;e[o[u]]<e[i]?t=u+1:n=u}e[i]<e[o[t]]&&(t>0&&(r[i]=o[t-1]),o[t]=i)}}t=o.length,n=o[t-1];for(;t-- >0;)o[t]=n,n=r[n];return o}(N)).length-1,$=E;$>=l;$--)h=n[$],-1===N[$-l]?f(e,h,r,i,o):z[P]===$-l?P--:g(e,m(h),o),null!=h.dom&&(o=n[$].dom);else for($=E;$>=l;$--)h=n[$],-1===N[$-l]&&f(e,h,r,i,o),null!=h.dom&&(o=n[$].dom)}}else{var L=t.length<n.length?t.length:n.length;for(l=l<a?l:a;l<L;l++)(d=t[l])===(h=n[l])||null==d&&null==h||(null==d?f(e,h,r,i,y(t,l+1,o)):null==h?x(d):p(e,d,h,r,y(t,l+1,o),i));t.length>L&&b(t,l,t.length),n.length>L&&s(e,n,l,n.length,r,o,i)}}}function p(t,n,r,o,l,u){var s=n.tag;if(s===r.tag){if(r.state=n.state,r.events=n.events,function(e,t){do{if(null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate){var n=a.call(e.attrs.onbeforeupdate,e,t);if(void 0!==n&&!n)break}if("string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate){var n=a.call(e.state.onbeforeupdate,e,t);if(void 0!==n&&!n)break}return!1}while(0);return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,!0}(r,n))return;if("string"==typeof s)switch(null!=r.attrs&&I(r.attrs,r,o),s){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children);t.dom=e.dom}(n,r);break;case"<":!function(e,t,n,r,o){t.children!==n.children?(m(t),d(e,n,r,o)):(n.dom=t.dom,n.domSize=t.domSize)}(t,n,r,u,l);break;case"[":!function(e,t,n,r,o,i){h(e,t.children,n.children,r,o,i);var l=0,a=n.children;if(n.dom=null,null!=a){for(var u=0;u<a.length;u++){var s=a[u];null!=s&&null!=s.dom&&(null==n.dom&&(n.dom=s.dom),l+=s.domSize||1)}1!==l&&(n.domSize=l)}}(t,n,r,o,l,u);break;default:!function(t,n,r,o){var l=n.dom=t.dom;o=i(n)||o,"textarea"===n.tag&&(null==n.attrs&&(n.attrs={}),null!=n.text&&(n.attrs.value=n.text,n.text=void 0));(function(e,t,n,r){if(null!=n)for(var o in n)k(e,o,t&&t[o],n[o],r);var i;if(null!=t)for(var o in t)null==(i=t[o])||null!=n&&null!=n[o]||S(e,o,i,r)})(n,t.attrs,n.attrs,o),null!=n.attrs&&null!=n.attrs.contenteditable?w(n):null!=t.text&&null!=n.text&&""!==n.text?t.text.toString()!==n.text.toString()&&(t.dom.firstChild.nodeValue=n.text):(null!=t.text&&(t.children=[e("#",void 0,void 0,t.text,void 0,t.dom.firstChild)]),null!=n.text&&(n.children=[e("#",void 0,void 0,n.text,void 0,void 0)]),h(l,t.children,n.children,r,null,o))}(n,r,o,u)}else!function(t,n,r,o,i,l){if(r.instance=e.normalize(a.call(r.state.view,r)),r.instance===r)throw Error("A view cannot return the vnode it received as argument");I(r.state,r,o),null!=r.attrs&&I(r.attrs,r,o);null!=r.instance?(null==n.instance?f(t,r.instance,o,l,i):p(t,n.instance,r.instance,o,i,l),r.dom=r.instance.dom,r.domSize=r.instance.domSize):null!=n.instance?(x(n.instance),r.dom=void 0,r.domSize=0):(r.dom=n.dom,r.domSize=n.domSize)}(t,n,r,o,l,u)}else x(n),f(t,r,o,u,l)}function v(e,t,n){for(var r=Object.create(null);t<n;t++){var o=e[t];if(null!=o){var i=o.key;null!=i&&(r[i]=t)}}return r}function m(e){var t=e.domSize;if(null!=t||null==e.dom){var n=r.createDocumentFragment();if(t>0){for(var o=e.dom;--t;)n.appendChild(o.nextSibling);n.insertBefore(o,n.firstChild)}return n}return e.dom}function y(e,t,n){for(;t<e.length;t++)if(null!=e[t]&&null!=e[t].dom)return e[t].dom;return n}function g(e,t,n){null!=n?e.insertBefore(t,n):e.appendChild(t)}function w(e){var t=e.children;if(null!=t&&1===t.length&&"<"===t[0].tag){var n=t[0].children;e.dom.innerHTML!==n&&(e.dom.innerHTML=n)}else if(null!=e.text||null!=t&&0!==t.length)throw new Error("Child node of a contenteditable must be trusted")}function b(e,t,n){for(var r=t;r<n;r++){var o=e[r];null!=o&&x(o)}}function x(e){var t,n=1,r=0,o=e.state;"string"!=typeof e.tag&&"function"==typeof e.state.onbeforeremove&&(null!=(t=a.call(e.state.onbeforeremove,e))&&"function"==typeof t.then&&(n++,t.then(i,i)));e.attrs&&"function"==typeof e.attrs.onbeforeremove&&(null!=(t=a.call(e.attrs.onbeforeremove,e))&&"function"==typeof t.then&&(n++,t.then(i,i)));function i(){if(++r===n&&(l(e,o),function e(t){"string"!=typeof t.tag&&"function"==typeof t.state.onremove&&a.call(t.state.onremove,t);t.attrs&&"function"==typeof t.attrs.onremove&&a.call(t.attrs.onremove,t);if("string"!=typeof t.tag)null!=t.instance&&e(t.instance);else{var n=t.children;if(Array.isArray(n))for(var r=0;r<n.length;r++){var o=n[r];null!=o&&e(o)}}}(e),e.dom)){for(var t=e.dom.parentNode,i=e.domSize||1;--i;)t.removeChild(e.dom.nextSibling);t.removeChild(e.dom)}}i()}function k(e,t,n,o,i){if("key"!==t&&"is"!==t&&null!=o&&!C(t)&&(n!==o||function(e,t){return"value"===t||"checked"===t||"selectedIndex"===t||"selected"===t&&e.dom===u()||"option"===e.tag&&e.dom.parentNode===r.activeElement}(e,t)||"object"==typeof o)){if("o"===t[0]&&"n"===t[1])return P(e,t,o);if("xlink:"===t.slice(0,6))e.dom.setAttributeNS("http://www.w3.org/1999/xlink",t.slice(6),o);else if("style"===t)O(e.dom,n,o);else if(E(e,t,i)){if("value"===t){if(("input"===e.tag||"textarea"===e.tag)&&e.dom.value===""+o&&e.dom===u())return;if("select"===e.tag&&null!==n&&e.dom.value===""+o)return;if("option"===e.tag&&null!==n&&e.dom.value===""+o)return}"input"===e.tag&&"type"===t?e.dom.setAttribute(t,o):e.dom[t]=o}else"boolean"==typeof o?o?e.dom.setAttribute(t,""):e.dom.removeAttribute(t):e.dom.setAttribute("className"===t?"class":t,o)}}function S(e,t,n,r){if("key"!==t&&"is"!==t&&null!=n&&!C(t))if("o"!==t[0]||"n"!==t[1]||C(t))if("style"===t)O(e.dom,n,null);else if(!E(e,t,r)||"className"===t||"value"===t&&("option"===e.tag||"select"===e.tag&&-1===e.dom.selectedIndex&&e.dom===u())||"input"===e.tag&&"type"===t){var o=t.indexOf(":");-1!==o&&(t=t.slice(o+1)),!1!==n&&e.dom.removeAttribute("className"===t?"class":t)}else e.dom[t]=null;else P(e,t,void 0)}function C(e){return"oninit"===e||"oncreate"===e||"onupdate"===e||"onremove"===e||"onbeforeremove"===e||"onbeforeupdate"===e}function E(e,t,n){return void 0===n&&(e.tag.indexOf("-")>-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}var j=/[A-Z]/g;function z(e){return"-"+e.toLowerCase()}function A(e){return"-"===e[0]&&"-"===e[1]?e:"cssFloat"===e?"float":e.replace(j,z)}function O(e,t,n){if(t===n);else if(null==n)e.style.cssText="";else if("object"!=typeof n)e.style.cssText=n;else if(null==t||"object"!=typeof t)for(var r in e.style.cssText="",n){null!=(o=n[r])&&e.style.setProperty(A(r),String(o))}else{for(var r in n){var o;null!=(o=n[r])&&(o=String(o))!==String(t[r])&&e.style.setProperty(A(r),o)}for(var r in t)null!=t[r]&&null==n[r]&&e.style.removeProperty(A(r))}}function N(){}function P(e,t,n){if(null!=e.events){if(e.events[t]===n)return;null==n||"function"!=typeof n&&"object"!=typeof n?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}else null==n||"function"!=typeof n&&"object"!=typeof n||(e.events=new N,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}function $(e,t,n){"function"==typeof e.oninit&&a.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(a.bind(e.oncreate,t))}function I(e,t,n){"function"==typeof e.onupdate&&n.push(a.bind(e.onupdate,t))}return N.prototype=Object.create(null),N.prototype.handleEvent=function(e){var t,r=this["on"+e.type];"function"==typeof r?t=r.call(e.currentTarget,e):"function"==typeof r.handleEvent&&r.handleEvent(e),!1===e.redraw?e.redraw=void 0:"function"==typeof n&&n(),!1===t&&(e.preventDefault(),e.stopPropagation())},{render:function(t,n){if(!t)throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var r=[],o=u(),i=t.namespaceURI;null==t.vnodes&&(t.textContent=""),n=e.normalizeChildren(Array.isArray(n)?n:[n]),h(t,t.vnodes,n,r,null,"http://www.w3.org/1999/xhtml"===i?void 0:i),t.vnodes=n,null!=o&&u()!==o&&"function"==typeof o.focus&&o.focus();for(var l=0;l<r.length;l++)r[l]()},setRedraw:function(e){return n=e}}};var p=function(e,t){var n=h(e),r=[],o=!1;function i(e){var t=r.indexOf(e);t>-1&&r.splice(t,2)}function l(){if(o)throw new Error("Nested m.redraw.sync() call");o=!0;for(var e=1;e<r.length;e+=2)try{r[e]()}catch(e){"undefined"!=typeof console&&console.error(e)}o=!1}var a=(t||function(e){var t=null;return function(){null===t&&(t=requestAnimationFrame(function(){t=null,e()}))}})(l);return a.sync=l,n.setRedraw(a),{subscribe:function(e,t){i(e),r.push(e,t)},unsubscribe:i,redraw:a,render:n.render}}(window);d.setCompletionCallback(p.redraw);var v;a.mount=(v=p,function(t,n){if(null===n)return v.render(t,[]),void v.unsubscribe(t);if(null==n.view&&"function"!=typeof n)throw new Error("m.mount(element, component) expects a component, not a vnode");var r=function(){v.render(t,e(n))};v.subscribe(t,r),r()});var m=u,y=function(e,t){if(null==t&&(t={}),""===e||null==e)return{};"?"===e.charAt(0)&&(e=e.slice(1));for(var n=e.split("&"),r={},o=0;o<n.length;o++){var i=n[o].split("="),l=decodeURIComponent(i[0]),a=2===i.length?decodeURIComponent(i[1]):"";"true"===a?a=!0:"false"===a&&(a=!1);var u=l.split(/\]\[?|\[/),s=t;l.indexOf("[")>-1&&u.pop();for(var f=0;f<u.length;f++){var c=u[f],d=u[f+1],h=""==d||!isNaN(parseInt(d,10)),p=f===u.length-1;if(""===c)null==r[l=u.slice(0,f).join()]&&(r[l]=Array.isArray(s)?s.length:0),c=r[l]++;p?s[c]=a:null==s[c]&&(s[c]=h?[]:{}),s=s[c]}}return t},g=function(e){var t=e.indexOf("?"),n=e.indexOf("#"),r=n<0?e.length:n,o=t<0?r:t,i=e.slice(0,o).replace(/\/{2,}/g,"/"),l={};return i?("/"!==i[0]&&(i="/"+i),i.length>1&&"/"===i[i.length-1]&&(i=i.slice(0,-1))):i="/",t>=0&&y(e.slice(t+1,r),l),n>=0&&y(e.slice(n+1),l),{path:i,params:l}};a.route=function(t,n){var r,o,i,l,a,u=function(e){var t,n="function"==typeof e.history.pushState,r="function"==typeof setImmediate?setImmediate:setTimeout;function o(t){var n=e.location[t].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);return"pathname"===t&&"/"!==n[0]&&(n="/"+n),n}var i={prefix:"#!",getPath:function(){return"#"===i.prefix.charAt(0)?o("hash").slice(i.prefix.length):"?"===i.prefix.charAt(0)?o("search").slice(i.prefix.length)+o("hash"):o("pathname").slice(i.prefix.length)+o("search")+o("hash")},setPath:function(t,r,o){if(t=c(t,r),n){var l=o?o.state:null,a=o?o.title:null;e.onpopstate(),o&&o.replace?e.history.replaceState(l,a,i.prefix+t):e.history.pushState(l,a,i.prefix+t)}else e.location.href=i.prefix+t},defineRoutes:function(o,l,a,u){var s=Object.keys(o).map(function(e){if("/"!==e.charAt(0))throw new SyntaxError("Routes must start with a `/`");if(/:([^\/\.-]+)(\.{3})?:/.test(e))throw new SyntaxError("Route parameter names must be separated with either `/`, `.`, or `-`");return{route:e,component:o[e],check:(t=e,n=g(t),r=Object.keys(n.params),i=[],l=new RegExp("^"+n.path.replace(/:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g,function(e,t,n){return null==t?"\\"+e:(i.push({k:t,r:"..."===n}),"..."===n?"(.*)":"."===n?"([^/]+)\\.":"([^/]+)"+(n||""))})+"$"),function(e){for(var t=0;t<r.length;t++)if(n.params[r[t]]!==e.params[r[t]])return!1;if(!i.length)return l.test(e.path);var o=l.exec(e.path);if(null==o)return!1;for(t=0;t<i.length;t++)e.params[i[t].k]=i[t].r?o[t+1]:decodeURIComponent(o[t+1]);return!0})};var t,n,r,i,l});if(null!=u){var c=g(u);if(!s.some(function(e){return e.check(c)}))throw new ReferenceError("Default route doesn't match any known routes")}function d(){var t=i.getPath(),n=g(t);f(n.params,e.history.state);for(var r=0;r<s.length;r++)if(s[r].check(n))return void l(s[r].component,n.params,t,s[r].route);a(t,n.params)}n?e.onpopstate=function(){t||(t=r(function(){t=null,d()}))}:"#"===i.prefix.charAt(0)&&(e.onhashchange=d),d()}};return i}(t),s=function(t,s,f){if(null==t)throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined");function c(){null!=r&&n.render(t,r(e(o,i.key,i)))}var d=function(){c(),d=n.redraw};n.subscribe(t,c);var h=function(e){if(e===s)throw new Error("Could not resolve default route "+s);u.setPath(s,null,{replace:!0})};u.defineRoutes(f,function(e,t,n,u){var s=a=function(e,u){s===a&&(o=null==u||"function"!=typeof u.view&&"function"!=typeof u?"div":u,i=t,l=n,a=null,r=(e.render||function(e){return e}).bind(e),d())};e.view||"function"==typeof e?s({},e):e.onmatch?m.resolve(e.onmatch(t,n,u)).then(function(t){s(e,t)},function(){h(n)}):s(e,"div")},h,s)};s.set=function(e,t,n){null!=a&&((n=n||{}).replace=!0),a=null,u.setPath(e,t,n)},s.get=function(){return l},s.prefix=function(e){u.prefix=e};var d=function(e,t){t.dom.setAttribute("href",u.prefix+t.attrs.href),t.dom.onclick=function(t){if(!(t.ctrlKey||t.metaKey||t.shiftKey||2===t.which)){t.preventDefault(),t.redraw=!1;var n=this.getAttribute("href");0===n.indexOf(u.prefix)&&(n=n.slice(u.prefix.length)),s.set(n,void 0,e)}}};return s.link=function(e){return null==e.tag?d.bind(d,e):d({},e)},s.param=function(e){return void 0!==i&&void 0!==e?i[e]:i},s}(window,p);var w=h(window);a.render=w.render,a.redraw=p.redraw,a.request=d.request,a.jsonp=d.jsonp,a.parseQueryString=y,a.buildQueryString=s,a.parsePathname=g,a.buildPathname=c,a.version="2.0.0-rc.5",a.vnode=e,a.PromisePolyfill=u,"undefined"!=typeof module?module.exports=a:window.m=a}(); |
@@ -1,24 +0,34 @@ | ||
# Change Log for ospec | ||
# Change log for ospec | ||
- [Upcoming](#upcoming) | ||
- [3.1.0](#310) | ||
- [3.0.1](#301) | ||
- [3.0.0](#300) | ||
- [2.1.0](#210) | ||
- [2.0.0](#200) | ||
- [1.4.1](#141) | ||
- [1.4.0](#140) | ||
- [1.3 and earlier](#13-and-earlier) | ||
## Upcoming... | ||
_2018-xx-yy_ | ||
### Upcoming... | ||
### 3.1.0 | ||
- ospec: Test results now include `.message` and `.context` regardless of whether the test passed or failed. (#2227 @robertakarobin) | ||
<!-- Add new lines here. Version number will be decided later --> | ||
- Add `spy.calls` array property to get the `this` and `arguments` values for any arbitrary call. | ||
- Add `spy.calls` array property to get the `this` and `arguments` values for any arbitrary call. (#2221 @isiahmeadows) | ||
- Added `.throws` and `.notThrows` assertions to ospec. (#2255 @robertakarobin) | ||
- Update `glob` dependency. | ||
## 3.0.1 | ||
_2018-06-30_ | ||
### 3.0.1 | ||
### Bug fix | ||
#### Bug fix | ||
- Move `glob` from `devDependencies` to `dependencies`, fix the test runner ([#2186](https://github.com/MithrilJS/mithril.js/pull/2186) [@porsager](https://github.com/porsager) | ||
## 3.0.0 | ||
_2018-06-20_ | ||
### Breaking | ||
### 3.0.0 | ||
#### Breaking | ||
- Better input checking to prevent misuses of the library. Misues of the library will now throw errors, rather than report failures. This may uncover bugs in your test suites. Since it is potentially a disruptive update this change triggers a semver major bump. ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
- Change the reserved character for hooks and test suite meta-information from `"__"` to `"\x01"`. Tests whose name start with `"\0x01"` will be rejected ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
### Features | ||
#### Features | ||
- Give async timeout a stack trace that points to the problematic test ([#2154](https://github.com/MithrilJS/mithril.js/pull/2154) [@gilbert](github.com/gilbert), [#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
@@ -29,3 +39,3 @@ - deprecate the `timeout` parameter in async tests in favour of `o.timeout()` for setting the timeout delay. The `timeout` parameter still works for v3, and will be removed in v4 ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
### Bug fixes | ||
#### Bug fixes | ||
- Detect duplicate calls to `done()` properly [#2162](https://github.com/MithrilJS/mithril.js/issues/2162) ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
@@ -38,14 +48,14 @@ - Don't try to report internal errors as assertion failures, throw them instead ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
## 2.1.0 | ||
_2018-05-25_ | ||
### Features | ||
### 2.1.0 | ||
#### Features | ||
- Pinpoint the `o.only()` call site ([#2157](https://github.com/MithrilJS/mithril.js/pull/2157)) | ||
- Improved wording, spacing and color-coding of report messages and errors ([#2147](https://github.com/MithrilJS/mithril.js/pull/2147), [@maranomynet](https://github.com/maranomynet)) | ||
### Bug fixes | ||
#### Bug fixes | ||
- Convert the exectuable back to plain ES5 [#2160](https://github.com/MithrilJS/mithril.js/issues/2160) ([#2161](https://github.com/MithrilJS/mithril.js/pull/2161)) | ||
## 2.0.0 | ||
_2018-05-09_ | ||
### 2.0.0 | ||
- Added `--require` feature to the ospec executable ([#2144](https://github.com/MithrilJS/mithril.js/pull/2144), [@gilbert](https://github.com/gilbert)) | ||
@@ -64,4 +74,4 @@ - In Node.js, ospec only uses colors when the output is sent to a terminal ([#2143](https://github.com/MithrilJS/mithril.js/pull/2143)) | ||
## 1.4.1 | ||
_2018-05-03_ | ||
### 1.4.1 | ||
- Identical to v1.4.0, but with UNIX-style line endings so that BASH is happy. | ||
@@ -71,4 +81,4 @@ | ||
## 1.4.0 | ||
_2017-12-01_ | ||
### 1.4.0 | ||
- Added support for async functions and promises in tests ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928), [@StephanHoyer](https://github.com/StephanHoyer)) | ||
@@ -80,5 +90,6 @@ - Error handling for async tests with `done` callbacks supports error as first argument ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928)) | ||
## 1.3 and earlier | ||
### 1.3 and earlier | ||
- Log using util.inspect to show object content instead of "[object Object]" ([#1661](https://github.com/MithrilJS/mithril.js/issues/1661), [@porsager](https://github.com/porsager)) | ||
- Shell command: Ignore hidden directories and files ([#1855](https://github.com/MithrilJS/mithril.js/pull/1855) [@pdfernhout)](https://github.com/pdfernhout)) | ||
- Library: Add the possibility to name new test suites ([#1529](https://github.com/MithrilJS/mithril.js/pull/1529)) |
{ | ||
"name": "ospec", | ||
"version": "3.0.1", | ||
"version": "3.1.0", | ||
"description": "Noiseless testing framework", | ||
"main": "ospec.js", | ||
"module": "ospec.mjs", | ||
"directories": { | ||
@@ -16,9 +15,6 @@ "test": "tests" | ||
}, | ||
"scripts": { | ||
"prepublishOnly": "node esm.js" | ||
}, | ||
"repository": "MithrilJS/mithril.js", | ||
"dependencies": { | ||
"glob": "^7.1.2" | ||
"glob": "^7.1.3" | ||
} | ||
} |
{ | ||
"name": "mithril", | ||
"version": "2.0.0-rc.4", | ||
"version": "2.0.0-rc.5", | ||
"description": "A framework for building brilliant applications", | ||
"author": "Leo Horie", | ||
"license": "MIT", | ||
"main": "mithril.js", | ||
"module": "mithril.mjs", | ||
"unpkg": "mithril.min.js", | ||
"repository": "MithrilJS/mithril.js", | ||
"scripts": { | ||
"dev": "node bundler/cli browser.js -output mithril.js -watch", | ||
"build": "npm run build-browser & npm run build-min && npm run build-esm", | ||
"build": "npm run build-browser & npm run build-min", | ||
"build-browser": "node bundler/cli browser.js -output mithril.js", | ||
"build-min": "node bundler/cli browser.js -output mithril.min.js -minify", | ||
"build-esm": "node esm.js", | ||
"precommit": "lint-staged", | ||
@@ -27,3 +25,3 @@ "lintdocs": "node docs/lint", | ||
"preversion": "npm run test", | ||
"version": "npm run build && git add mithril.js mithril.min.js mithril.mjs mithril.min.mjs", | ||
"version": "npm run build && git add mithril.js mithril.min.js", | ||
"postversion": "git push --follow-tags" | ||
@@ -36,11 +34,10 @@ }, | ||
"dedent": "^0.7.0", | ||
"eslint": "^5.9.0", | ||
"gh-pages": "^0.12.0", | ||
"glob": "^7.1.2", | ||
"eslint": "^5.13.0", | ||
"gh-pages": "^2.0.1", | ||
"istanbul": "^0.4.5", | ||
"lint-staged": "^4.0.4", | ||
"lint-staged": "^8.1.3", | ||
"locater": "^1.3.0", | ||
"marked": "^0.3.19", | ||
"marked": "^0.6.2", | ||
"pinpoint": "^1.1.0", | ||
"terser": "^3.10.11" | ||
"terser": "^3.16.1" | ||
}, | ||
@@ -47,0 +44,0 @@ "bin": { |
"use strict" | ||
module.exports = function(string) { | ||
// The extra `data` parameter is for if you want to append to an existing | ||
// parameters object. | ||
module.exports = function(string, data) { | ||
if (data == null) data = {} | ||
if (string === "" || string == null) return {} | ||
if (string.charAt(0) === "?") string = string.slice(1) | ||
var entries = string.split("&"), data = {}, counters = {} | ||
var entries = string.split("&"), counters = {} | ||
for (var i = 0; i < entries.length; i++) { | ||
@@ -25,8 +28,9 @@ var entry = entries[i].split("=") | ||
var key = levels.slice(0, j).join() | ||
if (counters[key] == null) counters[key] = 0 | ||
if (counters[key] == null) { | ||
counters[key] = Array.isArray(cursor) ? cursor.length : 0 | ||
} | ||
level = counters[key]++ | ||
} | ||
if (cursor[level] == null) { | ||
cursor[level] = isValue ? value : isNumber ? [] : {} | ||
} | ||
if (isValue) cursor[level] = value | ||
else if (cursor[level] == null) cursor[level] = isNumber ? [] : {} | ||
cursor = cursor[level] | ||
@@ -33,0 +37,0 @@ } |
@@ -96,2 +96,18 @@ "use strict" | ||
}) | ||
o("prefers later values", function() { | ||
var data = parseQueryString("a=1&b=2&a=3") | ||
o(data).deepEquals({a: "3", b: "2"}) | ||
}) | ||
o("continues to append to arrays between calls", function() { | ||
var data = {} | ||
parseQueryString("a[]=1&a[]=2", data) | ||
parseQueryString("a[]=3&a[]=4", data) | ||
o(data).deepEquals({a: ["1", "2", "3", "4"]}) | ||
}) | ||
o("continues to append to objects between calls", function() { | ||
var data = {} | ||
parseQueryString("a[b]=1&a[c]=2", data) | ||
parseQueryString("a[d]=3&a[e]=4", data) | ||
o(data).deepEquals({a: {b: "1", c: "2", d: "3", e: "4"}}) | ||
}) | ||
}) |
@@ -1,2 +0,2 @@ | ||
mithril.js [![NPM Version](https://img.shields.io/npm/v/mithril.svg)](https://www.npmjs.com/package/mithril) [![NPM License](https://img.shields.io/npm/l/mithril.svg)](https://www.npmjs.com/package/mithril) [![NPM Downloads](https://img.shields.io/npm/dm/mithril.svg)](https://www.npmjs.com/package/mithril) | ||
mithril.js [![NPM Version](https://img.shields.io/npm/v/mithril.svg)](https://www.npmjs.com/package/mithril) [![NPM License](https://img.shields.io/npm/l/mithril.svg)](https://www.npmjs.com/package/mithril) [![NPM Downloads](https://img.shields.io/npm/dm/mithril.svg)](https://www.npmjs.com/package/mithril) [![Donate at OpenCollective](https://img.shields.io/opencollective/all/mithriljs.svg?colorB=brightgreen)](https://opencollective.com/mithriljs) | ||
========== | ||
@@ -21,3 +21,3 @@ | ||
A modern client-side Javascript framework for building Single Page Applications. It's small (<!-- size -->8.88 KB<!-- /size --> gzipped), fast and provides routing and XHR utilities out of the box. | ||
A modern client-side Javascript framework for building Single Page Applications. It's small (<!-- size -->9.31 KB<!-- /size --> gzipped), fast and provides routing and XHR utilities out of the box. | ||
@@ -33,5 +33,5 @@ Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess 👍. | ||
```html | ||
<script src="https://unpkg.com/mithril"></script> | ||
<script src="https://unpkg.com/mithril@next/mithril.js"></script> | ||
<!-- or --> | ||
<script src="https://cdn.jsdelivr.net/npm/mithril/mithril.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/mithril@next/mithril.js"></script> | ||
``` | ||
@@ -38,0 +38,0 @@ |
"use strict" | ||
var buildQueryString = require("../querystring/build") | ||
var buildPathname = require("../pathname/build") | ||
@@ -14,3 +14,3 @@ module.exports = function($window, Promise) { | ||
var promise = new Promise(function(resolve, reject) { | ||
factory(url, args, function (data) { | ||
factory(buildPathname(url, args.params), args, function (data) { | ||
if (typeof args.type === "function") { | ||
@@ -58,27 +58,8 @@ if (Array.isArray(data)) { | ||
function interpolate(url, data, assemble) { | ||
if (data == null) return url | ||
url = url.replace(/:([^\/]+)/gi, function (m, key) { | ||
return data[key] != null ? data[key] : m | ||
}) | ||
if (assemble && data != null) { | ||
var querystring = buildQueryString(data) | ||
if (querystring) url += (url.indexOf("?") < 0 ? "?" : "&") + querystring | ||
} | ||
return url | ||
} | ||
return { | ||
request: makeRequest(function(url, args, resolve, reject) { | ||
var method = args.method != null ? args.method.toUpperCase() : "GET" | ||
var useBody = method !== "GET" && method !== "TRACE" && | ||
(typeof args.useBody !== "boolean" || args.useBody) | ||
var body = args.body | ||
var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(body instanceof $window.FormData) | ||
var data = args.data | ||
var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(data instanceof $window.FormData) | ||
if (useBody) { | ||
if (typeof args.serialize === "function") data = args.serialize(data) | ||
else if (!(data instanceof $window.FormData)) data = JSON.stringify(data) | ||
} | ||
var xhr = new $window.XMLHttpRequest(), | ||
@@ -93,5 +74,5 @@ aborted = false, | ||
xhr.open(method, interpolate(url, args.data, !useBody), typeof args.async !== "boolean" || args.async, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) | ||
xhr.open(method, url, args.async !== false, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) | ||
if (assumeJSON && useBody && !hasHeader(args, /^content-type$/i)) { | ||
if (assumeJSON && !hasHeader(args, /^content-type$/i)) { | ||
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") | ||
@@ -104,3 +85,3 @@ } | ||
if (args.timeout) xhr.timeout = args.timeout | ||
if (args.responseType) xhr.responseType = args.responseType | ||
xhr.responseType = args.responseType || (typeof args.extract === "function" ? "" : "json") | ||
@@ -122,3 +103,23 @@ for (var key in args.headers) { | ||
var success = (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || (/^file:\/\//i).test(url) | ||
var response = xhr.responseText | ||
// When the response type isn't "" or "text", | ||
// `xhr.responseText` is the wrong thing to use. | ||
// Browsers do the right thing and throw here, and we | ||
// should honor that and do the right thing by | ||
// preferring `xhr.response` where possible/practical. | ||
var response = xhr.response, message | ||
if (response == null) { | ||
try { | ||
response = xhr.responseText | ||
// Note: this snippet is intentionally *after* | ||
// `xhr.responseText` is accessed, since the | ||
// above will throw in modern browsers (thus | ||
// skipping the rest of this section). It's an | ||
// IE hack to detect and work around the lack of | ||
// native `responseType: "json"` support there. | ||
if (typeof args.extract !== "function" && xhr.responseType === "json") response = JSON.parse(response) | ||
} | ||
catch (e) { response = null } | ||
} | ||
if (typeof args.extract === "function") { | ||
@@ -129,9 +130,8 @@ response = args.extract(xhr, args) | ||
response = args.deserialize(response) | ||
} else { | ||
try {response = response ? JSON.parse(response) : null} | ||
catch (e) {throw new Error("Invalid JSON: " + response)} | ||
} | ||
if (success) resolve(response) | ||
else { | ||
var error = new Error(xhr.responseText) | ||
try { message = xhr.responseText } | ||
catch (e) { message = response } | ||
var error = new Error(message) | ||
error.code = xhr.status | ||
@@ -148,4 +148,6 @@ error.response = response | ||
if (useBody && data != null) xhr.send(data) | ||
else xhr.send() | ||
if (body == null) xhr.send() | ||
else if (typeof args.serialize === "function") xhr.send(args.serialize(body)) | ||
else if (body instanceof $window.FormData) xhr.send(body) | ||
else xhr.send(JSON.stringify(body)) | ||
}), | ||
@@ -156,12 +158,11 @@ jsonp: makeRequest(function(url, args, resolve, reject) { | ||
$window[callbackName] = function(data) { | ||
delete $window[callbackName] | ||
script.parentNode.removeChild(script) | ||
resolve(data) | ||
delete $window[callbackName] | ||
} | ||
script.onerror = function() { | ||
delete $window[callbackName] | ||
script.parentNode.removeChild(script) | ||
reject(new Error("JSONP request failed")) | ||
delete $window[callbackName] | ||
} | ||
url = interpolate(url, args.data, true) | ||
script.src = url + (url.indexOf("?") < 0 ? "?" : "&") + | ||
@@ -168,0 +169,0 @@ encodeURIComponent(args.callbackKey || "callback") + "=" + |
@@ -50,3 +50,3 @@ "use strict" | ||
}) | ||
jsonp({url: "/item", data: {a: "b", c: "d"}}).then(function(data) { | ||
jsonp({url: "/item", params: {a: "b", c: "d"}}).then(function(data) { | ||
delete data["callback"] | ||
@@ -53,0 +53,0 @@ o(data).deepEquals({a: "b", c: "d"}) |
@@ -82,3 +82,3 @@ "use strict" | ||
}) | ||
xhr({method: "GET", url: "/item", data: {x: "y"}}).then(function(data) { | ||
xhr({method: "GET", url: "/item", params: {x: "y"}}).then(function(data) { | ||
o(data).deepEquals({a: "?x=y"}) | ||
@@ -93,3 +93,3 @@ }).then(done) | ||
}) | ||
xhr({method: "POST", url: "/item", data: {x: "y"}}).then(function(data) { | ||
xhr({method: "POST", url: "/item", body: {x: "y"}}).then(function(data) { | ||
o(data).deepEquals({a: {x: "y"}}) | ||
@@ -104,3 +104,3 @@ }).then(done) | ||
}) | ||
xhr({method: "GET", url: "/item", data: {x: ":y"}}).then(function(data) { | ||
xhr({method: "GET", url: "/item", params: {x: ":y"}}).then(function(data) { | ||
o(data).deepEquals({a: "?x=%3Ay"}) | ||
@@ -115,3 +115,3 @@ }).then(done) | ||
}) | ||
xhr({method: "POST", url: "/item", data: {x: ":y"}}).then(function(data) { | ||
xhr({method: "POST", url: "/item", body: {x: ":y"}}).then(function(data) { | ||
o(data).deepEquals({a: {x: ":y"}}) | ||
@@ -123,7 +123,7 @@ }).then(done) | ||
"GET /item/y": function(request) { | ||
return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query})} | ||
return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: request.body})} | ||
} | ||
}) | ||
xhr({method: "GET", url: "/item/:x", data: {x: "y"}}).then(function(data) { | ||
o(data).deepEquals({a: "/item/y", b: "?x=y"}) | ||
xhr({method: "GET", url: "/item/:x", params: {x: "y"}}).then(function(data) { | ||
o(data).deepEquals({a: "/item/y", b: {}, c: null}) | ||
}).then(done) | ||
@@ -134,9 +134,69 @@ }) | ||
"POST /item/y": function(request) { | ||
return {status: 200, responseText: JSON.stringify({a: request.url, b: JSON.parse(request.body)})} | ||
return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: request.body})} | ||
} | ||
}) | ||
xhr({method: "POST", url: "/item/:x", data: {x: "y"}}).then(function(data) { | ||
o(data).deepEquals({a: "/item/y", b: {x: "y"}}) | ||
xhr({method: "POST", url: "/item/:x", params: {x: "y"}}).then(function(data) { | ||
o(data).deepEquals({a: "/item/y", b: {}, c: null}) | ||
}).then(done) | ||
}) | ||
o("works w/ parameterized url + body via GET", function(done) { | ||
mock.$defineRoutes({ | ||
"GET /item/y": function(request) { | ||
return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: JSON.parse(request.body)})} | ||
} | ||
}) | ||
xhr({method: "GET", url: "/item/:x", params: {x: "y"}, body: {a: "b"}}).then(function(data) { | ||
o(data).deepEquals({a: "/item/y", b: {}, c: {a: "b"}}) | ||
}).then(done) | ||
}) | ||
o("works w/ parameterized url + body via POST", function(done) { | ||
mock.$defineRoutes({ | ||
"POST /item/y": function(request) { | ||
return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: JSON.parse(request.body)})} | ||
} | ||
}) | ||
xhr({method: "POST", url: "/item/:x", params: {x: "y"}, body: {a: "b"}}).then(function(data) { | ||
o(data).deepEquals({a: "/item/y", b: {}, c: {a: "b"}}) | ||
}).then(done) | ||
}) | ||
o("works w/ parameterized url + query via GET", function(done) { | ||
mock.$defineRoutes({ | ||
"GET /item/y": function(request) { | ||
return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: request.body})} | ||
} | ||
}) | ||
xhr({method: "GET", url: "/item/:x", params: {x: "y", q: "term"}}).then(function(data) { | ||
o(data).deepEquals({a: "/item/y", b: "?q=term", c: null}) | ||
}).then(done) | ||
}) | ||
o("works w/ parameterized url + query via POST", function(done) { | ||
mock.$defineRoutes({ | ||
"POST /item/y": function(request) { | ||
return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: request.body})} | ||
} | ||
}) | ||
xhr({method: "POST", url: "/item/:x", params: {x: "y", q: "term"}}).then(function(data) { | ||
o(data).deepEquals({a: "/item/y", b: "?q=term", c: null}) | ||
}).then(done) | ||
}) | ||
o("works w/ parameterized url + query + body via GET", function(done) { | ||
mock.$defineRoutes({ | ||
"GET /item/y": function(request) { | ||
return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: JSON.parse(request.body)})} | ||
} | ||
}) | ||
xhr({method: "GET", url: "/item/:x", params: {x: "y", q: "term"}, body: {a: "b"}}).then(function(data) { | ||
o(data).deepEquals({a: "/item/y", b: "?q=term", c: {a: "b"}}) | ||
}).then(done) | ||
}) | ||
o("works w/ parameterized url + query + body via POST", function(done) { | ||
mock.$defineRoutes({ | ||
"POST /item/y": function(request) { | ||
return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: JSON.parse(request.body)})} | ||
} | ||
}) | ||
xhr({method: "POST", url: "/item/:x", params: {x: "y", q: "term"}, body: {a: "b"}}).then(function(data) { | ||
o(data).deepEquals({a: "/item/y", b: "?q=term", c: {a: "b"}}) | ||
}).then(done) | ||
}) | ||
o("works w/ array", function(done) { | ||
@@ -148,3 +208,3 @@ mock.$defineRoutes({ | ||
}) | ||
xhr({method: "POST", url: "/items", data: [{x: "y"}]}).then(function(data) { | ||
xhr({method: "POST", url: "/items", body: [{x: "y"}]}).then(function(data) { | ||
o(data).deepEquals({a: "/items", b: [{x: "y"}]}) | ||
@@ -211,3 +271,3 @@ }).then(done) | ||
}) | ||
xhr({method: "GET", url: "/item", serialize: serialize, data: {id: 1}}).then(function(data) { | ||
xhr({method: "GET", url: "/item", serialize: serialize, params: {id: 1}}).then(function(data) { | ||
o(data.body).equals("?id=1") | ||
@@ -226,3 +286,3 @@ }).then(done) | ||
}) | ||
xhr({method: "POST", url: "/item", serialize: serialize, data: {id: 1}}).then(function(data) { | ||
xhr({method: "POST", url: "/item", serialize: serialize, body: {id: 1}}).then(function(data) { | ||
o(data.body).equals("id=1") | ||
@@ -242,3 +302,3 @@ }).then(done) | ||
xhr({method: "GET", url: "/item", deserialize: deserialize}).then(function(data) { | ||
o(data).equals("{\"test\":123}") | ||
o(data).deepEquals({test: 123}) | ||
}).then(done) | ||
@@ -257,3 +317,3 @@ }) | ||
xhr({method: "POST", url: "/item", deserialize: deserialize}).then(function(data) { | ||
o(data).equals("{\"test\":123}") | ||
o(data).deepEquals({test: 123}) | ||
}).then(done) | ||
@@ -263,3 +323,3 @@ }) | ||
var extract = function() { | ||
return JSON.stringify({test: 123}) | ||
return {test: 123} | ||
} | ||
@@ -273,3 +333,3 @@ | ||
xhr({method: "GET", url: "/item", extract: extract}).then(function(data) { | ||
o(data).equals("{\"test\":123}") | ||
o(data).deepEquals({test: 123}) | ||
}).then(done) | ||
@@ -279,3 +339,3 @@ }) | ||
var extract = function() { | ||
return JSON.stringify({test: 123}) | ||
return {test: 123} | ||
} | ||
@@ -289,3 +349,3 @@ | ||
xhr({method: "POST", url: "/item", extract: extract}).then(function(data) { | ||
o(data).equals("{\"test\":123}") | ||
o(data).deepEquals({test: 123}) | ||
}).then(done) | ||
@@ -503,3 +563,4 @@ }) | ||
o(e instanceof Error).equals(true) | ||
o(e.message).equals(JSON.stringify({error: "error"})) | ||
o(e.message).equals("[object Object]") | ||
o(e.response).deepEquals({error: "error"}) | ||
o(e.code).equals(500) | ||
@@ -527,3 +588,4 @@ }).then(done) | ||
xhr({method: "GET", url: "/item"}).catch(function(e) { | ||
o(e.message).equals("Invalid JSON: error") | ||
o(e.message).equals("null") | ||
o(e.response).equals(null) | ||
}).then(done) | ||
@@ -530,0 +592,0 @@ }) |
"use strict" | ||
var buildQueryString = require("../querystring/build") | ||
var parseQueryString = require("../querystring/parse") | ||
var buildPathname = require("../pathname/build") | ||
var parsePathname = require("../pathname/parse") | ||
var compileTemplate = require("../pathname/compileTemplate") | ||
var assign = require("../pathname/assign") | ||
@@ -17,54 +19,11 @@ module.exports = function($window) { | ||
var asyncId | ||
function debounceAsync(callback) { | ||
return function() { | ||
if (asyncId != null) return | ||
asyncId = callAsync(function() { | ||
asyncId = null | ||
callback() | ||
}) | ||
} | ||
} | ||
function parsePath(path, queryData, hashData) { | ||
var queryIndex = path.indexOf("?") | ||
var hashIndex = path.indexOf("#") | ||
var pathEnd = queryIndex > -1 ? queryIndex : hashIndex > -1 ? hashIndex : path.length | ||
if (queryIndex > -1) { | ||
var queryEnd = hashIndex > -1 ? hashIndex : path.length | ||
var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd)) | ||
for (var key in queryParams) queryData[key] = queryParams[key] | ||
} | ||
if (hashIndex > -1) { | ||
var hashParams = parseQueryString(path.slice(hashIndex + 1)) | ||
for (var key in hashParams) hashData[key] = hashParams[key] | ||
} | ||
return path.slice(0, pathEnd) | ||
} | ||
var router = {prefix: "#!"} | ||
router.getPath = function() { | ||
var type = router.prefix.charAt(0) | ||
switch (type) { | ||
case "#": return normalize("hash").slice(router.prefix.length) | ||
case "?": return normalize("search").slice(router.prefix.length) + normalize("hash") | ||
default: return normalize("pathname").slice(router.prefix.length) + normalize("search") + normalize("hash") | ||
} | ||
if (router.prefix.charAt(0) === "#") return normalize("hash").slice(router.prefix.length) | ||
if (router.prefix.charAt(0) === "?") return normalize("search").slice(router.prefix.length) + normalize("hash") | ||
return normalize("pathname").slice(router.prefix.length) + normalize("search") + normalize("hash") | ||
} | ||
router.setPath = function(path, data, options) { | ||
var queryData = {}, hashData = {} | ||
path = parsePath(path, queryData, hashData) | ||
if (data != null) { | ||
for (var key in data) queryData[key] = data[key] | ||
path = path.replace(/:([^\/]+)/g, function(match, token) { | ||
delete queryData[token] | ||
return data[token] | ||
}) | ||
} | ||
var query = buildQueryString(queryData) | ||
if (query) path += "?" + query | ||
var hash = buildQueryString(hashData) | ||
if (hash) path += "#" + hash | ||
path = buildPathname(path, data) | ||
if (supportsPushState) { | ||
@@ -79,24 +38,33 @@ var state = options ? options.state : null | ||
} | ||
router.defineRoutes = function(routes, resolve, reject) { | ||
router.defineRoutes = function(routes, resolve, reject, defaultRoute) { | ||
var compiled = Object.keys(routes).map(function(route) { | ||
if (route.charAt(0) !== "/") throw new SyntaxError("Routes must start with a `/`") | ||
if ((/:([^\/\.-]+)(\.{3})?:/).test(route)) { | ||
throw new SyntaxError("Route parameter names must be separated with either `/`, `.`, or `-`") | ||
} | ||
return { | ||
route: route, | ||
component: routes[route], | ||
check: compileTemplate(route), | ||
} | ||
}) | ||
if (defaultRoute != null) { | ||
var defaultData = parsePathname(defaultRoute) | ||
if (!compiled.some(function (i) { return i.check(defaultData) })) { | ||
throw new ReferenceError("Default route doesn't match any known routes") | ||
} | ||
} | ||
function resolveRoute() { | ||
var path = router.getPath() | ||
var params = {} | ||
var pathname = parsePath(path, params, params) | ||
var data = parsePathname(path) | ||
var state = $window.history.state | ||
if (state != null) { | ||
for (var k in state) params[k] = state[k] | ||
} | ||
for (var route in routes) { | ||
var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$") | ||
assign(data.params, $window.history.state) | ||
if (matcher.test(pathname)) { | ||
pathname.replace(matcher, function() { | ||
var keys = route.match(/:[^\/]+/g) || [] | ||
var values = [].slice.call(arguments, 1, -2) | ||
for (var i = 0; i < keys.length; i++) { | ||
params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i]) | ||
} | ||
resolve(routes[route], params, path, route) | ||
}) | ||
for (var i = 0; i < compiled.length; i++) { | ||
if (compiled[i].check(data)) { | ||
resolve(compiled[i].component, data.params, path, compiled[i].route) | ||
return | ||
@@ -106,6 +74,14 @@ } | ||
reject(path, params) | ||
reject(path, data.params) | ||
} | ||
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute) | ||
if (supportsPushState) { | ||
$window.onpopstate = function() { | ||
if (asyncId) return | ||
asyncId = callAsync(function() { | ||
asyncId = null | ||
resolveRoute() | ||
}) | ||
} | ||
} | ||
else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute | ||
@@ -112,0 +88,0 @@ resolveRoute() |
@@ -25,10 +25,10 @@ "use strict" | ||
router.defineRoutes({"/a": {data: 1}}, onRouteChange, onFail) | ||
callAsync(function() { | ||
o(onRouteChange.callCount).equals(1) | ||
done() | ||
}) | ||
}) | ||
o("resolves to route", function(done) { | ||
@@ -42,3 +42,3 @@ $window.location.href = prefix + "/test" | ||
o(onFail.callCount).equals(0) | ||
done() | ||
@@ -56,3 +56,3 @@ }) | ||
o(onFail.callCount).equals(0) | ||
done() | ||
@@ -70,3 +70,3 @@ }) | ||
o(onFail.callCount).equals(0) | ||
done() | ||
@@ -88,3 +88,3 @@ }) | ||
o(onFail.callCount).equals(0) | ||
done() | ||
@@ -102,3 +102,3 @@ }) | ||
o(onFail.callCount).equals(0) | ||
done() | ||
@@ -116,3 +116,3 @@ }) | ||
o(onFail.callCount).equals(0) | ||
done() | ||
@@ -130,3 +130,3 @@ }) | ||
o(onFail.callCount).equals(0) | ||
done() | ||
@@ -144,3 +144,3 @@ }) | ||
o(onFail.callCount).equals(0) | ||
done() | ||
@@ -158,3 +158,3 @@ }) | ||
o(onFail.callCount).equals(0) | ||
done() | ||
@@ -172,3 +172,3 @@ }) | ||
o(onFail.callCount).equals(0) | ||
done() | ||
@@ -178,2 +178,15 @@ }) | ||
o("handles route with search and hash + duplicate params", function(done) { | ||
$window.location.href = prefix + "/test?a=b#a=d" | ||
router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) | ||
callAsync(function() { | ||
o(onRouteChange.callCount).equals(1) | ||
o(onRouteChange.args).deepEquals([{data: 1}, {a: "d"}, "/test?a=b#a=d", "/test"]) | ||
o(onFail.callCount).equals(0) | ||
done() | ||
}) | ||
}) | ||
o("calls reject", function(done) { | ||
@@ -186,3 +199,3 @@ $window.location.href = prefix + "/test" | ||
o(onFail.args).deepEquals(["/test", {}]) | ||
done() | ||
@@ -199,3 +212,3 @@ }) | ||
o(onFail.args).deepEquals(["/test?a=b#c=d", {a: "b", c: "d"}]) | ||
done() | ||
@@ -212,3 +225,3 @@ }) | ||
o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) | ||
done() | ||
@@ -225,3 +238,3 @@ }) | ||
o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) | ||
done() | ||
@@ -242,3 +255,3 @@ }) | ||
o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) | ||
done() | ||
@@ -259,3 +272,3 @@ }) | ||
o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) | ||
done() | ||
@@ -275,3 +288,3 @@ }) | ||
o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) | ||
done() | ||
@@ -291,3 +304,3 @@ }) | ||
o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) | ||
done() | ||
@@ -303,3 +316,3 @@ }) | ||
o(onRouteChange.callCount).equals(1) | ||
done() | ||
@@ -306,0 +319,0 @@ }) |
@@ -58,3 +58,3 @@ "use strict" | ||
o(router.getPath()).equals("/other/x/y/z?c=d#e=f") | ||
done() | ||
@@ -71,3 +71,3 @@ }) | ||
o(router.getPath()).equals("/ö?ö=ö#ö=ö") | ||
done() | ||
@@ -84,3 +84,3 @@ }) | ||
o(router.getPath()).equals("/ö?ö=ö#ö=ö") | ||
done() | ||
@@ -102,3 +102,3 @@ }) | ||
o(router.getPath()).equals("/other/x/y/z?c=d#e=f") | ||
done() | ||
@@ -116,3 +116,3 @@ }) | ||
o(router.getPath()).equals("/other/x/y/z?c=d#e=f") | ||
done() | ||
@@ -128,4 +128,4 @@ }) | ||
o(router.getPath()).equals("/other/x/y/z?c=d&e=f") | ||
o(router.getPath()).equals("/other/x/y%2Fz?c=d&e=f") | ||
done() | ||
@@ -143,3 +143,3 @@ }) | ||
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + "/") | ||
done() | ||
@@ -159,3 +159,3 @@ }) | ||
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "") + "test") | ||
done() | ||
@@ -172,3 +172,3 @@ }) | ||
o($window.history.state).deepEquals({a: 1}) | ||
done() | ||
@@ -175,0 +175,0 @@ }) |
# Change log for stream | ||
## 2.0.0 | ||
- renamed HALT to SKIP [#2207](https://github.com/MithrilJS/mithril.js/pull/2207) | ||
- rewrote implementation [#2207](https://github.com/MithrilJS/mithril.js/pull/2207) | ||
- stream: Removed `valueOf` & `toString` methods ([#2150](https://github.com/MithrilJS/mithril.js/pull/2150) | ||
- [Upcoming](#upcoming) | ||
- [v2.0.0](#v200) | ||
- [v1.1.0](#v110) | ||
## 1.1.0 | ||
- stream: Move the "use strict" directive inside the IIFE [#1831](https://github.com/MithrilJS/mithril.js/issues/1831) ([#1893](https://github.com/MithrilJS/mithril.js/pull/1893)) | ||
### Upcoming... | ||
### 2.0.0 | ||
- when a stream conditionally returns HALT, dependant stream will also end ([#2200](https://github.com/MithrilJS/mithril.js/pull/2200), [#2369](https://github.com/MithrilJS/mithril.js/pull/2369)) | ||
- Add `stream.lift` as a user-friendly alternative to `merge -> map` or `combine` ([#1944](https://github.com/MithrilJS/mithril.js/issues/1944)) | ||
- renamed HALT to SKIP ([#2207](https://github.com/MithrilJS/mithril.js/pull/2207)) | ||
- rewrote implementation ([#2207](https://github.com/MithrilJS/mithril.js/pull/2207)) | ||
- Removed `valueOf` & `toString` methods ([#2150](https://github.com/MithrilJS/mithril.js/pull/2150) | ||
### 1.1.0 | ||
- Move the "use strict" directive inside the IIFE [#1831](https://github.com/MithrilJS/mithril.js/issues/1831) ([#1893](https://github.com/MithrilJS/mithril.js/pull/1893)) |
{ | ||
"name": "mithril-stream", | ||
"version": "1.1.0", | ||
"version": "2.0.0", | ||
"description": "Streaming data, mithril-style", | ||
"main": "stream.js", | ||
"module": "stream.mjs", | ||
"directories": { | ||
@@ -8,0 +7,0 @@ "test": "tests" |
@@ -27,7 +27,9 @@ /* eslint-disable */ | ||
function stream(v) { | ||
if (arguments.length && v !== Stream.SKIP && open(stream)) { | ||
if (arguments.length && v !== Stream.SKIP) { | ||
value = v | ||
stream.changing() | ||
stream.state = "active" | ||
dependentStreams.forEach(function(s, i) { s(dependentFns[i](value)) }) | ||
if (open(stream)) { | ||
stream.changing() | ||
stream.state = "active" | ||
dependentStreams.forEach(function(s, i) { s(dependentFns[i](value)) }) | ||
} | ||
} | ||
@@ -40,2 +42,3 @@ | ||
stream.state = arguments.length && value !== Stream.SKIP ? "active" : "pending" | ||
stream.parents = [] | ||
@@ -45,3 +48,2 @@ stream.changing = function() { | ||
dependentStreams.forEach(function(s) { | ||
s.dependent && s.dependent.changing() | ||
s.changing() | ||
@@ -55,2 +57,3 @@ }) | ||
: Stream() | ||
target.parents.push(stream) | ||
@@ -67,4 +70,5 @@ dependentStreams.push(target) | ||
if (value === true) { | ||
stream.parents.forEach(function (p) {p.unregisterChild(stream)}) | ||
stream.state = "ended" | ||
dependentStreams.length = dependentFns.length = 0 | ||
stream.parents.length = dependentStreams.length = dependentFns.length = 0 | ||
} | ||
@@ -81,2 +85,10 @@ return value | ||
stream.unregisterChild = function(child) { | ||
var childIndex = dependentStreams.indexOf(child) | ||
if (childIndex !== -1) { | ||
dependentStreams.splice(childIndex, 1) | ||
dependentFns.splice(childIndex, 1) | ||
} | ||
} | ||
Object.defineProperty(stream, "end", { | ||
@@ -101,4 +113,4 @@ get: function() { return end || createEnd() } | ||
streams.forEach(function(s) { | ||
s.map(function(value) { | ||
var mappers = streams.map(function(s) { | ||
return s.map(function(value) { | ||
changed.push(s) | ||
@@ -111,5 +123,13 @@ if (ready || streams.every(function(s) { return s.state !== "pending" })) { | ||
return value | ||
}, Stream.SKIP).parent = stream | ||
}, Stream.SKIP) | ||
}) | ||
var endStream = stream.end.map(function(value) { | ||
if (value === true) { | ||
mappers.forEach(function(mapper) { mapper.end(true) }) | ||
endStream.end(true) | ||
} | ||
return undefined | ||
}) | ||
return stream | ||
@@ -116,0 +136,0 @@ } |
@@ -264,2 +264,11 @@ "use strict" | ||
}) | ||
o("combine callback not called when child stream was ended", function () { | ||
var spy = o.spy() | ||
var a = Stream(1) | ||
var b = Stream(2) | ||
var mapped = Stream.combine(spy, [a, b]) | ||
mapped.end(true) | ||
a(11) | ||
o(spy.callCount).equals(1) | ||
}) | ||
}) | ||
@@ -483,2 +492,8 @@ o.spec("lift", function() { | ||
}) | ||
o("ended stream works like a container", function() { | ||
var stream = Stream(1) | ||
stream.end(true) | ||
stream(2) | ||
o(stream()).equals(2) | ||
}) | ||
}) | ||
@@ -549,2 +564,10 @@ o.spec("toJSON", function() { | ||
}) | ||
o("mapping function is not invoked after ending", function () { | ||
var stream = Stream(undefined) | ||
var fn = o.spy() | ||
var mapped = stream.map(fn) | ||
mapped.end(true) | ||
stream(undefined) | ||
o(fn.callCount).equals(1) | ||
}) | ||
}) | ||
@@ -551,0 +574,0 @@ o.spec("ap", function() { |
@@ -46,2 +46,11 @@ "use strict" | ||
} | ||
this.responseType = "" | ||
this.response = null | ||
Object.defineProperty(this, "responseText", {get: function() { | ||
if (this.responseType === "" || this.responseType === "text") { | ||
return this.response | ||
} else { | ||
throw new Error("Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was '" + this.responseType + "').") | ||
} | ||
}}) | ||
this.send = function(body) { | ||
@@ -53,3 +62,9 @@ var self = this | ||
self.status = data.status | ||
self.responseText = data.responseText | ||
// Match spec | ||
if (self.responseType === "json") { | ||
try { self.response = JSON.parse(data.responseText) } | ||
catch (e) { /* ignore */ } | ||
} else { | ||
self.response = data.responseText | ||
} | ||
} else { | ||
@@ -56,0 +71,0 @@ self.status = 0 |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
12
218
6
1454596
22147