213 lines
No EOL
12 KiB
HTML
213 lines
No EOL
12 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<title>Mithril</title>
|
|
<link href="http://fonts.googleapis.com/css?family=Open+Sans:300italic" rel="stylesheet" />
|
|
<link href="lib/prism/prism.css" rel="stylesheet" />
|
|
<link href="style.css" rel="stylesheet" />
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<nav class="container">
|
|
<a href="index.html" class="logo"><span>○</span> Mithril</a>
|
|
<a href="getting-started.html">Guide</a>
|
|
<a href="mithril.html">API</a>
|
|
<a href="community.html">Community</a>
|
|
<a href="mithril.min.zip">Download</a>
|
|
<a href="http://github.com/lhorie/mithril.js" target="_blank">Github</a>
|
|
</nav>
|
|
</header>
|
|
<main>
|
|
<section class="content">
|
|
<div class="container">
|
|
<div class="row">
|
|
<div class="col(3,3,12)">
|
|
<h2 id="core-topics">Core Topics</h2>
|
|
<ul>
|
|
<li><a href="installation.html">Installation</a></li>
|
|
<li><a href="getting-started.html">Getting Started</a></li>
|
|
<li><a href="routing.html">Routing</a></li>
|
|
<li><a href="web-services.html">Web Services</a></li>
|
|
<li><a href="components.html">Components</a></li>
|
|
</ul>
|
|
<h2 id="advanced-topics.html">Advanced Topics</h2>
|
|
<ul>
|
|
<li><a href="compiling-templates.html">Compiling Templates</a></li>
|
|
<li><a href="auto-redrawing.html">The Auto-Redrawing System</a></li>
|
|
<li><a href="integration.html">Integrating with Other Libraries</a></li>
|
|
</ul>
|
|
<h2 id="misc">Misc</h2>
|
|
<ul>
|
|
<li><a href="comparison.html">Differences from Other MVC Frameworks</a></li>
|
|
<li><a href="practices.html">Good Practices</a></li>
|
|
<li><a href="tools.html">Useful Tools</a></li>
|
|
</ul>
|
|
</div>
|
|
<div class="col(9,9,12)">
|
|
<h2 id="web-services">Web Services</h2>
|
|
<p>Mithril provides a high-level utility for working with web services, which allows writing asynchronous code relatively procedurally.</p>
|
|
<p>It provides a number of useful features out of the box:</p>
|
|
<ul>
|
|
<li>The ability to get an early reference to a container that will hold the asynchronous response</li>
|
|
<li>The ability to queue operations to be performed after the asynchronous request completes</li>
|
|
<li>The ability to "cast" the response to a class of your choice</li>
|
|
<li>The ability to unwrap data in a response that includes metadata properties</li>
|
|
</ul>
|
|
<hr>
|
|
<h3 id="basic-usage">Basic usage</h3>
|
|
<p>The basic usage pattern for <code>m.request</code> returns an <a href="mithril.prop.html"><code>m.prop</code></a> getter-setter, which is populated when the AJAX request completes.</p>
|
|
<p>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.</p>
|
|
<pre><code class="lang-javascript">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"}]</code></pre>
|
|
<p>Note that this getter-setter holds an <em>undefined</em> value until the AJAX request completes. Attempting to unwrap its value early will likely result in errors.</p>
|
|
<p>The returned getter-setter also implements the <a href="mithril.deferred.html">promise</a> interface (also known as a <em>thennable</em>): this is the mechanism you should always use to queue operations to be performed on the data from the web service.</p>
|
|
<p>The simplest use case of this feature is to implement functional value assignment via <code>m.prop</code> (i.e. the same thing as above). You can bind a pre-existing getter-setter by passing it in as a parameter to a <code>.then</code> method:</p>
|
|
<pre><code class="lang-javascript">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"}]</code></pre>
|
|
<p>This syntax allows you to bind intermediate results before piping them down for further processing, for example:</p>
|
|
<pre><code class="lang-javascript">var users = m.prop([]); //default value
|
|
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>
|
|
<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>
|
|
</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>
|
|
<p>In the example below, the <code>listEven</code> method returns a getter-setter that resolves to a list of users containing only users whose id is even.</p>
|
|
<pre><code class="lang-javascript">//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()
|
|
}</code></pre>
|
|
<h4 id="bind-redirection-code">Bind redirection code</h4>
|
|
<p>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.</p>
|
|
<p>In the example below, we use the previously defined <code>listEven</code> model method and queue a controller-level function that redirects to another page if the user list is empty.</p>
|
|
<pre><code class="lang-javascript">//controller
|
|
var controller = function() {
|
|
this.users = User.listEven().then(function(users) {
|
|
if (users.length == 0) m.route("/add");
|
|
})
|
|
}</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>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
|
|
var controller = function() {
|
|
this.error = m.prop("")
|
|
|
|
this.users = User.listEven().then(function(users) {
|
|
if (users.length == 0) m.route("/add");
|
|
}, this.error)
|
|
}</code></pre>
|
|
<p>If the controller doesn't already have a success callback to run after a request resolves, you can still bind errors like this:</p>
|
|
<pre><code class="lang-javascript">//controller
|
|
var controller = function() {
|
|
this.error = m.prop("")
|
|
|
|
this.users = User.listEven().then(null, this.error)
|
|
}</code></pre>
|
|
<hr>
|
|
<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>
|
|
</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>
|
|
<pre><code class="lang-javascript">var users = m.request({method: "GET", url: "/user"})
|
|
.then(console.log);
|
|
.then(function(users) {
|
|
//add one more user to the response
|
|
return users.concat({name: "Jane"})
|
|
})
|
|
|
|
//assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]`
|
|
//then when resolved (e.g. in a view), the `users` getter-setter will contain a list of users
|
|
//i.e. users() //[{name: "John"}, {name: "Mary"}, {name: "Jane"}]</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>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);
|
|
}
|
|
|
|
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"</code></pre>
|
|
<hr>
|
|
<h3 id="unwrapping-response-data">Unwrapping Response Data</h3>
|
|
<p>Often, web services return the relevant data wrapped in objects that contain metadata.</p>
|
|
<p>Mithril allows you to unwrap the relevant data, by providing two callback hooks: <code>unwrapSuccess</code> and <code>unwrapError</code>.</p>
|
|
<p>These hooks allow you to unwrap different parts of the response data depending on whether it succeed or failed.</p>
|
|
<pre><code class="lang-javascript">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"}]</code></pre>
|
|
<hr>
|
|
<h3 id="using-different-data-transfer-formats">Using Different Data Transfer Formats</h3>
|
|
<p>By default, <code>m.request</code> uses JSON to send and receive data to web services. You can override this by providing <code>serialize</code> and <code>deserialize</code> options:</p>
|
|
<pre><code class="lang-javascript">var users = m.request({
|
|
method: "GET",
|
|
url: "/user",
|
|
serialize: mySerializer,
|
|
deserialize: myDeserializer
|
|
});</code></pre>
|
|
<p>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.</p>
|
|
<pre><code class="lang-javascript">var file = m.request({
|
|
method: "GET",
|
|
url: "myfile.txt",
|
|
deserialize: function(value) {return value;}
|
|
});</code></pre>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
<footer>
|
|
<div class="container">
|
|
Released under the <a href="http://opensource.org/licenses/MIT" target="_blank">MIT license</a>
|
|
<br />© 2014 Leo Horie
|
|
</div>
|
|
</footer>
|
|
<script src="lib/prism/prism.js"></script>
|
|
</body>
|
|
</html> |