From 5d85a6bb0eacf0ce62c95dcd72eeddd0f1d34204 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Mon, 30 Mar 2015 15:54:43 -0400 Subject: [PATCH] v0.1.33 publish --- Gruntfile.js | 2 +- archive/v0.1.33/README.md | 78 + archive/v0.1.33/auto-redrawing.html | 152 + archive/v0.1.33/benchmarks.html | 98 + archive/v0.1.33/bower.json | 20 + archive/v0.1.33/change-log.html | 505 ++ archive/v0.1.33/community.html | 88 + archive/v0.1.33/comparison.html | 130 + .../v0.1.33/comparisons/angular.parsing.html | 2 + .../comparisons/angular.rendering.html | 14 + .../v0.1.33/comparisons/angular.safety.html | 13 + .../v0.1.33/comparisons/backbone.parsing.html | 4 + .../comparisons/backbone.rendering.html | 30 + .../v0.1.33/comparisons/backbone.safety.html | 29 + .../v0.1.33/comparisons/jquery.parsing.html | 2 + .../v0.1.33/comparisons/jquery.rendering.html | 17 + .../v0.1.33/comparisons/jquery.safety.html | 16 + .../v0.1.33/comparisons/mithril.parsing.html | 2 + .../comparisons/mithril.rendering.html | 20 + .../v0.1.33/comparisons/mithril.safety.html | 19 + .../v0.1.33/comparisons/react.parsing.html | 2 + .../v0.1.33/comparisons/react.rendering.html | 24 + archive/v0.1.33/comparisons/react.safety.html | 24 + archive/v0.1.33/component.json | 10 + archive/v0.1.33/components.html | 252 + archive/v0.1.33/getting-started.html | 505 ++ archive/v0.1.33/how-to-read-signatures.html | 208 + archive/v0.1.33/index.html | 267 ++ archive/v0.1.33/installation.html | 122 + archive/v0.1.33/integration.html | 162 + archive/v0.1.33/lib/prism/prism.css | 126 + archive/v0.1.33/lib/prism/prism.js | 9 + archive/v0.1.33/mithril-tests.js | 4126 +++++++++++++++++ archive/v0.1.33/mithril.computation.html | 280 ++ archive/v0.1.33/mithril.d.ts | 168 + archive/v0.1.33/mithril.deferred.html | 278 ++ archive/v0.1.33/mithril.deps.html | 139 + archive/v0.1.33/mithril.html | 552 +++ archive/v0.1.33/mithril.js | 1071 +++++ archive/v0.1.33/mithril.min.js | 8 + archive/v0.1.33/mithril.min.js.map | 1 + archive/v0.1.33/mithril.min.zip | Bin 0 -> 22 bytes archive/v0.1.33/mithril.module.html | 215 + archive/v0.1.33/mithril.prop.html | 200 + archive/v0.1.33/mithril.redraw.html | 234 + archive/v0.1.33/mithril.render.html | 190 + archive/v0.1.33/mithril.request.html | 577 +++ archive/v0.1.33/mithril.route.html | 371 ++ archive/v0.1.33/mithril.sync.html | 131 + archive/v0.1.33/mithril.trust.html | 141 + archive/v0.1.33/mithril.withAttr.html | 146 + archive/v0.1.33/mithril.xhr.html | 94 + archive/v0.1.33/optimizing-performance.html | 122 + archive/v0.1.33/package.json | 11 + archive/v0.1.33/pages.json | 6 + archive/v0.1.33/practices.html | 118 + archive/v0.1.33/refactoring.html | 62 + archive/v0.1.33/roadmap.html | 121 + archive/v0.1.33/routing.html | 134 + archive/v0.1.33/style.css | 92 + archive/v0.1.33/tools.html | 111 + archive/v0.1.33/tools/template-compiler.sjs | 84 + archive/v0.1.33/tools/template-converter.html | 9 + archive/v0.1.33/tools/template-converter.js | 90 + archive/v0.1.33/web-services.html | 231 + docs/change-log.md | 18 + mithril.min.js | 4 +- mithril.min.js.map | 2 +- 68 files changed, 13085 insertions(+), 4 deletions(-) create mode 100644 archive/v0.1.33/README.md create mode 100644 archive/v0.1.33/auto-redrawing.html create mode 100644 archive/v0.1.33/benchmarks.html create mode 100644 archive/v0.1.33/bower.json create mode 100644 archive/v0.1.33/change-log.html create mode 100644 archive/v0.1.33/community.html create mode 100644 archive/v0.1.33/comparison.html create mode 100644 archive/v0.1.33/comparisons/angular.parsing.html create mode 100644 archive/v0.1.33/comparisons/angular.rendering.html create mode 100644 archive/v0.1.33/comparisons/angular.safety.html create mode 100644 archive/v0.1.33/comparisons/backbone.parsing.html create mode 100644 archive/v0.1.33/comparisons/backbone.rendering.html create mode 100644 archive/v0.1.33/comparisons/backbone.safety.html create mode 100644 archive/v0.1.33/comparisons/jquery.parsing.html create mode 100644 archive/v0.1.33/comparisons/jquery.rendering.html create mode 100644 archive/v0.1.33/comparisons/jquery.safety.html create mode 100644 archive/v0.1.33/comparisons/mithril.parsing.html create mode 100644 archive/v0.1.33/comparisons/mithril.rendering.html create mode 100644 archive/v0.1.33/comparisons/mithril.safety.html create mode 100644 archive/v0.1.33/comparisons/react.parsing.html create mode 100644 archive/v0.1.33/comparisons/react.rendering.html create mode 100644 archive/v0.1.33/comparisons/react.safety.html create mode 100644 archive/v0.1.33/component.json create mode 100644 archive/v0.1.33/components.html create mode 100644 archive/v0.1.33/getting-started.html create mode 100644 archive/v0.1.33/how-to-read-signatures.html create mode 100644 archive/v0.1.33/index.html create mode 100644 archive/v0.1.33/installation.html create mode 100644 archive/v0.1.33/integration.html create mode 100644 archive/v0.1.33/lib/prism/prism.css create mode 100644 archive/v0.1.33/lib/prism/prism.js create mode 100644 archive/v0.1.33/mithril-tests.js create mode 100644 archive/v0.1.33/mithril.computation.html create mode 100644 archive/v0.1.33/mithril.d.ts create mode 100644 archive/v0.1.33/mithril.deferred.html create mode 100644 archive/v0.1.33/mithril.deps.html create mode 100644 archive/v0.1.33/mithril.html create mode 100644 archive/v0.1.33/mithril.js create mode 100644 archive/v0.1.33/mithril.min.js create mode 100644 archive/v0.1.33/mithril.min.js.map create mode 100644 archive/v0.1.33/mithril.min.zip create mode 100644 archive/v0.1.33/mithril.module.html create mode 100644 archive/v0.1.33/mithril.prop.html create mode 100644 archive/v0.1.33/mithril.redraw.html create mode 100644 archive/v0.1.33/mithril.render.html create mode 100644 archive/v0.1.33/mithril.request.html create mode 100644 archive/v0.1.33/mithril.route.html create mode 100644 archive/v0.1.33/mithril.sync.html create mode 100644 archive/v0.1.33/mithril.trust.html create mode 100644 archive/v0.1.33/mithril.withAttr.html create mode 100644 archive/v0.1.33/mithril.xhr.html create mode 100644 archive/v0.1.33/optimizing-performance.html create mode 100644 archive/v0.1.33/package.json create mode 100644 archive/v0.1.33/pages.json create mode 100644 archive/v0.1.33/practices.html create mode 100644 archive/v0.1.33/refactoring.html create mode 100644 archive/v0.1.33/roadmap.html create mode 100644 archive/v0.1.33/routing.html create mode 100644 archive/v0.1.33/style.css create mode 100644 archive/v0.1.33/tools.html create mode 100644 archive/v0.1.33/tools/template-compiler.sjs create mode 100644 archive/v0.1.33/tools/template-converter.html create mode 100644 archive/v0.1.33/tools/template-converter.js create mode 100644 archive/v0.1.33/web-services.html diff --git a/Gruntfile.js b/Gruntfile.js index ef499ada..40e9039d 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,7 +1,7 @@ module.exports = function(grunt) { _ = require('lodash'); - var version = "0.1.32"; + var version = "0.1.33"; var inputFolder = "./docs"; var tempFolder = "./temp"; diff --git a/archive/v0.1.33/README.md b/archive/v0.1.33/README.md new file mode 100644 index 00000000..15ea1990 --- /dev/null +++ b/archive/v0.1.33/README.md @@ -0,0 +1,78 @@ +[![Join the chat at https://gitter.im/lhorie/mithril.js](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/lhorie/mithril.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Build Status](https://travis-ci.org/lhorie/mithril.js.svg?branch=master)](https://travis-ci.org/lhorie/mithril.js) + +# Mithril + +A Javascript Framework for Building Brilliant Applications + +See the [website](http://lhorie.github.io/mithril) for documentation + +There's also a [blog](http://lhorie.github.io/mithril-blog) and a [mailing list](https://groups.google.com/forum/#!forum/mithriljs) + +--- + +## What is Mithril? + +Mithril is a client-side MVC framework - a tool to organize code in a way that is easy to think about and to maintain. + +### Light-weight + +- Only 5kb gzipped, no dependencies +- Small API, small learning curve + +### Robust + +- Safe-by-default templates +- Hierarchical MVC via components + +### Fast + +- Virtual DOM diffing and compilable templates +- Intelligent auto-redrawing system + +--- + +## Sample code + +```javascript +//namespace +var app = {}; + +//model +app.PageList = function() { + return m.request({method: "GET", url: "pages.json"}); +}; + +//controller +app.controller = function() { + var pages = app.PageList(); + return { + pages: pages, + rotate: function() { + pages().push(pages().shift()); + } + } +}; + +//view +app.view = function(ctrl) { + return [ + ctrl.pages().map(function(page) { + return m("a", {href: page.url}, page.title); + }), + m("button", {onclick: ctrl.rotate}, "Rotate links") + ]; +}; + + +//initialize +m.module(document.getElementById("example"), app); +``` + +--- + +### Learn more + +- [Tutorial](http://lhorie.github.io/mithril/getting-started.html) +- [Differences from Other Frameworks](http://lhorie.github.io/mithril/comparison.html) +- [Benchmarks](http://lhorie.github.io/mithril/benchmarks.html) \ No newline at end of file diff --git a/archive/v0.1.33/auto-redrawing.html b/archive/v0.1.33/auto-redrawing.html new file mode 100644 index 00000000..d2fbbbda --- /dev/null +++ b/archive/v0.1.33/auto-redrawing.html @@ -0,0 +1,152 @@ + + + + The Auto-Redrawing System - Mithril + + + + +
+ +
+
+
+
+
+ +
+

The Auto-Redrawing System

+

Mithril is designed around the principle that data always flows from the model to the view. This makes it easy to reason about the state of the UI and to test it. In order to implement this principle, the rendering engine must run a redraw algorithm globally to ensure no parts of the UI are out of sync with the data. While at first glance, it may seem expensive to run a global redraw every time data changes, Mithril makes it possible to do this efficiently thanks to its fast diffing algorithm, which only updates the DOM where it needs to be updated. Because the DOM is by far the largest bottleneck in rendering engines, Mithril's approach of running a diff against a virtual representation of the DOM and only batching changes to the real DOM as needed is surprisingly performant.

+

In addition, Mithril attempts to intelligently redraw only when it is appropriate in an application lifecycle. Most frameworks redraw aggressively and err on the side of redrawing too many times because, as it turns out, determining the best time to do a redraw is quite complicated if we want to be as efficient as possible.

+

Mithril employs a variety of mechanisms to decide the best time and the best strategy to redraw. By default, Mithril is configured to auto-redraw from scratch after module controllers are initialized, and it is configured to diff after event handlers are triggered. In addition, it's possible for non-Mithril asynchronous callbacks to trigger auto-redrawing by calling m.startComputation and m.endComputation in appropriate places (see below). Any code that is between a m.startComputation and its respective m.endComputation call is said to live in the context of its respective pair of function calls.

+

It's possible to defer a redraw by calling m.request or by manually nesting m.startComputation and m.endComputation contexts. The way the redrawing engine defers redrawing is by keeping an internal counter that is incremented by m.startComputation and decremented by m.endComputation. Once that counter reaches zero, Mithril redraws. By strategically placing calls to this pair of functions, it is possible to stack asynchronous data services in any number of ways within a context without the need to pass state variables around the entire application. The end result is that you can call m.request and other integrated data services seamlessly, and Mithril will wait for all of the asynchronous operations to complete before attempting to redraw.

+

In addition to being aware of data availability when deciding to redraw, Mithril is also aware of browser availability: if several redraws are triggered in a short amount of time, Mithril batches them so that at most only one redraw happens within a single animation frame (around 16ms). Since computer screens are not able to display changes faster than a frame, this optimization saves CPU cycles and helps UIs stay responsive even in the face of spammy data changes.

+

Mithril also provides several hooks to control its redrawing behavior with a deep level of granularity: m.startComputation and m.endComputation create redrawable contexts. m.redraw forces a redraw to happen in the next available frame (or optionally, it can redraw immediately for synchronous processing). m.redraw.strategy can change the way Mithril runs the next scheduled redraw. Finally, the low-level m.render can also be used if a developer chooses to opt out of rest of the framework altogether.

+
+

Integrating with The Auto-Redrawing System

+

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

+

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

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

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

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

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

+

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

+
+

Integrating multiple execution threads

+

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

+

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

+

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

+

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

+

Layered integration

+

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

+

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

+

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

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

Comprehensive integration

+

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

+

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

+
var doSomething = function(callback) {
+    m.startComputation(); //call `startComputation` before everything else
+
+    jQuery.ajax("/something").done(function() {
+        doStuff();
+        jQuery.ajax("/another").done(function() {
+            doMoreStuff();
+            jQuery.ajax("/more").done(function() {
+                if (callback) callback();
+
+                m.endComputation(); //call `endComputation` at the end of everything
+            });
+        });
+    });
+};
+
+ +
+
+
+
+
+ + + + diff --git a/archive/v0.1.33/benchmarks.html b/archive/v0.1.33/benchmarks.html new file mode 100644 index 00000000..51bc0854 --- /dev/null +++ b/archive/v0.1.33/benchmarks.html @@ -0,0 +1,98 @@ + + + + Benchmarks - Mithril + + + + +
+ +
+
+
+
+
+ +
+

Benchmarks

+

These benchmarks were designed to measure Javascript running time for Mithril in comparison with other popular Javascript MVC frameworks. Javascript running time is significant because the gzipped size of a framework can be misleading in terms of how much code actually runs on page loads. In my experience, page loads happen far more commonly than one would expect in single page applications: power users open multiple tabs, and mobile users open and close the browser very frequently. And as far as templating engines go, the initial page load represents the worst case for the rendering algorithm since there is very little room for performance optimization tricks. It's arguably also one of the most important metrics when it comes to performance.

+

The numbers shown here are best-run results for all frameworks, except for Mithril's case, for which I'm taking the worst-run result. The numbers aren't statistically rigorous (e.g. I didn't bother to calculate standard deviation), but they should be enough to give a rough idea of what is faster than what.

+

Generally speaking, these tests are making a deliberate effort to be biased in favor of other frameworks: for example, I don't load "optional-but-usually-used-in-real-life" things like the router module for Angular, or Marionette in Backbone's case, and I load the entirety of Mithril. In addition, this test deliberately avoids triggering requestAnimationFrame-based performance optimizations for Mithril, since this optimization does not exist in many frameworks and severely skews numbers in Mithril's favor in CPU-intensive situations like parallax sites. I'm also NOT using the Mithril template compiler, which would also skew the benchmark in Mithril's favor.

+

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

+
+
+
+

Loading

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

Rendering

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

Feel free to implement versions of the tests above in other frameworks, if you wish. The code is very simple.

+
+

TodoMVC Benchmark

+

There's a TodoMVC benchmark with a variety of popular and obscure frameworks here:

+

http://matt-esch.github.io/mercury-perf/

+

The benchmark consists of creating 100 todos, marking them as completed, and then deleting them. It aims to give an idea of how frameworks perform under real-world-ish conditions running idiomatic code (as opposed to micro-benchmarks, which tend to take advantage of obscure tricks and aggressive optimizations that sacrifice maintainability for extra speed).

+ +
+
+
+
+
+ + + + diff --git a/archive/v0.1.33/bower.json b/archive/v0.1.33/bower.json new file mode 100644 index 00000000..19352239 --- /dev/null +++ b/archive/v0.1.33/bower.json @@ -0,0 +1,20 @@ +{ + "name": "mithril", + "version": "0.1.33", + "main": "mithril.min.js", + "description": "A Javascript Framework for building brilliant applications", + "authors": ["Leo Horie "], + "license": "MIT", + "ignore": [ + "*.html", + "*.css", + "*.zip", + "*.json", + "mithril-tests.js", + "archive", + "comparisons", + "lib", + "tools" + ], + "keywords": ["mithril", "mvc", "framework"] +} \ No newline at end of file diff --git a/archive/v0.1.33/change-log.html b/archive/v0.1.33/change-log.html new file mode 100644 index 00000000..5082f2bc --- /dev/null +++ b/archive/v0.1.33/change-log.html @@ -0,0 +1,505 @@ + + + + Change Log - Mithril + + + + +
+ +
+
+
+
+
+ +
+

Change Log

+

v0.1.33 - maintenance

+

Bug Fixes:

+
    +
  • fix diff bug when mixing undefined in a tree #524
  • +
  • fix reference to map file in package.json for cdnjs
  • +
  • fix links in documentation
  • +
+
+

v0.1.32 - maintenance

+

Bug Fixes:

+
    +
  • fix regression caused by #454
  • +
+
+

v0.1.31 - maintenance

+

News:

+
    +
  • Typescript definitions are more strongly typed
  • +
  • m.request's unwrapSuccess and unwrapError callbacks now receive the XMLHttpRequest instance as a second parameter
  • +
  • 3rd parameter for m.route(route, params, shouldReplaceHistory) is now public
  • +
  • exact routes now have higher precedence than routes w/ variables #452
  • +
  • there's now a retain flag to control on-route-change diff strategy on a per-element basis
  • +
+

Bug Fixes:

+
    +
  • fix routing bug in IE9 #320
  • +
  • fix ordering bug in m.trust when using HTML entities #453
  • +
  • set promise's default value to initialValue if coming from m.request #454
  • +
  • fix dom element ownership bug when mixing keyed elements and third party plugin elements #463
  • +
  • fix edge case in flatten algorithm #448
  • +
  • prevent unnecessary DOM move operation when mixing keyed and unkeyed elements #398
  • +
  • revert #382 due to diff regression #512
  • +
+
+

v0.1.30 - maintenance

+

Bug Fixes:

+
    +
  • fix history.back() regression #435
  • +
  • fix module.view's this association regression in Haxe environment #434
  • +
  • fix array serialization syntax in querystrings #440
  • +
+
+

v0.1.29 - maintenance

+

News:

+
    +
  • Calling m.module without a module now unloads the current one #420
  • +
  • Both controller and view properties in modules are now optional
  • +
+

Bug Fixes:

+
    +
  • prevent empty class attributes #382
  • +
  • array-to-querystring serialization in m.request now behaves like jQuery #426
  • +
  • fix querystring detection bug in pathname mode #425
  • +
  • don't add history entry if reloading from a link #428
  • +
  • fix key association when DOM order is modified by external code #424
  • +
+
+

v0.1.28 - maintenance

+

News:

+
    +
  • Landed some performance improvements
  • +
+

Bug Fixes:

+
    +
  • throw error if root element is null in m.module/m.route #388
  • +
+
+

v0.1.27 - maintenance

+

Bug Fixes:

+
    +
  • prevent strategy("none") event contamination #378
  • +
  • fix equality strictness #379
  • +
  • fix keys bug when list has nulls #299
  • +
  • make sure empty value in option tag creates attribute #380
  • +
+
+

v0.1.26 - maintenance

+

Bug Fixes:

+
    +
  • make sure input[type] is CSS-targetable #364
  • +
  • throw error if m.route.param is called before initializing routes #361
  • +
+
+

v0.1.25 - maintenance

+

Bug Fixes:

+
    +
  • fixed input cursor jumping regression
  • +
  • fixed interop bug when QUnit and AMD are used at the same time #355
  • +
  • fixed route arg duplication in edge case #352
  • +
  • prevented meaningless error in Chrome edge case #358
  • +
+
+

v0.1.24 - maintenance

+

Bug Fixes:

+
    +
  • Prevent rogue is attribute from being created in Chrome
  • +
  • Fix data regression in m.request
  • +
+
+

v0.1.23 - maintenance

+

News:

+
    +
  • There's now support for extended custom elements (e.g. m("button[is=my-button]"))
  • +
  • m.request now supports a initialValue option to help prevent type errors in views when using the background option
  • +
+

Bug Fixes:

+
    +
  • docs now have anchor links for easier navigation
  • +
  • fixed a bunch of IE8 issues #298
  • +
  • fixed handling of method option in JSONP mode #292
  • +
  • fixed source map files
  • +
  • fixed handling of select[multiple]
  • +
  • fixed template compiler edge case #286
  • +
  • fixed pathname bug in m.route #290
  • +
  • fixed pathname querystring bug in routed links #304
  • +
  • fixed handling of value in inputs when model value is not in sync with input value #336
  • +
+
+

v0.1.22 - maintenance

+

News:

+
    +
  • docs now have anchor links for easier navigation
  • +
  • there is more documentation for things that weren't that clear
  • +
  • json-p support added
  • +
  • m() now supports splat for children (e.g. m("div", m("a"), m("b"), m("i")) for nicer Coffeescript syntax
  • +
  • by popular demand, m.module now returns a controller instance
  • +
+

Bug Fixes:

+
    +
  • gracefully degrade on IE exceptions when setting invalid values
  • +
  • fixes for Typescript definition file
  • +
  • fixed bug in keys algorithm when mixing keyed and unkeyed elements #246
  • +
  • added promise exception monitor and reverted promise exception handling semantics to v0.1.19 semantics (see docs)
  • +
  • fixed redraw scheduling bug in old version of IE
  • +
  • fixed incorrect diff when document is root, and html element is omitted
  • +
  • fixed querystring clobbering in links w/ config:m.route #261
  • +
  • fixed rare bug that made events get dropped #214
  • +
  • don't send Content-Type header if there's no request data #280
  • +
+
+

v0.1.21 - maintenance

+

News:

+
    +
  • passing a promise to an m.prop now populates it with the resolved value upon resolution, and returns undefined otherwise
  • +
  • m.redraw can now be forced to called synchronously
  • +
+

Bug Fixes:

+
    +
  • fixed handling of + character in m.route.param #204
  • +
  • fixed corner case for undefined children in diff #206
  • +
  • fixed context.onunload for array items #200
  • +
  • fixed handling on comments in HTML converter tool
  • +
+
+

v0.1.20 - maintenance

+

News:

+
    +
  • redraw strategy can now be modified via m.redraw.strategy
  • +
  • math tags now automatically get created with the MathML namespace
  • +
+

Bug Fixes:

+
    +
  • fixed IE8 null reference exception in m
  • +
  • fixed IE8 empty-text-node-in-input issue #195
  • +
  • fixed m.sync resolution when passed an empty array #191
  • +
+
+

v0.1.19 - maintenance

+

Bug Fixes:

+
    +
  • fixed double redraw when events fire simultaneously #151
  • +
  • fixed node insertion bug when using document as root #153
  • +
  • prevent routes from reverting to original route in some cases
  • +
  • fixed nested array ordering #156
  • +
  • fixed key ordering in interpolation case #157
  • +
+
+

v0.1.18 - maintenance

+

Bug Fixes:

+
    +
  • routing now correctly clears diff cache #148
  • +
  • fixed incorrect context unloading when reattaching a child to a new parent
  • +
+
+

v0.1.17 - maintenance

+

News:

+
    +
  • config contexts can now have an onunload property for clean up tasks after elements are detached from the document
  • +
  • route changes now re-render from scratch, rather than attempting a virtual dom diff
  • +
  • virtual elements that are children of an array can now accept a key attribute which maintains the identity of the underlying DOM elements when the array gets shuffled #98
  • +
+

Bug Fixes:

+
    +
  • fixed a subtree directive bug that happened in inputs inside loops
  • +
  • fixed select.value so that the correct option is displayed on first render
  • +
  • in m.request, non-idempotent methods now automatically send appropriate Content-Type header if serialize is JSON.stringify #139
  • +
  • m selectors now correctly handle empty attribute values like [href='']
  • +
  • pre-existing nodes in a root element now get cleared if there's no cell cache associated with the element #60
  • +
+
+

v0.1.16 - maintenance

+

News:

+
    +
  • controller::onunload now receives an event parameter so that the unloading can be aborted #135
  • +
+

Bug Fixes:

+
    +
  • prevent route change when only hash changes in non-hash mode #107
  • +
  • config now always runs after template is attached to document #109
  • +
  • fix null reference exception with Browserify #110
  • +
  • fix nested array removal edge cases #120
  • +
  • ignore redraw calls when controller is not ready #127
  • +
  • fix null reference exception in nested array edge case #129
  • +
  • fix a contenteditable null reference error #134
  • +
  • fix textarea value diffing when value is a node inside an array #136
  • +
  • fix diff bug with trusted strings #138
  • +
+

Breaking changes:

+
    +
  • Due to the poor level of compatibility between XDomainRequest and XHR2, XDomainRequest is no longer called internally by Mithril. If you need to use CORS in IE9 or lower, you will need to return an XDomainRequest instance from m.request's config method #121
  • +
+
+

v0.1.15 - maintenance

+

Bug Fixes:

+
    +
  • m.sync now correctly passes arguments to resolver in same order as input arguments #96
  • +
  • fixed diff deletion bug #99
  • +
  • updating textarea attributes updates its value correctly #100
  • +
+
+

v0.1.14 - maintenance

+

News:

+
    +
  • The signature of m now accepts virtual elements as the second parameter of the function.
  • +
  • m.route(path, params) now accepts an argument that gets parsed as a querystring.
  • +
  • routes now ignore trailing slashes #88
  • +
+

Bug Fixes:

+
    +
  • Resolving promises early without a value now works #85
  • +
  • Throwing exceptions within m.request now follow the same resolution procedure as m.deferred #86
  • +
  • Promises now always update their m.prop on success (and leave the m.prop alone on error)
  • +
  • Nested arrays no longer cause double removal of elements #87
  • +
  • HTTP error codes now correctly reject promises
  • +
+
+

v0.1.13 - maintenance

+

News:

+
    +
  • m.module now runs clean-up code in root module controllers that implement an onunload instance method #82
  • +
+

Bug Fixes:

+
    +
  • Removing CSS rules now diffs correctly #79
  • +
+
+

v0.1.12 - maintenance

+

News:

+ +

Bug Fixes:

+
    +
  • Fix link location in links using config: m.route after redraws #74
  • +
  • Fixed support for list attribute in inputs #69
  • +
  • Fixed URL decoding in route params #75
  • +
+
+

v0.1.11 - maintenance

+

News:

+
    +
  • Added m.route() overload to allow reading of current route #61
  • +
  • Added background option to m.request to allow requests that don't affect rendering #62
  • +
+

Bug Fixes:

+
    +
  • Links using config: m.route can now be opened in new tab correctly #64
  • +
  • Fixed diff within contenteditable areas #65
  • +
+
+

v0.1.10 - maintenance

+

News:

+
    +
  • Added social buttons to homepage
  • +
+

Bug Fixes:

+
    +
  • Bi-directional bindings no longer wipe out cursor position in Chrome #58
  • +
+
+

v0.1.9 - maintenance

+

News:

+
    +
  • Added comparison with React to homepage
  • +
  • Added support for multi-island apps #34
  • +
  • m.prop is now JSON-serializable #54
  • +
  • Added extract option to m.request to allow access to response metadata #53
  • +
+

Bug Fixes:

+
    +
  • Fixed node index displacement by null/undefined nodes #56
  • +
  • Fixed mock's insertBefore and appendChild when dealing w/ reattachments
  • +
+

Breaking changes:

+
    +
  • changing an id in a virtual element now recreates the element, instead of recycling it #55
  • +
+
+

v0.1.8 - maintenance

+

News:

+
    +
  • Mock now contains a basic insertAdjacentHTML implementation to enable better testing of m.trust / m.render interactions
  • +
+

Bug Fixes:

+
    +
  • Fixed ordering bug in deep subchildren #51
  • +
  • Fixed ordering bug with trusted strings #51
  • +
+
+

v0.1.7 - maintenance

+

News:

+
    +
  • Mithril will be on a accelerated release cycle for the rest of the v0.1.x series. This means CDNs may lag behind in versions, so it's recommended that you either use one of the supported NodeJS package managers or fork from the Github repo directly. More information can be found here
  • +
+

Bug Fixes:

+
    +
  • Fixed ordering bug when virtual element is preceded by array #50
  • +
+
+

v0.1.6 - maintenance

+

Bug Fixes:

+
    +
  • Fixed serious bug when mixing cached text nodes with new virtual elements #49
  • +
+
+

v0.1.5 - maintenance

+

News:

+ +

Bug Fixes:

+
    +
  • Fixed serious ordering problem when mixing arrays with virtual elements #48
  • +
+
+

v0.1.4 - maintenance

+

News:

+
    +
  • added regression tests for reported bugs
  • +
  • added support for SVG
  • +
+

Bug Fixes:

+
    +
  • URLs with port numbers are now handled correctly #40
  • +
  • NPM package now contains unminified version for map files #39
  • +
  • fixed ordering issue when mixing newly created virtual elements with elements from cache #44
  • +
  • fixed caching bug in links w/ config option attached #43
  • +
  • fixed attribute update bug when an element has both oninput and onkeydown handlers #36
  • +
+
+

v0.1.3 - maintenance

+

News:

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

Bug Fixes:

+
    +
  • diff no longer touch the DOM when processing style attributes and event handlers
  • +
  • returning a thennable to a resolution callback in m.deferred().promise now causes the promise to adopt its state
  • +
  • diff now correctly clears subtree if null or undefined is passed as a node
  • +
+
+

v0.1.2 - maintenance

+

News:

+ +

Bug Fixes:

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

v0.1.1 - maintenance

+

News:

+ +

Bug Fixes:

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

Breaking changes:

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

    +

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

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

v0.1 - Initial release

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/community.html b/archive/v0.1.33/community.html new file mode 100644 index 00000000..72a65c9f --- /dev/null +++ b/archive/v0.1.33/community.html @@ -0,0 +1,88 @@ + + + + Community - Mithril + + + + +
+ +
+
+
+
+
+ +
+

Community

+

Learn Mithril

+

Read Mithril tutorials and articles about web app development.

+

Go to the Learn Mithril site

+
+

Gitter

+

There's a Gitter chat room. This is a great place to ask questions, discuss ideas and connect with Mithril users.

+

You can sign in with your Github credentials.

+
+

Mailing List

+

Looking for a place to talk about Mithril? Suggestions?

+

Feel free to post on the mailing list

+
+

StackOverflow

+

Looking for help on StackOverflow? Tag your questions with mithril.js.

+

Want to help fellow Mithril developers and gain karma while at it? Keep an eye on the tagged questions

+
+

IRC

+

Join the #mithriljs IRC channel on Freenode.

+
+

Projects and Snippets

+

A collection of projects and snippets created by Mithril users. A great place to find useful tools.

+

Go to the Mithril wiki

+
+

Bug Tracker

+

You can file bugs in the issues page on Github

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/comparison.html b/archive/v0.1.33/comparison.html new file mode 100644 index 00000000..f44d390a --- /dev/null +++ b/archive/v0.1.33/comparison.html @@ -0,0 +1,130 @@ + + + + How is Mithril Different from Other Frameworks - Mithril + + + + +
+ +
+
+
+
+
+ +
+

How is Mithril Different from Other Frameworks

+

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

+

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

+

Code Size

+

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

+

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

+

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

+

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

+

Documentation

+

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

+

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

+

Mithril has more documentation in its Github repo than source code, and none of the documentation is auto-generated.

+

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

+

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

+

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

+

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

+

Architecture

+

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

+

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

+

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

+

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

+

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

+

View Layer Paradigm

+

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

+

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

+

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

+
+

Specific Framework Comparisons

+

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

+

jQuery

+

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

+

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

+

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

+

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

+

Backbone

+

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

+

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

+

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

+

In contrast, Mithril encourages you to develop applications using the patterns found throughout this guide, and discourages the use of "bastardized" MVC pattern variations.

+

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

+

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

+

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

+

Angular

+

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

+

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

+

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

+

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

+

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

+

Ember

+

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

+

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

+

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

+

React

+

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

+

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

+

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

+

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

+

Knockout

+

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

+

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

+

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

+

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

+

Vue

+

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

+

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

+

The most relevant difference is that Vue uses browser features that don't work (and cannot be made to work) in Internet Explorer 8 and lower. Mithril does not rely on unpolyfillable features, so developers can support browsers all the way back to IE6 and Blackberry by using shims if support for those older browsers is required.

+

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

+

In contrast, Mithril avoids "magic" types.

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

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

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

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

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

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

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

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

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

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

+
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.33/comparisons/react.safety.html b/archive/v0.1.33/comparisons/react.safety.html new file mode 100644 index 00000000..daf870b4 --- /dev/null +++ b/archive/v0.1.33/comparisons/react.safety.html @@ -0,0 +1,24 @@ + + + +

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

+
+
+ + + \ No newline at end of file diff --git a/archive/v0.1.33/component.json b/archive/v0.1.33/component.json new file mode 100644 index 00000000..8696d3c7 --- /dev/null +++ b/archive/v0.1.33/component.json @@ -0,0 +1,10 @@ +{ + "name": "mithril", + "description": "A Javascript framework for building brilliant applications", + "keywords": ["mvc", "framework"], + "repository": "lhorie/mithril", + "main": "mithril.js", + "scripts": ["mithril.js"], + "version": "0.1.33", + "license": "MIT" +} \ No newline at end of file diff --git a/archive/v0.1.33/components.html b/archive/v0.1.33/components.html new file mode 100644 index 00000000..2f8c55e1 --- /dev/null +++ b/archive/v0.1.33/components.html @@ -0,0 +1,252 @@ + + + + Components - Mithril + + + + +
+ +
+
+
+
+
+ +
+

Components

+

Widgetization

+

Components are Mithril's mechanism for hierarchical MVC.

+

In Mithril, modules are components. In scenarios where components don't need to cross-communicate a lot (for example, a dashboard full of unrelated widgets), it's often convenient to use fat controllers (i.e. controllers that hold state and methods).

+

Here's an example of a hierarchy of such components:

+
//root module
+var dashboard = {};
+
+dashboard.controller = function() {
+    this.userProfile = new userProfile.controller();
+    this.projectList = new projectList.controller();
+}
+
+dashboard.view = function(ctrl) {
+    return [
+        userProfile.view(ctrl.userProfile)
+        projectList.view(ctrl.projectList)
+    ]
+}
+
+

In the snippet above, there are three modules: dashboard, userProfile and projectList. Each of the sub-components can reasonably be rendered as a standalone page, but here we see how we can put them together to create a bigger page.

+

An important point to note is that if you have fat controllers, you should never instantiate a controller class from a view (or call a function that does it). Views are re-rendered as a result of events firing, and can clobber state from sub-component controllers.

+
+

Divide and conquer

+

Another common reason why people need components is that some pages are inherently large and complex, and need to be sub-divided into smaller pieces in order to help keep code maintainable.

+

In these cases, components often need to communicate with one another frequently and in often unexpected ways. Because of the requirement of interconnectedness, the pattern of using fat controllers is not a good fit.

+

Instead, the best way to organize these types of components is to move code out of controllers into view-models.

+

A view-model can be thought of a special type of model entities. You are probably familiar with the idea of model entities being ORM classes to map to database tables, but in reality, the model layer is an abstract area where you should be putting everything related to data and the business logic surround it.

+

View-models are, by definition, entities that hold data about the state of the application. For example, which tab is open, which filters are applied to a grid, the temporary value of a resettable input, etc. This type of data typically doesn't fit in the ORM schema because it relates to the UI, and not the canonical data.

+

Refactoring a fat controller into using view-models allow better accessibility of data and provides a scalable structure for organizing and scoping non-ORM state.

+

Here's an example that illustrates how we can migrate from a fat-controller-based codebase to thin controllers. Suppose we have a module that displays a list of users:

+
var userList = {}
+
+userList.controller = function() {
+    this.users = m.request({method: "GET", url: "/users"})
+}
+
+userList.view = function(ctrl) {
+    return ctrl.users().map(function(user) {
+        return user.name
+    })
+}
+
+

Here you can see that the controller holds the state for the list of users. The problem with this is that with a large hierarchy of components, it becomes cumbersome to find this particular controller instance from any given view, and therefore it's difficult to access its data and call methods on it.

+
var userList = {}
+
+userList.controller = function() {
+    userList.vm.init()
+}
+
+userList.vm = {}
+userList.vm.init = function() {
+    this.users = m.request({method: "GET", url: "/users"})
+}
+
+userList.view = function() {
+    return userList.vm.users().map(function(user) {
+        return user.name
+    })
+}
+
+

This pattern allows us to access the data for the userList module from anywhere in the application, as long as the view model has been initialized. Notice that refactoring from the fat controller is easy: we simply moved the controller function body into the init function of the view model, and changed the reference to the controller in the view.

+

It's then possible to extend this pattern to create view-model dependency trees:

+
userList.controller = function() {
+    //here we specify that this component will require with a `search` view-model
+    userList.vm.init()
+    search.vm.init()
+}
+
+

With that, we have a guarantee that all data and all methods from the required view-models will be available from anywhere within this component, even if it has multiple sub-views and view-models.

+

You might have noticed that we're simply sub-dividing a component into smaller pieces and not providing controllers for each of these pieces. You should be aware that these pieces aren't Mithril modules (because they don't contain both a view function AND a controller function), and therefore they are not components.

+

Checking whether there is a controller for a unit of functionality gives you a dead simple way to tell whether your sub-divided code is merely an organized part of a bigger component, or whether it is a truly modular and reusable component itself.

+

If we decide that a unit of functionality is indeed a reusable component, we can simply add a controller to it so that it follows the module interface.

+
//assuming we already have a view in `search`, adding a controller lets us use `search` as an independent component
+search.controller = function() {
+    search.vm.init()
+}
+
+userList.controller = function() {
+    userList.vm.init()
+
+    //the controller encapsulates the scope that it is responsible for
+    new search.controller()
+}
+
+

It's strongly recommended that you consider adopting the pattern of using thin controllers and view models. Moving logic out of fat controllers into the model layer brings your code structure closer to the original MVC pattern (where controllers merely exist to tell the views what actions are possible within a given context), and can dramatically reduce the complexity of cross-communicating modules in the long run.

+

Scoping to namespaces

+

Sometimes you might find that organizing code into various namespaces results in repetitive declarations of the namespace.

+
//repetitive namespace declarations
+myApp.users.index.controller = function() {/*...*/}
+
+myApp.users.index.vm = {/*...*/}
+
+myApp.users.index.view = function() {
+    return myApp.users.index.vm.something
+}
+
+

There's no rule for how you should organize code, and given that namespacing is often achieved with simple javascript, you can use simple javascript patterns to alias a long namespace and reduce the amount of typing:

+
!function() {
+    var module = myApp.users.index = {}
+
+    module.vm = {/*...*/}
+
+    module.controller = function() {/*...*/}
+
+    module.view = function() {
+        var vm = module.vm
+
+        return vm.something
+    }
+}()
+
+
+

Librarization

+

Applications often require reusable UI controls that aren't provided out of the box by HTML. Let's walk through how one might implement one. In this example, we'll create a very simple autocompleter control.

+

We can start building it as an singleton module as we did with our components in the previous section. Here's how an implementation might look like:

+
var autocompleter = {}
+autocompleter.vm = {
+    term: m.prop(""),
+    filter: function(item) {
+        return autocompleter.vm.term() && item.name.toLowerCase().indexOf(autocompleter.vm.term().toLowerCase()) > -1
+    }
+}
+autocompleter.view = function(ctrl) {
+    var vm = autocompleter.vm
+    return [
+        m("div", [
+            m("input", {oninput: m.withAttr("value", vm.term), value: vm.term})
+        ]),
+        ctrl.data().filter(vm.filter).map(function(item) {
+            return m("div", {onclick: ctrl.binds.bind(this, item)}, item.name);
+        })
+    ];
+}
+
+

As with our earlier examples, we put logic and UI state in a view model entity, and use it from our view. The <input> updates the term getter-setter via a binding, and the filter function takes care of slicing the data set to display only relevant matches as a user types.

+

The problem with this component, as it stands, is that the module and its view-model are singletons, so the component can only be used once in a page. Fortunately this is easy to fix: we can simply put the whole thing in a factory function.

+
var autocompleter = function() {
+    var autocompleter = {}
+    autocompleter.vm = {
+        term: m.prop(""),
+        search: function(value) {
+            autocompleter.vm.term(value.toLowerCase())
+        },
+        filter: function(item) {
+            return autocompleter.vm.term() && item.name.toLowerCase().indexOf(autocompleter.vm.term()) > -1
+        }
+    }
+    autocompleter.view = function(ctrl) {
+        return [
+            m("div", [
+                m("input", {oninput: m.withAttr("value", autocompleter.vm.search)})
+            ]),
+            ctrl.data().filter(autocompleter.vm.filter).map(function(item) {
+                return m("div", {onclick: ctrl.binds.bind(this, item)}, item.name);
+            })
+        ];
+    }
+    return autocompleter
+}
+
+

As you can see, the code is exactly the same as before, with the exception that it is wrapped in a function that returns the module. This allows us to easily create copies of the autocompleter:

+
//here's an example of using the autocompleter
+var dashboard = {}
+dashboard.controller = function() {
+    dashboard.vm.init()
+}
+dashboard.vm = {}
+dashboard.vm.init = function() {
+    this.users = m.prop([{id: 1, name: "John"}, {id: 2, name: "Bob"}, {id: 2, name: "Mary"}]);
+    this.selectedUser = m.prop()
+    this.userAC = new autocompleter()
+
+    this.projects = m.prop([{id: 1, name: "John's project"}, {id: 2, name: "Bob's project"}, {id: 2, name: "Mary's project"}]);
+    this.selectedProject = m.prop()
+    this.projectAC = new autocompleter()
+};
+
+dashboard.view = function() {
+    var vm = dashboard.vm
+    return m("div", [
+        vm.userAC.view({data: vm.users, binds: vm.selectedUser}),
+        vm.projectAC.view({data: vm.projects, binds: vm.selectedProject}),
+    ]);
+};
+
+//initialize
+m.module(document.body, dashboard);
+
+

In the usage example above, we created a dashboard top-level module, and instantiated two autocompleter modules, along with some data to populate the autocompleter, and getter-setters to bind data to.

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/getting-started.html b/archive/v0.1.33/getting-started.html new file mode 100644 index 00000000..1844bb4a --- /dev/null +++ b/archive/v0.1.33/getting-started.html @@ -0,0 +1,505 @@ + + + + Getting Started - Mithril + + + + +
+ +
+
+
+
+
+ +
+

Getting Started

+

What is Mithril?

+

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

+

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

+

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

+

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

+

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

+
+

A Simple Application

+

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

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

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

+
+

Model

+

In Mithril, an application typically lives in a namespace and contains modules. Modules are merely structures that represent a viewable "page" or a part of a page. In addition, an application can be organizationally divided into three major layers: Model, Controller and View.

+

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

+

In Mithril, a module is an object that contains two functions: controller and view.

+
//an empty Mithril module
+var myModule = {
+    controller: function() {},
+    view: function() {}
+}
+

In addition to holding a controller and a view, a module is typically also used to store data that pertains to it.

+

Let's create a module.

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

Typically, model entities are reusable and live outside of modules (e.g. var User = ...). In our example, since the whole application lives in one module, we're going to use the module as a namespace for our model entities.

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

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

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

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

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

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

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

According to the classic definition of the MVC pattern, the model layer is responsible for data storage, state management and business logic.

+

You can see that our classes above fit the criteria: they have all the methods and properties that they need to be assembled into a meaningful state. A Todo can be instantiated, and have its properties changed. The list can have todo items added to it via the push method. And so on.

+

View-Model

+

Our next step is to write a view-model that will use our model classes. A view-model is a model level entity that stores UI state. In many frameworks UI state is typically stored in a controller, but doing so makes the code harder to scale since controllers aren't designed to be data providers. In Mithril, UI state is understood to be model data, even though it doesn't necessarily map to a database ORM entity.

+

View-models are also responsible for handling business logic that revolves around UI-specific restrictions. For example a form might have an input and a cancel button. In such a case, it's the view-model's responsibility to track the current state of the input vs the original state and to apply a cancellation, if required. In the event the form was saved, then view-model would delegate saving to a more appropriate ORM model entity.

+

In the case of our todo application, the view-model needs a few things: it needs to track a running list of todos and a field for adding new todos, and it needs to handle the logic of adding to the todo and the implications of this action of the UI.

+
//define the view-model
+todo.vm = {
+    init: function() {
+        //a running list of todos
+        todo.vm.list = new todo.TodoList();
+
+        //a slot to store the name of a new todo before it is created
+        todo.vm.description = m.prop('');
+
+        //adds a todo to the list, and clears the description field for user convenience
+        todo.vm.add = function(description) {
+            if (description()) {
+                todo.vm.list.push(new todo.Todo({description: description()}));
+                todo.vm.description("");
+            }
+        };
+    }
+};
+
+

The code above defines a view-model object called vm. It is simply a javascript object that has a init function. This function initializes the vm object with three members: list, which is simply an array, description, which is an m.prop getter-setter function with an empty string as the initial value, and add, which is a method that adds a new Todo instance to list if an input description getter-setter is not an empty string.

+

Later in this guide, we'll pass the description property as the parameter to this function. When we get there, I'll explain why we're passing description as an argument instead of simply using OOP-style member association.

+

You can use the view-model like this:

+
//initialize our view-model
+todo.vm.init();
+
+todo.vm.description(); //[empty string]
+
+//try adding a to-do
+todo.vm.add(todo.vm.description);
+todo.vm.list.length; //0, because you can't add a to-do with an empty description
+
+//add it properly
+todo.vm.description("Write code");
+todo.vm.add(todo.vm.description);
+todo.vm.list.length; //1
+
+
+

Controller

+

In classic MVC, the role of the controller is to dispatch actions from the view to the model layer. In traditional server-side frameworks, the controller layer is of large significance because the nature of HTTP requests, responses and the framework abstractions that are exposed to developers require that the controller act as an adapter layer to transform the serialized data from HTTP requests to something that can be passed to ORM model methods.

+

In client-side MVC, however, this dissonance doesn't exist, and controllers can be extremely simple. Mithril controllers can be stripped down to a bare minimum, so that they only perform a single essential role: to expose a scoped set of model-level functionality. As you may recall, models are responsible for encapsulating business logic, and view-models encapsulate logic that pertains specifically to UI state, so there's really nothing else for a controller to abstract away, and all it needs to do is expose a slice of the model layer that pertains to the UI that is currently in view.

+

In other words, all our controller needs to do is this:

+
todo.controller = function() {
+    todo.vm.init()
+}
+
+
+

View

+

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

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

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

+

For the purposes of testing out our code so far, the view can be rendered using the m.render method:

+
m.render(document, todo.view());
+
+

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

+

This renders the following markup:

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

Note that m.render is a very low level method in Mithril that draws only once and doesn't attempt to run the auto-redrawing system. In order to enable auto-redrawing, the todo module must be initialized by either calling m.module or by creating a route definition with m.route. Also note that, unlike observable-based frameworks like Knockout.js, setting a value in a m.prop getter-setter does NOT trigger redrawing side-effects in Mithril.

+
+

Data Bindings

+

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

+
//binding a model value to an input in a template
+m("input", {value: todo.vm.description()})
+
+

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

+
todo.vm.init();
+
+todo.vm.description(); // empty string
+m.render(document, todo.view()); // input is blank
+
+todo.vm.description("Write code"); //set the description in the controller
+m.render(document, todo.view()); // input now says "Write code"
+
+

At a glance it may seem like we're doing something very expensive by redrawing, but as it turns out, calling the todo.view method multiple times does not actually re-render the entire template. Internally, Mithril keeps a virtual representation of the DOM in cache, scans for changes, and then only modifies the absolute minimum required to apply the change to the DOM. In practice, this results in surprisingly fast re-rendering.

+

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

+

Note that the example above only sets the value of the input element in the DOM, but it never reads it. This means that typing something on the input and then re-rendering will clobber the text on screen.

+
+

Fortunately, bindings can also be bi-directional: that is, they can be coded in such a way that, in addition to setting the DOM value, it's also possible to read it as a user types, and then update the description getter-setter in the view-model.

+

Here's the most basic way of implementing the view-to-model part of the binding:

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

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

+

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

+

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

+

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

+

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

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

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

+
+

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

+
var vm = todo.vm
+
+m("button", {onclick: vm.add.bind(vm, vm.description)}, "Add")
+
+

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

+

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

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

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

+

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

+

Mithril uses them in other interesting ways elsewhere.

+

Clever readers will probably notice that we can refactor the add method to make it much simpler:

+
vm.add = function() {
+    if (vm.description()) {
+        vm.list.push(new todo.Todo({description: vm.description()}));
+        vm.description("");
+    }
+};
+
+

The difference with the modified version is that add no longer takes an argument.

+

With this, we can make the onclick binding on the template much simpler:

+
m("button", {onclick: todo.vm.add}, "Add")
+

The only reason I talked about partial application here was to make you aware of that technique, since it becomes useful when dealing with parameterized event handlers. In real life, given a choice, you should always pick the simplest idiom for your use case.

+
+

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

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

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

+

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

+

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

+
+

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

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

Here are the highlights of the template above:

+
    +
  • The template is rendered as a child of the implicit <html> element of the document.
  • +
  • The text input saves its value to the todo.vm.description getter-setter we defined earlier.
  • +
  • The button calls the todo.vm.add method when clicked.
  • +
  • The table lists all the existing to-dos, if any.
  • +
  • The checkboxes save their value to the task.done getter setter.
  • +
  • The description gets crossed out via CSS if the task is marked as done.
  • +
  • When updates happen, the template is not wholly re-rendered - only the changes are applied.
  • +
+
+

So far, we've been using m.render to manually redraw after we made a change to the data. However, as I mentioned before, you can enable an auto-redrawing system, by initializing the todo module via m.module.

+
//render the todo module inside the document DOM node
+m.module(document, {controller: todo.controller, view: todo.view});
+
+

Mithril's auto-redrawing system keeps track of controller stability, and only redraws the view once it detects that the controller has finished running all of its code, including asynchronous AJAX payloads. Likewise, it intelligently waits for asynchronous services inside event handlers to complete before redrawing.

+

You can learn more about how redrawing heuristics work here.

+
+

Summary

+

Here's the application code in its entirety:

+
<!doctype html>
+<script src="mithril.min.js"></script>
+<script>
+//this application only has one module: todo
+var todo = {};
+
+//for simplicity, we use this module to namespace the model classes
+
+//the Todo class has two properties
+todo.Todo = function(data) {
+    this.description = m.prop(data.description);
+    this.done = m.prop(false);
+};
+
+//the TodoList class is a list of Todo's
+todo.TodoList = Array;
+
+//the view-model tracks a running list of todos,
+//stores a description for new todos before they are created
+//and takes care of the logic surrounding when adding is permitted
+//and clearing the input after adding a todo to the list
+todo.vm = (function() {
+    var vm = {}
+    vm.init = function() {
+        //a running list of todos
+        vm.list = new todo.TodoList();
+
+        //a slot to store the name of a new todo before it is created
+        vm.description = m.prop("");
+
+        //adds a todo to the list, and clears the description field for user convenience
+        vm.add = function() {
+            if (vm.description()) {
+                vm.list.push(new todo.Todo({description: vm.description()}));
+                vm.description("");
+            }
+        };
+    }
+    return vm
+}())
+
+//the controller defines what part of the model is relevant for the current page
+//in our case, there's only one view-model that handles everything
+todo.controller = function() {
+    todo.vm.init()
+}
+
+//here's the view
+todo.view = function() {
+    return m("html", [
+        m("body", [
+            m("input", {onchange: m.withAttr("value", todo.vm.description), value: todo.vm.description()}),
+            m("button", {onclick: todo.vm.add}, "Add"),
+            m("table", [
+                todo.vm.list.map(function(task, index) {
+                    return m("tr", [
+                        m("td", [
+                            m("input[type=checkbox]", {onclick: m.withAttr("checked", task.done), checked: task.done()})
+                        ]),
+                        m("td", {style: {textDecoration: task.done() ? "line-through" : "none"}}, task.description()),
+                    ])
+                })
+            ])
+        ])
+    ]);
+};
+
+//initialize the application
+m.module(document, {controller: todo.controller, view: todo.view});
+</script>
+
+
+

Notes on Architecture

+

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

+

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

+

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

+

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

+

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

+

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

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

becomes:

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

In the view-model, we aliased the native Array class for TodoList. Be aware that by using the native Array class, we're making an implicit statement that we are going to support all of the standard Array methods as part of our API.

+

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

+

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

+

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

+
todo.TodoList = Array;
+
+

becomes:

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

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

+
+

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

+

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

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

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

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

    +
  • +
  • You get the ability to automate linting, unit testing and minifying of the entire view layer.

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

    +
  • +
+

And if you really do want to use HTML syntax after all, you can use a package called MSX.

+

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

+

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

+

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

+

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

+

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

+

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

+
+

Learn More

+

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

+ +

Advanced Topics

+ +

Misc

+ + +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/how-to-read-signatures.html b/archive/v0.1.33/how-to-read-signatures.html new file mode 100644 index 00000000..45d688c3 --- /dev/null +++ b/archive/v0.1.33/how-to-read-signatures.html @@ -0,0 +1,208 @@ + + + + How to Read Signatures - Mithril + + + + +
+ +
+
+
+
+
+ +
+

How to Read Signatures

+

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

+

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

+

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

+
ReturnType methodName(ParameterType1 param1, ParameterType2 param2)
+
+

Optional Parameters

+

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

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

Type Placeholders

+

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

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

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

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

Arrays

+

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

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

Objects as Key-Value Maps

+

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

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

Objects as Class Interfaces

+

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

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

Type Aliasing

+

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

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

Mixin Types

+

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

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

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

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

Polymorphic Types

+

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

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

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

+

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

+
void test(Object { any | void config(DOMElement) } value)
+
+
//example of a valid function call
+test({ first: "first", config: function(element) { /*do stuff*/ } })
+
+

Nullable Types

+

A question mark ? after a type denotes that a value can be either of that type or undefined.

+
XMLHttpRequest? config()
+
+

In the example above, the config function is expected to return either an instance of the XMLHttpRequest object or undefined

+

Splat

+

An ellipsis ... means that an array argument can instead be a list of arguments

+
VirtualElement m(String selector [, Attributes attributes] [, Children... children])
+
+

In the example above, we can define children as an array:

+
m("div", {title: "foo"}, ["child 1", "child 2", "child 3"])
+
+

And we also define it as a list of arguments (note that lack of square brackets)

+
m("div", {title: "foo"}, "child 1", "child 2", "child 3")
+
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/index.html b/archive/v0.1.33/index.html new file mode 100644 index 00000000..5ceedc77 --- /dev/null +++ b/archive/v0.1.33/index.html @@ -0,0 +1,267 @@ + + + + Mithril + + + + +
+ +
+ +
+
+
+

Mithril

+ +

A Javascript Framework for Building Brilliant Applications

+ +

+ Guide + Download v0.1.33 +

+ + + + + + + Flattr +
+
+ +
+
+

What is Mithril?

+

Mithril is a client-side MVC framework - a tool to organize code in a way that is easy to think about and to maintain.

+
+
+
+

Light-weight

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

Robust

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

Fast

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

Sample code

+ +
//namespace
+var app = {};
+
+//model
+app.PageList = function() {
+	return m.request({method: "GET", url: "pages.json"});
+};
+
+//controller
+app.controller = function() {
+	var pages = app.PageList();
+	return {
+		pages: pages,
+		rotate: function() {
+			pages().push(pages().shift());
+		}
+	}
+};
+
+//view
+app.view = function(ctrl) {
+	return [
+		ctrl.pages().map(function(page) {
+			return m("a", {href: page.url}, page.title);
+		}),
+		m("button", {onclick: ctrl.rotate}, "Rotate links")
+	];
+};
+
+//initialize
+m.module(document.getElementById("example"), app);
+ +
+
+

Output

+
+ + +
+
+
+
+ +
+
+

Performance

+

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

+
+
+

Loading

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

Rendering

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

Safety

+

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

+

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

+
+
+

Test Summary

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

Guide

+

Build a simple application. Learn the ropes

+

Read Guide

+
+
+ +
+
+

API

+

Docs and code samples for your reference

+

Read docs

+
+
+ +
+
+

Learn Mithril

+

Weekly articles on how to use Mithril to its full potential.

+

Read articles

+
+
+ +
+
+

Mailing List

+

Post questions and discuss Mithril related topics.

+

Go to mailing list

+
+
+
+
+ + +
+
+
+ Released under the MIT license
+ © 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/installation.html b/archive/v0.1.33/installation.html new file mode 100644 index 00000000..b577e280 --- /dev/null +++ b/archive/v0.1.33/installation.html @@ -0,0 +1,122 @@ + + + + Installation - Mithril + + + + +
+ +
+
+
+
+
+ +
+

Installation

+

Mithril is available from a variety of sources:

+
+

Direct download

+

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

+

Links to older versions can be found in the change log.

+

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

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

Note that in order to support older versions of IE, you need to include some shims.

+
+

CDNs (Content Delivery Networks)

+

You can also find Mithril in cdnjs and jsDelivr.

+

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

+

cdnjs

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

jsDelivr

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

NPM

+

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

+

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

+
npm install mithril
+

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

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

Bower

+

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

+

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

+
npm install -g bower
+

And you can download Mithril by typing this:

+
bower install mithril
+

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

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

Component

+

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

+

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

+
npm install -g component
+

And you can download Mithril by typing this:

+
component install lhorie/mithril
+

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

+
<script src="/components/lhorie/mithril/master/mithril.js"></script>
+
+
+

Rails

+

Jordan Humphreys created a gem to allow integration with Rails:

+

Mithril-Rails

+

It includes support for the MSX HTML templating syntax from Jonathan Buchanan.

+
+

Github

+

You can also fork the latest stable project directly from Github.

+

If you want to use the bleeding edge version, you can fork the development repository.

+

Note that Mithril uses the next branch as the stable branch, instead of master, because contributors usually use master for pull requests. Therefore, the master branch should be considered unstable, and should not be used.

+

Be aware that even though Mithril has tests running in a continuous integration environment, the bleeding edge version might occasionally break. If you're interested in helping improve Mithril, you're welcome to use the bleeding edge version and report any bugs that you find.

+

In order to update a forked version of Mithril, follow the instructions on this page.

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/integration.html b/archive/v0.1.33/integration.html new file mode 100644 index 00000000..3537a0f1 --- /dev/null +++ b/archive/v0.1.33/integration.html @@ -0,0 +1,162 @@ + + + + Integrating with Other Libraries - Mithril + + + + +
+ +
+
+
+
+
+ +
+

Integrating with Other Libraries

+

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

+

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

+

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

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

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+
+

Integrating to legacy code

+

If you need to add separate widgets to different places on a same page, you can simply initialize each widget as you would a regular Mithril application (i.e. use m.render, m.module or m.route).

+

There's just one caveat: while simply initializing multiple "islands" in this fashion works, their initialization calls are not aware of each other and can cause redraws too frequently. To optimize rendering, you should add a m.startComputation call before the first widget initialization call, and a m.endComputation after the last widget initialization call in each execution thread.

+
m.startComputation()
+
+m.module(document.getElementById("widget1-container"), widget1)
+
+m.module(document.getElementById("widget2-container"), widget1)
+
+m.endComputation()
+
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/lib/prism/prism.css b/archive/v0.1.33/lib/prism/prism.css new file mode 100644 index 00000000..1e61e11d --- /dev/null +++ b/archive/v0.1.33/lib/prism/prism.css @@ -0,0 +1,126 @@ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.builtin { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { + color: #a67f59; + background: hsla(0,0%,100%,.5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + + +.token.regex, +.token.important { + color: #e90; +} + +.token.important { + font-weight: bold; +} + +.token.entity { + cursor: help; +} + diff --git a/archive/v0.1.33/lib/prism/prism.js b/archive/v0.1.33/lib/prism/prism.js new file mode 100644 index 00000000..b7f84870 --- /dev/null +++ b/archive/v0.1.33/lib/prism/prism.js @@ -0,0 +1,9 @@ +/** + * Prism: Lightweight, robust, elegant syntax highlighting + * MIT license http://www.opensource.org/licenses/mit-license.php/ + * @author Lea Verou http://lea.verou.me + */(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.util.clone(e[i]));return r;case"Array":return e.slice()}return e}},languages:{extend:function(e,n){var r=t.util.clone(t.languages[e]);for(var i in n)r[i]=n[i];return r},insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);t.util.type(e)==="Object"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent;if(!f)return;f=f.replace(/&/g,"&").replace(/e.length)break e;if(p instanceof i)continue;a.lastIndex=0;var d=a.exec(p);if(d){l&&(c=d[1].length);var v=d.index-1+c,d=d[0].slice(c),m=d.length,g=v+m,y=p.slice(0,v+1),b=p.slice(g+1),w=[h,1];y&&w.push(y);var E=new i(u,f?t.tokenize(d,f):d);w.push(E);b&&w.push(b);Array.prototype.splice.apply(s,w)}}}return s},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e,r,i){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]")return e.map(function(t){return n.stringify(t,r,e)}).join("");var s={type:e.type,content:n.stringify(e.content,r,i),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:i};s.type=="comment"&&(s.attributes.spellcheck="true");t.hooks.run("wrap",s);var o="";for(var u in s.attributes)o+=u+'="'+(s.attributes[u]||"")+'"';return"<"+s.tag+' class="'+s.classes.join(" ")+'" '+o+">"+s.content+""};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})();; +Prism.languages.markup={comment:/<!--[\w\W]*?-->/g,prolog:/<\?.+?\?>/,doctype:/<!DOCTYPE.+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|\w+))?\s*)*\/?>/gi,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,inside:{punctuation:/=|>|"/g}},punctuation:/\/?>/g,"attr-name":{pattern:/[\w:-]+/g,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/gi};Prism.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))});; +Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/ig,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,"function":{pattern:/[a-z0-9_]+\(/ig,inside:{punctuation:/\(/}}, number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|(&){1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g}; +; +Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|get|set|new|with|typeof|try|throw|catch|finally|null|break|continue)\b/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g});Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}});Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<|<)script[\w\W]*?(>|>)[\w\W]*?(<|<)\/script(>|>)/ig,inside:{tag:{pattern:/(<|<)script[\w\W]*?(>|>)|(<|<)\/script(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});; diff --git a/archive/v0.1.33/mithril-tests.js b/archive/v0.1.33/mithril-tests.js new file mode 100644 index 00000000..2f633609 --- /dev/null +++ b/archive/v0.1.33/mithril-tests.js @@ -0,0 +1,4126 @@ +var m = (function app(window, undefined) { + var OBJECT = "[object Object]", ARRAY = "[object Array]", STRING = "[object String]", FUNCTION = "function"; + var type = {}.toString; + var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/; + var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/; + + // caching commonly used variables + var $document, $location, $requestAnimationFrame, $cancelAnimationFrame; + + // self invoking function needed because of the way mocks work + function initialize(window){ + $document = window.document; + $location = window.location; + $cancelAnimationFrame = window.cancelAnimationFrame || window.clearTimeout; + $requestAnimationFrame = window.requestAnimationFrame || window.setTimeout; + } + + initialize(window); + + + /** + * @typedef {String} Tag + * A string that looks like -> div.classname#id[param=one][param2=two] + * Which describes a DOM node + */ + + /** + * + * @param {Tag} The DOM node tag + * @param {Object=[]} optional key-value pairs to be mapped to DOM attrs + * @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array, or splat (optional) + * + */ + function m() { + var args = [].slice.call(arguments); + var hasAttrs = args[1] != null && type.call(args[1]) === OBJECT && !("tag" in args[1]) && !("subtree" in args[1]); + var attrs = hasAttrs ? args[1] : {}; + var classAttrName = "class" in attrs ? "class" : "className"; + var cell = {tag: "div", attrs: {}}; + var match, classes = []; + if (type.call(args[0]) != STRING) throw new Error("selector in m(selector, attrs, children) should be a string") + while (match = parser.exec(args[0])) { + if (match[1] === "" && match[2]) cell.tag = match[2]; + else if (match[1] === "#") cell.attrs.id = match[2]; + else if (match[1] === ".") classes.push(match[2]); + else if (match[3][0] === "[") { + var pair = attrParser.exec(match[3]); + cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" :true) + } + } + if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" "); + + + var children = hasAttrs ? args.slice(2) : args.slice(1); + if (children.length === 1 && type.call(children[0]) === ARRAY) { + cell.children = children[0] + } + else { + cell.children = children + } + + for (var attrName in attrs) { + if (attrName === classAttrName) { + var className = cell.attrs[attrName] + cell.attrs[attrName] = (className && attrs[attrName] ? className + " " : className || "") + attrs[attrName]; + } + else cell.attrs[attrName] = attrs[attrName] + } + return cell + } + function build(parentElement, parentTag, parentCache, parentIndex, data, cached, shouldReattach, index, editable, namespace, configs) { + //`build` is a recursive function that manages creation/diffing/removal of DOM elements based on comparison between `data` and `cached` + //the diff algorithm can be summarized as this: + //1 - compare `data` and `cached` + //2 - if they are different, copy `data` to `cached` and update the DOM based on what the difference is + //3 - recursively apply this algorithm for every array and for the children of every virtual element + + //the `cached` data structure is essentially the same as the previous redraw's `data` data structure, with a few additions: + //- `cached` always has a property called `nodes`, which is a list of DOM elements that correspond to the data represented by the respective virtual element + //- in order to support attaching `nodes` as a property of `cached`, `cached` is *always* a non-primitive object, i.e. if the data was a string, then cached is a String instance. If data was `null` or `undefined`, cached is `new String("")` + //- `cached also has a `configContext` property, which is the state storage object exposed by config(element, isInitialized, context) + //- when `cached` is an Object, it represents a virtual element; when it's an Array, it represents a list of elements; when it's a String, Number or Boolean, it represents a text node + + //`parentElement` is a DOM element used for W3C DOM API calls + //`parentTag` is only used for handling a corner case for textarea values + //`parentCache` is used to remove nodes in some multi-node cases + //`parentIndex` and `index` are used to figure out the offset of nodes. They're artifacts from before arrays started being flattened and are likely refactorable + //`data` and `cached` are, respectively, the new and old nodes being diffed + //`shouldReattach` is a flag indicating whether a parent node was recreated (if so, and if this node is reused, then this node must reattach itself to the new parent) + //`editable` is a flag that indicates whether an ancestor is contenteditable + //`namespace` indicates the closest HTML namespace as it cascades down from an ancestor + //`configs` is a list of config functions to run after the topmost `build` call finishes running + + //there's logic that relies on the assumption that null and undefined data are equivalent to empty strings + //- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements (e.g. function foo() {if (cond) return m("div")} + //- it simplifies diffing code + //data.toString() is null if data is the return value of Console.log in Firefox + try {if (data == null || data.toString() == null) data = "";} catch (e) {data = ""} + if (data.subtree === "retain") return cached; + var cachedType = type.call(cached), dataType = type.call(data); + if (cached == null || cachedType !== dataType) { + if (cached != null) { + if (parentCache && parentCache.nodes) { + var offset = index - parentIndex; + var end = offset + (dataType === ARRAY ? data : cached.nodes).length; + clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end)) + } + else if (cached.nodes) clear(cached.nodes, cached) + } + cached = new data.constructor; + if (cached.tag) cached = {}; //if constructor creates a virtual dom element, use a blank object as the base cached node instead of copying the virtual el (#277) + cached.nodes = [] + } + + if (dataType === ARRAY) { + //recursively flatten array + for (var i = 0, len = data.length; i < len; i++) { + if (type.call(data[i]) === ARRAY) { + data = data.concat.apply([], data); + i-- //check current index again and flatten until there are no more nested arrays at that index + len = data.length + } + } + + var nodes = [], intact = cached.length === data.length, subArrayCount = 0; + + //keys algorithm: sort elements without recreating them if keys are present + //1) create a map of all existing keys, and mark all for deletion + //2) add new keys to map and mark them for addition + //3) if key exists in new list, change action from deletion to a move + //4) for each key, handle its corresponding action as marked in previous steps + //5) copy unkeyed items into their respective gaps + var DELETION = 1, INSERTION = 2 , MOVE = 3; + var existing = {}, unkeyed = [], shouldMaintainIdentities = false; + for (var i = 0; i < cached.length; i++) { + if (cached[i] && cached[i].attrs && cached[i].attrs.key != null) { + shouldMaintainIdentities = true; + existing[cached[i].attrs.key] = {action: DELETION, index: i} + } + } + + data = data.filter(function(x) {return x != null}) + + var guid = 0 + for (var i = 0, len = data.length; i < len; i++) { + if (data[i] && data[i].attrs && data[i].attrs.key == null) data[i].attrs.key = "__mithril__" + guid++ + } + + if (shouldMaintainIdentities) { + var keysDiffer = false + if (data.length != cached.length) keysDiffer = true + else for (var i = 0, cachedCell, dataCell; cachedCell = cached[i], dataCell = data[i]; i++) { + if (cachedCell.attrs && dataCell.attrs && cachedCell.attrs.key != dataCell.attrs.key) { + keysDiffer = true + break + } + } + + if (keysDiffer) { + for (var i = 0, len = data.length; i < len; i++) { + if (data[i] && data[i].attrs) { + if (data[i].attrs.key != null) { + var key = data[i].attrs.key; + if (!existing[key]) existing[key] = {action: INSERTION, index: i}; + else existing[key] = { + action: MOVE, + index: i, + from: existing[key].index, + element: cached.nodes[existing[key].index] || $document.createElement("div") + } + } + else unkeyed.push({index: i, element: parentElement.childNodes[i] || $document.createElement("div")}) + } + } + var actions = [] + for (var prop in existing) actions.push(existing[prop]) + var changes = actions.sort(sortChanges); + var newCached = new Array(cached.length) + newCached.nodes = cached.nodes.slice() + + for (var i = 0, change; change = changes[i]; i++) { + if (change.action === DELETION) { + clear(cached[change.index].nodes, cached[change.index]); + newCached.splice(change.index, 1) + } + if (change.action === INSERTION) { + var dummy = $document.createElement("div"); + dummy.key = data[change.index].attrs.key; + parentElement.insertBefore(dummy, parentElement.childNodes[change.index] || null); + newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]}) + newCached.nodes[change.index] = dummy + } + + if (change.action === MOVE) { + if (parentElement.childNodes[change.index] !== change.element && change.element !== null) { + parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null) + } + newCached[change.index] = cached[change.from] + newCached.nodes[change.index] = change.element + } + } + for (var i = 0, len = unkeyed.length; i < len; i++) { + var change = unkeyed[i]; + parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null); + newCached[change.index] = cached[change.index] + newCached.nodes[change.index] = change.element + } + cached = newCached; + } + } + //end key algorithm + + for (var i = 0, cacheCount = 0, len = data.length; i < len; i++) { + //diff each item in the array + var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs); + if (item === undefined) continue; + if (!item.nodes.intact) intact = false; + if (item.$trusted) { + //fix offset of next element if item was a trusted string w/ more than one html element + //the first clause in the regexp matches elements + //the second clause (after the pipe) matches text nodes + subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || [0]).length + } + else subArrayCount += type.call(item) === ARRAY ? item.length : 1; + cached[cacheCount++] = item + } + if (!intact) { + //diff the array itself + + //update the list of DOM nodes by collecting the nodes from each item + for (var i = 0, len = data.length; i < len; i++) { + if (cached[i] != null) nodes.push.apply(nodes, cached[i].nodes) + } + //remove items from the end of the array if the new array is shorter than the old one + //if errors ever happen here, the issue is most likely a bug in the construction of the `cached` data structure somewhere earlier in the program + for (var i = 0, node; node = cached.nodes[i]; i++) { + if (node.parentNode != null && nodes.indexOf(node) < 0) clear([node], [cached[i]]) + } + if (data.length < cached.length) cached.length = data.length; + cached.nodes = nodes + } + } + else if (data != null && dataType === OBJECT) { + if (!data.attrs) data.attrs = {}; + if (!cached.attrs) cached.attrs = {}; + + var dataAttrKeys = Object.keys(data.attrs) + var hasKeys = dataAttrKeys.length > ("key" in data.attrs ? 1 : 0) + //if an element is different enough from the one in cache, recreate it + if (data.tag != cached.tag || dataAttrKeys.join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id || (m.redraw.strategy() == "all" && cached.configContext && cached.configContext.retain !== true) || (m.redraw.strategy() == "diff" && cached.configContext && cached.configContext.retain === false)) { + if (cached.nodes.length) clear(cached.nodes); + if (cached.configContext && typeof cached.configContext.onunload === FUNCTION) cached.configContext.onunload() + } + if (type.call(data.tag) != STRING) return; + + var node, isNew = cached.nodes.length === 0; + if (data.attrs.xmlns) namespace = data.attrs.xmlns; + else if (data.tag === "svg") namespace = "http://www.w3.org/2000/svg"; + else if (data.tag === "math") namespace = "http://www.w3.org/1998/Math/MathML"; + if (isNew) { + if (data.attrs.is) node = namespace === undefined ? $document.createElement(data.tag, data.attrs.is) : $document.createElementNS(namespace, data.tag, data.attrs.is); + else node = namespace === undefined ? $document.createElement(data.tag) : $document.createElementNS(namespace, data.tag); + cached = { + tag: data.tag, + //set attributes first, then create children + attrs: hasKeys ? setAttributes(node, data.tag, data.attrs, {}, namespace) : data.attrs, + children: data.children != null && data.children.length > 0 ? + build(node, data.tag, undefined, undefined, data.children, cached.children, true, 0, data.attrs.contenteditable ? node : editable, namespace, configs) : + data.children, + nodes: [node] + }; + if (cached.children && !cached.children.nodes) cached.children.nodes = []; + //edge case: setting value on doesn't work before children exist, so set it again after children have been created + if (data.tag === "select" && data.attrs.value) setAttributes(node, data.tag, {value: data.attrs.value}, {}, namespace); + parentElement.insertBefore(node, parentElement.childNodes[index] || null) + } + else { + node = cached.nodes[0]; + if (hasKeys) setAttributes(node, data.tag, data.attrs, cached.attrs, namespace); + cached.children = build(node, data.tag, undefined, undefined, data.children, cached.children, false, 0, data.attrs.contenteditable ? node : editable, namespace, configs); + cached.nodes.intact = true; + if (shouldReattach === true && node != null) parentElement.insertBefore(node, parentElement.childNodes[index] || null) + } + //schedule configs to be called. They are called after `build` finishes running + if (typeof data.attrs["config"] === FUNCTION) { + var context = cached.configContext = cached.configContext || {retain: (m.redraw.strategy() == "diff") || undefined}; + + // bind + var callback = function(data, args) { + return function() { + return data.attrs["config"].apply(data, args) + } + }; + configs.push(callback(data, [node, !isNew, context, cached])) + } + } + else if (typeof data != FUNCTION) { + //handle text nodes + var nodes; + if (cached.nodes.length === 0) { + if (data.$trusted) { + nodes = injectHTML(parentElement, index, data) + } + else { + nodes = [$document.createTextNode(data)]; + if (!parentElement.nodeName.match(voidElements)) parentElement.insertBefore(nodes[0], parentElement.childNodes[index] || null) + } + cached = "string number boolean".indexOf(typeof data) > -1 ? new data.constructor(data) : data; + cached.nodes = nodes + } + else if (cached.valueOf() !== data.valueOf() || shouldReattach === true) { + nodes = cached.nodes; + if (!editable || editable !== $document.activeElement) { + if (data.$trusted) { + clear(nodes, cached); + nodes = injectHTML(parentElement, index, data) + } + else { + //corner case: replacing the nodeValue of a text node that is a child of a textarea/contenteditable doesn't work + //we need to update the value property of the parent textarea or the innerHTML of the contenteditable element instead + if (parentTag === "textarea") parentElement.value = data; + else if (editable) editable.innerHTML = data; + else { + if (nodes[0].nodeType === 1 || nodes.length > 1) { //was a trusted string + clear(cached.nodes, cached); + nodes = [$document.createTextNode(data)] + } + parentElement.insertBefore(nodes[0], parentElement.childNodes[index] || null); + nodes[0].nodeValue = data + } + } + } + cached = new data.constructor(data); + cached.nodes = nodes + } + else cached.nodes.intact = true + } + + return cached + } + function sortChanges(a, b) {return a.action - b.action || a.index - b.index} + function setAttributes(node, tag, dataAttrs, cachedAttrs, namespace) { + for (var attrName in dataAttrs) { + var dataAttr = dataAttrs[attrName]; + var cachedAttr = cachedAttrs[attrName]; + if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr)) { + cachedAttrs[attrName] = dataAttr; + try { + //`config` isn't a real attributes, so ignore it + if (attrName === "config" || attrName == "key") continue; + //hook event handlers to the auto-redrawing system + else if (typeof dataAttr === FUNCTION && attrName.indexOf("on") === 0) { + node[attrName] = autoredraw(dataAttr, node) + } + //handle `style: {...}` + else if (attrName === "style" && dataAttr != null && type.call(dataAttr) === OBJECT) { + for (var rule in dataAttr) { + if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule] + } + for (var rule in cachedAttr) { + if (!(rule in dataAttr)) node.style[rule] = "" + } + } + //handle SVG + else if (namespace != null) { + if (attrName === "href") node.setAttributeNS("http://www.w3.org/1999/xlink", "href", dataAttr); + else if (attrName === "className") node.setAttribute("class", dataAttr); + else node.setAttribute(attrName, dataAttr) + } + //handle cases that are properties (but ignore cases where we should use setAttribute instead) + //- list and form are typically used as strings, but are DOM element references in js + //- when using CSS selectors (e.g. `m("[style='']")`), style is used as a string, but it's an object in js + else if (attrName in node && !(attrName === "list" || attrName === "style" || attrName === "form" || attrName === "type" || attrName === "width" || attrName === "height")) { + //#348 don't set the value if not needed otherwise cursor placement breaks in Chrome + if (tag !== "input" || node[attrName] !== dataAttr) node[attrName] = dataAttr + } + else node.setAttribute(attrName, dataAttr) + } + catch (e) { + //swallow IE's invalid argument errors to mimic HTML's fallback-to-doing-nothing-on-invalid-attributes behavior + if (e.message.indexOf("Invalid argument") < 0) throw e + } + } + //#348 dataAttr may not be a string, so use loose comparison (double equal) instead of strict (triple equal) + else if (attrName === "value" && tag === "input" && node.value != dataAttr) { + node.value = dataAttr + } + } + return cachedAttrs + } + function clear(nodes, cached) { + for (var i = nodes.length - 1; i > -1; i--) { + if (nodes[i] && nodes[i].parentNode) { + try {nodes[i].parentNode.removeChild(nodes[i])} + catch (e) {} //ignore if this fails due to order of events (see http://stackoverflow.com/questions/21926083/failed-to-execute-removechild-on-node) + cached = [].concat(cached); + if (cached[i]) unload(cached[i]) + } + } + if (nodes.length != 0) nodes.length = 0 + } + function unload(cached) { + if (cached.configContext && typeof cached.configContext.onunload === FUNCTION) { + cached.configContext.onunload(); + cached.configContext.onunload = null + } + if (cached.children) { + if (type.call(cached.children) === ARRAY) { + for (var i = 0, child; child = cached.children[i]; i++) unload(child) + } + else if (cached.children.tag) unload(cached.children) + } + } + function injectHTML(parentElement, index, data) { + var nextSibling = parentElement.childNodes[index]; + if (nextSibling) { + var isElement = nextSibling.nodeType != 1; + var placeholder = $document.createElement("span"); + if (isElement) { + parentElement.insertBefore(placeholder, nextSibling || null); + placeholder.insertAdjacentHTML("beforebegin", data); + parentElement.removeChild(placeholder) + } + else nextSibling.insertAdjacentHTML("beforebegin", data) + } + else parentElement.insertAdjacentHTML("beforeend", data); + var nodes = []; + while (parentElement.childNodes[index] !== nextSibling) { + nodes.push(parentElement.childNodes[index]); + index++ + } + return nodes + } + function autoredraw(callback, object) { + return function(e) { + e = e || event; + m.redraw.strategy("diff"); + m.startComputation(); + try {return callback.call(object, e)} + finally { + endFirstComputation() + } + } + } + + var html; + var documentNode = { + appendChild: function(node) { + if (html === undefined) html = $document.createElement("html"); + if ($document.documentElement && $document.documentElement !== node) { + $document.replaceChild(node, $document.documentElement) + } + else $document.appendChild(node); + this.childNodes = $document.childNodes + }, + insertBefore: function(node) { + this.appendChild(node) + }, + childNodes: [] + }; + var nodeCache = [], cellCache = {}; + m.render = function(root, cell, forceRecreation) { + var configs = []; + if (!root) throw new Error("Please ensure the DOM element exists before rendering a template into it."); + var id = getCellCacheKey(root); + var isDocumentRoot = root === $document; + var node = isDocumentRoot || root === $document.documentElement ? documentNode : root; + if (isDocumentRoot && cell.tag != "html") cell = {tag: "html", attrs: {}, children: cell}; + if (cellCache[id] === undefined) clear(node.childNodes); + if (forceRecreation === true) reset(root); + cellCache[id] = build(node, null, undefined, undefined, cell, cellCache[id], false, 0, null, undefined, configs); + for (var i = 0, len = configs.length; i < len; i++) configs[i]() + }; + function getCellCacheKey(element) { + var index = nodeCache.indexOf(element); + return index < 0 ? nodeCache.push(element) - 1 : index + } + + m.trust = function(value) { + value = new String(value); + value.$trusted = true; + return value + }; + + function gettersetter(store) { + var prop = function() { + if (arguments.length) store = arguments[0]; + return store + }; + + prop.toJSON = function() { + return store + }; + + return prop + } + + m.prop = function (store) { + //note: using non-strict equality check here because we're checking if store is null OR undefined + if (((store != null && type.call(store) === OBJECT) || typeof store === FUNCTION) && typeof store.then === FUNCTION) { + return propify(store) + } + + return gettersetter(store) + }; + + var roots = [], modules = [], controllers = [], lastRedrawId = null, lastRedrawCallTime = 0, computePostRedrawHook = null, prevented = false, topModule; + var FRAME_BUDGET = 16; //60 frames per second = 1 call per 16 ms + m.module = function(root, module) { + if (!root) throw new Error("Please ensure the DOM element exists before rendering a template into it."); + var index = roots.indexOf(root); + if (index < 0) index = roots.length; + var isPrevented = false; + if (controllers[index] && typeof controllers[index].onunload === FUNCTION) { + var event = { + preventDefault: function() {isPrevented = true} + }; + controllers[index].onunload(event) + } + if (!isPrevented) { + m.redraw.strategy("all"); + m.startComputation(); + roots[index] = root; + var currentModule = topModule = module = module || {}; + var controller = new (module.controller || function() {}); + //controllers may call m.module recursively (via m.route redirects, for example) + //this conditional ensures only the last recursive m.module call is applied + if (currentModule === topModule) { + controllers[index] = controller; + modules[index] = module + } + endFirstComputation(); + return controllers[index] + } + }; + m.redraw = function(force) { + //lastRedrawId is a positive number if a second redraw is requested before the next animation frame + //lastRedrawID is null if it's the first redraw and not an event handler + if (lastRedrawId && force !== true) { + //when setTimeout: only reschedule redraw if time between now and previous redraw is bigger than a frame, otherwise keep currently scheduled timeout + //when rAF: always reschedule redraw + if (new Date - lastRedrawCallTime > FRAME_BUDGET || $requestAnimationFrame === window.requestAnimationFrame) { + if (lastRedrawId > 0) $cancelAnimationFrame(lastRedrawId); + lastRedrawId = $requestAnimationFrame(redraw, FRAME_BUDGET) + } + } + else { + redraw(); + lastRedrawId = $requestAnimationFrame(function() {lastRedrawId = null}, FRAME_BUDGET) + } + }; + m.redraw.strategy = m.prop(); + var blank = function() {return ""} + function redraw() { + for (var i = 0, root; root = roots[i]; i++) { + if (controllers[i]) { + m.render(root, modules[i].view ? modules[i].view(controllers[i]) : blank()) + } + } + //after rendering within a routed context, we need to scroll back to the top, and fetch the document title for history.pushState + if (computePostRedrawHook) { + computePostRedrawHook(); + computePostRedrawHook = null + } + lastRedrawId = null; + lastRedrawCallTime = new Date; + m.redraw.strategy("diff") + } + + var pendingRequests = 0; + m.startComputation = function() {pendingRequests++}; + m.endComputation = function() { + pendingRequests = Math.max(pendingRequests - 1, 0); + if (pendingRequests === 0) m.redraw() + }; + var endFirstComputation = function() { + if (m.redraw.strategy() == "none") { + pendingRequests-- + m.redraw.strategy("diff") + } + else m.endComputation(); + } + + m.withAttr = function(prop, withAttrCallback) { + return function(e) { + e = e || event; + var currentTarget = e.currentTarget || this; + withAttrCallback(prop in currentTarget ? currentTarget[prop] : currentTarget.getAttribute(prop)) + } + }; + + //routing + var modes = {pathname: "", hash: "#", search: "?"}; + var redirect = function() {}, routeParams, currentRoute; + m.route = function() { + //m.route() + if (arguments.length === 0) return currentRoute; + //m.route(el, defaultRoute, routes) + else if (arguments.length === 3 && type.call(arguments[1]) === STRING) { + var root = arguments[0], defaultRoute = arguments[1], router = arguments[2]; + redirect = function(source) { + var path = currentRoute = normalizeRoute(source); + if (!routeByValue(root, router, path)) { + m.route(defaultRoute, true) + } + }; + var listener = m.route.mode === "hash" ? "onhashchange" : "onpopstate"; + window[listener] = function() { + var path = $location[m.route.mode] + if (m.route.mode === "pathname") path += $location.search + if (currentRoute != normalizeRoute(path)) { + redirect(path) + } + }; + computePostRedrawHook = setScroll; + window[listener]() + } + //config: m.route + else if (arguments[0].addEventListener || arguments[0].attachEvent) { + var element = arguments[0]; + var isInitialized = arguments[1]; + var context = arguments[2]; + element.href = (m.route.mode !== 'pathname' ? $location.pathname : '') + modes[m.route.mode] + this.attrs.href; + if (element.addEventListener) { + element.removeEventListener("click", routeUnobtrusive); + element.addEventListener("click", routeUnobtrusive) + } + else { + element.detachEvent("onclick", routeUnobtrusive); + element.attachEvent("onclick", routeUnobtrusive) + } + } + //m.route(route, params) + else if (type.call(arguments[0]) === STRING) { + var oldRoute = currentRoute; + currentRoute = arguments[0]; + var args = arguments[1] || {} + var queryIndex = currentRoute.indexOf("?") + var params = queryIndex > -1 ? parseQueryString(currentRoute.slice(queryIndex + 1)) : {} + for (var i in args) params[i] = args[i] + var querystring = buildQueryString(params) + var currentPath = queryIndex > -1 ? currentRoute.slice(0, queryIndex) : currentRoute + if (querystring) currentRoute = currentPath + (currentPath.indexOf("?") === -1 ? "?" : "&") + querystring; + + var shouldReplaceHistoryEntry = (arguments.length === 3 ? arguments[2] : arguments[1]) === true || oldRoute === arguments[0]; + + if (window.history.pushState) { + computePostRedrawHook = function() { + window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, $document.title, modes[m.route.mode] + currentRoute); + setScroll() + }; + redirect(modes[m.route.mode] + currentRoute) + } + else { + $location[m.route.mode] = currentRoute + redirect(modes[m.route.mode] + currentRoute) + } + } + }; + m.route.param = function(key) { + if (!routeParams) throw new Error("You must call m.route(element, defaultRoute, routes) before calling m.route.param()") + return routeParams[key] + }; + m.route.mode = "search"; + function normalizeRoute(route) { + return route.slice(modes[m.route.mode].length) + } + function routeByValue(root, router, path) { + routeParams = {}; + + var queryStart = path.indexOf("?"); + if (queryStart !== -1) { + routeParams = parseQueryString(path.substr(queryStart + 1, path.length)); + path = path.substr(0, queryStart) + } + + // Get all routes and check if there's + // an exact match for the current path + var keys = Object.keys(router); + var index = keys.indexOf(path); + if(index !== -1){ + m.module(root, router[keys [index]]); + return true; + } + + for (var route in router) { + if (route === path) { + m.module(root, router[route]); + return true + } + + var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$"); + + if (matcher.test(path)) { + path.replace(matcher, function() { + var keys = route.match(/:[^\/]+/g) || []; + var values = [].slice.call(arguments, 1, -2); + for (var i = 0, len = keys.length; i < len; i++) routeParams[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i]) + m.module(root, router[route]) + }); + return true + } + } + } + function routeUnobtrusive(e) { + e = e || event; + if (e.ctrlKey || e.metaKey || e.which === 2) return; + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + var currentTarget = e.currentTarget || e.srcElement; + var args = m.route.mode === "pathname" && currentTarget.search ? parseQueryString(currentTarget.search.slice(1)) : {}; + while (currentTarget && currentTarget.nodeName.toUpperCase() != "A") currentTarget = currentTarget.parentNode + m.route(currentTarget[m.route.mode].slice(modes[m.route.mode].length), args) + } + function setScroll() { + if (m.route.mode != "hash" && $location.hash) $location.hash = $location.hash; + else window.scrollTo(0, 0) + } + function buildQueryString(object, prefix) { + var duplicates = {} + var str = [] + for (var prop in object) { + var key = prefix ? prefix + "[" + prop + "]" : prop + var value = object[prop] + var valueType = type.call(value) + var pair = (value === null) ? encodeURIComponent(key) : + valueType === OBJECT ? buildQueryString(value, key) : + valueType === ARRAY ? value.reduce(function(memo, item) { + if (!duplicates[key]) duplicates[key] = {} + if (!duplicates[key][item]) { + duplicates[key][item] = true + return memo.concat(encodeURIComponent(key) + "=" + encodeURIComponent(item)) + } + return memo + }, []).join("&") : + encodeURIComponent(key) + "=" + encodeURIComponent(value) + if (value !== undefined) str.push(pair) + } + return str.join("&") + } + function parseQueryString(str) { + var pairs = str.split("&"), params = {}; + for (var i = 0, len = pairs.length; i < len; i++) { + var pair = pairs[i].split("="); + var key = decodeURIComponent(pair[0]) + var value = pair.length == 2 ? decodeURIComponent(pair[1]) : null + if (params[key] != null) { + if (type.call(params[key]) !== ARRAY) params[key] = [params[key]] + params[key].push(value) + } + else params[key] = value + } + return params + } + m.route.buildQueryString = buildQueryString + m.route.parseQueryString = parseQueryString + + function reset(root) { + var cacheKey = getCellCacheKey(root); + clear(root.childNodes, cellCache[cacheKey]); + cellCache[cacheKey] = undefined + } + + m.deferred = function () { + var deferred = new Deferred(); + deferred.promise = propify(deferred.promise); + return deferred + }; + function propify(promise, initialValue) { + var prop = m.prop(initialValue); + promise.then(prop); + prop.then = function(resolve, reject) { + return propify(promise.then(resolve, reject), initialValue) + }; + return prop + } + //Promiz.mithril.js | Zolmeister | MIT + //a modified version of Promiz.js, which does not conform to Promises/A+ for two reasons: + //1) `then` callbacks are called synchronously (because setTimeout is too slow, and the setImmediate polyfill is too big + //2) throwing subclasses of Error cause the error to be bubbled up instead of triggering rejection (because the spec does not account for the important use case of default browser error handling, i.e. message w/ line number) + function Deferred(successCallback, failureCallback) { + var RESOLVING = 1, REJECTING = 2, RESOLVED = 3, REJECTED = 4; + var self = this, state = 0, promiseValue = 0, next = []; + + self["promise"] = {}; + + self["resolve"] = function(value) { + if (!state) { + promiseValue = value; + state = RESOLVING; + + fire() + } + return this + }; + + self["reject"] = function(value) { + if (!state) { + promiseValue = value; + state = REJECTING; + + fire() + } + return this + }; + + self.promise["then"] = function(successCallback, failureCallback) { + var deferred = new Deferred(successCallback, failureCallback); + if (state === RESOLVED) { + deferred.resolve(promiseValue) + } + else if (state === REJECTED) { + deferred.reject(promiseValue) + } + else { + next.push(deferred) + } + return deferred.promise + }; + + function finish(type) { + state = type || REJECTED; + next.map(function(deferred) { + state === RESOLVED && deferred.resolve(promiseValue) || deferred.reject(promiseValue) + }) + } + + function thennable(then, successCallback, failureCallback, notThennableCallback) { + if (((promiseValue != null && type.call(promiseValue) === OBJECT) || typeof promiseValue === FUNCTION) && typeof then === FUNCTION) { + try { + // count protects against abuse calls from spec checker + var count = 0; + then.call(promiseValue, function(value) { + if (count++) return; + promiseValue = value; + successCallback() + }, function (value) { + if (count++) return; + promiseValue = value; + failureCallback() + }) + } + catch (e) { + m.deferred.onerror(e); + promiseValue = e; + failureCallback() + } + } else { + notThennableCallback() + } + } + + function fire() { + // check if it's a thenable + var then; + try { + then = promiseValue && promiseValue.then + } + catch (e) { + m.deferred.onerror(e); + promiseValue = e; + state = REJECTING; + return fire() + } + thennable(then, function() { + state = RESOLVING; + fire() + }, function() { + state = REJECTING; + fire() + }, function() { + try { + if (state === RESOLVING && typeof successCallback === FUNCTION) { + promiseValue = successCallback(promiseValue) + } + else if (state === REJECTING && typeof failureCallback === "function") { + promiseValue = failureCallback(promiseValue); + state = RESOLVING + } + } + catch (e) { + m.deferred.onerror(e); + promiseValue = e; + return finish() + } + + if (promiseValue === self) { + promiseValue = TypeError(); + finish() + } + else { + thennable(then, function () { + finish(RESOLVED) + }, finish, function () { + finish(state === RESOLVING && RESOLVED) + }) + } + }) + } + } + m.deferred.onerror = function(e) { + if (type.call(e) === "[object Error]" && !e.constructor.toString().match(/ Error/)) throw e + }; + + m.sync = function(args) { + var method = "resolve"; + function synchronizer(pos, resolved) { + return function(value) { + results[pos] = value; + if (!resolved) method = "reject"; + if (--outstanding === 0) { + deferred.promise(results); + deferred[method](results) + } + return value + } + } + + var deferred = m.deferred(); + var outstanding = args.length; + var results = new Array(outstanding); + if (args.length > 0) { + for (var i = 0; i < args.length; i++) { + args[i].then(synchronizer(i, true), synchronizer(i, false)) + } + } + else deferred.resolve([]); + + return deferred.promise + }; + function identity(value) {return value} + + function ajax(options) { + if (options.dataType && options.dataType.toLowerCase() === "jsonp") { + var callbackKey = "mithril_callback_" + new Date().getTime() + "_" + (Math.round(Math.random() * 1e16)).toString(36); + var script = $document.createElement("script"); + + window[callbackKey] = function(resp) { + script.parentNode.removeChild(script); + options.onload({ + type: "load", + target: { + responseText: resp + } + }); + window[callbackKey] = undefined + }; + + script.onerror = function(e) { + script.parentNode.removeChild(script); + + options.onerror({ + type: "error", + target: { + status: 500, + responseText: JSON.stringify({error: "Error making jsonp request"}) + } + }); + window[callbackKey] = undefined; + + return false + }; + + script.onload = function(e) { + return false + }; + + script.src = options.url + + (options.url.indexOf("?") > 0 ? "&" : "?") + + (options.callbackKey ? options.callbackKey : "callback") + + "=" + callbackKey + + "&" + buildQueryString(options.data || {}); + $document.body.appendChild(script) + } + else { + var xhr = new window.XMLHttpRequest; + xhr.open(options.method, options.url, true, options.user, options.password); + xhr.onreadystatechange = function() { + if (xhr.readyState === 4) { + if (xhr.status >= 200 && xhr.status < 300) options.onload({type: "load", target: xhr}); + else options.onerror({type: "error", target: xhr}) + } + }; + if (options.serialize === JSON.stringify && options.data && options.method !== "GET") { + xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") + } + if (options.deserialize === JSON.parse) { + xhr.setRequestHeader("Accept", "application/json, text/*"); + } + if (typeof options.config === FUNCTION) { + var maybeXhr = options.config(xhr, options); + if (maybeXhr != null) xhr = maybeXhr + } + + var data = options.method === "GET" || !options.data ? "" : options.data + if (data && (type.call(data) != STRING && data.constructor != window.FormData)) { + throw "Request data should be either be a string or FormData. Check the `serialize` option in `m.request`"; + } + xhr.send(data); + return xhr + } + } + function bindData(xhrOptions, data, serialize) { + if (xhrOptions.method === "GET" && xhrOptions.dataType != "jsonp") { + var prefix = xhrOptions.url.indexOf("?") < 0 ? "?" : "&"; + var querystring = buildQueryString(data); + xhrOptions.url = xhrOptions.url + (querystring ? prefix + querystring : "") + } + else xhrOptions.data = serialize(data); + return xhrOptions + } + function parameterizeUrl(url, data) { + var tokens = url.match(/:[a-z]\w+/gi); + if (tokens && data) { + for (var i = 0; i < tokens.length; i++) { + var key = tokens[i].slice(1); + url = url.replace(tokens[i], data[key]); + delete data[key] + } + } + return url + } + + m.request = function(xhrOptions) { + if (xhrOptions.background !== true) m.startComputation(); + var deferred = new Deferred(); + var isJSONP = xhrOptions.dataType && xhrOptions.dataType.toLowerCase() === "jsonp"; + var serialize = xhrOptions.serialize = isJSONP ? identity : xhrOptions.serialize || JSON.stringify; + var deserialize = xhrOptions.deserialize = isJSONP ? identity : xhrOptions.deserialize || JSON.parse; + var extract = xhrOptions.extract || function(xhr) { + return xhr.responseText.length === 0 && deserialize === JSON.parse ? null : xhr.responseText + }; + xhrOptions.url = parameterizeUrl(xhrOptions.url, xhrOptions.data); + xhrOptions = bindData(xhrOptions, xhrOptions.data, serialize); + xhrOptions.onload = xhrOptions.onerror = function(e) { + try { + e = e || event; + var unwrap = (e.type === "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity; + var response = unwrap(deserialize(extract(e.target, xhrOptions)), e.target); + if (e.type === "load") { + if (type.call(response) === ARRAY && xhrOptions.type) { + for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i]) + } + else if (xhrOptions.type) response = new xhrOptions.type(response) + } + deferred[e.type === "load" ? "resolve" : "reject"](response) + } + catch (e) { + m.deferred.onerror(e); + deferred.reject(e) + } + if (xhrOptions.background !== true) m.endComputation() + }; + ajax(xhrOptions); + deferred.promise = propify(deferred.promise, xhrOptions.initialValue); + return deferred.promise + }; + + //testing API + m.deps = function(mock) { + initialize(window = mock || window); + return window; + }; + //for internal testing only, do not use `m.deps.factory` + m.deps.factory = app; + + return m +})(typeof window != "undefined" ? window : {}); + +if (typeof module != "undefined" && module !== null && module.exports) module.exports = m; +else if (typeof define === "function" && define.amd) define(function() {return m}); diff --git a/archive/v0.1.33/mithril.min.js b/archive/v0.1.33/mithril.min.js new file mode 100644 index 00000000..36d0bcff --- /dev/null +++ b/archive/v0.1.33/mithril.min.js @@ -0,0 +1,8 @@ +/* +Mithril v0.1.33 +http://github.com/lhorie/mithril.js +(c) Leo Horie +License: MIT +*/ +var m=function a(b,c){function d(a){C=a.document,D=a.location,F=a.cancelAnimationFrame||a.clearTimeout,E=a.requestAnimationFrame||a.setTimeout}function e(){var a,b=[].slice.call(arguments),c=!(null==b[1]||K.call(b[1])!==G||"tag"in b[1]||"subtree"in b[1]),d=c?b[1]:{},e="class"in d?"class":"className",f={tag:"div",attrs:{}},g=[];if(K.call(b[0])!=I)throw new Error("selector in m(selector, attrs, children) should be a string");for(;a=L.exec(b[0]);)if(""===a[1]&&a[2])f.tag=a[2];else if("#"===a[1])f.attrs.id=a[2];else if("."===a[1])g.push(a[2]);else if("["===a[3][0]){var h=M.exec(a[3]);f.attrs[h[1]]=h[3]||(h[2]?"":!0)}g.length>0&&(f.attrs[e]=g.join(" "));var i=b.slice(c?2:1);f.children=1===i.length&&K.call(i[0])===H?i[0]:i;for(var j in d)if(j===e){var k=f.attrs[j];f.attrs[j]=(k&&d[j]?k+" ":k||"")+d[j]}else f.attrs[j]=d[j];return f}function f(a,b,d,j,l,m,n,o,p,q,r){try{(null==l||null==l.toString())&&(l="")}catch(s){l=""}if("retain"===l.subtree)return m;var t=K.call(m),u=K.call(l);if(null==m||t!==u){if(null!=m)if(d&&d.nodes){var v=o-j,w=v+(u===H?l:m.nodes).length;i(d.nodes.slice(v,w),d.slice(v,w))}else m.nodes&&i(m.nodes,m);m=new l.constructor,m.tag&&(m={}),m.nodes=[]}if(u===H){for(var x=0,y=l.length;y>x;x++)K.call(l[x])===H&&(l=l.concat.apply([],l),x--,y=l.length);for(var z=[],A=m.length===l.length,B=0,D=1,E=2,F=3,L={},M=[],O=!1,x=0;xx;x++)l[x]&&l[x].attrs&&null==l[x].attrs.key&&(l[x].attrs.key="__mithril__"+P++);if(O){var Q=!1;if(l.length!=m.length)Q=!0;else for(var R,S,x=0;R=m[x],S=l[x];x++)if(R.attrs&&S.attrs&&R.attrs.key!=S.attrs.key){Q=!0;break}if(Q){for(var x=0,y=l.length;y>x;x++)if(l[x]&&l[x].attrs)if(null!=l[x].attrs.key){var T=l[x].attrs.key;L[T]=L[T]?{action:F,index:x,from:L[T].index,element:m.nodes[L[T].index]||C.createElement("div")}:{action:E,index:x}}else M.push({index:x,element:a.childNodes[x]||C.createElement("div")});var U=[];for(var V in L)U.push(L[V]);var W=U.sort(g),X=new Array(m.length);X.nodes=m.nodes.slice();for(var Y,x=0;Y=W[x];x++){if(Y.action===D&&(i(m[Y.index].nodes,m[Y.index]),X.splice(Y.index,1)),Y.action===E){var Z=C.createElement("div");Z.key=l[Y.index].attrs.key,a.insertBefore(Z,a.childNodes[Y.index]||null),X.splice(Y.index,0,{attrs:{key:l[Y.index].attrs.key},nodes:[Z]}),X.nodes[Y.index]=Z}Y.action===F&&(a.childNodes[Y.index]!==Y.element&&null!==Y.element&&a.insertBefore(Y.element,a.childNodes[Y.index]||null),X[Y.index]=m[Y.from],X.nodes[Y.index]=Y.element)}for(var x=0,y=M.length;y>x;x++){var Y=M[x];a.insertBefore(Y.element,a.childNodes[Y.index]||null),X[Y.index]=m[Y.index],X.nodes[Y.index]=Y.element}m=X}}for(var x=0,$=0,y=l.length;y>x;x++){var _=f(a,b,m,o,l[x],m[$],n,o+B||B,p,q,r);_!==c&&(_.nodes.intact||(A=!1),B+=_.$trusted?(_.match(/<[^\/]|\>\s*[^<]/g)||[0]).length:K.call(_)===H?_.length:1,m[$++]=_)}if(!A){for(var x=0,y=l.length;y>x;x++)null!=m[x]&&z.push.apply(z,m[x].nodes);for(var ab,x=0;ab=m.nodes[x];x++)null!=ab.parentNode&&z.indexOf(ab)<0&&i([ab],[m[x]]);l.length("key"in l.attrs?1:0);if((l.tag!=m.tag||bb.join()!=Object.keys(m.attrs).join()||l.attrs.id!=m.attrs.id||"all"==e.redraw.strategy()&&m.configContext&&m.configContext.retain!==!0||"diff"==e.redraw.strategy()&&m.configContext&&m.configContext.retain===!1)&&(m.nodes.length&&i(m.nodes),m.configContext&&typeof m.configContext.onunload===J&&m.configContext.onunload()),K.call(l.tag)!=I)return;var ab,db=0===m.nodes.length;if(l.attrs.xmlns?q=l.attrs.xmlns:"svg"===l.tag?q="http://www.w3.org/2000/svg":"math"===l.tag&&(q="http://www.w3.org/1998/Math/MathML"),db?(ab=l.attrs.is?q===c?C.createElement(l.tag,l.attrs.is):C.createElementNS(q,l.tag,l.attrs.is):q===c?C.createElement(l.tag):C.createElementNS(q,l.tag),m={tag:l.tag,attrs:cb?h(ab,l.tag,l.attrs,{},q):l.attrs,children:null!=l.children&&l.children.length>0?f(ab,l.tag,c,c,l.children,m.children,!0,0,l.attrs.contenteditable?ab:p,q,r):l.children,nodes:[ab]},m.children&&!m.children.nodes&&(m.children.nodes=[]),"select"===l.tag&&l.attrs.value&&h(ab,l.tag,{value:l.attrs.value},{},q),a.insertBefore(ab,a.childNodes[o]||null)):(ab=m.nodes[0],cb&&h(ab,l.tag,l.attrs,m.attrs,q),m.children=f(ab,l.tag,c,c,l.children,m.children,!1,0,l.attrs.contenteditable?ab:p,q,r),m.nodes.intact=!0,n===!0&&null!=ab&&a.insertBefore(ab,a.childNodes[o]||null)),typeof l.attrs.config===J){var eb=m.configContext=m.configContext||{retain:"diff"==e.redraw.strategy()||c},fb=function(a,b){return function(){return a.attrs.config.apply(a,b)}};r.push(fb(l,[ab,!db,eb,m]))}}else if(typeof l!=J){var z;0===m.nodes.length?(l.$trusted?z=k(a,o,l):(z=[C.createTextNode(l)],a.nodeName.match(N)||a.insertBefore(z[0],a.childNodes[o]||null)),m="string number boolean".indexOf(typeof l)>-1?new l.constructor(l):l,m.nodes=z):m.valueOf()!==l.valueOf()||n===!0?(z=m.nodes,p&&p===C.activeElement||(l.$trusted?(i(z,m),z=k(a,o,l)):"textarea"===b?a.value=l:p?p.innerHTML=l:((1===z[0].nodeType||z.length>1)&&(i(m.nodes,m),z=[C.createTextNode(l)]),a.insertBefore(z[0],a.childNodes[o]||null),z[0].nodeValue=l)),m=new l.constructor(l),m.nodes=z):m.nodes.intact=!0}return m}function g(a,b){return a.action-b.action||a.index-b.index}function h(a,b,c,d,e){for(var f in c){var g=c[f],h=d[f];if(f in d&&h===g)"value"===f&&"input"===b&&a.value!=g&&(a.value=g);else{d[f]=g;try{if("config"===f||"key"==f)continue;if(typeof g===J&&0===f.indexOf("on"))a[f]=l(g,a);else if("style"===f&&null!=g&&K.call(g)===G){for(var i in g)(null==h||h[i]!==g[i])&&(a.style[i]=g[i]);for(var i in h)i in g||(a.style[i]="")}else null!=e?"href"===f?a.setAttributeNS("http://www.w3.org/1999/xlink","href",g):"className"===f?a.setAttribute("class",g):a.setAttribute(f,g):f in a&&"list"!==f&&"style"!==f&&"form"!==f&&"type"!==f&&"width"!==f&&"height"!==f?("input"!==b||a[f]!==g)&&(a[f]=g):a.setAttribute(f,g)}catch(j){if(j.message.indexOf("Invalid argument")<0)throw j}}}return d}function i(a,b){for(var c=a.length-1;c>-1;c--)if(a[c]&&a[c].parentNode){try{a[c].parentNode.removeChild(a[c])}catch(d){}b=[].concat(b),b[c]&&j(b[c])}0!=a.length&&(a.length=0)}function j(a){if(a.configContext&&typeof a.configContext.onunload===J&&(a.configContext.onunload(),a.configContext.onunload=null),a.children)if(K.call(a.children)===H)for(var b,c=0;b=a.children[c];c++)j(b);else a.children.tag&&j(a.children)}function k(a,b,c){var d=a.childNodes[b];if(d){var e=1!=d.nodeType,f=C.createElement("span");e?(a.insertBefore(f,d||null),f.insertAdjacentHTML("beforebegin",c),a.removeChild(f)):d.insertAdjacentHTML("beforebegin",c)}else a.insertAdjacentHTML("beforeend",c);for(var g=[];a.childNodes[b]!==d;)g.push(a.childNodes[b]),b++;return g}function l(a,b){return function(c){c=c||event,e.redraw.strategy("diff"),e.startComputation();try{return a.call(b,c)}finally{ab()}}}function m(a){var b=Q.indexOf(a);return 0>b?Q.push(a)-1:b}function n(a){var b=function(){return arguments.length&&(a=arguments[0]),a};return b.toJSON=function(){return a},b}function o(){for(var a,b=0;a=T[b];b++)V[b]&&e.render(a,U[b].view?U[b].view(V[b]):$());Y&&(Y(),Y=null),W=null,X=new Date,e.redraw.strategy("diff")}function p(a){return a.slice(db[e.route.mode].length)}function q(a,b,c){bb={};var d=c.indexOf("?");-1!==d&&(bb=u(c.substr(d+1,c.length)),c=c.substr(0,d));var f=Object.keys(b),g=f.indexOf(c);if(-1!==g)return e.module(a,b[f[g]]),!0;for(var h in b){if(h===c)return e.module(a,b[h]),!0;var i=new RegExp("^"+h.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(i.test(c))return c.replace(i,function(){for(var c=h.match(/:[^\/]+/g)||[],d=[].slice.call(arguments,1,-2),f=0,g=c.length;g>f;f++)bb[c[f].replace(/:|\./g,"")]=decodeURIComponent(d[f]);e.module(a,b[h])}),!0}}function r(a){if(a=a||event,!a.ctrlKey&&!a.metaKey&&2!==a.which){a.preventDefault?a.preventDefault():a.returnValue=!1;for(var b=a.currentTarget||a.srcElement,c="pathname"===e.route.mode&&b.search?u(b.search.slice(1)):{};b&&"A"!=b.nodeName.toUpperCase();)b=b.parentNode;e.route(b[e.route.mode].slice(db[e.route.mode].length),c)}}function s(){"hash"!=e.route.mode&&D.hash?D.hash=D.hash:b.scrollTo(0,0)}function t(a,b){var d={},e=[];for(var f in a){var g=b?b+"["+f+"]":f,h=a[f],i=K.call(h),j=null===h?encodeURIComponent(g):i===G?t(h,g):i===H?h.reduce(function(a,b){return d[g]||(d[g]={}),d[g][b]?a:(d[g][b]=!0,a.concat(encodeURIComponent(g)+"="+encodeURIComponent(b)))},[]).join("&"):encodeURIComponent(g)+"="+encodeURIComponent(h);h!==c&&e.push(j)}return e.join("&")}function u(a){for(var b=a.split("&"),c={},d=0,e=b.length;e>d;d++){var f=b[d].split("="),g=decodeURIComponent(f[0]),h=2==f.length?decodeURIComponent(f[1]):null;null!=c[g]?(K.call(c[g])!==H&&(c[g]=[c[g]]),c[g].push(h)):c[g]=h}return c}function v(a){var b=m(a);i(a.childNodes,R[b]),R[b]=c}function w(a,b){var c=e.prop(b);return a.then(c),c.then=function(c,d){return w(a.then(c,d),b)},c}function x(a,b){function c(a){l=a||j,n.map(function(a){l===i&&a.resolve(m)||a.reject(m)})}function d(a,b,c,d){if((null!=m&&K.call(m)===G||typeof m===J)&&typeof a===J)try{var f=0;a.call(m,function(a){f++||(m=a,b())},function(a){f++||(m=a,c())})}catch(g){e.deferred.onerror(g),m=g,c()}else d()}function f(){var j;try{j=m&&m.then}catch(n){return e.deferred.onerror(n),m=n,l=h,f()}d(j,function(){l=g,f()},function(){l=h,f()},function(){try{l===g&&typeof a===J?m=a(m):l===h&&"function"==typeof b&&(m=b(m),l=g)}catch(f){return e.deferred.onerror(f),m=f,c()}m===k?(m=TypeError(),c()):d(j,function(){c(i)},c,function(){c(l===g&&i)})})}var g=1,h=2,i=3,j=4,k=this,l=0,m=0,n=[];k.promise={},k.resolve=function(a){return l||(m=a,l=g,f()),this},k.reject=function(a){return l||(m=a,l=h,f()),this},k.promise.then=function(a,b){var c=new x(a,b);return l===i?c.resolve(m):l===j?c.reject(m):n.push(c),c.promise}}function y(a){return a}function z(a){if(!a.dataType||"jsonp"!==a.dataType.toLowerCase()){var d=new b.XMLHttpRequest;if(d.open(a.method,a.url,!0,a.user,a.password),d.onreadystatechange=function(){4===d.readyState&&(d.status>=200&&d.status<300?a.onload({type:"load",target:d}):a.onerror({type:"error",target:d}))},a.serialize===JSON.stringify&&a.data&&"GET"!==a.method&&d.setRequestHeader("Content-Type","application/json; charset=utf-8"),a.deserialize===JSON.parse&&d.setRequestHeader("Accept","application/json, text/*"),typeof a.config===J){var e=a.config(d,a);null!=e&&(d=e)}var f="GET"!==a.method&&a.data?a.data:"";if(f&&K.call(f)!=I&&f.constructor!=b.FormData)throw"Request data should be either be a string or FormData. Check the `serialize` option in `m.request`";return d.send(f),d}var g="mithril_callback_"+(new Date).getTime()+"_"+Math.round(1e16*Math.random()).toString(36),h=C.createElement("script");b[g]=function(d){h.parentNode.removeChild(h),a.onload({type:"load",target:{responseText:d}}),b[g]=c},h.onerror=function(){return h.parentNode.removeChild(h),a.onerror({type:"error",target:{status:500,responseText:JSON.stringify({error:"Error making jsonp request"})}}),b[g]=c,!1},h.onload=function(){return!1},h.src=a.url+(a.url.indexOf("?")>0?"&":"?")+(a.callbackKey?a.callbackKey:"callback")+"="+g+"&"+t(a.data||{}),C.body.appendChild(h)}function A(a,b,c){if("GET"===a.method&&"jsonp"!=a.dataType){var d=a.url.indexOf("?")<0?"?":"&",e=t(b);a.url=a.url+(e?d+e:"")}else a.data=c(b);return a}function B(a,b){var c=a.match(/:[a-z]\w+/gi);if(c&&b)for(var d=0;dk;k++)e[k]()},e.trust=function(a){return a=new String(a),a.$trusted=!0,a},e.prop=function(a){return(null!=a&&K.call(a)===G||typeof a===J)&&typeof a.then===J?w(a):n(a)};var S,T=[],U=[],V=[],W=null,X=0,Y=null,Z=16;e.module=function(a,b){if(!a)throw new Error("Please ensure the DOM element exists before rendering a template into it.");var c=T.indexOf(a);0>c&&(c=T.length);var d=!1;if(V[c]&&typeof V[c].onunload===J){var f={preventDefault:function(){d=!0}};V[c].onunload(f)}if(!d){e.redraw.strategy("all"),e.startComputation(),T[c]=a;var g=S=b=b||{},h=new(b.controller||function(){});return g===S&&(V[c]=h,U[c]=b),ab(),V[c]}},e.redraw=function(a){W&&a!==!0?(new Date-X>Z||E===b.requestAnimationFrame)&&(W>0&&F(W),W=E(o,Z)):(o(),W=E(function(){W=null},Z))},e.redraw.strategy=e.prop();var $=function(){return""},_=0;e.startComputation=function(){_++},e.endComputation=function(){_=Math.max(_-1,0),0===_&&e.redraw()};var ab=function(){"none"==e.redraw.strategy()?(_--,e.redraw.strategy("diff")):e.endComputation()};e.withAttr=function(a,b){return function(c){c=c||event;var d=c.currentTarget||this;b(a in d?d[a]:d.getAttribute(a))}};var bb,cb,db={pathname:"",hash:"#",search:"?"},eb=function(){};return e.route=function(){if(0===arguments.length)return cb;if(3===arguments.length&&K.call(arguments[1])===I){var a=arguments[0],c=arguments[1],d=arguments[2];eb=function(b){var f=cb=p(b);q(a,d,f)||e.route(c,!0)};var f="hash"===e.route.mode?"onhashchange":"onpopstate";b[f]=function(){var a=D[e.route.mode];"pathname"===e.route.mode&&(a+=D.search),cb!=p(a)&&eb(a)},Y=s,b[f]()}else if(arguments[0].addEventListener||arguments[0].attachEvent){{var g=arguments[0];arguments[1],arguments[2]}g.href=("pathname"!==e.route.mode?D.pathname:"")+db[e.route.mode]+this.attrs.href,g.addEventListener?(g.removeEventListener("click",r),g.addEventListener("click",r)):(g.detachEvent("onclick",r),g.attachEvent("onclick",r))}else if(K.call(arguments[0])===I){var h=cb;cb=arguments[0];var i=arguments[1]||{},j=cb.indexOf("?"),k=j>-1?u(cb.slice(j+1)):{};for(var l in i)k[l]=i[l];var m=t(k),n=j>-1?cb.slice(0,j):cb;m&&(cb=n+(-1===n.indexOf("?")?"?":"&")+m);var o=(3===arguments.length?arguments[2]:arguments[1])===!0||h===arguments[0];b.history.pushState?(Y=function(){b.history[o?"replaceState":"pushState"](null,C.title,db[e.route.mode]+cb),s()},eb(db[e.route.mode]+cb)):(D[e.route.mode]=cb,eb(db[e.route.mode]+cb))}},e.route.param=function(a){if(!bb)throw new Error("You must call m.route(element, defaultRoute, routes) before calling m.route.param()");return bb[a]},e.route.mode="search",e.route.buildQueryString=t,e.route.parseQueryString=u,e.deferred=function(){var a=new x;return a.promise=w(a.promise),a},e.deferred.onerror=function(a){if("[object Error]"===K.call(a)&&!a.constructor.toString().match(/ Error/))throw a},e.sync=function(a){function b(a,b){return function(e){return g[a]=e,b||(c="reject"),0===--f&&(d.promise(g),d[c](g)),e}}var c="resolve",d=e.deferred(),f=a.length,g=new Array(f);if(a.length>0)for(var h=0;h + + + m.module - Mithril + + + + +
+ +
+
+
+
+
+ +
+

m.module

+
+ +
+

A module is an Object with two keys: controller and view. Each of those should point to a Javascript function. Note that the name of both properties should be lower-cased.

+
//a valid module
+{controller: function() {}, view: function() {}}
+
+

When using m.module, Mithril instantiates controllers as if they were class constructors. However, controllers may return objects if you want to use that Javascript feature to have more fine-grained control over a controller's lifecycle.

+

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

+

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

+

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

+

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

+

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

+
+

Usage

+

You can make anonymous modules out of existing classes

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

Typically, however, modules and namespaces are used interchangeably.

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

Modules can also be used as components in order to assemble bigger systems. You can read more about componentization here

+
+

Unloading modules

+

If a module's controller implements an instance method called onunload, this method will be called when a new m.module call updates the root DOM element tied to the module in question.

+
var module1 = {};
+module1.controller = function() {
+    this.onunload = function() {
+        console.log("unloading module 1");
+    };
+};
+module1.view = function() {};
+
+m.module(document, module1);
+
+
+
+var module2 = {};
+module2.controller = function() {};
+module1.view = function() {};
+
+m.module(document, module2); // logs "unloading module 1"
+
+

This mechanism is useful to clear timers and unsubscribe event handlers. If you have a hierarchy of components, you can recursively call onunload on all the components in the tree or use a pubsub library to unload specific components on demand.

+

You can also use this event to prevent a module from being unloaded (e.g. to alert a user to save their changes before navigating away from a page)

+
var module1 = {}
+module1.controller = function() {
+    this.onunload = function(e) {
+        if (!confirm("are you sure you want to leave this page?")) e.preventDefault()
+    }
+}
+
+

Normally, calling m.module will return the controller instance for that module, but there's one corner case: if preventDefault is called from a controller's onunload method as a result of calling m.module, then the m.module call will not instantiate the new controller, and will return undefined.

+

To unload a module without loading another module, you can simply call m.module without a module parameter:

+
m.module(rootElement, null);
+
+
+

Signature

+

How to read signatures

+
Object module(DOMElement rootElement, Module module)
+
+where:
+    Module :: Object { Controller, void view(Object controllerInstance) }
+    Controller :: void controller() | void controller() { prototype: void unload(UnloadEvent e) }
+    UnloadEvent :: Object {void preventDefault()}
+
+
    +
  • DOMElement rootElement

    +

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

    +
  • +
  • Module module

    +

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

    +

    The controller class is instantiated immediately and a reference is returned upon calling m.module.

    +

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

    +

    Note that controllers can manually instantiate child controllers (since they are simply Javascript constructors), and likewise, views can call child views and manually pass the child controller instances down the the child view constructors. You should avoid instantiating controllers from views, since views can be rendered many times across the lifecycle of a page, and a redraw might wipe out sub-controller data, if it houses any.

    +

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

    +

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

    +
  • +
  • returns Object controllerInstance

    +

    An instance of the controller constructor

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/mithril.prop.html b/archive/v0.1.33/mithril.prop.html new file mode 100644 index 00000000..88d3d7a0 --- /dev/null +++ b/archive/v0.1.33/mithril.prop.html @@ -0,0 +1,200 @@ + + + + m.prop - Mithril + + + + +
+ +
+
+
+
+
+ +
+

m.prop

+
+ +
+

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

+

Note that modifying the values of m.prop getter-setters does not trigger redrawing. Instead, Mithril's redrawing system relies on m.startComputation and m.endComputation. These functions are internally called by Mithril when you initialize a module via m.module or m.route, and when you trigger event handlers that were created within templates with m().

+
+

Usage

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

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

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

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

+

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

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

Third-party promise library support

+

If a promise is passed into m.prop(), a Mithril promise is returned. Mithril promises are also getter-setter functions, which are populated with the resolved value if the promise is fulfilled successfully.

+

Until the promise is resolved, the value of the prop will resolve to undefined

+

Here's an example using the Q promise library:

+
var deferred = Q.defer()
+var users = m.prop(deferred.promise)
+
+users() // undefined
+
+deferred.resolve("Hello")
+
+//wait for next tick for Q's A+ compliant promise to actually resolve
+setTimeout(function() {
+
+    users() // Hello
+    users.then(function(value) {
+        console.log(value) //Hello
+    })
+
+}, 1000)
+
+
+

Serializing getter-setters

+

Getter-setters are JSON-serializable:

+
var data = {foo: m.prop("bar")};
+JSON.stringify(data); // '{"foo": "bar"}'
+
+

This allows getter-setters to be passed directly as parameters to m.request, for example.

+
+

Signature

+

How to read signatures

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

    +

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

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

    +

    A getter-setter method.

    +
      +
    • any value (optional)

      +

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

      +

      If not provided, return the current internally stored value.

      +
    • +
    • returns any value

      +

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

      +
    • +
    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/mithril.redraw.html b/archive/v0.1.33/mithril.redraw.html new file mode 100644 index 00000000..b516ef25 --- /dev/null +++ b/archive/v0.1.33/mithril.redraw.html @@ -0,0 +1,234 @@ + + + + m.redraw - Mithril + + + + +
+ +
+
+
+
+
+ +
+

m.redraw

+
+ +
+

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

+

This method is called internally by Mithril's auto-redrawing system. Usually you don't need to call it manually unless you are doing recurring asynchronous operations (i.e. using setInterval) or if you want to decouple slow running background requests from the rendering context (see the background option in m.request.

+

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

+

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

+

Note that calling this method will not do anything if a module was not activated via either m.module() or m.route(). This means that m.redraw doesn't do anything when instantiating controllers and rendering views via m.render manually.

+

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

+

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

+

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

+
+

Changing redraw strategy

+

If you need to change how Mithril performs a redraw, you can change the value of the m.redraw.strategy getter-setter to either "all", "diff" or "none". The new strategy will apply to the next scheduled redraw, if any. By default, Mithril sets this value to "all" before running controller constructors, and it sets it to "diff" before event handlers are triggered.

+

After the redraw, Mithril resets the value of the flag to either "all" or "diff", depending on whether the redraw was due to a route change or not.

+

Changing the flag outside of a redrawable context does nothing since the flag gets reset when entering one of the documented redrawable contexts above.

+

When the flag is set to "all", Mithril throws away the current view and redraws from scratch. This is the default for going from one route to another.

+

When the flag is set to "diff", Mithril performs a diff between the old view and the new view and applies patches to the DOM only where needed.

+

When the flag is set to "none", Mithril skips the next redraw. You don't need to change this flag to something else again later, since Mithril does that for you.

+
var module1 = {}
+module1.controller = function() {
+    //this module will attempt to diff its template when routing, as opposed to re-creating the view from scratch.
+    //this allows config contexts to live across route changes, if its element does not need to be recreated by the diff
+    m.redraw.strategy("diff")
+}
+module1.view = function() {
+    return m("h1", {config: module1.config}, "test") //assume all routes display the same thing
+}
+module1.config = function(el, isInit, ctx) {
+    if (!isInit) ctx.data = "foo" //we wish to initialize this only once, even if the route changes
+}
+
+

Common reasons why one might need to change redraw strategy are:

+
    +
  • in order to avoid the full-page recreation when changing routes, for the sake of performance of global 3rd party components

    +
    //diff when routing, instead of redrawing from scratch
    +//this preserves the `<input>` element and its 3rd party plugin after route changes, since the `<input>` doesn't change
    +var module1 = {}
    +module1.controller = function() {
    +   m.redraw.strategy("diff")
    +}
    +module1.view = function() {
    +   return [
    +       m("h1", "Hello Foo"),
    +       m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
    +   ]
    +}
    +
    +var module2 = {}
    +module2.controller = function() {
    +   m.redraw.strategy("diff")
    +}
    +module2.view = function() {
    +   return [
    +       m("h1", "Hello Bar"),
    +       m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
    +   ]
    +}
    +
    +m.route(document.body, "/foo", {
    +   "/foo": module1,
    +   "/bar": module2,
    +})
    +
    +
  • +
  • in order to prevent redraw when dealing with keypress events where the event's keyCode is not of interest

    +
    //model
    +var saved = false
    +function save(e) {
    +   if (e.keyCode == 13) {
    +       //this causes a redraw, since event handlers active auto-redrawing by default
    +       saved = true
    +   }
    +   else {
    +       //we don't care about other keys, so don't redraw
    +       m.redraw.strategy("none")
    +   }
    +}
    +
    +//view
    +var view = function() {
    +   return [
    +       m("button[type=button]", {onkeypress: save}, "Save"),
    +       saved ? "Saved" : ""
    +   ]
    +}
    +
    +
  • +
+

Note that the redraw strategy is a global setting that affects the entire template trees of all modules on the page. In order to prevent redraws in some parts of an application, but not others, see subtree directives

+
+

Preventing redraws on events

+

Sometimes you only care about a particular condition in an event and want the event to not trigger a redraw if this condition is not met. +For example, you might only be interested in running a redraw if a user presses the space bar, and you might not want to waste a redraw if the user presses any other key. In that case, it's possible to skip redrawing altogether by calling m.redraw.strategy("none")

+
m("input", {onkeydown: function(e) {
+    if (e.keyCode == 13) vm.save() //do things and re-render only if the `enter` key was pressed
+    else m.redraw.strategy("none") //otherwise, ignore
+}})
+
+

There are some important semantic caveats for m.redraw.strategy("none") that you should be aware of: Setting the strategy to "none" only affects synchronous redraws. As soon as the event handler returns, the strategy is set back to "diff".

+

If you set strategy to "none" but then proceed to trigger a redraw asynchronously, either via start/endComputation, m.redraw or m.request, a redraw will occur, using the "diff" strategy.

+

Additionally, calling m.redraw synchronously after calling m.redraw.strategy("none") resets the strategy to "diff".

+

Lastly, be aware that if a user action triggers more than one event handler (for example, oninput and onkeypress, or an event bubbling up to event handlers in multiple ancestor elements), every event triggers a redraw by default. Setting strategy to none in any one of those handlers will not affect the redrawing strategy of other handlers (and remember that strategy("none") has no effect on asynchronous redraws).

+
+

Forcing redraw

+

If you find yourself needing to redraw before the browsers normal redraw cycle, you can force a synchronous redraw by passing a boolean true as a parameter to m.redraw.

+
m.redraw(true) // force
+
+

Normally, you should only do this if you need to synchronously read a value from the DOM that requires a browser repaint (e.g. offsetTop or a CSS rule). If you need to read DOM values, try to read them all at once, because alternating reading and writing to the DOM causes multiple browser repaints, and repaints are expensive.

+
+

Signature

+

How to read signatures

+
void redraw([Boolean forceSync]) { GetterSetter strategy }
+
+where:
+    GetterSetter :: String getterSetter([String value])
+
+
    +
  • Boolean forceSync (optional)

    +

    If set to true, forces the redraw to be synchronous. By default, event handlers schedule redraws to be done asynchronously in order to allow simultaneous events to run before redrawing (for example, the keypress and input are often used together for inputs). Defaults to false

    +
  • +
  • +

    m.redraw.strategy

    +

    GetterSetter strategy

    +

    The m.redraw.strategy getter-setter indicates how the next module redraw will occur. It can be one of three values:

    +
      +
    • "all" - recreates the DOM tree from scratch
    • +
    • "diff" - updates only DOM elements if needed
    • +
    • "none" - leaves the DOM tree intact
    • +
    +

    This value can be programmatically changed in controllers and event handlers to modify the next redrawing strategy. It is modified internally by Mithril to the value "all" before running controller constructors, and to the value "diff" after all redraws.

    +

    Calling this function without arguments returns the currently assigned redraw strategy.

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/mithril.render.html b/archive/v0.1.33/mithril.render.html new file mode 100644 index 00000000..f764e73f --- /dev/null +++ b/archive/v0.1.33/mithril.render.html @@ -0,0 +1,190 @@ + + + + m.render - Mithril + + + + +
+ +
+
+
+
+
+ +
+

m.render

+
+ +
+

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

+

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

+

Note that, unlike many templating engines, this "smart diff" feature does not affect things like cursor placement in inputs and focus, and is therefore safe to call during user interactions. There are, however, some limitations to the diff algorithm that require you to add key attributes in some edge cases.

+
+

Usage

+

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

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

yields:

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

Subtree Directives

+

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

+
{subtree: "retain"}
+
+

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

+

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

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

Signature

+

How to read signatures

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

    +

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

    +
  • +
  • Children children

    +

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

    +

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

    +

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

    +
  • +
  • Boolean forceRecreation

    +

    If set to true, rendering a new virtual tree will completely overwrite an existing one without attempting to diff against it

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/mithril.request.html b/archive/v0.1.33/mithril.request.html new file mode 100644 index 00000000..09861340 --- /dev/null +++ b/archive/v0.1.33/mithril.request.html @@ -0,0 +1,577 @@ + + + + m.request - Mithril + + + + +
+ +
+
+
+
+
+ +
+

m.request

+
+ +
+

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

+

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

+

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

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

Basic usage

+

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

+

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

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

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

+

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

+

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

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

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

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

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

+

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

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

Processing web service data

+

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

+

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

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

Bind redirection code

+

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

+

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

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

Binding errors

+

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

+

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

+

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

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

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

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

Queuing Operations

+

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

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

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

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

Casting the Response Data to a Class

+

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

+

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

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

Unwrapping Response Data

+

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

+

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

+

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

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

Using Different Data Transfer Formats

+

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

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

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

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

File uploads with FormData

+

To use the HTML5 FormData object as the payload for a request, you need to override the serialize option. By default, serialize converts an object to JSON, but in the case of a FormData payload, you want to pass the object intact.

+
//assume the file comes from an HTML5 drag-n-drop event
+var file = e.dataTransfer.files[0]
+
+var data = new FormData();
+data.append("file", file)
+
+m.request({
+    method: "POST",
+    url: "/upload",
+    serialize: function(data) {return data}
+})
+
+
+

Using variable data formats

+

By default, Mithril assumes both success and error responses are in JSON format, but some servers may not return JSON responses when returning HTTP error codes (e.g. 404)

+

You can get around this issue by using extract

+
var nonJsonErrors = function(xhr) {
+  return xhr.status > 200 ? JSON.stringify(xhr.responseText) : xhr.responseText
+}
+
+m.request({method: "GET", url: "/foo/bar.x", extract: nonJsonErrors})
+  .then(function(data) {}, function(error) {console.log(error)})
+
+
+

Extracting Metadata from the Response

+

The extract method can be used to read metadata from HTTP response headers or the status field of an XMLHttpRequest.

+
var extract = function(xhr, xhrOptions) {
+    if (xhrOptions.method == "HEAD") return xhr.getResponseHeader("x-item-count")
+    else return xhr.responseText
+}
+
+m.request({method: "POST", url: "/foo", extract: extract});
+
+
+

Custom request rejections

+

If you want to be able to handle a condition as an error in a promise rejection handler, you can throw an Error from extract to reject the promise.

+

This is useful, for example, if you received invalid JSON from the server in production and you want to display a message to the user saying that the server is offline.

+
var extract = function(xhr, xhrOptions) {
+    try {
+        return JSON.stringify(xhr.responseText)
+    }
+    catch (e) {
+        //e instanceof SyntaxError == true
+        //by default `e` would be caught by Mithril's promise exception monitor and rethrown to the console
+        //this new error follows Promises/A+ specifications and triggers a rejection in the downstream promises without hitting the console.
+        throw new Error("Server is offline")
+    }
+}
+
+m.request({method: "POST", url: "/foo", extract: extract});
+
+

You can read more about the promise exception monitor here.

+
+

Configuring the underlying XMLHttpRequest

+

The config option can be used to arbitrarily configure the native XMLHttpRequest instance and to access properties that would not be accessible otherwise.

+

The example below shows how to configure a request where the server expects requests to have a Content-Type: application/json header

+
var xhrConfig = function(xhr) {
+    xhr.setRequestHeader("Content-Type", "application/json");
+}
+
+m.request({method: "POST", url: "/foo", config: xhrConfig});
+
+
+

Aborting a request

+

The config option can also be used to retrieve the XMLHttpRequest instance for aborting the request. This idiom can also be used to attach onprogress event handlers.

+
var transport = m.prop();
+
+m.request({method: "POST", url: "/foo", config: transport});
+
+//the `transport` getter-setter contains an instance of XMLHttpRequest
+transport().abort();
+
+
+

Using JSON-P

+

To make JSON-P requests, add the dataType option instead of method. You should not add the callback querystring parameter; Mithril already does that internally.

+
m.request({dataType: "jsonp", url: "/api/User"});
+
+

Some services (e.g. Flickr) don't follow the convention of calling the callback parameter callback. In order to specify the name of the querystring parameter that indicates the callback function, use the callbackKey option:

+
m.request({
+    dataType: "jsonp",
+    callbackKey: "jsoncallback",
+    url: "http://api.flickr.com/services/feeds/photos_public.gne?tags=monkey&tagmode=any&format=json"
+});
+
+
+

Rendering before web service requests finish

+

By default, Mithril waits for web service requests to complete before attempting a redraw. This ensures that data being accessed in the view isn't nullable as a result of asynchronous data not being available yet.

+

However, sometimes we do want to be able to redraw before a web service request completes, either because one web service out of many is slow, or because we don't need its response in order to redraw.

+

Setting the background option to true prevents a request from affecting redrawing. This means it's possible for a view to attempt to use data before it is available. You can specify an initial value for the m.request getter-setter in order to avoid having to write defensive code against potential null reference exceptions:

+
var demo = {}
+
+demo.controller = function() {
+    return {
+        users: m.request({method: "GET", url: "/api/user", background: true, initialValue: []})
+    }
+}
+
+//in the view
+demo.view = function(ctrl) {
+    //This view gets rendered before the request above completes
+    //Calling .map doesn't throw an error because we defined the initial value to be an empty array, instead of undefined
+    return ctrl.users().map(function() {
+        return m("div", user.name)
+    })
+}
+
+
+

Signature

+

How to read signatures

+
Promise request(Options options)
+
+where:
+    Promise :: GetterSetter { Promise then(any successCallback(any value), any errorCallback(any value)) }
+    GetterSetter :: any getterSetter([any value])
+    Options :: XHROptions | JSONPOptions
+    XHROptions :: Object {
+        String method,
+        String url,
+        [String user,]
+        [String password,]
+        [Object<any> data,]
+        [Boolean background,]
+        [any initialValue,]
+        [any unwrapSuccess(any data, XMLHttpRequest xhr),]
+        [any unwrapError(any data, XMLHttpRequest xhr),]
+        [String serialize(any dataToSerialize),]
+        [any deserialize(String dataToDeserialize),]
+        [any extract(XMLHttpRequest xhr, XHROptions options),]
+        [void type(Object<any> data),]
+        [XMLHttpRequest? config(XMLHttpRequest xhr, XHROptions options)]
+    }
+    JSONPOptions :: Object {
+        String dataType,
+        String url,
+        String callbackKey,
+        Object<any> data
+    }
+
+
    +
  • XHROptions options

    +

    A map of options for the XMLHttpRequest

    +
      +
    • String method

      +

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

      +
    • +
    • String url

      +

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

      +
    • +
    • String user (optional)

      +

      A user for HTTP authentication. Defaults to undefined

      +
    • +
    • String password (optional)

      +

      A password for HTTP authentication. Defaults to undefined

      +
    • +
    • Object data (optional)

      +

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

      +
    • +
    • Boolean background (optional)

      +

      Determines whether the m.request can affect template rendering. Defaults to false.

      +

      If this option is set to true, then the request does NOT call m.startComputation / m.endComputation, and therefore the completion of the request does not trigger an update of the view, even if data has been changed. This option is useful for running operations in the background (i.e. without user intervention). It's strongly recommended that you set an initialValue option in ALL requests if you set the background option to true.

      +

      In order to force a redraw after a background request, use m.redraw, or m.startComputation / m.endComputation.

      +
      var demo = {}
      +
      +demo.controller = function() {
      +  return {
      +      users: m.request({method: "GET", url: "/api/user", background: true, initialValue: []}).then(function(value) {
      +          //force redraw
      +          m.redraw()
      +          return value
      +      })
      +  }
      +}
      +
      +demo.view = function(ctrl) {
      +  //this view renders twice (once immediately, and once after the request above completes)
      +  return ctrl.users.map(function(user) {
      +      return m("div", user.name)
      +  })
      +}
      +
      +

      It's recommended that you always set an initialValue when setting the background option to true.

      +
    • +
    • any initialValue (optional)

      +

      The value that populates the returned getter-setter before the request completes. This is useful when using the background option, in order to avoid the need for null checks in views that may be attempting to access the returned getter-setter before the asynchronous request resolves.

      +

      It is strongly recommended that you always set this option to avoid future surprises.

      +
    • +
    • any unwrapSuccess(any data, XMLHttpRequest xhr) (optional)

      +

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

      +

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

      +

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

      +
        +
      • Object | Array data

        +

        The data to unwrap

        +
      • +
      • returns Object | Array unwrappedData

        +

        The unwrapped data

        +
      • +
      +
    • +
    • any unwrapError(any data, XMLHttpRequest xhr) (optional)

      +

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

      +

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

      +
        +
      • Object | Array data

        +

        The data to unwrap

        +
      • +
      • returns Object | Array unwrappedData

        +

        The unwrapped data

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

      +

      Method to use to serialize the request data

      +

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

      +
        +
      • any dataToSerialize

        +

        Data to be serialized

        +
      • +
      • returns String serializedData

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

      +

      Method to use to deserialize the response data

      +

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

      +
        +
      • String dataToDeserialize

        +

        Data to be deserialized

        +
      • +
      • returns any deserializedData

        +
      • +
      +
    • +
    • any extract(XMLHttpRequest xhr, XHROptions options) (optional)

      +

      Method to use to extract the data from the raw XMLHttpRequest. This is useful when the relevant data is either in a response header or the status field.

      +

      If this parameter is falsy, the default value is a function that returns xhr.responseText.

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

      +

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

      +

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

      +

      For example, if type is the following class:

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

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

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

      +

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

      +
        +
      • XMLHttpRequest xhr

        +

        The XMLHttpRequest instance.

        +
      • +
      • XHROptions options

        +

        The options parameter that was passed into m.request call

        +
      • +
      • returns XMLHttpRequest? xhr

        +

        You may return an XHR-like object (e.g. a XDomainRequest instance) to override the provided XHR instance altogether.

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

    +

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

    +
  • +
+
+
    +
  • JSONPOptions options

    +

    A map of options for JSONP requests

    +
      +
    • String dataType

      +

      Must be the string "jsonp"

      +
    • +
    • String url

      +

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

      +
    • +
    • String callbackKey

      +

      The name of the querystring key that defines the name of the callback function to be called by the response. Defaults to "callback"

      +

      This option is useful for web services that use uncommon conventions for defining jsonp callbacks (e.g. foo.com/?jsonpCallback=doSomething)

      +
    • +
    • Object data (optional)

      +

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

      +
    • +
    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/mithril.route.html b/archive/v0.1.33/mithril.route.html new file mode 100644 index 00000000..c9f8a789 --- /dev/null +++ b/archive/v0.1.33/mithril.route.html @@ -0,0 +1,371 @@ + + + + m.route - Mithril + + + + +
+ +
+
+
+
+
+ +
+

m.route

+
+ +
+

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

+

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

+

This method overloads four different units of functionality:

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

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

    +
  • +
  • m.route() - returns the currently active route

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

    +
  • +
+

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

+
+

Defining routes

+

Usage

+

To define a list of routes, you need to specify a host DOM element, a default route and a key-value map of possible routes and respective modules to be rendered. You don't need to call m.module to initialize your modules if you define a list of routes - m.route calls it for you.

+

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

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

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

+

The example below shows a route that takes an userID parameter

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

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

+
<body><div>johndoe</div></body>
+
+

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

+

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

+

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

+

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

+
+

Variadic routes

+

We can append an ellipsis (...) to the name of a route argument to allow it to match URL snippets that contain slashes:

+
m.route(document.body, "/files/pictures/pic1.jpg", {
+    "/files/:file...": gallery
+});
+
+m.route.param("file") === "pictures/pic1.jpg"
+
+
m.route(document.body, "/blog/2014/01/20/articles", {
+    "/blog/:date.../articles": articleList
+});
+
+m.route.param("date") === "2014/01/20"
+
+

Note that Mithril checks for route matches in the order the routes are defined, so you should put variadic routes at the bottom of the list to prevent them from matching other more specific routes.

+
m.route(document.body, "/blog/archive/2014", {
+    "/blog/:date...": module1, //for the default path in the line above, this route matches first!
+    "/blog/archive/:year": module2
+});
+
+m.route.param("date") === "archive/2014"
+
+//the routes should be flipped around to get `m.route.param("year") == "2014"`
+

+

Routes with querystrings

+

In addition to route parameters, it's possible to pass arbitrary data to m.route.param using the querystring

+
m.route("/grid?sortby=date&dir=desc")
+
+var sortBy = m.route.param("sortby") // "date"
+var dir = m.route.param("dir") // "desc"
+
+
+

Running clean up code on route change

+

If a module's controller implements an instance method called onunload, this method will be called when a route changes.

+
var home = {};
+home.controller = function() {
+    this.onunload = function() {
+        console.log("unloading home module");
+    };
+};
+
+var dashboard = {};
+dashboard.controller = function() {};
+dashboard.view = function() {};
+
+//go to the default route (home)
+m.route(document.body, "/", {
+    "/": home,
+    "/dashboard": dashboard,
+});
+
+//re-route to dashboard
+m.route("/dashboard"); // logs "unloading home"
+
+

This mechanism is useful to clear timers and unsubscribe event handlers. If you have a hierarchy of components, you can recursively call unload on all the components in the tree or use a pubsub library to unload specific components on demand.

+
+

Signature

+

How to read signatures

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

    +

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

    +
  • +
  • String defaultRoute

    +

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

    +
  • +
  • Object routes

    +

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

    +

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

    +

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

    +

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

    +

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

    +

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

    +

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

    +
  • +
  • +

    m.route.mode

    +

    String mode

    +

    The m.route.mode property defines which URL portion is used to implement the routing mechanism. Its value can be set to either "search", "hash" or "pathname". Default value is "search". Note that if you're changing this configuration value, you should change it before calling m.route.

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

      +

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

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

      +

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

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

      +

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

      +

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

      +

      Note that in order to use the pathname mode, the application must be run from the root URL.

      +
    • +
    +
  • +
  • +

    m.route.param

    +

    String param(String key)

    +

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

    +

    A route without parameters looks like this:

    +

    "/path/to/page/"

    +

    A route with parameters might look like this:

    +

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

    +

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

    +

    Querystring parameters in a route are also available in this collection automatically.

    +

    "/grid?sortby=date" - here, m.route.param("sortby") returns "date"

    +
      +
    • String key

      +

      The name of a route parameter

      +
    • +
    • returns String value

      +

      The value that maps to the parameter specified by key

      +
    • +
    +
  • +
  • +

    m.route.buildQueryString

    +

    String buildQueryString(Object data)

    +

    Serializes an object into its URI encoded querystring representation, following the same serialization conventions as URI.js

    +
      +
    • Object data

      +

      An object to be serialized

      +
    • +
    • returns String querystring

      +

      The serialized representation of the input data

      +
    • +
    +
  • +
  • +

    m.route.parseQueryString

    +

    Object parseQueryString(String data)

    +

    Deserializes an object into from a URI encoded querystring representation, following the same deserialization conventions as URI.js

    +
      +
    • Object data

      +

      An object to be deserialized

      +
    • +
    • returns String querystring

      +

      The deserialized representation of the input data

      +
    • +
    +
  • +
+
+

+

Redirecting

+

Usage

+

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

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

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

+
+

Signature

+

How to read signatures

+
void route(String path [, any params] [, Boolean shouldReplaceHistory])
+
+
    +
  • String path

    +

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

    +
  • +
  • any params

    +

    Parameters to pass as a querystring

    +
  • +
  • Boolean shouldReplaceHistory

    +

    If set to true, replaces the current history entry, instead of adding a new one. Defaults to false.

    +
  • +
+
+

+

Reading the currently active route

+

Usage

+

Mithril updates the native location object after rendering in order to allow the browser's history.pushState API to correctly show descriptive history entries (e.g. for Chrome's Ctrl+H page).

+

In order to retrieve the currently active route in a controller, you can use m.route(). This returns the portion of the URL determined by m.route.mode (minus the ? or # symbols for the search and hash modes, respectively).

+
//if the location bar is "http://example.com/?/foo/bar"
+//and m.route.mode is `search`
+//then `currentRoute == "/foo/bar"`
+var currentRoute = m.route();
+
+
+

Signature

+

How to read signatures

+
String route()
+
+
    +
  • returns String route

    +

    returns the currently active route

    +
  • +
+
+

+

Mode abstraction

+

Usage

+

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

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

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

+

See m() for more information on virtual elements.

+
+

Signature

+

How to read signatures

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

    +

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

    +
  • +
  • Boolean isInitialized

    +

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

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/mithril.sync.html b/archive/v0.1.33/mithril.sync.html new file mode 100644 index 00000000..5c152510 --- /dev/null +++ b/archive/v0.1.33/mithril.sync.html @@ -0,0 +1,131 @@ + + + + m.sync - Mithril + + + + +
+ +
+
+
+
+
+ +
+

m.sync

+

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

+
+

Usage

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

Signature

+

How to read signatures

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

    +

    A list of promises to synchronize

    +
  • +
  • return Promise promise

    +

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

    +

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

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/mithril.trust.html b/archive/v0.1.33/mithril.trust.html new file mode 100644 index 00000000..65aa4fae --- /dev/null +++ b/archive/v0.1.33/mithril.trust.html @@ -0,0 +1,141 @@ + + + + m.trust - Mithril + + + + +
+ +
+
+
+
+
+ +
+

m.trust

+

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

+

This method flags a string as trusted HTML.

+

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

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

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

    +
  • +
+

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

+

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

+

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

+

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

+

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

+

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

+

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

+
+

Usage

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

yields:

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

Signature

+

How to read signatures

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

    +

    A string containing HTML markup

    +
  • +
  • returns String trustedHtml

    +

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

    +

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

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/mithril.withAttr.html b/archive/v0.1.33/mithril.withAttr.html new file mode 100644 index 00000000..89eeca49 --- /dev/null +++ b/archive/v0.1.33/mithril.withAttr.html @@ -0,0 +1,146 @@ + + + + m.withAttr - Mithril + + + + +
+ +
+
+
+
+
+ +
+

m.withAttr

+

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

+

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

+

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

+

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

+
+

Usage

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

A contrived example of bi-directional data binding

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

Signature

+

How to read signatures

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

    +

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

    +
  • +
  • void callback(any value)

    +

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

    +
      +
    • any value

      +

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

      +
    • +
    +
  • +
  • returns EventHandler handler

    +

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

    +
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/mithril.xhr.html b/archive/v0.1.33/mithril.xhr.html new file mode 100644 index 00000000..923cc44c --- /dev/null +++ b/archive/v0.1.33/mithril.xhr.html @@ -0,0 +1,94 @@ + + + + Mithril + + + + +
+ +
+
+
+ +
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/optimizing-performance.html b/archive/v0.1.33/optimizing-performance.html new file mode 100644 index 00000000..64cf3376 --- /dev/null +++ b/archive/v0.1.33/optimizing-performance.html @@ -0,0 +1,122 @@ + + + + Optimizing Performance - Mithril + + + + +
+ +
+
+
+
+
+ +
+

Optimizing Performance

+

There's a number of ways to improve Mithril performance for the rare cases where pages are too complex for their own good.

+

First and foremost, you should think hard about whether performance optimization is truly your last resort. By nature, optimizing performance make aggressive assumptions that can break in edge cases and it yields difficult to understand code. As a rule of thumb, you should never implement performance optimizations if another solution is available.

+

For example, if you are building a table with thousands of rows and finding that the template is slow, you should first consider making the table show less items, because from a user experience perspective, no one is realistically going to scan through thousands of records. The effort to make the table paginated, searchable or filterable can improve the user experience in addition to solving the performance problem both on redraws and on initial page load.

+
+

Compiling templates

+

You can optionally pre-compile templates that use m() by running the template-compiler.sjs macro with Sweet.js. This step isn't required in order to use Mithril, but it's an easy way to squeeze a little bit more performance out of an application, without the need for code changes.

+

Compiling a template transforms the nested function calls of a template into a raw virtual DOM tree (which is merely a collection of native Javascript objects that is ready to be rendered via m.render). This means that compiled templates don't need to parse the string in m("div#foo") and they don't incur the cost of the function call.

+

It's worth mentioning that Mithril has built-in mechanisms elsewhere that take care of real bottlenecks like browser repaint management and DOM updating. This optional compilation tool is merely "icing on the cake" that speeds up the Javascript run-time of templates (which is already fast, even without compilation - see the performance section on the homepage).

+

The macro takes regular Mithril templates like the one below:

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

It pre-processes the m() call and replaces it with its output:

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

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

+
+

Installing NodeJS and SweetJS for one-off compilations

+

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

+

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

+
npm install -g sweet.js
+

To compile a file, type:

+
sjs -r -m /template-compiler.sjs -o <output-filename>.js <input-filename>.js
+

+

Automating Compilation

+

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

+

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

+
npm install -g grunt-cli
+

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

+

package.json

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

Gruntfile.js

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

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

+

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

+
grunt
+

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

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/package.json b/archive/v0.1.33/package.json new file mode 100644 index 00000000..31931bf8 --- /dev/null +++ b/archive/v0.1.33/package.json @@ -0,0 +1,11 @@ +{ + "name": "mithril", + "description": "A Javascript Framework for building brilliant applications", + "keywords": ["mvc", "framework"], + "version": "0.1.33", + "author": "Leo Horie ", + "repository": {"type": "git", "url": "https://github.com/lhorie/mithril"}, + "main": "mithril.js", + "licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}], + "files": ["mithril.min.js", "mithril.min.map", "mithril.js", "README.*"] +} diff --git a/archive/v0.1.33/pages.json b/archive/v0.1.33/pages.json new file mode 100644 index 00000000..e915b6ac --- /dev/null +++ b/archive/v0.1.33/pages.json @@ -0,0 +1,6 @@ +[ +{"title": "Getting Started", "url": "getting-started.html"}, +{"title": "Documentation", "url": "mithril.html"}, +{"title": "Mithril Blog", "url": "http://lhorie.github.io/mithril-blog/"}, +{"title": "Mailing List", "url": "https://groups.google.com/forum/#!forum/mithriljs"} +] \ No newline at end of file diff --git a/archive/v0.1.33/practices.html b/archive/v0.1.33/practices.html new file mode 100644 index 00000000..47ded878 --- /dev/null +++ b/archive/v0.1.33/practices.html @@ -0,0 +1,118 @@ + + + + How Should Code Be Organized - Mithril + + + + +
+ +
+
+
+
+
+ +
+

How Should Code Be Organized

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+
+

File Separation

+

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

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

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

+

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

+

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

+

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

+

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

+
+

Usage of m.redraw

+

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

+

Calling this method while using m.module or m.route should only be done if you have recurring asynchronous view updates (i.e. something that uses setInterval).

+

If you're integrating other non-recurring services (e.g. calling setTimeout), you should use m.startComputation / m.endComputation instead.

+

This is the most potentially expensive method in Mithril and should not be used at a rate faster than the rate at which the native requestAnimationFrame method fires (i.e. the rate at which browsers are comfortable calling recurring rendering-intensive code). Typically, this rate is around 60 calls per second.

+

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

+

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

+

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

+
+

Usage of keys

+

If you need to sort lists, or delete items from them, or splice them in any way, you should use the key attribute to maintain referential integrity between the data and the DOM.

+

Not using keys still works in some cases, but might trigger more expensive code paths within the redrawing algorithm.

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/refactoring.html b/archive/v0.1.33/refactoring.html new file mode 100644 index 00000000..8113a164 --- /dev/null +++ b/archive/v0.1.33/refactoring.html @@ -0,0 +1,62 @@ + + + + Mithril + + + + +
+ +
+
+
+ +
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/roadmap.html b/archive/v0.1.33/roadmap.html new file mode 100644 index 00000000..491120ea --- /dev/null +++ b/archive/v0.1.33/roadmap.html @@ -0,0 +1,121 @@ + + + + Roadmap - Mithril + + + + +
+ +
+
+
+
+
+ +
+

Roadmap

+

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

+

Utilities

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

Components

+
    +
  • Autocompleter
  • +
  • Date/time picker
  • +
  • Swipe-to-show panel
  • +
  • Tree
  • +
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/routing.html b/archive/v0.1.33/routing.html new file mode 100644 index 00000000..0c82ec77 --- /dev/null +++ b/archive/v0.1.33/routing.html @@ -0,0 +1,134 @@ + + + + Routing - Mithril + + + + +
+ +
+
+
+
+
+ +
+

Routing

+

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

+

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

+

Mithril provides utilities to handle three different aspect of routing:

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

Defining routes

+

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

+

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

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

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

+

The example below shows a route that takes a userID parameter.

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

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

+
<body>johndoe</body>
+
+

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

+

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

+

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

+

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

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

    +

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

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

    +

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

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

    +

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

    +

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

    +
  • +
+
+

Redirecting

+

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

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

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

+
+

Mode abstraction

+

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

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

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

+

See m() for more information on virtual elements.

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

Tools

+

HTML-to-Mithril Template Converter

+

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

+

Template Converter

+
+

Automatic HTML-to-Mithril Template Converter

+

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

+

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

+

The tool allows you to write code like this:

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

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

+

This tool is also available as a Rails gem, created by Jordan Humphreys.

+
+

Mithril Template Compiler

+

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

+

Compiling Templates

+
+

Typescript Support

+

There's a type definition file that you can use to add Mithril support to Typescript

+

mithril.d.ts

+

You can use it by adding a reference to your Typescript files. This will allow the compiler to type-check calls to the Mithril API.

+
/// <reference path="mithril.d.ts" />
+
+
+

Internet Explorer Compatibility

+

Mithril relies on some ECMAScript 5 features, namely: Array::indexOf, Array::map and Object::keys, as well as the JSON object. Internet Explorer 8 lacks native support for some of these features.

+

The easiest way to polyfill these features is to include this script:

+
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.0.3/es5-shim.min.js"></script>
+
+

This will provide all the polyfills required for the browser. You can alternatively include only specific polyfills:

+
<script src="http://cdn.polyfill.io/v1/polyfill.min.js?features=Array.prototype.indexOf,Object.keys,Function.prototype.bind,Array.prototype.forEach,JSON"></script>
+
+

You can also use other polyfills to support these features in IE7.

+ +

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

+

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

+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/archive/v0.1.33/tools/template-compiler.sjs b/archive/v0.1.33/tools/template-compiler.sjs new file mode 100644 index 00000000..ae1eb7eb --- /dev/null +++ b/archive/v0.1.33/tools/template-compiler.sjs @@ -0,0 +1,84 @@ +/* +Compiles Mithril templates + +Requires sweet.js (https://github.com/mozilla/sweet.js) +Installation: npm install -g sweet.js +Usage: sjs --module /template-compiler.sjs --output .js .js +*/ + +macro m_impl { + case { _ ($selector, $dynAttrs ..., $children ...) } => { + var selectorSyntax = #{$selector}; + var selector = unwrapSyntax(selectorSyntax); + + var dynAttrsSyntax = #{$dynAttrs ...}; + try { + var dynAttrs = unwrapSyntax(dynAttrsSyntax); + + var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g; + var attrParser = /\[(.+?)=("|'|)(.+?)\2\]/; + var _match = null; + var classes = []; + var cell = {tag: "div", attrs: {}, children: []}; + + while (_match = parser.exec(selector)) { + if (_match[1] == "") cell.tag = _match[2]; + else if (_match[1] == "#") cell.attrs.id = _match[2]; + else if (_match[1] == ".") classes.push(_match[2]); + else if (_match[3][0] == "[") { + var pair = attrParser.exec(_match[3]); + cell.attrs[pair[1]] = pair[3]; + } + } + if (classes.length > 0) cell.attrs["class"] = classes.join(" "); + + var tag = makeValue(cell.tag, #{here}); + var attrsBody = Object.keys(cell.attrs).reduce(function(memo, attrName) { + return memo.concat([ + makeValue(attrName, #{here}), + makePunc(":", #{here}), + makeValue(cell.attrs[attrName], #{here}), + makePunc(",", #{here}) + ]); + }, []).concat(dynAttrs.inner); + var attrs = [makeDelim("{}", attrsBody, #{here})]; + letstx $tag = [tag], $attrs = attrs; + + return #{ ({tag: $tag, attrs: $attrs , children: $children ...}) }; + } + catch (e) { + return #{ m_impl($tag, {}, [$dynAttrs ..., $children ...]) } + } + } + case { _ ($selector, $partial ...) } => { + var partialSyntax = #{$partial ...}; + try { + var partial = unwrapSyntax(partialSyntax); + var isTag = partial.inner && partial.inner.length > 2 && (partial.inner[0].token.value == "tag" && partial.inner[1].token.value == ":") + return !isTag ? #{m_impl($selector, $partial ..., [])} : #{m_impl($selector, {}, $partial ...)}; + } + catch (e) { + return #{m_impl($selector, {}, $partial ...)} + } + } + case { _ ($selector) } => { + return #{m_impl($selector, {}, [])}; + } +} + +let m = macro { + case { _ ($selector, $dynAttrs ..., $children ...) } => { + return #{m_impl($selector, $dynAttrs ..., $children ...)} + } + case { _ ($selector, $partial ...) } => { + return #{m_impl($selector, $partial ...)} + } + case { _ ($selector) } => { + return #{m_impl($selector, {}, [])}; + } + case { _ } => { + return #{m}; + } +} + +export m; diff --git a/archive/v0.1.33/tools/template-converter.html b/archive/v0.1.33/tools/template-converter.html new file mode 100644 index 00000000..e3ec7de5 --- /dev/null +++ b/archive/v0.1.33/tools/template-converter.html @@ -0,0 +1,9 @@ +

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

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

Web Services

+

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

+

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

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

Basic usage

+

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

+

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

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

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

+

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

+

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

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

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

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

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

+

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

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

Processing web service data

+

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

+

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

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

Bind redirection code

+

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

+

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

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

Binding errors

+

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

+

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

+

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

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

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

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

Queuing Operations

+

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

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

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

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

Casting the Response Data to a Class

+

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

+

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

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

Unwrapping Response Data

+

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

+

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

+

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

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

Using Different Data Transfer Formats

+

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

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

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

+
var file = m.request({
+    method: "GET",
+    url: "myfile.txt",
+    deserialize: function(value) {return value;}
+});
+
+ +
+
+
+
+
+
+
+ Released under the MIT license +
© 2014 Leo Horie +
+
+ + + diff --git a/docs/change-log.md b/docs/change-log.md index d889581a..daae6c6d 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -1,5 +1,23 @@ ## Change Log +[v0.1.33](/mithril/archive/v0.1.33) - maintenance + +### Bug Fixes: + +- fix diff bug when mixing `undefined` in a tree [#524](https://github.com/lhorie/mithril.js/issues/524) +- fix reference to map file in package.json for cdnjs +- fix links in documentation + +--- + +[v0.1.32](/mithril/archive/v0.1.32) - maintenance + +### Bug Fixes: + +- fix regression caused by [#454](https://github.com/lhorie/mithril.js/issues/454) + +--- + [v0.1.31](/mithril/archive/v0.1.31) - maintenance ### News: diff --git a/mithril.min.js b/mithril.min.js index 38e878cb..36d0bcff 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,8 +1,8 @@ /* -Mithril v0.1.32 +Mithril v0.1.33 http://github.com/lhorie/mithril.js (c) Leo Horie License: MIT */ -var m=function a(b,c){function d(a){C=a.document,D=a.location,F=a.cancelAnimationFrame||a.clearTimeout,E=a.requestAnimationFrame||a.setTimeout}function e(){var a,b=[].slice.call(arguments),c=!(null==b[1]||K.call(b[1])!==G||"tag"in b[1]||"subtree"in b[1]),d=c?b[1]:{},e="class"in d?"class":"className",f={tag:"div",attrs:{}},g=[];if(K.call(b[0])!=I)throw new Error("selector in m(selector, attrs, children) should be a string");for(;a=L.exec(b[0]);)if(""===a[1]&&a[2])f.tag=a[2];else if("#"===a[1])f.attrs.id=a[2];else if("."===a[1])g.push(a[2]);else if("["===a[3][0]){var h=M.exec(a[3]);f.attrs[h[1]]=h[3]||(h[2]?"":!0)}g.length>0&&(f.attrs[e]=g.join(" "));var i=b.slice(c?2:1);f.children=1===i.length&&K.call(i[0])===H?i[0]:i;for(var j in d)if(j===e){var k=f.attrs[j];f.attrs[j]=(k&&d[j]?k+" ":k||"")+d[j]}else f.attrs[j]=d[j];return f}function f(a,b,d,j,l,m,n,o,p,q,r){try{(null==l||null==l.toString())&&(l="")}catch(s){l=""}if("retain"===l.subtree)return m;var t=K.call(m),u=K.call(l);if(null==m||t!==u){if(null!=m)if(d&&d.nodes){var v=o-j,w=v+(u===H?l:m.nodes).length;i(d.nodes.slice(v,w),d.slice(v,w))}else m.nodes&&i(m.nodes,m);m=new l.constructor,m.tag&&(m={}),m.nodes=[]}if(u===H){for(var x=0,y=l.length;y>x;x++)K.call(l[x])===H&&(l=l.concat.apply([],l),x--,y=l.length);for(var z=[],A=m.length===l.length,B=0,D=1,E=2,F=3,L={},M=[],O=!1,x=0;xx;x++)l[x]&&l[x].attrs&&null==l[x].attrs.key&&(l[x].attrs.key="__mithril__"+P++);if(O){l.indexOf(null)>-1&&(l=l.filter(function(a){return null!=a}));var Q=!1;if(l.length!=m.length)Q=!0;else for(var R,S,x=0;R=m[x],S=l[x];x++)if(R.attrs&&S.attrs&&R.attrs.key!=S.attrs.key){Q=!0;break}if(Q){for(var x=0,y=l.length;y>x;x++)if(l[x]&&l[x].attrs)if(null!=l[x].attrs.key){var T=l[x].attrs.key;L[T]=L[T]?{action:F,index:x,from:L[T].index,element:m.nodes[L[T].index]||C.createElement("div")}:{action:E,index:x}}else M.push({index:x,element:a.childNodes[x]||C.createElement("div")});var U=[];for(var V in L)U.push(L[V]);var W=U.sort(g),X=new Array(m.length);X.nodes=m.nodes.slice();for(var Y,x=0;Y=W[x];x++){if(Y.action===D&&(i(m[Y.index].nodes,m[Y.index]),X.splice(Y.index,1)),Y.action===E){var Z=C.createElement("div");Z.key=l[Y.index].attrs.key,a.insertBefore(Z,a.childNodes[Y.index]||null),X.splice(Y.index,0,{attrs:{key:l[Y.index].attrs.key},nodes:[Z]}),X.nodes[Y.index]=Z}Y.action===F&&(a.childNodes[Y.index]!==Y.element&&null!==Y.element&&a.insertBefore(Y.element,a.childNodes[Y.index]||null),X[Y.index]=m[Y.from],X.nodes[Y.index]=Y.element)}for(var x=0,y=M.length;y>x;x++){var Y=M[x];a.insertBefore(Y.element,a.childNodes[Y.index]||null),X[Y.index]=m[Y.index],X.nodes[Y.index]=Y.element}m=X}}for(var x=0,$=0,y=l.length;y>x;x++){var _=f(a,b,m,o,l[x],m[$],n,o+B||B,p,q,r);_!==c&&(_.nodes.intact||(A=!1),B+=_.$trusted?(_.match(/<[^\/]|\>\s*[^<]/g)||[0]).length:K.call(_)===H?_.length:1,m[$++]=_)}if(!A){for(var x=0,y=l.length;y>x;x++)null!=m[x]&&z.push.apply(z,m[x].nodes);for(var ab,x=0;ab=m.nodes[x];x++)null!=ab.parentNode&&z.indexOf(ab)<0&&i([ab],[m[x]]);l.length("key"in l.attrs?1:0);if((l.tag!=m.tag||bb.join()!=Object.keys(m.attrs).join()||l.attrs.id!=m.attrs.id||"all"==e.redraw.strategy()&&m.configContext&&m.configContext.retain!==!0||"diff"==e.redraw.strategy()&&m.configContext&&m.configContext.retain===!1)&&(m.nodes.length&&i(m.nodes),m.configContext&&typeof m.configContext.onunload===J&&m.configContext.onunload()),K.call(l.tag)!=I)return;var ab,db=0===m.nodes.length;if(l.attrs.xmlns?q=l.attrs.xmlns:"svg"===l.tag?q="http://www.w3.org/2000/svg":"math"===l.tag&&(q="http://www.w3.org/1998/Math/MathML"),db?(ab=l.attrs.is?q===c?C.createElement(l.tag,l.attrs.is):C.createElementNS(q,l.tag,l.attrs.is):q===c?C.createElement(l.tag):C.createElementNS(q,l.tag),m={tag:l.tag,attrs:cb?h(ab,l.tag,l.attrs,{},q):l.attrs,children:null!=l.children&&l.children.length>0?f(ab,l.tag,c,c,l.children,m.children,!0,0,l.attrs.contenteditable?ab:p,q,r):l.children,nodes:[ab]},m.children&&!m.children.nodes&&(m.children.nodes=[]),"select"===l.tag&&l.attrs.value&&h(ab,l.tag,{value:l.attrs.value},{},q),a.insertBefore(ab,a.childNodes[o]||null)):(ab=m.nodes[0],cb&&h(ab,l.tag,l.attrs,m.attrs,q),m.children=f(ab,l.tag,c,c,l.children,m.children,!1,0,l.attrs.contenteditable?ab:p,q,r),m.nodes.intact=!0,n===!0&&null!=ab&&a.insertBefore(ab,a.childNodes[o]||null)),typeof l.attrs.config===J){var eb=m.configContext=m.configContext||{retain:"diff"==e.redraw.strategy()||c},fb=function(a,b){return function(){return a.attrs.config.apply(a,b)}};r.push(fb(l,[ab,!db,eb,m]))}}else if(typeof l!=J){var z;0===m.nodes.length?(l.$trusted?z=k(a,o,l):(z=[C.createTextNode(l)],a.nodeName.match(N)||a.insertBefore(z[0],a.childNodes[o]||null)),m="string number boolean".indexOf(typeof l)>-1?new l.constructor(l):l,m.nodes=z):m.valueOf()!==l.valueOf()||n===!0?(z=m.nodes,p&&p===C.activeElement||(l.$trusted?(i(z,m),z=k(a,o,l)):"textarea"===b?a.value=l:p?p.innerHTML=l:((1===z[0].nodeType||z.length>1)&&(i(m.nodes,m),z=[C.createTextNode(l)]),a.insertBefore(z[0],a.childNodes[o]||null),z[0].nodeValue=l)),m=new l.constructor(l),m.nodes=z):m.nodes.intact=!0}return m}function g(a,b){return a.action-b.action||a.index-b.index}function h(a,b,c,d,e){for(var f in c){var g=c[f],h=d[f];if(f in d&&h===g)"value"===f&&"input"===b&&a.value!=g&&(a.value=g);else{d[f]=g;try{if("config"===f||"key"==f)continue;if(typeof g===J&&0===f.indexOf("on"))a[f]=l(g,a);else if("style"===f&&null!=g&&K.call(g)===G){for(var i in g)(null==h||h[i]!==g[i])&&(a.style[i]=g[i]);for(var i in h)i in g||(a.style[i]="")}else null!=e?"href"===f?a.setAttributeNS("http://www.w3.org/1999/xlink","href",g):"className"===f?a.setAttribute("class",g):a.setAttribute(f,g):f in a&&"list"!==f&&"style"!==f&&"form"!==f&&"type"!==f&&"width"!==f&&"height"!==f?("input"!==b||a[f]!==g)&&(a[f]=g):a.setAttribute(f,g)}catch(j){if(j.message.indexOf("Invalid argument")<0)throw j}}}return d}function i(a,b){for(var c=a.length-1;c>-1;c--)if(a[c]&&a[c].parentNode){try{a[c].parentNode.removeChild(a[c])}catch(d){}b=[].concat(b),b[c]&&j(b[c])}0!=a.length&&(a.length=0)}function j(a){if(a.configContext&&typeof a.configContext.onunload===J&&(a.configContext.onunload(),a.configContext.onunload=null),a.children)if(K.call(a.children)===H)for(var b,c=0;b=a.children[c];c++)j(b);else a.children.tag&&j(a.children)}function k(a,b,c){var d=a.childNodes[b];if(d){var e=1!=d.nodeType,f=C.createElement("span");e?(a.insertBefore(f,d||null),f.insertAdjacentHTML("beforebegin",c),a.removeChild(f)):d.insertAdjacentHTML("beforebegin",c)}else a.insertAdjacentHTML("beforeend",c);for(var g=[];a.childNodes[b]!==d;)g.push(a.childNodes[b]),b++;return g}function l(a,b){return function(c){c=c||event,e.redraw.strategy("diff"),e.startComputation();try{return a.call(b,c)}finally{ab()}}}function m(a){var b=Q.indexOf(a);return 0>b?Q.push(a)-1:b}function n(a){var b=function(){return arguments.length&&(a=arguments[0]),a};return b.toJSON=function(){return a},b}function o(){for(var a,b=0;a=T[b];b++)V[b]&&e.render(a,U[b].view?U[b].view(V[b]):$());Y&&(Y(),Y=null),W=null,X=new Date,e.redraw.strategy("diff")}function p(a){return a.slice(db[e.route.mode].length)}function q(a,b,c){bb={};var d=c.indexOf("?");-1!==d&&(bb=u(c.substr(d+1,c.length)),c=c.substr(0,d));var f=Object.keys(b),g=f.indexOf(c);if(-1!==g)return e.module(a,b[f[g]]),!0;for(var h in b){if(h===c)return e.module(a,b[h]),!0;var i=new RegExp("^"+h.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(i.test(c))return c.replace(i,function(){for(var c=h.match(/:[^\/]+/g)||[],d=[].slice.call(arguments,1,-2),f=0,g=c.length;g>f;f++)bb[c[f].replace(/:|\./g,"")]=decodeURIComponent(d[f]);e.module(a,b[h])}),!0}}function r(a){if(a=a||event,!a.ctrlKey&&!a.metaKey&&2!==a.which){a.preventDefault?a.preventDefault():a.returnValue=!1;for(var b=a.currentTarget||a.srcElement,c="pathname"===e.route.mode&&b.search?u(b.search.slice(1)):{};b&&"A"!=b.nodeName.toUpperCase();)b=b.parentNode;e.route(b[e.route.mode].slice(db[e.route.mode].length),c)}}function s(){"hash"!=e.route.mode&&D.hash?D.hash=D.hash:b.scrollTo(0,0)}function t(a,b){var d={},e=[];for(var f in a){var g=b?b+"["+f+"]":f,h=a[f],i=K.call(h),j=null===h?encodeURIComponent(g):i===G?t(h,g):i===H?h.reduce(function(a,b){return d[g]||(d[g]={}),d[g][b]?a:(d[g][b]=!0,a.concat(encodeURIComponent(g)+"="+encodeURIComponent(b)))},[]).join("&"):encodeURIComponent(g)+"="+encodeURIComponent(h);h!==c&&e.push(j)}return e.join("&")}function u(a){for(var b=a.split("&"),c={},d=0,e=b.length;e>d;d++){var f=b[d].split("="),g=decodeURIComponent(f[0]),h=2==f.length?decodeURIComponent(f[1]):null;null!=c[g]?(K.call(c[g])!==H&&(c[g]=[c[g]]),c[g].push(h)):c[g]=h}return c}function v(a){var b=m(a);i(a.childNodes,R[b]),R[b]=c}function w(a,b){var c=e.prop(b);return a.then(c),c.then=function(c,d){return w(a.then(c,d),b)},c}function x(a,b){function c(a){l=a||j,n.map(function(a){l===i&&a.resolve(m)||a.reject(m)})}function d(a,b,c,d){if((null!=m&&K.call(m)===G||typeof m===J)&&typeof a===J)try{var f=0;a.call(m,function(a){f++||(m=a,b())},function(a){f++||(m=a,c())})}catch(g){e.deferred.onerror(g),m=g,c()}else d()}function f(){var j;try{j=m&&m.then}catch(n){return e.deferred.onerror(n),m=n,l=h,f()}d(j,function(){l=g,f()},function(){l=h,f()},function(){try{l===g&&typeof a===J?m=a(m):l===h&&"function"==typeof b&&(m=b(m),l=g)}catch(f){return e.deferred.onerror(f),m=f,c()}m===k?(m=TypeError(),c()):d(j,function(){c(i)},c,function(){c(l===g&&i)})})}var g=1,h=2,i=3,j=4,k=this,l=0,m=0,n=[];k.promise={},k.resolve=function(a){return l||(m=a,l=g,f()),this},k.reject=function(a){return l||(m=a,l=h,f()),this},k.promise.then=function(a,b){var c=new x(a,b);return l===i?c.resolve(m):l===j?c.reject(m):n.push(c),c.promise}}function y(a){return a}function z(a){if(!a.dataType||"jsonp"!==a.dataType.toLowerCase()){var d=new b.XMLHttpRequest;if(d.open(a.method,a.url,!0,a.user,a.password),d.onreadystatechange=function(){4===d.readyState&&(d.status>=200&&d.status<300?a.onload({type:"load",target:d}):a.onerror({type:"error",target:d}))},a.serialize===JSON.stringify&&a.data&&"GET"!==a.method&&d.setRequestHeader("Content-Type","application/json; charset=utf-8"),a.deserialize===JSON.parse&&d.setRequestHeader("Accept","application/json, text/*"),typeof a.config===J){var e=a.config(d,a);null!=e&&(d=e)}var f="GET"!==a.method&&a.data?a.data:"";if(f&&K.call(f)!=I&&f.constructor!=b.FormData)throw"Request data should be either be a string or FormData. Check the `serialize` option in `m.request`";return d.send(f),d}var g="mithril_callback_"+(new Date).getTime()+"_"+Math.round(1e16*Math.random()).toString(36),h=C.createElement("script");b[g]=function(d){h.parentNode.removeChild(h),a.onload({type:"load",target:{responseText:d}}),b[g]=c},h.onerror=function(){return h.parentNode.removeChild(h),a.onerror({type:"error",target:{status:500,responseText:JSON.stringify({error:"Error making jsonp request"})}}),b[g]=c,!1},h.onload=function(){return!1},h.src=a.url+(a.url.indexOf("?")>0?"&":"?")+(a.callbackKey?a.callbackKey:"callback")+"="+g+"&"+t(a.data||{}),C.body.appendChild(h)}function A(a,b,c){if("GET"===a.method&&"jsonp"!=a.dataType){var d=a.url.indexOf("?")<0?"?":"&",e=t(b);a.url=a.url+(e?d+e:"")}else a.data=c(b);return a}function B(a,b){var c=a.match(/:[a-z]\w+/gi);if(c&&b)for(var d=0;dk;k++)e[k]()},e.trust=function(a){return a=new String(a),a.$trusted=!0,a},e.prop=function(a){return(null!=a&&K.call(a)===G||typeof a===J)&&typeof a.then===J?w(a):n(a)};var S,T=[],U=[],V=[],W=null,X=0,Y=null,Z=16;e.module=function(a,b){if(!a)throw new Error("Please ensure the DOM element exists before rendering a template into it.");var c=T.indexOf(a);0>c&&(c=T.length);var d=!1;if(V[c]&&typeof V[c].onunload===J){var f={preventDefault:function(){d=!0}};V[c].onunload(f)}if(!d){e.redraw.strategy("all"),e.startComputation(),T[c]=a;var g=S=b=b||{},h=new(b.controller||function(){});return g===S&&(V[c]=h,U[c]=b),ab(),V[c]}},e.redraw=function(a){W&&a!==!0?(new Date-X>Z||E===b.requestAnimationFrame)&&(W>0&&F(W),W=E(o,Z)):(o(),W=E(function(){W=null},Z))},e.redraw.strategy=e.prop();var $=function(){return""},_=0;e.startComputation=function(){_++},e.endComputation=function(){_=Math.max(_-1,0),0===_&&e.redraw()};var ab=function(){"none"==e.redraw.strategy()?(_--,e.redraw.strategy("diff")):e.endComputation()};e.withAttr=function(a,b){return function(c){c=c||event;var d=c.currentTarget||this;b(a in d?d[a]:d.getAttribute(a))}};var bb,cb,db={pathname:"",hash:"#",search:"?"},eb=function(){};return e.route=function(){if(0===arguments.length)return cb;if(3===arguments.length&&K.call(arguments[1])===I){var a=arguments[0],c=arguments[1],d=arguments[2];eb=function(b){var f=cb=p(b);q(a,d,f)||e.route(c,!0)};var f="hash"===e.route.mode?"onhashchange":"onpopstate";b[f]=function(){var a=D[e.route.mode];"pathname"===e.route.mode&&(a+=D.search),cb!=p(a)&&eb(a)},Y=s,b[f]()}else if(arguments[0].addEventListener||arguments[0].attachEvent){{var g=arguments[0];arguments[1],arguments[2]}g.href=("pathname"!==e.route.mode?D.pathname:"")+db[e.route.mode]+this.attrs.href,g.addEventListener?(g.removeEventListener("click",r),g.addEventListener("click",r)):(g.detachEvent("onclick",r),g.attachEvent("onclick",r))}else if(K.call(arguments[0])===I){var h=cb;cb=arguments[0];var i=arguments[1]||{},j=cb.indexOf("?"),k=j>-1?u(cb.slice(j+1)):{};for(var l in i)k[l]=i[l];var m=t(k),n=j>-1?cb.slice(0,j):cb;m&&(cb=n+(-1===n.indexOf("?")?"?":"&")+m);var o=(3===arguments.length?arguments[2]:arguments[1])===!0||h===arguments[0];b.history.pushState?(Y=function(){b.history[o?"replaceState":"pushState"](null,C.title,db[e.route.mode]+cb),s()},eb(db[e.route.mode]+cb)):(D[e.route.mode]=cb,eb(db[e.route.mode]+cb))}},e.route.param=function(a){if(!bb)throw new Error("You must call m.route(element, defaultRoute, routes) before calling m.route.param()");return bb[a]},e.route.mode="search",e.route.buildQueryString=t,e.route.parseQueryString=u,e.deferred=function(){var a=new x;return a.promise=w(a.promise),a},e.deferred.onerror=function(a){if("[object Error]"===K.call(a)&&!a.constructor.toString().match(/ Error/))throw a},e.sync=function(a){function b(a,b){return function(e){return g[a]=e,b||(c="reject"),0===--f&&(d.promise(g),d[c](g)),e}}var c="resolve",d=e.deferred(),f=a.length,g=new Array(f);if(a.length>0)for(var h=0;h0&&(f.attrs[e]=g.join(" "));var i=b.slice(c?2:1);f.children=1===i.length&&K.call(i[0])===H?i[0]:i;for(var j in d)if(j===e){var k=f.attrs[j];f.attrs[j]=(k&&d[j]?k+" ":k||"")+d[j]}else f.attrs[j]=d[j];return f}function f(a,b,d,j,l,m,n,o,p,q,r){try{(null==l||null==l.toString())&&(l="")}catch(s){l=""}if("retain"===l.subtree)return m;var t=K.call(m),u=K.call(l);if(null==m||t!==u){if(null!=m)if(d&&d.nodes){var v=o-j,w=v+(u===H?l:m.nodes).length;i(d.nodes.slice(v,w),d.slice(v,w))}else m.nodes&&i(m.nodes,m);m=new l.constructor,m.tag&&(m={}),m.nodes=[]}if(u===H){for(var x=0,y=l.length;y>x;x++)K.call(l[x])===H&&(l=l.concat.apply([],l),x--,y=l.length);for(var z=[],A=m.length===l.length,B=0,D=1,E=2,F=3,L={},M=[],O=!1,x=0;xx;x++)l[x]&&l[x].attrs&&null==l[x].attrs.key&&(l[x].attrs.key="__mithril__"+P++);if(O){var Q=!1;if(l.length!=m.length)Q=!0;else for(var R,S,x=0;R=m[x],S=l[x];x++)if(R.attrs&&S.attrs&&R.attrs.key!=S.attrs.key){Q=!0;break}if(Q){for(var x=0,y=l.length;y>x;x++)if(l[x]&&l[x].attrs)if(null!=l[x].attrs.key){var T=l[x].attrs.key;L[T]=L[T]?{action:F,index:x,from:L[T].index,element:m.nodes[L[T].index]||C.createElement("div")}:{action:E,index:x}}else M.push({index:x,element:a.childNodes[x]||C.createElement("div")});var U=[];for(var V in L)U.push(L[V]);var W=U.sort(g),X=new Array(m.length);X.nodes=m.nodes.slice();for(var Y,x=0;Y=W[x];x++){if(Y.action===D&&(i(m[Y.index].nodes,m[Y.index]),X.splice(Y.index,1)),Y.action===E){var Z=C.createElement("div");Z.key=l[Y.index].attrs.key,a.insertBefore(Z,a.childNodes[Y.index]||null),X.splice(Y.index,0,{attrs:{key:l[Y.index].attrs.key},nodes:[Z]}),X.nodes[Y.index]=Z}Y.action===F&&(a.childNodes[Y.index]!==Y.element&&null!==Y.element&&a.insertBefore(Y.element,a.childNodes[Y.index]||null),X[Y.index]=m[Y.from],X.nodes[Y.index]=Y.element)}for(var x=0,y=M.length;y>x;x++){var Y=M[x];a.insertBefore(Y.element,a.childNodes[Y.index]||null),X[Y.index]=m[Y.index],X.nodes[Y.index]=Y.element}m=X}}for(var x=0,$=0,y=l.length;y>x;x++){var _=f(a,b,m,o,l[x],m[$],n,o+B||B,p,q,r);_!==c&&(_.nodes.intact||(A=!1),B+=_.$trusted?(_.match(/<[^\/]|\>\s*[^<]/g)||[0]).length:K.call(_)===H?_.length:1,m[$++]=_)}if(!A){for(var x=0,y=l.length;y>x;x++)null!=m[x]&&z.push.apply(z,m[x].nodes);for(var ab,x=0;ab=m.nodes[x];x++)null!=ab.parentNode&&z.indexOf(ab)<0&&i([ab],[m[x]]);l.length("key"in l.attrs?1:0);if((l.tag!=m.tag||bb.join()!=Object.keys(m.attrs).join()||l.attrs.id!=m.attrs.id||"all"==e.redraw.strategy()&&m.configContext&&m.configContext.retain!==!0||"diff"==e.redraw.strategy()&&m.configContext&&m.configContext.retain===!1)&&(m.nodes.length&&i(m.nodes),m.configContext&&typeof m.configContext.onunload===J&&m.configContext.onunload()),K.call(l.tag)!=I)return;var ab,db=0===m.nodes.length;if(l.attrs.xmlns?q=l.attrs.xmlns:"svg"===l.tag?q="http://www.w3.org/2000/svg":"math"===l.tag&&(q="http://www.w3.org/1998/Math/MathML"),db?(ab=l.attrs.is?q===c?C.createElement(l.tag,l.attrs.is):C.createElementNS(q,l.tag,l.attrs.is):q===c?C.createElement(l.tag):C.createElementNS(q,l.tag),m={tag:l.tag,attrs:cb?h(ab,l.tag,l.attrs,{},q):l.attrs,children:null!=l.children&&l.children.length>0?f(ab,l.tag,c,c,l.children,m.children,!0,0,l.attrs.contenteditable?ab:p,q,r):l.children,nodes:[ab]},m.children&&!m.children.nodes&&(m.children.nodes=[]),"select"===l.tag&&l.attrs.value&&h(ab,l.tag,{value:l.attrs.value},{},q),a.insertBefore(ab,a.childNodes[o]||null)):(ab=m.nodes[0],cb&&h(ab,l.tag,l.attrs,m.attrs,q),m.children=f(ab,l.tag,c,c,l.children,m.children,!1,0,l.attrs.contenteditable?ab:p,q,r),m.nodes.intact=!0,n===!0&&null!=ab&&a.insertBefore(ab,a.childNodes[o]||null)),typeof l.attrs.config===J){var eb=m.configContext=m.configContext||{retain:"diff"==e.redraw.strategy()||c},fb=function(a,b){return function(){return a.attrs.config.apply(a,b)}};r.push(fb(l,[ab,!db,eb,m]))}}else if(typeof l!=J){var z;0===m.nodes.length?(l.$trusted?z=k(a,o,l):(z=[C.createTextNode(l)],a.nodeName.match(N)||a.insertBefore(z[0],a.childNodes[o]||null)),m="string number boolean".indexOf(typeof l)>-1?new l.constructor(l):l,m.nodes=z):m.valueOf()!==l.valueOf()||n===!0?(z=m.nodes,p&&p===C.activeElement||(l.$trusted?(i(z,m),z=k(a,o,l)):"textarea"===b?a.value=l:p?p.innerHTML=l:((1===z[0].nodeType||z.length>1)&&(i(m.nodes,m),z=[C.createTextNode(l)]),a.insertBefore(z[0],a.childNodes[o]||null),z[0].nodeValue=l)),m=new l.constructor(l),m.nodes=z):m.nodes.intact=!0}return m}function g(a,b){return a.action-b.action||a.index-b.index}function h(a,b,c,d,e){for(var f in c){var g=c[f],h=d[f];if(f in d&&h===g)"value"===f&&"input"===b&&a.value!=g&&(a.value=g);else{d[f]=g;try{if("config"===f||"key"==f)continue;if(typeof g===J&&0===f.indexOf("on"))a[f]=l(g,a);else if("style"===f&&null!=g&&K.call(g)===G){for(var i in g)(null==h||h[i]!==g[i])&&(a.style[i]=g[i]);for(var i in h)i in g||(a.style[i]="")}else null!=e?"href"===f?a.setAttributeNS("http://www.w3.org/1999/xlink","href",g):"className"===f?a.setAttribute("class",g):a.setAttribute(f,g):f in a&&"list"!==f&&"style"!==f&&"form"!==f&&"type"!==f&&"width"!==f&&"height"!==f?("input"!==b||a[f]!==g)&&(a[f]=g):a.setAttribute(f,g)}catch(j){if(j.message.indexOf("Invalid argument")<0)throw j}}}return d}function i(a,b){for(var c=a.length-1;c>-1;c--)if(a[c]&&a[c].parentNode){try{a[c].parentNode.removeChild(a[c])}catch(d){}b=[].concat(b),b[c]&&j(b[c])}0!=a.length&&(a.length=0)}function j(a){if(a.configContext&&typeof a.configContext.onunload===J&&(a.configContext.onunload(),a.configContext.onunload=null),a.children)if(K.call(a.children)===H)for(var b,c=0;b=a.children[c];c++)j(b);else a.children.tag&&j(a.children)}function k(a,b,c){var d=a.childNodes[b];if(d){var e=1!=d.nodeType,f=C.createElement("span");e?(a.insertBefore(f,d||null),f.insertAdjacentHTML("beforebegin",c),a.removeChild(f)):d.insertAdjacentHTML("beforebegin",c)}else a.insertAdjacentHTML("beforeend",c);for(var g=[];a.childNodes[b]!==d;)g.push(a.childNodes[b]),b++;return g}function l(a,b){return function(c){c=c||event,e.redraw.strategy("diff"),e.startComputation();try{return a.call(b,c)}finally{ab()}}}function m(a){var b=Q.indexOf(a);return 0>b?Q.push(a)-1:b}function n(a){var b=function(){return arguments.length&&(a=arguments[0]),a};return b.toJSON=function(){return a},b}function o(){for(var a,b=0;a=T[b];b++)V[b]&&e.render(a,U[b].view?U[b].view(V[b]):$());Y&&(Y(),Y=null),W=null,X=new Date,e.redraw.strategy("diff")}function p(a){return a.slice(db[e.route.mode].length)}function q(a,b,c){bb={};var d=c.indexOf("?");-1!==d&&(bb=u(c.substr(d+1,c.length)),c=c.substr(0,d));var f=Object.keys(b),g=f.indexOf(c);if(-1!==g)return e.module(a,b[f[g]]),!0;for(var h in b){if(h===c)return e.module(a,b[h]),!0;var i=new RegExp("^"+h.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(i.test(c))return c.replace(i,function(){for(var c=h.match(/:[^\/]+/g)||[],d=[].slice.call(arguments,1,-2),f=0,g=c.length;g>f;f++)bb[c[f].replace(/:|\./g,"")]=decodeURIComponent(d[f]);e.module(a,b[h])}),!0}}function r(a){if(a=a||event,!a.ctrlKey&&!a.metaKey&&2!==a.which){a.preventDefault?a.preventDefault():a.returnValue=!1;for(var b=a.currentTarget||a.srcElement,c="pathname"===e.route.mode&&b.search?u(b.search.slice(1)):{};b&&"A"!=b.nodeName.toUpperCase();)b=b.parentNode;e.route(b[e.route.mode].slice(db[e.route.mode].length),c)}}function s(){"hash"!=e.route.mode&&D.hash?D.hash=D.hash:b.scrollTo(0,0)}function t(a,b){var d={},e=[];for(var f in a){var g=b?b+"["+f+"]":f,h=a[f],i=K.call(h),j=null===h?encodeURIComponent(g):i===G?t(h,g):i===H?h.reduce(function(a,b){return d[g]||(d[g]={}),d[g][b]?a:(d[g][b]=!0,a.concat(encodeURIComponent(g)+"="+encodeURIComponent(b)))},[]).join("&"):encodeURIComponent(g)+"="+encodeURIComponent(h);h!==c&&e.push(j)}return e.join("&")}function u(a){for(var b=a.split("&"),c={},d=0,e=b.length;e>d;d++){var f=b[d].split("="),g=decodeURIComponent(f[0]),h=2==f.length?decodeURIComponent(f[1]):null;null!=c[g]?(K.call(c[g])!==H&&(c[g]=[c[g]]),c[g].push(h)):c[g]=h}return c}function v(a){var b=m(a);i(a.childNodes,R[b]),R[b]=c}function w(a,b){var c=e.prop(b);return a.then(c),c.then=function(c,d){return w(a.then(c,d),b)},c}function x(a,b){function c(a){l=a||j,n.map(function(a){l===i&&a.resolve(m)||a.reject(m)})}function d(a,b,c,d){if((null!=m&&K.call(m)===G||typeof m===J)&&typeof a===J)try{var f=0;a.call(m,function(a){f++||(m=a,b())},function(a){f++||(m=a,c())})}catch(g){e.deferred.onerror(g),m=g,c()}else d()}function f(){var j;try{j=m&&m.then}catch(n){return e.deferred.onerror(n),m=n,l=h,f()}d(j,function(){l=g,f()},function(){l=h,f()},function(){try{l===g&&typeof a===J?m=a(m):l===h&&"function"==typeof b&&(m=b(m),l=g)}catch(f){return e.deferred.onerror(f),m=f,c()}m===k?(m=TypeError(),c()):d(j,function(){c(i)},c,function(){c(l===g&&i)})})}var g=1,h=2,i=3,j=4,k=this,l=0,m=0,n=[];k.promise={},k.resolve=function(a){return l||(m=a,l=g,f()),this},k.reject=function(a){return l||(m=a,l=h,f()),this},k.promise.then=function(a,b){var c=new x(a,b);return l===i?c.resolve(m):l===j?c.reject(m):n.push(c),c.promise}}function y(a){return a}function z(a){if(!a.dataType||"jsonp"!==a.dataType.toLowerCase()){var d=new b.XMLHttpRequest;if(d.open(a.method,a.url,!0,a.user,a.password),d.onreadystatechange=function(){4===d.readyState&&(d.status>=200&&d.status<300?a.onload({type:"load",target:d}):a.onerror({type:"error",target:d}))},a.serialize===JSON.stringify&&a.data&&"GET"!==a.method&&d.setRequestHeader("Content-Type","application/json; charset=utf-8"),a.deserialize===JSON.parse&&d.setRequestHeader("Accept","application/json, text/*"),typeof a.config===J){var e=a.config(d,a);null!=e&&(d=e)}var f="GET"!==a.method&&a.data?a.data:"";if(f&&K.call(f)!=I&&f.constructor!=b.FormData)throw"Request data should be either be a string or FormData. Check the `serialize` option in `m.request`";return d.send(f),d}var g="mithril_callback_"+(new Date).getTime()+"_"+Math.round(1e16*Math.random()).toString(36),h=C.createElement("script");b[g]=function(d){h.parentNode.removeChild(h),a.onload({type:"load",target:{responseText:d}}),b[g]=c},h.onerror=function(){return h.parentNode.removeChild(h),a.onerror({type:"error",target:{status:500,responseText:JSON.stringify({error:"Error making jsonp request"})}}),b[g]=c,!1},h.onload=function(){return!1},h.src=a.url+(a.url.indexOf("?")>0?"&":"?")+(a.callbackKey?a.callbackKey:"callback")+"="+g+"&"+t(a.data||{}),C.body.appendChild(h)}function A(a,b,c){if("GET"===a.method&&"jsonp"!=a.dataType){var d=a.url.indexOf("?")<0?"?":"&",e=t(b);a.url=a.url+(e?d+e:"")}else a.data=c(b);return a}function B(a,b){var c=a.match(/:[a-z]\w+/gi);if(c&&b)for(var d=0;dk;k++)e[k]()},e.trust=function(a){return a=new String(a),a.$trusted=!0,a},e.prop=function(a){return(null!=a&&K.call(a)===G||typeof a===J)&&typeof a.then===J?w(a):n(a)};var S,T=[],U=[],V=[],W=null,X=0,Y=null,Z=16;e.module=function(a,b){if(!a)throw new Error("Please ensure the DOM element exists before rendering a template into it.");var c=T.indexOf(a);0>c&&(c=T.length);var d=!1;if(V[c]&&typeof V[c].onunload===J){var f={preventDefault:function(){d=!0}};V[c].onunload(f)}if(!d){e.redraw.strategy("all"),e.startComputation(),T[c]=a;var g=S=b=b||{},h=new(b.controller||function(){});return g===S&&(V[c]=h,U[c]=b),ab(),V[c]}},e.redraw=function(a){W&&a!==!0?(new Date-X>Z||E===b.requestAnimationFrame)&&(W>0&&F(W),W=E(o,Z)):(o(),W=E(function(){W=null},Z))},e.redraw.strategy=e.prop();var $=function(){return""},_=0;e.startComputation=function(){_++},e.endComputation=function(){_=Math.max(_-1,0),0===_&&e.redraw()};var ab=function(){"none"==e.redraw.strategy()?(_--,e.redraw.strategy("diff")):e.endComputation()};e.withAttr=function(a,b){return function(c){c=c||event;var d=c.currentTarget||this;b(a in d?d[a]:d.getAttribute(a))}};var bb,cb,db={pathname:"",hash:"#",search:"?"},eb=function(){};return e.route=function(){if(0===arguments.length)return cb;if(3===arguments.length&&K.call(arguments[1])===I){var a=arguments[0],c=arguments[1],d=arguments[2];eb=function(b){var f=cb=p(b);q(a,d,f)||e.route(c,!0)};var f="hash"===e.route.mode?"onhashchange":"onpopstate";b[f]=function(){var a=D[e.route.mode];"pathname"===e.route.mode&&(a+=D.search),cb!=p(a)&&eb(a)},Y=s,b[f]()}else if(arguments[0].addEventListener||arguments[0].attachEvent){{var g=arguments[0];arguments[1],arguments[2]}g.href=("pathname"!==e.route.mode?D.pathname:"")+db[e.route.mode]+this.attrs.href,g.addEventListener?(g.removeEventListener("click",r),g.addEventListener("click",r)):(g.detachEvent("onclick",r),g.attachEvent("onclick",r))}else if(K.call(arguments[0])===I){var h=cb;cb=arguments[0];var i=arguments[1]||{},j=cb.indexOf("?"),k=j>-1?u(cb.slice(j+1)):{};for(var l in i)k[l]=i[l];var m=t(k),n=j>-1?cb.slice(0,j):cb;m&&(cb=n+(-1===n.indexOf("?")?"?":"&")+m);var o=(3===arguments.length?arguments[2]:arguments[1])===!0||h===arguments[0];b.history.pushState?(Y=function(){b.history[o?"replaceState":"pushState"](null,C.title,db[e.route.mode]+cb),s()},eb(db[e.route.mode]+cb)):(D[e.route.mode]=cb,eb(db[e.route.mode]+cb))}},e.route.param=function(a){if(!bb)throw new Error("You must call m.route(element, defaultRoute, routes) before calling m.route.param()");return bb[a]},e.route.mode="search",e.route.buildQueryString=t,e.route.parseQueryString=u,e.deferred=function(){var a=new x;return a.promise=w(a.promise),a},e.deferred.onerror=function(a){if("[object Error]"===K.call(a)&&!a.constructor.toString().match(/ Error/))throw a},e.sync=function(a){function b(a,b){return function(e){return g[a]=e,b||(c="reject"),0===--f&&(d.promise(g),d[c](g)),e}}var c="resolve",d=e.deferred(),f=a.length,g=new Array(f);if(a.length>0)for(var h=0;h