Merge remote-tracking branch 'origin/rewrite' into rewrite

This commit is contained in:
Leo Horie 2017-01-16 09:11:04 -05:00
commit b8e8afffa8
2 changed files with 47 additions and 47 deletions

View file

@ -94,7 +94,7 @@ We can verify that both the enter and exit animations work by mounting the `Togg
m.mount(document.body, Toggler) m.mount(document.body, Toggler)
``` ```
Note that the `onbeforeremove` hook only fires on the element that loses its `parentNode` when an elements gets detached from the DOM. This behavior is by design and exists to prevent a potential jarring user experience where every conceivable exit animation on the page would run on a route change. If your exit animation is not running, make sure to attach the `onbeforeremove` handler as high up the tree as it makes sense to ensure that your animation code is called. Note that the `onbeforeremove` hook only fires on the element that loses its `parentNode` when an element gets detached from the DOM. This behavior is by design and exists to prevent a potential jarring user experience where every conceivable exit animation on the page would run on a route change. If your exit animation is not running, make sure to attach the `onbeforeremove` handler as high up the tree as it makes sense to ensure that your animation code is called.
--- ---
@ -102,4 +102,4 @@ Note that the `onbeforeremove` hook only fires on the element that loses its `pa
When creating animations, it's recommended that you only use the `opacity` and `transform` CSS rules, since these can be hardware-accelerated by modern browsers and yield better performance than animating `top`, `left`, `width`, and `height`. When creating animations, it's recommended that you only use the `opacity` and `transform` CSS rules, since these can be hardware-accelerated by modern browsers and yield better performance than animating `top`, `left`, `width`, and `height`.
It's also recommended that you avoid the `box-shadow` rule and selectors like `:nth-child`, since these are also resource intensive options. Other things that can be expensive include large or dynamically scaled images and overlapping elements with different `position` values (e.g. an absolute postioned element over a fixed element). It's also recommended that you avoid the `box-shadow` rule and selectors like `:nth-child`, since these are also resource intensive options. Other things that can be expensive include large or dynamically scaled images and overlapping elements with different `position` values (e.g. an absolute postioned element over a fixed element).

View file

@ -13,14 +13,14 @@ First let's create an entry point for the application. Create a file `index.html
<title>My Application</title> <title>My Application</title>
</head> </head>
<body> <body>
<script src="app.js"></script> <script src="bin/app.js"></script>
</body> </body>
</html> </html>
``` ```
The `<!doctype html>` line indicates this is an HTML 5 document. The first `charset` meta tag indicates the encoding of the document and the `viewport` meta tag dictates how mobile browsers should scale the page. The `title` tag contains the text to be displayed on the browser tab for this application, and the `script` tag indicates what is the path to the Javascript file that controls the application. The `<!doctype html>` line indicates this is an HTML 5 document. The first `charset` meta tag indicates the encoding of the document and the `viewport` meta tag dictates how mobile browsers should scale the page. The `title` tag contains the text to be displayed on the browser tab for this application, and the `script` tag indicates what is the path to the Javascript file that controls the application.
We could create the entire application in a single Javascript file, but doing so would make it difficult to navigate the codebase later on. Instead, let's split the code into *modules*, and assemble these modules into a *bundle* `app.js`. We could create the entire application in a single Javascript file, but doing so would make it difficult to navigate the codebase later on. Instead, let's split the code into *modules*, and assemble these modules into a *bundle* `bin/app.js`.
There are many ways to setup a bundler tool, but most are distributed via NPM. In fact, most modern Javascript libraries and tools are distributed that way, including Mithril. NPM stands for Node.js Package Manager. To download NPM, [install Node.js](https://nodejs.org/en/); NPM is installed automatically with it. Once you have Node.js and NPM installed, open the command line and run this command: There are many ways to setup a bundler tool, but most are distributed via NPM. In fact, most modern Javascript libraries and tools are distributed that way, including Mithril. NPM stands for Node.js Package Manager. To download NPM, [install Node.js](https://nodejs.org/en/); NPM is installed automatically with it. Once you have Node.js and NPM installed, open the command line and run this command:
@ -61,7 +61,7 @@ module.exports = User
Next we create a function that will trigger an XHR call. Let's call it `loadList` Next we create a function that will trigger an XHR call. Let's call it `loadList`
```javascript ```javascript
// models/User.js // src/models/User.js
var m = require("mithril") var m = require("mithril")
var User = { var User = {
@ -77,7 +77,7 @@ module.exports = User
Then we can add an `m.request` call to make an XHR request. For this tutorial, we'll make XHR calls to the [REM](http://rem-rest-api.herokuapp.com/) API, a mock REST API designed for rapid prototyping. This API returns a list of users from the `GET http://rem-rest-api.herokuapp.com/api/users` endpoint. Let's use `m.request` to make an XHR request and populate our data with the response of that endpoint. Then we can add an `m.request` call to make an XHR request. For this tutorial, we'll make XHR calls to the [REM](http://rem-rest-api.herokuapp.com/) API, a mock REST API designed for rapid prototyping. This API returns a list of users from the `GET http://rem-rest-api.herokuapp.com/api/users` endpoint. Let's use `m.request` to make an XHR request and populate our data with the response of that endpoint.
```javascript ```javascript
// models/User.js // src/models/User.js
var m = require("mithril") var m = require("mithril")
var User = { var User = {
@ -99,7 +99,7 @@ module.exports = User
The `method` option is an [HTTP method](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods). To retrieve data from the server without causing side-effects on the server, we need to use the `GET` method. The `url` is the address for the API endpoint. The `withCredentials: true` line indicates that we're using cookies (which is a requirement for the REM API). The `method` option is an [HTTP method](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods). To retrieve data from the server without causing side-effects on the server, we need to use the `GET` method. The `url` is the address for the API endpoint. The `withCredentials: true` line indicates that we're using cookies (which is a requirement for the REM API).
The `m.request` call returns a Promise that resolves to the data from the endpoint. By default, Mithril assumes a HTTP response body are in JSON format and automatically parses it into a Javascript object or array. The `.then` callback runs when the XHR request completes. In this case, the callback assigns the `reesult.data` array to `User.list`. The `m.request` call returns a Promise that resolves to the data from the endpoint. By default, Mithril assumes a HTTP response body are in JSON format and automatically parses it into a Javascript object or array. The `.then` callback runs when the XHR request completes. In this case, the callback assigns the `result.data` array to `User.list`.
Notice we also have a `return` statement in `loadList`. This is a general good practice when working with Promises, which allows us to register more callbacks to run after the completion of the XHR request. Notice we also have a `return` statement in `loadList`. This is a general good practice when working with Promises, which allows us to register more callbacks to run after the completion of the XHR request.
@ -112,15 +112,17 @@ Now, let's create a view module so that we can display data from our User model
Create a file called `src/views/UserList.js`. First, let's include Mithril and our model, since we'll need to use both: Create a file called `src/views/UserList.js`. First, let's include Mithril and our model, since we'll need to use both:
```javascript ```javascript
// src/views/UserList.js
var m = require("mithril") var m = require("mithril")
var User = require("../model/User") var User = require("../models/User")
``` ```
Next, let's create a Mithril component. A component is simply an object that has a `view` method: Next, let's create a Mithril component. A component is simply an object that has a `view` method:
```javascript ```javascript
// src/views/UserList.js
var m = require("mithril") var m = require("mithril")
var User = require("../model/User") var User = require("../models/User")
module.exports = { module.exports = {
view: function() { view: function() {
@ -135,7 +137,7 @@ Let's use Mithril hyperscript to create a list of items. Hyperscript is the most
```javascript ```javascript
var m = require("mithril") var m = require("mithril")
var User = require("../model/User") var User = require("../models/User")
module.exports = { module.exports = {
view: function() { view: function() {
@ -149,16 +151,15 @@ The `".user-list"` string is a CSS selector, and as you would expect, `.user-lis
Now, let's reference the list of users from the model we created earlier (`User.list`) to dynamically loop through data: Now, let's reference the list of users from the model we created earlier (`User.list`) to dynamically loop through data:
```javascript ```javascript
// src/views/UserList.js
var m = require("mithril") var m = require("mithril")
var User = require("../model/User") var User = require("../models/User")
module.exports = { module.exports = {
view: function() { view: function() {
return m(".user-list", [ return m(".user-list", User.list.map(function(user) {
User.list.map(function(user) { return m(".user-list-item", user.firstName + " " + user.lastName)
return m(".user-list-item", user.firstName + " " + user.lastName) }))
})
])
} }
} }
``` ```
@ -168,17 +169,16 @@ Since `User.list` is a Javascript array, and since hyperscript views are just Ja
The problem, of course, is that we never called the `User.loadList` function. Therefore, `User.list` is still an empty array, and thus this view would render a blank page. Since we want `User.loadList` to be called when we render this component, we can take advantage of component [lifecycle methods](lifecycle-methods.md): The problem, of course, is that we never called the `User.loadList` function. Therefore, `User.list` is still an empty array, and thus this view would render a blank page. Since we want `User.loadList` to be called when we render this component, we can take advantage of component [lifecycle methods](lifecycle-methods.md):
```javascript ```javascript
// src/views/UserList.js
var m = require("mithril") var m = require("mithril")
var User = require("../model/User") var User = require("../models/User")
module.exports = { module.exports = {
oninit: User.loadList, oninit: User.loadList,
view: function() { view: function() {
return m(".user-list", [ return m(".user-list", User.list.map(function(user) {
User.list.map(function(user) { return m(".user-list-item", user.firstName + " " + user.lastName)
return m(".user-list-item", user.firstName + " " + user.lastName) }))
})
])
} }
} }
``` ```
@ -189,12 +189,13 @@ Also notice we **didn't** do `oninit: User.loadList()` (with parentheses at the
--- ---
Let's render the view from the entry point file `index.js` we created earlier: Let's render the view from the entry point file `src/index.js` we created earlier:
```javascript ```javascript
// src/index.js
var m = require("mithril") var m = require("mithril")
var UserList = require("./view/UserList") var UserList = require("./views/UserList")
m.mount(document.body, UserList) m.mount(document.body, UserList)
``` ```
@ -221,7 +222,7 @@ To add styles, let's first create a file called `styles.css` and include it in t
<link href="styles.css" rel="stylesheet" /> <link href="styles.css" rel="stylesheet" />
</head> </head>
<body> <body>
<script src="app.js"></script> <script src="bin/app.js"></script>
</body> </body>
</html> </html>
``` ```
@ -249,9 +250,10 @@ Routing means binding a screen to a unique URL, to create the ability to go from
We can add routing by changing the `m.mount` call to a `m.route` call: We can add routing by changing the `m.mount` call to a `m.route` call:
```javascript ```javascript
// src/index.js
var m = require("mithril") var m = require("mithril")
var UserList = require("./view/UserList") var UserList = require("./views/UserList")
m.route(document.body, "/list", { m.route(document.body, "/list", {
"/list": UserList "/list": UserList
@ -278,14 +280,14 @@ module.exports = {
} }
``` ```
Then we can `require` this new module from `index.js` Then we can `require` this new module from `src/index.js`
```javascript ```javascript
// index.js // src/index.js
var m = require("mithril") var m = require("mithril")
var UserList = require("./view/UserList") var UserList = require("./views/UserList")
var UserForm = require("./view/UserForm") var UserForm = require("./views/UserForm")
m.route(document.body, "/list", { m.route(document.body, "/list", {
"/list": UserList "/list": UserList
@ -295,11 +297,11 @@ m.route(document.body, "/list", {
And finally, we can create a route that references it: And finally, we can create a route that references it:
```javascript ```javascript
// index.js // src/index.js
var m = require("mithril") var m = require("mithril")
var UserList = require("./view/UserList") var UserList = require("./views/UserList")
var UserForm = require("./view/UserForm") var UserForm = require("./views/UserForm")
m.route(document.body, "/list", { m.route(document.body, "/list", {
"/list": UserList, "/list": UserList,
@ -344,7 +346,7 @@ body,.input,.button {font:normal 16px Verdana;margin:0;}
.button:hover {background:#e8e8e8;} .button:hover {background:#e8e8e8;}
``` ```
Right now, this component does nothing to respond to user events. Let's add some code to our `User` model in `src/model/User.js`. This is how the code is right now: Right now, this component does nothing to respond to user events. Let's add some code to our `User` model in `src/models/User.js`. This is how the code is right now:
```javascript ```javascript
// src/models/User.js // src/models/User.js
@ -408,7 +410,7 @@ Notice we added a `User.current` property, and a `User.load(id)` method which po
```javascript ```javascript
// src/views/UserForm.js // src/views/UserForm.js
var m = require("mithril") var m = require("mithril")
var User = require("./model/User") var User = require("../models/User")
module.exports = { module.exports = {
oninit: function(vnode) {User.load(vnode.attrs.id)}, oninit: function(vnode) {User.load(vnode.attrs.id)},
@ -431,16 +433,14 @@ Now, let's modify the `UserList` view so that we can navigate from there to a `U
```javascript ```javascript
// src/views/UserList.js // src/views/UserList.js
var m = require("mithril") var m = require("mithril")
var User = require("../model/User") var User = require("../models/User")
module.exports = { module.exports = {
oninit: User.loadList, oninit: User.loadList,
view: function() { view: function() {
return m(".user-list", [ return m(".user-list", User.list.map(function(user) {
User.list.map(function(user) { return m("a.user-list-item", {href: "/edit/" + user.id, oncreate: m.route.link}, user.firstName + " " + user.lastName)
return m("a.user-list-item", {href: "/edit/" + user.id, oncreate: m.route.link}, user.firstName + " " + user.lastName) }))
})
])
} }
} }
``` ```
@ -456,7 +456,7 @@ The form itself still doesn't save when you press "Save". Let's make this form w
```javascript ```javascript
// src/views/UserForm.js // src/views/UserForm.js
var m = require("mithril") var m = require("mithril")
var User = require("../model/User") var User = require("../models/User")
module.exports = { module.exports = {
oninit: function(vnode) {User.load(vnode.attrs.id)}, oninit: function(vnode) {User.load(vnode.attrs.id)},
@ -573,15 +573,15 @@ body,.input,.button {font:normal 16px Verdana;margin:0;}
.button:hover {background:#e8e8e8;} .button:hover {background:#e8e8e8;}
``` ```
Let's change the router in `index.js` to add our layout into the mix: Let's change the router in `src/index.js` to add our layout into the mix:
```javascript ```javascript
// index.js // src/index.js
var m = require("mithril") var m = require("mithril")
var UserList = require("./view/UserList") var UserList = require("./views/UserList")
var UserForm = require("./view/UserForm") var UserForm = require("./views/UserForm")
var Layout = require("./view/Layout") var Layout = require("./views/Layout")
m.route(document.body, "/list", { m.route(document.body, "/list", {
"/list": { "/list": {