editorial
This commit is contained in:
parent
d9243f4998
commit
0550079bfc
19 changed files with 93 additions and 91 deletions
|
|
@ -71,9 +71,9 @@ var doStuff = function() {
|
|||
<hr>
|
||||
<h3 id="integrating-multiple-execution-threads">Integrating multiple execution threads</h3>
|
||||
<p>When <a href="integration.html">integrating with third party libraries</a>, you might find that you need to call asynchronous methods from outside of Mithril's API.</p>
|
||||
<p>In order to integrate non-trivial asynchronous code to Mithril's auto-redrawing system, you need to ensure all execution threads call <code>m.startComputation</code> / <code>m.endComputation</code>.</p>
|
||||
<p>In order to integrate non-trivial asynchronous code with Mithril's auto-redrawing system, you need to ensure all execution threads call <code>m.startComputation</code> / <code>m.endComputation</code>.</p>
|
||||
<p>An execution thread is basically any amount of code that runs before other asynchronous threads start to run.</p>
|
||||
<p>Integrating multiple execution threads can be done in a two different ways: in a layered fashion or in comprehensive fashion</p>
|
||||
<p>Integrating multiple execution threads can be done in two different ways: in a layered fashion or in comprehensive fashion.</p>
|
||||
<h4 id="layered-integration">Layered integration</h4>
|
||||
<p>Layered integration is recommended for modular code where many different APIs may be put together at the application level.</p>
|
||||
<p>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.</p>
|
||||
|
|
@ -105,7 +105,7 @@ var doBoth = function(callback) {
|
|||
})
|
||||
};</code></pre>
|
||||
<h4 id="comprehensive-integration">Comprehensive integration</h4>
|
||||
<p>Comprehensive integration is recommended if integrating a monolithic series of asynchronous operations. In contrast to layered integration, it minimizes the number of <code>m.startComputation</code> / <code>m.endComputation</code> to avoid clutter.</p>
|
||||
<p>Comprehensive integration is recommended if integrating a monolithic series of asynchronous operations. In contrast to layered integration, it minimizes the number of <code>m.startComputation</code> / <code>m.endComputation</code> calls to avoid clutter.</p>
|
||||
<p>The example below shows a convoluted series of AJAX requests implemented with a third party library.</p>
|
||||
<pre><code class="lang-javascript">var doSomething = function(callback) {
|
||||
m.startComputation(); //call `startComputation` before everything else
|
||||
|
|
|
|||
|
|
@ -45,10 +45,10 @@
|
|||
</div>
|
||||
<div class="col(9,9,12)">
|
||||
<h2 id="benchmarks">Benchmarks</h2>
|
||||
<p>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 are 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 are very little room for performance optimization tricks. It's arguably also <a href="http://blog.kissmetrics.com/loading-time/">one of the most important metric when it comes to performance</a>.</p>
|
||||
<p>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 <a href="http://blog.kissmetrics.com/loading-time/">one of the most important metrics when it comes to performance</a>.</p>
|
||||
<p>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.</p>
|
||||
<p>Generally speaking, these tests are making a deliberate effort to be <strong>biased in favor of other frameworks:</strong> 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 <code>requestAnimationFrame</code>-based performance optimizations for Mithril, since this optimization does not exist in many frameworks and <a href="http://jsperf.com/angular-vs-knockout-vs-ember/308"><em>severely</em> skews numbers in Mithril's favor</a> in CPU-intensive situations like parallax sites. I'm also NOT using the <a href="compiling-templates.html">Mithril template compiler</a>, which would also skew the benchmark in Mithril's favor.</p>
|
||||
<p>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).</p>
|
||||
<p>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).</p>
|
||||
<div class="performance" style="padding:0 0 0 10px;">
|
||||
<div class="row">
|
||||
<div class="col(4,4,6)">
|
||||
|
|
|
|||
|
|
@ -50,13 +50,13 @@
|
|||
<h3 id="code-size">Code Size</h3>
|
||||
<p>One of the most obvious differences between Mithril and most frameworks is in file size: Mithril is around 4kb gzipped and has no dependencies on other libraries.</p>
|
||||
<p>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.</p>
|
||||
<p>This cost might be less of a concern in single page apps, but not necessarily if the app is typically opened simultaneously in multiple tabs, or in less powerful devices.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<h3 id="documentation">Documentation</h3>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>Mithril has more documentation in this site than the amount of code in the framework, and none of the documentation is auto-generated.</p>
|
||||
<p>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).</p>
|
||||
<p>Mithril has more documentation in its Github repo than source code, and none of the documentation is auto-generated.</p>
|
||||
<p>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).</p>
|
||||
<p>In addition, this guide section covers topics related to how to fit all the pieces together.</p>
|
||||
<p>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.</p>
|
||||
<p>Given how young Mithril is, hopefully you can appreciate the level of commitment for providing good documentation.</p>
|
||||
|
|
@ -81,24 +81,24 @@
|
|||
<h3 id="backbone">Backbone</h3>
|
||||
<p>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.</p>
|
||||
<p>As with jQuery, Mithril differs from Backbone by enforcing view code to be written in a declarative style.</p>
|
||||
<p>Another marking difference is that Backbone is workflow agnostic, that is, there's no one idiomatic way to organize applications. This is good for framework adoption, but not necessarily ideal for team scalability and codebase discoverability.</p>
|
||||
<p>In contrast, Mithril encourages that applications be developed using the patterns found throughout this guide. This discourages "bastardized" MVC pattern variations and architecturing style fragmentation.</p>
|
||||
<p>One technical aspect that is also different is that Backbone is heavily event-oriented. Mithril, on the other hand, purposely avoids the observer pattern in an attempt to abolish "come-from hell", i.e. a class of debugging problems where you don't know what triggers some code because of a long chain of events triggering other events.</p>
|
||||
<p>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.</p>
|
||||
<p>In contrast, Mithril encourages you to develop applications using the patterns found throughout this guide, and discourages the use of "bastardized" MVC pattern variations.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<h3 id="angular">Angular</h3>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>One thing you may have noticed in the homepage is that, out of the box, Angular is not as performant as other frameworks. Steep performance degradation is a notoriously common issue in non-trivial Angular applications and there are several third party libraries which attempt to get around performance problems. Speaking from experience, it's generally difficult to reason about performance in Angular.</p>
|
||||
<p>One thing you may have noticed on the <a href="http://lhorie.github.io/mithril/index.html#performance">Mithril homepage</a> 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.</p>
|
||||
<p>Mithril takes some learnings from that and implements a templating redrawing system that renders less aggressively, is less complex and is easier to profile.</p>
|
||||
<p>A noteworthy difference between Angular and Mithril is in framework complexity: Angular implements several subsystems that would seem more logical in programming language implementations (e.g. a parser, a dynamic scoping mechanism, decorators, etc). Mithril, on the other hand, tries to provide only features to support a more classic MVC paradigm.</p>
|
||||
<p>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.</p>
|
||||
<h3 id="ember">Ember</h3>
|
||||
<p>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.</p>
|
||||
<p>The biggest difference between Ember and Mithril is summarized in the Architecture section above: Ember's comprehensiveness come at a cost of a steep learning curve, and a high degree of vendor lock-in.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<h3 id="react">React</h3>
|
||||
<p>React is a templating engine developed by Facebook. It's relevant for comparison because it uses the same architecture as Mithril's templating engine: i.e. it acknowledges that DOM operations are the bottleneck of templating systems, and implements a virtual DOM tree which keeps track of changes and only applies diffs to the real DOM where needed.</p>
|
||||
<p>The most visible difference between React and Mithril is that React's <em>JSX</em> 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.</p>
|
||||
<p>The most visible difference between React and Mithril is that React's <em>JSX</em> 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.</p>
|
||||
<p>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.</p>
|
||||
<p>Note also that, despite having a bigger scope, Mithril has a smaller file size than React.</p>
|
||||
<h3 id="knockout">Knockout</h3>
|
||||
|
|
@ -107,7 +107,7 @@
|
|||
<p>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.</p>
|
||||
<p>As with Angular, Knockout templates are written in HTML, and therefore have the same pros and cons as Angular templates.</p>
|
||||
<h3 id="vue">Vue</h3>
|
||||
<p>Vue is a relatively new and unknown templating engine, but it boasts impressive results in its performance benchmark.</p>
|
||||
<p>Vue is a relatively new templating engine, but it boasts impressive results in its performance benchmark.</p>
|
||||
<p>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).</p>
|
||||
<p>The most relevant difference is that Vue uses browser features that don't work (and cannot be made to work) in Internet Explorer 8. Mithril allows developers to support browsers all the way back to IE6 and Blackberry.</p>
|
||||
<p>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.</p>
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
<h2 id="compiling-templates">Compiling Templates</h2>
|
||||
<p>You can optionally pre-compile templates that use <code>m()</code> by running the <a href="tools/template-compiler.sjs"><code>template-compiler.sjs</code></a> macro with <a href="https://github.com/mozilla/sweet.js">Sweet.js</a>. 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.</p>
|
||||
<p>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 <a href="mithril.render.html"><code>m.render</code></a>). This means that compiled templates don't need to parse the string in <code>m("div#foo")</code> and they don't incur the cost of the function call.</p>
|
||||
<p>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 performance test in the homepage).</p>
|
||||
<p>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 <a href="http://lhorie.github.io/mithril/index.html#performance">performance section on the homepage</a>).</p>
|
||||
<p>The macro takes regular Mithril templates like the one below:</p>
|
||||
<pre><code class="lang-javascript">var view = function() {
|
||||
return m("a", {href: "http://google.com"}, "test");
|
||||
|
|
|
|||
|
|
@ -103,8 +103,8 @@ projectList.view = function(ctrl) {
|
|||
|
||||
//initialize
|
||||
m.module(document.body, dashboard);</code></pre>
|
||||
<p>As you can see, components look exactly like regular modules - it's turtles all the way down! Remember that modules are simply dumb containers for <code>controller</code> and <code>view</code> classes.</p>
|
||||
<p>This means components are decoupled both <em>horizontally</em> and <em>vertically</em>. It's possible to refactor each component as a isolated unit of logic (which itself follows the MVC pattern). And we can do so without touching the rest of the application (as long as the component API stays the same).</p>
|
||||
<p>As you can see, components look exactly like regular modules - it's <a href="https://en.wikipedia.org/wiki/Turtles_all_the_way_down">turtles all the way down</a>! Remember that modules are simply dumb containers for <code>controller</code> and <code>view</code> classes.</p>
|
||||
<p>This means components are decoupled both <em>horizontally</em> and <em>vertically</em>. It's possible to refactor each component as an isolated unit of logic (which itself follows the MVC pattern). And we can do so without touching the rest of the application (as long as the component API stays the same).</p>
|
||||
<p>Similarly, it's possible to mix and match different classes to make mix-in anonymous components (e.g. it's straightforward to build several views - for, say, a mobile app - that use the same controller).</p>
|
||||
<p>It's also possible to keep references to parent and even sibling components. This is useful, for example, when implementing notification badges in a navigation component, which are triggered and managed by other components in the system.</p>
|
||||
<hr>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
<div class="col(9,9,12)">
|
||||
<h2 id="getting-started">Getting Started</h2>
|
||||
<h3 id="what-is-mithril-">What is Mithril?</h3>
|
||||
<p>Mithril is a client-side Javascript MVC framework, i.e. it's a tool to make application code divided into a data layer (called "<strong>M</strong>odel"), a UI layer (called <strong>V</strong>iew), and a glue layer (called <strong>C</strong>ontroller)</p>
|
||||
<p>Mithril is a client-side Javascript MVC framework, i.e. it's a tool to make application code divided into a data layer (called <strong>M</strong>odel), a UI layer (called <strong>V</strong>iew), and a glue layer (called <strong>C</strong>ontroller)</p>
|
||||
<p>Mithril is around 4kb gzipped thanks to its <a href="mithril.html">small, focused, API</a>. 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.</p>
|
||||
<p>The goal of the framework is to make application code discoverable, readable and maintainable, and hopefully help you become an even better developer.</p>
|
||||
<p>Unlike some frameworks, Mithril tries very hard to avoid locking you into a web of dependencies: you can use as <em>little</em> of the framework as you need.</p>
|
||||
|
|
@ -62,7 +62,7 @@
|
|||
<p>Yes, this is valid HTML 5! According to the specs, the <code><html></code>, <code><head></code> and <code><body></code> tags can be omitted, but their respective DOM elements will still be there implicitly when a browser renders that markup.</p>
|
||||
<hr>
|
||||
<h3 id="model">Model</h3>
|
||||
<p>In Mithril, typically an application lives in an namespace and contains modules. Modules are merely structures that represent a viewable "page" or component.</p>
|
||||
<p>In Mithril, an application typically lives in a namespace and contains modules. Modules are merely structures that represent a viewable "page" or component.</p>
|
||||
<p>For simplicity, our application will have only one module, and we're going to use it as the namespace for our application:</p>
|
||||
<pre><code class="lang-markup"><script>
|
||||
//this application only has one module: todo
|
||||
|
|
@ -113,7 +113,7 @@ list.length; //0</code></pre>
|
|||
<hr>
|
||||
<h3 id="controller">Controller</h3>
|
||||
<p>Our next step is to write a controller that will use our model classes.</p>
|
||||
<pre><code class="lang-javascript">//the controller uses 3 model-level entities, of which one is a custom defined class:
|
||||
<pre><code class="lang-javascript">//the controller uses three model-level entities, of which one is a custom defined class:
|
||||
//`Todo` is the central class in this application
|
||||
//`list` is merely a generic array, with standard array methods
|
||||
//`description` is a temporary storage box that holds a string
|
||||
|
|
@ -184,7 +184,7 @@ m.render(document, todo.view(ctrl));</code></pre>
|
|||
</html></code></pre>
|
||||
<hr>
|
||||
<h4 id="data-bindings">Data Bindings</h4>
|
||||
<p>Let's implement a <strong>data binding</strong> on the text input. Data bindings connect a DOM element to a javascript variable so that updating one updates the other.</p>
|
||||
<p>Let's implement a <strong>data binding</strong> on the text input. Data bindings connect a DOM element to a Javascript variable so that updating one updates the other.</p>
|
||||
<pre><code class="lang-javascript">m("input")
|
||||
|
||||
//becomes
|
||||
|
|
@ -196,7 +196,7 @@ m.render(todo.view(ctrl)); // input is empty
|
|||
ctrl.description("Write code"); //set the description in the controller
|
||||
m.render(todo.view(ctrl)); // input now says "Write code"</code></pre>
|
||||
<p>Note that calling the <code>todo.view</code> method multiple times does not re-render the entire template.</p>
|
||||
<p>Mithril internally keeps a virtual representation of the DOM in cache, scans for changes, and then only modifies the minimum required to apply the change.</p>
|
||||
<p>Internally, Mithril keeps a virtual representation of the DOM in cache, scans for changes, and then only modifies the minimum required to apply the change.</p>
|
||||
<p>In this case, Mithril only touches the <code>value</code> attribute of the input.</p>
|
||||
<hr>
|
||||
<p>Bindings can also be <strong>bi-directional</strong>: that is, they can be made such that, in addition to what we saw just now, a user typing on the input updates the description getter-setter.</p>
|
||||
|
|
@ -210,7 +210,7 @@ m.render(todo.view(ctrl)); // input now says "Write code"</code></pre>
|
|||
<pre><code class="lang-javascript">onchange: function(e) {
|
||||
ctrl.description(e.target["value"]);
|
||||
}</code></pre>
|
||||
<p>The difference, aside from the cosmetic avoidance of anonymous functions, is that the <code>m.withAttr</code> 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 <code>DOMElement::getAttribute()</code></p>
|
||||
<p>The difference, aside from the cosmetic avoidance of anonymous functions, is that the <code>m.withAttr</code> 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 <code>DOMElement::getAttribute()</code></p>
|
||||
<hr>
|
||||
<p>In addition to bi-directional data binding, we can also bind parameterized functions to events:</p>
|
||||
<pre><code class="lang-javascript">m("button", {onclick: ctrl.add.bind(ctrl, ctrl.description)}, "Add")</code></pre>
|
||||
|
|
@ -271,22 +271,22 @@ m("table", [
|
|||
};</code></pre>
|
||||
<p>Here are the highlights of the template above:</p>
|
||||
<ul>
|
||||
<li>The template is rendered as a child of the implicit <code><html></code> element of the document</li>
|
||||
<li>The text input saves its value to the <code>ctrl.description</code> getter-setter we defined earlier</li>
|
||||
<li>The template is rendered as a child of the implicit <code><html></code> element of the document.</li>
|
||||
<li>The text input saves its value to the <code>ctrl.description</code> getter-setter we defined earlier.</li>
|
||||
<li><p>The button calls the <code>ctrl.add</code> method when clicked. The <code>.bind(ctrl, ctrl.description)</code> idiom is a <a href="http://en.wikipedia.org/wiki/Partial_application">partial application</a>.</p>
|
||||
<p>In this example, it's only used to maintain the scope binding for the <code>this</code> parameter in the controller method, but typically it's also used to bind parameters to the function without the need to declare a wrapper anonymous function.</p>
|
||||
</li>
|
||||
<li>The table lists all the existing to-dos, if any.</li>
|
||||
<li>The checkboxes save their value to the <code>task.done</code> getter setter</li>
|
||||
<li>The description gets crossed out via CSS if the task is marked as done</li>
|
||||
<li>When updates happen, the template is not wholly re-rendered - only the changes are applied</li>
|
||||
<li>The checkboxes save their value to the <code>task.done</code> getter setter.</li>
|
||||
<li>The description gets crossed out via CSS if the task is marked as done.</li>
|
||||
<li>When updates happen, the template is not wholly re-rendered - only the changes are applied.</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<p>When running the classes in this application separately, you have full control and full responsibility for determining when to redraw the view.</p>
|
||||
<p>However, Mithril does provide another utility to make this task automatic.</p>
|
||||
<p>However, Mithril does provide another utility to make this task automatic: <a href="http://lhorie.github.io/mithril/auto-redrawing.html">the Auto-Redrawing System</a>.</p>
|
||||
<p>In order to enable Mithril's auto-redrawing system, we run the code as a Mithril module:</p>
|
||||
<pre><code class="lang-javascript">m.module(document, todo);</code></pre>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>Also note that this mechanism itself is not asynchronous if it doesn't need to be: Mithril does not need to wait for the next browser repaint frame to redraw - it doesn't even need to wait for the document ready event on the first redraw - it will redraw immediately upon script completion, if able to.</p>
|
||||
<hr>
|
||||
<h3 id="summary">Summary</h3>
|
||||
|
|
@ -308,7 +308,7 @@ todo.Todo = function(data) {
|
|||
//the TodoList class is a list of Todo's
|
||||
todo.TodoList = Array;
|
||||
|
||||
//the controller uses 3 model-level entities, of which one is a custom defined class:
|
||||
//the controller uses three model-level entities, of which one is a custom defined class:
|
||||
//`Todo` is the central class in this application
|
||||
//`list` is merely a generic array, with standard array methods
|
||||
//`description` is a temporary storage box that holds a string
|
||||
|
|
@ -355,9 +355,9 @@ m.module(document, todo);
|
|||
<h3 id="model">Model</h3>
|
||||
<p>Idiomatic Mithril code is meant to apply good programming conventions and be easy to refactor.</p>
|
||||
<p>In the application above, notice how the Todo class can easily be moved to a different module if code re-organization is required.</p>
|
||||
<p>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 which requires almost no framework-specific learning curve.</p>
|
||||
<p>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.</p>
|
||||
<p><a href="mithril.prop.html"><code>m.prop</code></a> is a simple but surprisingly versatile tool: it's composable, it enables <a href="http://en.wikipedia.org/wiki/Uniform_data_access">uniform data access</a> and allows a higher degree of decoupling when major refactoring is required.</p>
|
||||
<p>When said refactoring is unavoidable, the developer can simply replace the <code>m.prop</code> call with an appropriate getter-setter implementation, instead of having to grep for API usage across the entire application.</p>
|
||||
<p>When refactoring is unavoidable, the developer can simply replace the <code>m.prop</code> call with an appropriate getter-setter implementation, instead of having to grep for API usage across the entire application.</p>
|
||||
<p>For example, if todo descriptions needed to always be uppercased, one could simply change the <code>description</code> getter-setter:</p>
|
||||
<pre><code class="lang-javascript">this.description = m.prop(data.description)</code></pre>
|
||||
<p>becomes:</p>
|
||||
|
|
@ -370,7 +370,7 @@ this.description = function(value) {
|
|||
return description;
|
||||
}</code></pre>
|
||||
<p>According to Mithril's philosophy, <code>list</code> and <code>description</code> are also considered model-level entities. This is a subtle but important point: model entities don't need to be full-blown custom classes.</p>
|
||||
<p>Native javascript classes are quite appropriate for storing primitive and structured data. Since in this case they are indeed being used to store data - even if temporarily - they are model entities!</p>
|
||||
<p>Native Javascript classes are quite appropriate for storing primitive and structured data. Since in this case they are indeed being used to store data - even if temporarily - they are model entities!</p>
|
||||
<p>Be aware that by using the native Array class for a list, we're making an implicit statement that we are going to support all of the standard Array methods as part of our API.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
|
|
@ -411,14 +411,14 @@ this.description = function(value) {
|
|||
</li>
|
||||
<li><p>You get the ability to automate linting, unit testing and minifying of the entire view layer - and you are even able to use Closure Compiler's Advanced Mode without needing extensive annotations.</p>
|
||||
</li>
|
||||
<li><p>It provides full Turing completeness: full control over evaluation eagerness/lazyness and caching in templates. You can even build components that take other components as first-class-citizen parameters!</p>
|
||||
<li><p>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!</p>
|
||||
</li>
|
||||
<li><p>Turtles all the way down: you don't need write custom data binding code in jQuery for every possible user interaction, and you don't need to support a complicated "directive" layer to be able to fit some types of components into the system.</p>
|
||||
<li><p><a href="https://en.wikipedia.org/wiki/Turtles_all_the_way_down">Turtles all the way down</a>: you don't need write custom data binding code in jQuery for every possible user interaction, and you don't need to support a complicated "directive" layer to be able to fit some types of components into the system.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>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.</p>
|
||||
<p>Another feature - the optional <code>m()</code> utility - allows writing terse templates in a declarative style using CSS shorthands, similar to popular HTML preprocessors from server-side MVC frameworks.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>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 <a href="mithril.withAttr.html"><code>m.withAttr</code></a>, <a href="mithril.prop.html"><code>m.prop</code></a> and the native <code>.bind()</code> method for partial application).</p>
|
||||
<p>If you've been interested in learning or using Functional Programming in the real world, Mithril provides very pragmatic opportunities to get into it.</p>
|
||||
|
|
@ -438,6 +438,8 @@ this.description = function(value) {
|
|||
</ul>
|
||||
<h2 id="misc">Misc</h2>
|
||||
<ul>
|
||||
<li><a href="comparison">Differences from Other MVC Frameworks</a></li>
|
||||
<li><a href="benchmarks">Benchmarks</a></li>
|
||||
<li><a href="practices">Good Practices</a></li>
|
||||
<li><a href="tools">Useful Tools</a></li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@
|
|||
</div>
|
||||
<div class="col(9,9,12)">
|
||||
<h2 id="how-to-read-signatures">How to Read Signatures</h2>
|
||||
<p>Rather than providing concrete classes like other frameworks, Mithril provides methods that operate on plain old javascript objects (POJOs) that match given signatures.</p>
|
||||
<p>A signature is a description of its static type. For functions, it shows what are the parameters of the function, its return value and their expected types. For objects and arrays, it shows the expected data structure and the expected types of their members.</p>
|
||||
<p>Rather than providing concrete classes like other frameworks, Mithril provides methods that operate on plain old Javascript objects (POJOs) that match given signatures.</p>
|
||||
<p>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.</p>
|
||||
<p>Method signatures in this documentation follow a syntax similar to Java syntax, with some extra additions:</p>
|
||||
<pre><code class="lang-clike">ReturnType methodName(ParameterType1 param1, ParameterType2 param2)</code></pre>
|
||||
<h3 id="optional-parameters">Optional Parameters</h3>
|
||||
|
|
@ -121,7 +121,7 @@ var a = aComplexTypeValue
|
|||
typeof a == "function" // true
|
||||
"label" in a // true
|
||||
a.label = "first"</code></pre>
|
||||
<h3 id="polimorphic-types">Polimorphic Types</h3>
|
||||
<h3 id="polymorphic-types">Polymorphic Types</h3>
|
||||
<p>When multiple (but not all) types are accepted, the pipe <code>|</code> is used to delimit the list of valid types</p>
|
||||
<pre><code class="lang-clike">void test(Children children, Value value)
|
||||
|
||||
|
|
@ -132,7 +132,7 @@ where:
|
|||
test(["test", 2], "second")
|
||||
test([1, 2, 3], "second")
|
||||
test([1, "test", 3], 2)</code></pre>
|
||||
<p>Pipe syntax within Object curly brace syntax means that for a specific key name has specific type requirements.</p>
|
||||
<p>Pipe syntax within Object curly brace syntax means that, for a specific key, name has specific type requirements.</p>
|
||||
<p>In the example below, the <code>value</code> parameter should be a key-value map. This map may contain a key called <code>config</code>, whose value should be a function.</p>
|
||||
<pre><code class="lang-clike">void test(Object { any | void config(DOMElement) } value)</code></pre>
|
||||
<pre><code class="lang-javascript">//example of a valid function call
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ m.module(document.getElementById("example"), app);
|
|||
|
||||
<section class="performance">
|
||||
<div class="container">
|
||||
<h2>Performance</h2>
|
||||
<h2 id="performance">Performance</h2>
|
||||
<p>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). <a href="benchmarks.html">Read more</a></p>
|
||||
<div class="row">
|
||||
<div class="col(4,4,6)">
|
||||
|
|
@ -242,4 +242,4 @@ m.module(document.getElementById("example"), app);
|
|||
</footer>
|
||||
<script src="lib/prism/prism.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -49,16 +49,16 @@
|
|||
<hr>
|
||||
<h3 id="direct-download">Direct download</h3>
|
||||
<p>You can <a href="http://lhorie.github.io/mithril/mithril.min.zip">download a zip of the latest version version here</a>.</p>
|
||||
<p>Links to older versions can be found in the <a href="change-log.html">change log</a></p>
|
||||
<p>Links to older versions can be found in the <a href="change-log.html">change log</a>.</p>
|
||||
<p>In order to use Mithril, extract it from the zip file and point a script tag to the <code>.js</code> file:</p>
|
||||
<pre><code class="lang-markup"><script src="mithril.min.js"></script></code></pre>
|
||||
<hr>
|
||||
<h3 id="cdns-content-delivery-networks-">CDNs (Content Delivery Networks)</h3>
|
||||
<p>You can also find Mithril in <a href="http://cdnjs.com/libraries/mithril/">cdnjs</a> and <a href="http://www.jsdelivr.com/#!mithril">jsdelivr</a></p>
|
||||
<p>You can also find Mithril in <a href="http://cdnjs.com/libraries/mithril/">cdnjs</a> and <a href="http://www.jsdelivr.com/#!mithril">jsDelivr</a>.</p>
|
||||
<p>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.</p>
|
||||
<h4 id="cdnjs">CdnJs</h4>
|
||||
<h4 id="cdnjs">cdnjs</h4>
|
||||
<pre><code class="lang-markup"><script src="//cdnjs.cloudflare.com/ajax/libs/mithril/0.1.14/mithril.min.js"></script></code></pre>
|
||||
<h4 id="jsdelivr">JsDelivr</h4>
|
||||
<h4 id="jsdelivr">jsDelivr</h4>
|
||||
<pre><code class="lang-markup"><script src="//cdn.jsdelivr.net/mithril/0.1.14/mithril.min.js"></script></code></pre>
|
||||
<hr>
|
||||
<h3 id="npm">NPM</h3>
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ m.module(document.body, dashboard);</code></pre>
|
|||
<p>This can be done by simply calling <code>m.startComputation</code> at the beginning, and <code>m.endComputation</code> 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.</p>
|
||||
<p>For example, if you were to call a web service using <code>m.request</code>, you would not need to add more calls to <code>m.startComputation</code> / <code>m.endComputation</code> (you would still need the first pair in the event handler, though).</p>
|
||||
<p>On the other hand, if you were to call a web service using jQuery, then you would be responsible for adding a <code>m.startComputation</code> call before the jQuery ajax call, and for adding a <code>m.endComputation</code> call at the end of the completion callback, in addition to the calls within the <code>change</code> event handler. Refer to the <a href="auto-redrawing.html"><code>auto-redrawing</code></a> guide for an example.</p>
|
||||
<p>One important note about the <code>config</code> method is that you should avoid calling <code>m.redraw</code>, <code>m.startComputation</code> and <code>m.endComputation</code> in the <code>config</code> function's execution thread. (An execution thread is basically any amount of code that runs before other asynchronous threads start to run)</p>
|
||||
<p>One important note about the <code>config</code> method is that you should avoid calling <code>m.redraw</code>, <code>m.startComputation</code> and <code>m.endComputation</code> in the <code>config</code> function's execution thread. (An execution thread is basically any amount of code that runs before other asynchronous threads start to run.)</p>
|
||||
<p>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.</p>
|
||||
<p>The <code>dashboard</code> module in the example shows how a developer would consume the select2 component.</p>
|
||||
<p>You should always document integration components so that others can find out what attribute parameters can be used to initialize the component.</p>
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ m("a[name=top]"); //yields <a name="top"></a>
|
|||
m("[contenteditable]"); //yields <div contenteditable></div>
|
||||
|
||||
m("a#google.external[href='http://google.com']", "Google"); //yields <a id="google" class="external" href="http://google.com">Google</a></code></pre>
|
||||
<p>Each <code>m()</code> call creates a virtual DOM element, that is, a javascript object that represents a DOM element, and which is eventually converted into one.</p>
|
||||
<p>Each <code>m()</code> call creates a virtual DOM element, that is, a Javascript object that represents a DOM element, and which is eventually converted into one.</p>
|
||||
<p>You can, of course, nest virtual elements:</p>
|
||||
<pre><code class="lang-javascript">m("ul", [
|
||||
m("li", "item 1"),
|
||||
|
|
@ -132,7 +132,7 @@ m.render(document.body, [
|
|||
</body></code></pre>
|
||||
<p>As you can see, flow control is done with vanilla Javascript. This allows the developer to abstract away any aspect of the template at will.</p>
|
||||
<hr>
|
||||
<p>Note that you can use both javascript property names and HTML attribute names to set values in the <code>attributes</code> argument, but you should pass a value of appropriate type. If an attribute has the same name in Javascript and in HTML, then Mithril assumes you're setting the Javascript property.</p>
|
||||
<p>Note that you can use both Javascript property names and HTML attribute names to set values in the <code>attributes</code> argument, but you should pass a value of appropriate type. If an attribute has the same name in Javascript and in HTML, then Mithril assumes you're setting the Javascript property.</p>
|
||||
<pre><code class="lang-javascript">m("div", {class: "widget"}); //yields <div class="widget"></div>
|
||||
|
||||
m("div", {className: "widget"}); //yields <div class="widget"></div>
|
||||
|
|
@ -278,7 +278,7 @@ m("a[href='/dashboard']", {config: m.route}, "Dashboard&q
|
|||
<p>If it's a SubtreeDirective with the value "retain", it will retain the existing DOM tree in place, if any. See <a href="mithril.render#subtree-directives">subtree directives</a> for more information.</p>
|
||||
</li>
|
||||
<li><p><strong>returns</strong> VirtualElement</p>
|
||||
<p>The returned VirtualElement is a javascript data structure that represents the DOM element to be rendered by <a href="mithril.render"><code>m.render</code></a></p>
|
||||
<p>The returned VirtualElement is a Javascript data structure that represents the DOM element to be rendered by <a href="mithril.render"><code>m.render</code></a></p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -176,8 +176,8 @@ where:
|
|||
<p>The controller class is instantiated immediately upon calling <code>m.module</code>.</p>
|
||||
<p>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.</p>
|
||||
<p>Note that controllers can manually instantiate child controllers (since they are simply Javascript constructors), and likewise, views can instantiate child views and manually pass the child controller instances down the the child view constructors.</p>
|
||||
<p>This "turtles all the way down" approach is the heart of Mithril's component system.</p>
|
||||
<p>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).</p>
|
||||
<p>This "<a href="https://en.wikipedia.org/wiki/Turtles_all_the_way_down">turtles all the way down</a>" approach is the heart of Mithril's component system.</p>
|
||||
<p>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).</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ var doSomething = function() { /*...*/ }
|
|||
|
||||
m.request({method: "GET", url: "/user"}).then(users).then(doSomething)</code></pre>
|
||||
<p>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.</p>
|
||||
<p>The thennable mechanism is intended to be used in 3 ways:</p>
|
||||
<p>The thennable mechanism is intended to be used in three ways:</p>
|
||||
<ul>
|
||||
<li>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)</li>
|
||||
<li>in the controller layer: to bind redirection code upon a condition</li>
|
||||
|
|
@ -152,7 +152,7 @@ var controller = function() {
|
|||
<li>in the controller, to redirect after a model service resolves.</li>
|
||||
<li>in the controller, to bind error messages</li>
|
||||
</ul>
|
||||
<p>In the example below, we take advantage of queuing to debug the ajax response data prior to doing further processing on the user list</p>
|
||||
<p>In the example below, we take advantage of queuing to debug the AJAX response data prior to doing further processing on the user list</p>
|
||||
<pre><code class="lang-javascript">var users = m.request({method: "GET", url: "/user"})
|
||||
.then(console.log);
|
||||
.then(function(users) {
|
||||
|
|
@ -165,7 +165,7 @@ var controller = function() {
|
|||
//i.e. users() //[{name: "John"}, {name: "Mary"}, {name: "Jane"}]</code></pre>
|
||||
<hr>
|
||||
<h3 id="casting-the-response-data-to-a-class">Casting the Response Data to a Class</h3>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>In the example below, <code>User.list</code> returns a list of <code>User</code> instances.</p>
|
||||
<pre><code class="lang-javascript">var User = function(data) {
|
||||
this.name = m.prop(data.name);
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
<h2 id="m-route">m.route</h2>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>This method overloads 4 different units of functionality:</p>
|
||||
<p>This method overloads four different units of functionality:</p>
|
||||
<ul>
|
||||
<li><p><code>m.route(rootElement, defaultRoute, routes)</code> - defines the available URLs in an application, and their respective modules</p>
|
||||
</li>
|
||||
|
|
@ -80,7 +80,7 @@
|
|||
<h3 id="defining-routes">Defining routes</h3>
|
||||
<h4 id="usage">Usage</h4>
|
||||
<p>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 <a href="mithril.module.html">modules</a> to be rendered.</p>
|
||||
<p>The example below defines 3 routes, to be rendered in <code><body></code>. <code>home</code>, <code>login</code> and <code>dashboard</code> are modules. We'll see how to define a module in a bit.</p>
|
||||
<p>The example below defines three routes, to be rendered in <code><body></code>. <code>home</code>, <code>login</code> and <code>dashboard</code> are modules. We'll see how to define a module in a bit.</p>
|
||||
<pre><code class="lang-javascript">m.route(document.body, "/", {
|
||||
"/": home,
|
||||
"/login": login,
|
||||
|
|
@ -109,7 +109,7 @@ m.route.mode = "hash";</code></pre>
|
|||
<pre><code class="lang-markup"><body>johndoe</body></code></pre>
|
||||
<p>Above, <code>dashboard</code> is a module. It contains a <code>controller</code> and a <code>view</code> properties. When the URL matches a route, the respective module's controller is instantiated and passed as a parameter to the view.</p>
|
||||
<p>In this case, since there's only route, the app redirects to the default route <code>"/dashboard/johndoe"</code>.</p>
|
||||
<p>The string <code>johndoe</code> is bound to the <code>:userID</code> parameter, which can be retrived programmatically in the controller via <code>m.route.param("userID")</code>.</p>
|
||||
<p>The string <code>johndoe</code> is bound to the <code>:userID</code> parameter, which can be retrieved programmatically in the controller via <code>m.route.param("userID")</code>.</p>
|
||||
<p>The <code>m.route.mode</code> defines which part of the URL to use for routing.</p>
|
||||
<hr>
|
||||
<h4 id="variadic-routes">Variadic routes</h4>
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
<h2 id="m-trust">m.trust</h2>
|
||||
<p>If you're writing a template for a view, use <code>m()</code> instead.</p>
|
||||
<p>This method flags a string as trusted HTML.</p>
|
||||
<p>Trusted HTML is allowed to render arbitrary, potentially invalid markup, as well as run arbitrary javascript, and therefore the developer is responsible for either:</p>
|
||||
<p>Trusted HTML is allowed to render arbitrary, potentially invalid markup, as well as run arbitrary Javascript, and therefore the developer is responsible for either:</p>
|
||||
<ul>
|
||||
<li><p>sanitizing the markup contained in the string, or</p>
|
||||
</li>
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
<p>Note that browsers ignore <code><script></code> tags that have been inserted into the DOM via innerHTML. They do this because once the element is ready (and thus, has an accessible <code>innerHTML</code> property), their rendering engines cannot backtrack to the parsing-stage if the script calls something like <code>document.write("</body>")</code>.</p>
|
||||
<p>For this reason, <code>m.trust</code> will not auto-run <code><script></code> tags from trusted strings.</p>
|
||||
<p>Browsers do, however, allow scripts to be run asynchronously via a number of execution points, such as the <code>onload</code> or <code>onerror</code> attributes in <code><img></code> and <code><iframe></code>.</p>
|
||||
<p>IE also allows running of javascript via CSS behaviors in <code><link></code>/<code><style></code> tags and <code>style</code> attributes.</p>
|
||||
<p>IE also allows running of Javascript via CSS behaviors in <code><link></code>/<code><style></code> tags and <code>style</code> attributes.</p>
|
||||
<p>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 <code>" onload="alert(1)</code>.</p>
|
||||
<p>Mithril templates are defended against these attacks by default, except when markup is injected via <code>m.trust</code>.</p>
|
||||
<p>It is the developer's responsibility to ensure the input to <code>m.trust</code> cannot be maliciously modified by user-entered data.</p>
|
||||
|
|
@ -100,7 +100,7 @@ m.render("body", [
|
|||
<p>A string containing HTML markup</p>
|
||||
</li>
|
||||
<li><p><strong>returns String trustedHtml</strong></p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -50,11 +50,11 @@
|
|||
<p>Data manipulation should be done in model classes, such that controllers never have entities lying around in temporarily invalid states.</p>
|
||||
<p>Mithril's design strongly encourages all entity logic to be handled in atomic model layer methods (in the sense of entity state stability).</p>
|
||||
<p>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.</p>
|
||||
<p>This design decision comes from experience with DRY and <a href="http://en.wikipedia.org/wiki/Bus_factor">"bus factor"</a> of large, highly relational model layers.</p>
|
||||
<p>This is in stark contrast to the ActiveRecord pattern of other frameworks, where model entities are largely object representations of database entities and these entities are manipulated in controllers in an ad-hoc field-by-field fashion, and the "committed" via a <code>save</code> method.</p>
|
||||
<p>Because Mithril encourages all entity logic to be done in the model layer, it's idiomatic to create modules with model-level classes that deal specifically with relationship between entities, when there isn't already a model entity that can logically hold the relational business logic.</p>
|
||||
<p>This design decision comes from experience with <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a> and the <a href="http://en.wikipedia.org/wiki/Bus_factor">"bus factor"</a> of large, highly relational model layers.</p>
|
||||
<p>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 <code>save</code> method.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>DOM manipulation should be done in the view via <a href="mithril"><code>m()</code> and <code>config</code></a>. Controllers may explicitly call <a href="mithril.redraw"><code>m.redraw</code></a>, but, if possible, it's preferable to abstract this into a service which integrates w/ Mithril's auto-redrawing system (see. <a href="mithril.computation"><code>m.startComputation</code> / <code>m.endComputation</code></a>)</p>
|
||||
<p>DOM manipulation should be done in the view via <a href="mithril"><code>m()</code> and <code>config</code></a>. Controllers may explicitly call <a href="mithril.redraw"><code>m.redraw</code></a>, but, if possible, it's preferable to abstract this into a service which integrates with Mithril's auto-redrawing system (see <a href="mithril.computation"><code>m.startComputation</code> / <code>m.endComputation</code></a>).</p>
|
||||
<hr>
|
||||
<h2 id="file-separation">File Separation</h2>
|
||||
<p>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:</p>
|
||||
|
|
@ -77,7 +77,7 @@ app.view = function(ctrl) {
|
|||
<p>You can use task automation tools such as GruntJS to concatenate the files back together for production.</p>
|
||||
<p>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.</p>
|
||||
<p>You may choose to declare the namespace in a separate file or have the build system generate it on demand, instead.</p>
|
||||
<p>You should avoid grouping classes by the MVC layer they belong to, i.e. don't create 3 files called model.js, controllers.js and views.js.</p>
|
||||
<p>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.</p>
|
||||
<p>That organization pattern needlessly ties unrelated aspects of the application together and dilutes the clarity of modules.</p>
|
||||
<hr>
|
||||
<h2 id="global-namespace-hygiene">Global Namespace Hygiene</h2>
|
||||
|
|
@ -96,7 +96,7 @@ app.view = function(ctrl) {
|
|||
<hr>
|
||||
<h2 id="usage-of-m-redraw">Usage of m.redraw</h2>
|
||||
<p><code>m.redraw</code> is a method that allows you to render a template outside the scope of Mithril's auto-redrawing system.</p>
|
||||
<p>Calling of this method while using <code>m.module</code> or <code>m.route</code> should only be done if you have recurring asynchronous view updates (i.e. something that uses setInterval).</p>
|
||||
<p>Calling this method while using <code>m.module</code> or <code>m.route</code> should only be done if you have recurring asynchronous view updates (i.e. something that uses setInterval).</p>
|
||||
<p>If you're integrating other non-recurring services (e.g. calling setTimeout), you should use <a href="mithril.computation.html"><code>m.startComputation</code> / <code>m.emdComputation</code></a> instead.</p>
|
||||
<p>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 <code>requestAnimationFrame</code> 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.</p>
|
||||
<p>If you call this method more often than that, Mithril may ignore calls or defer them to the next browser repaint cycle.</p>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
</div>
|
||||
<div class="col(9,9,12)">
|
||||
<h2 id="routing">Routing</h2>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>Mithril provides utilities to handle three different aspect of routing:</p>
|
||||
<ul>
|
||||
|
|
@ -56,14 +56,14 @@
|
|||
<hr>
|
||||
<h3 id="defining-routes">Defining routes</h3>
|
||||
<p>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 <a href="mithril.module.html">modules</a> to be rendered.</p>
|
||||
<p>The example below defines 3 routes, to be rendered in <code><body></code>. <code>home</code>, <code>login</code> and <code>dashboard</code> are modules. We'll see how to define a module in a bit.</p>
|
||||
<p>The example below defines three routes, to be rendered in <code><body></code>. <code>home</code>, <code>login</code> and <code>dashboard</code> are modules. We'll see how to define a module in a bit.</p>
|
||||
<pre><code class="lang-javascript">m.route(document.body, "/", {
|
||||
"/": home,
|
||||
"/login": login,
|
||||
"/dashboard": dashboard,
|
||||
});</code></pre>
|
||||
<p>Routes can take arguments, by prefixing words with a colon <code>:</code></p>
|
||||
<p>The example below shows a route that takes an <code>userID</code> parameter</p>
|
||||
<p>Routes can take arguments, by prefixing words with a colon <code>:</code>.</p>
|
||||
<p>The example below shows a route that takes a <code>userID</code> parameter.</p>
|
||||
<pre><code class="lang-javascript">//a sample module
|
||||
var dashboard = {
|
||||
controller: function() {
|
||||
|
|
@ -83,10 +83,10 @@ m.route(document.body, "/dashboard/johndoe", {
|
|||
m.route.mode = "hash";</code></pre>
|
||||
<p>This redirects to the URL <code>http://server/#/dashboard/johndoe</code> and yields:</p>
|
||||
<pre><code class="lang-markup"><body>johndoe</body></code></pre>
|
||||
<p>Above, <code>dashboard</code> is a module. It contains a <code>controller</code> and a <code>view</code> properties. When the URL matches a route, the respective module's controller is instantiated and passed as a parameter to the view.</p>
|
||||
<p>In this case, since there's only route, the app redirects to the default route <code>"/dashboard/johndoe"</code> and, under the hood, it calls <code>m.module(document.body, dashboard)</code>.</p>
|
||||
<p>The string <code>johndoe</code> is bound to the <code>:userID</code> parameter, which can be retrived programmatically in the controller via <code>m.route.param("userID")</code>.</p>
|
||||
<p>The <code>m.route.mode</code> 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"</p>
|
||||
<p>Above, <code>dashboard</code> is a module. It contains <code>controller</code> and <code>view</code> properties. When the URL matches a route, the respective module's controller is instantiated and passed as a parameter to the view.</p>
|
||||
<p>In this case, since there's only one route, the app redirects to the default route <code>"/dashboard/johndoe"</code> and, under the hood, it calls <code>m.module(document.body, dashboard)</code>.</p>
|
||||
<p>The string <code>johndoe</code> is bound to the <code>:userID</code> parameter, which can be retrieved programmatically in the controller via <code>m.route.param("userID")</code>.</p>
|
||||
<p>The <code>m.route.mode</code> 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".</p>
|
||||
<ul>
|
||||
<li><p><code>search</code> mode uses the querystring. This allows named anchors (i.e. <code><a href="#top">Back to top</a></code>, <code><a name="top"></a></code>) to work on the page, but routing changes causes page refreshes in IE8, due to its lack of support for <code>history.pushState</code>.</p>
|
||||
<p>Example URL: <code>http://server/?/path/to/page</code></p>
|
||||
|
|
@ -94,14 +94,14 @@ m.route.mode = "hash";</code></pre>
|
|||
<li><p><code>hash</code> 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.</p>
|
||||
<p>Example URL: <code>http://server/#/path/to/page</code></p>
|
||||
</li>
|
||||
<li><p><code>pathname</code> mode allows routing URLs that contains no special characters, however this mode requires server-side setup in order to support bookmarking and page refreshes. It also causes page refreshes in IE8.</p>
|
||||
<li><p><code>pathname</code> 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.</p>
|
||||
<p>Example URL: <code>http://server/path/to/page</code></p>
|
||||
<p>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.</p>
|
||||
<p>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 <a href="https://httpd.apache.org/docs/current/mod/mod_rewrite.html">mod_rewrite</a>.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<h3 id="redirecting">Redirecting</h3>
|
||||
<p>You can programmatically redirec to another page. Given the example in the "Defining Routes" section:</p>
|
||||
<p>You can programmatically redirect to another page. Given the example in the "Defining Routes" section:</p>
|
||||
<pre><code class="lang-javascript">m.route("/dashboard/marysue");</code></pre>
|
||||
<p>redirects to <code>http://server/#/dashboard/marysue</code></p>
|
||||
<hr>
|
||||
|
|
|
|||
|
|
@ -77,11 +77,11 @@ var doSomething = function() { /*...*/ }
|
|||
|
||||
m.request({method: "GET", url: "/user"}).then(users).then(doSomething)</code></pre>
|
||||
<p>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.</p>
|
||||
<p>The thennable mechanism is intended to be used in 3 ways:</p>
|
||||
<p>The thennable mechanism is intended to be used in three ways:</p>
|
||||
<ul>
|
||||
<li>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)</li>
|
||||
<li>in the controller layer: to bind redirection code upon a condition</li>
|
||||
<li>in the controller layer: to bind error messages</li>
|
||||
<li>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)</li>
|
||||
<li>In the controller layer: to bind redirection code upon a condition</li>
|
||||
<li>In the controller layer: to bind error messages</li>
|
||||
</ul>
|
||||
<h4 id="processing-web-service-data">Processing web service data</h4>
|
||||
<p>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.</p>
|
||||
|
|
@ -109,7 +109,7 @@ var controller = function() {
|
|||
})
|
||||
}</code></pre>
|
||||
<h4 id="binding-errors">Binding errors</h4>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>In the example below, we bind an error getter-setter to our previous controller so that the <code>error</code> variable gets populated if the server throws an error.</p>
|
||||
<pre><code class="lang-javascript">//controller
|
||||
|
|
@ -131,11 +131,11 @@ var controller = function() {
|
|||
<h3 id="queuing-operations">Queuing Operations</h3>
|
||||
<p>As you saw, you can chain operations that act on the response data. Typically this is required in three situations:</p>
|
||||
<ul>
|
||||
<li>in model-level methods if client-side processing is needed to make the data useful for a controller or view.</li>
|
||||
<li>in the controller, to redirect after a model service resolves.</li>
|
||||
<li>in the controller, to bind error messages</li>
|
||||
<li>In model-level methods if client-side processing is needed to make the data useful for a controller or view</li>
|
||||
<li>In the controller, to redirect after a model service resolves</li>
|
||||
<li>In the controller, to bind error messages</li>
|
||||
</ul>
|
||||
<p>In the example below, we take advantage of queuing to debug the ajax response data prior to doing further processing on the user list</p>
|
||||
<p>In the example below, we take advantage of queuing to debug the AJAX response data prior to doing further processing on the user list:</p>
|
||||
<pre><code class="lang-javascript">var users = m.request({method: "GET", url: "/user"})
|
||||
.then(console.log);
|
||||
.then(function(users) {
|
||||
|
|
@ -148,7 +148,7 @@ var controller = function() {
|
|||
//i.e. users() //[{name: "John"}, {name: "Mary"}, {name: "Jane"}]</code></pre>
|
||||
<hr>
|
||||
<h3 id="casting-the-response-data-to-a-class">Casting the Response Data to a Class</h3>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>In the example below, <code>User.list</code> returns a list of <code>User</code> instances.</p>
|
||||
<pre><code class="lang-javascript">var User = function(data) {
|
||||
this.name = m.prop(data.name);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue