docs and macro fix

This commit is contained in:
Leo Horie 2014-10-29 09:19:02 -04:00
parent 98c413b55b
commit cf3b77c161
8 changed files with 84 additions and 73 deletions

View file

@ -102,7 +102,7 @@ Checking whether there is a controller for a unit of functionality gives you a d
If we decide that a unit of functionality is indeed a reusable component, we can simply add a controller to it so that it follows the module interface.
```
```javascript
//assuming we already have a view in `search`, adding a controller lets us use `search` as an independent component
search.controller = function() {
search.vm.init()

View file

@ -1,15 +0,0 @@
## Designing a Model Layer
When creating the data layer of an application, it's common for developers to settle on an ActiveRecord pattern (in a nutshell that means one class equals one database table). However, this choice is often done out of tradition rather than based on its merits.
The problem, as it specifically relates to frontend MVC, is that UIs are becoming more focused on end-user needs and the one-entity-type-per-request doesn't always support that end goal well.
Take for example an application's dashboard. It typically displays a summary of various things for the current logged in user, usually ranked in some not-so-trivial way (e.g. five most popular projects, or the projects with the latest activity, or upcoming team vacations, etc). If you think in terms of a traditional SQL database, it's pretty clear that basic CRUD is inadequate: you're going to need some joins, ranking, filtering, and possibly even more complex things like recursiveness - in other words, you're going to be needing the full extent of a modern relational database's features.
When we think of RESTful APIs over HTTP, the years of research and work done in the database world generally feel like an afterthought: we generally represent relationships with URL hacks (e.g. `/user/1/project/10`), but this pattern tends to fall apart with more complex requirements (e.g. one would expect that a "team vacations" resource returns both a list of users hierarchically related to the logged in user, as well as their vacation information: start date, end date, vacation days left, etc (remember vacation can span across weekends or statutory holidays, so that's another join or two depending on the employee's country!).
Consider that this dashboard probably displays some user information. Where should this data come from? One request for the user info, and another for the user's vacations? What about the team? If the dashboard shows other team metrics, where should that basic team info come from? How many HTTP requests are we up to now?
If the dashboard has self-contained customizable widgets, are we requesting duplicate information from different modules?
As you start running into situations like this, it's clear that naively requesting ActiveRecord entries from separate controllers quickly becomes unwieldy.

View file

@ -110,7 +110,7 @@ If you need to add separate widgets to different places on a same page, you can
There's just one caveat: while simply initializing multiple "islands" in this fashion works, their initialization calls are not aware of each other and can cause redraws too frequently. To optimize rendering, you should add a `m.startComputation` call before the first widget initialization call, and a `m.endComputation` after the last widget initialization call in each execution thread.
```
```javascript
m.startComputation()
m.module(document.getElementById("widget1-container"), widget1)

View file

@ -7,57 +7,62 @@ Usage: sjs --module /template-compiler.sjs --output <output-filename>.js <input-
*/
macro m {
case { _ ($selector) } => {
return #{m($selector, {}, [])};
}
case { _ ($selector, $partial) } => {
var partialSyntax = #{$partial};
var partial = unwrapSyntax(partialSyntax);
return partial.value == "{}" ? #{m($selector, $partial, [])} : #{m($selector, {}, $partial)};
}
case { _ ($selector, $dynAttrs, $children) } => {
case { _ ($selector, $dynAttrs ..., $children ...) } => {
var selectorSyntax = #{$selector};
var selector = unwrapSyntax(selectorSyntax);
var dynAttrsSyntax = #{$dynAttrs};
var dynAttrs = unwrapSyntax(dynAttrsSyntax);
var childrenSyntax = #{$children};
var children = unwrapSyntax(childrenSyntax);
var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g;
var attrParser = /\[(.+?)=("|'|)(.+?)\2\]/;
var _match = null;
var classes = [];
var cell = {tag: "div", attrs: {}, children: []};
while (_match = parser.exec(selector)) {
if (_match[1] == "") cell.tag = _match[2];
else if (_match[1] == "#") cell.attrs.id = _match[2];
else if (_match[1] == ".") classes.push(_match[2]);
else if (_match[3][0] == "[") {
var pair = attrParser.exec(_match[3]);
cell.attrs[pair[1]] = pair[3];
var dynAttrsSyntax = #{$dynAttrs ...};
try {
var dynAttrs = unwrapSyntax(dynAttrsSyntax);
var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g;
var attrParser = /\[(.+?)=("|'|)(.+?)\2\]/;
var _match = null;
var classes = [];
var cell = {tag: "div", attrs: {}, children: []};
while (_match = parser.exec(selector)) {
if (_match[1] == "") cell.tag = _match[2];
else if (_match[1] == "#") cell.attrs.id = _match[2];
else if (_match[1] == ".") classes.push(_match[2]);
else if (_match[3][0] == "[") {
var pair = attrParser.exec(_match[3]);
cell.attrs[pair[1]] = pair[3];
}
}
if (classes.length > 0) cell.attrs["class"] = classes.join(" ");
var tag = makeValue(cell.tag, #{here});
var attrsBody = Object.keys(cell.attrs).reduce(function(memo, attrName) {
return memo.concat([
makeValue(attrName, #{here}),
makePunc(":", #{here}),
makeValue(cell.attrs[attrName], #{here}),
makePunc(",", #{here})
]);
}, []).concat(dynAttrs.inner);
var attrs = [makeDelim("{}", attrsBody, #{here})];
letstx $tag = [tag], $attrs = attrs;
return #{ ({tag: $tag, attrs: $attrs , children: $children ...}) };
}
if (classes.length > 0) cell.attrs["class"] = classes.join(" ");
var tag = makeValue(cell.tag, #{here});
var attrsBody = Object.keys(cell.attrs).reduce(function(memo, attrName) {
return memo.concat([
makeValue(attrName, #{here}),
makePunc(":", #{here}),
makeValue(cell.attrs[attrName], #{here}),
makePunc(",", #{here})
]);
}, []).concat(dynAttrs.inner);
var attrs = [makeDelim("{}", attrsBody, #{here})];
var children = cell.children.map(function(child) {
return makeValue(child, #{here});
})
letstx $tag = [tag], $attrs = attrs;
return #{ ({tag: $tag, attrs: $attrs , children: $children}) };
catch (e) {
return #{ m($tag, {}, [$dynAttrs ..., $children ...]) }
}
}
case { _ ($selector, $partial ...) } => {
var partialSyntax = #{$partial ...};
try {
var partial = unwrapSyntax(partialSyntax);
var isTag = partial.inner && partial.inner.length > 2 && (partial.inner[0].token.value == "tag" && partial.inner[1].token.value == ":")
return !isTag ? #{m($selector, $partial ..., [])} : #{m($selector, {}, $partial ...)};
}
catch (e) {
return #{m($selector, {}, $partial ...)}
}
}
case { _ ($selector) } => {
return #{m($selector, {}, [])};
}
case { _ } => {
return #{Mithril};

View file

@ -132,9 +132,11 @@ m("div", {class: "widget"}); //yields <div class="widget"></div>
m("div", {className: "widget"}); //yields <div class="widget"></div>
m("input", {readonly: true}); //yields <input readonly />
m("button", {onclick: alert}); //yields <button></button>, which alerts its event argument when clicked
//note this uses the Javascript syntax (uppercase "O") for `readonly`
//in order to set the boolean javascript property instead of the HTML attribute
m("input", {readOnly: true}); //yields <input readonly />
```
---
@ -157,11 +159,15 @@ Mithril also does not auto-camel-case CSS properties on inline style attributes,
```javascript
m("div", {style: {textAlign: "center"}}); //yields <div style="text-align:center;"></div>
m("div", {style: {cssFloat: "left"}}); //yields <div style="float:left;"></div>
//this does not work
m("div", {style: {"text-align": "center"}});
m("div", {style: {float: "left"}});
```
You can find the [Javascript syntax for all the CSS rules here](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Properties_Reference).
You can, however, use CSS syntax when defining style rules as inline strings:
```javascript
@ -184,7 +190,7 @@ var name = m.prop("")
m("input", {oninput: m.withAttr("value", name), value: name()})
```
In the code above, the `oninput` event handler updates the `name` getter-setter, and the Mithril auto-redrawing system redraws the template in order to update the displayed value. You can read more about the [`m.prop` getter-setter utility here](mithril.prop.md) and the [`m.withAttr` event handler factory here](mithril.withattr.md). You can also [learn how the redrawing system works here](auto-redrawing.md).
In the code above, the `oninput` event handler updates the `name` getter-setter, and the Mithril auto-redrawing system redraws the template in order to update the displayed value. You can read more about the [`m.prop` getter-setter utility here](mithril.prop.md) and the [`m.withAttr` event handler factory here](mithril.withAttr.md). You can also [learn how the redrawing system works here](auto-redrawing.md).
Note that Mithril always considers the model layer data to be canonical. This means that in the code below, the input on screen will overwritten by the model data any time a redraw happens:

View file

@ -31,7 +31,7 @@ name("Mary"); //Mary
var b = name(); //b == "Mary"
```
It can be used in conjunction with [`m.withAttr`](mithril.withattr.md) to implement data binding in the view-to-model direction and to provide uniform data access for model entity properties.
It can be used in conjunction with [`m.withAttr`](mithril.withAttr.md) to implement data binding in the view-to-model direction and to provide uniform data access for model entity properties.
```javascript
//a contrived example of bi-directional data binding

View file

@ -10,6 +10,7 @@
- [Casting the Response Data to a Class](#casting-the-response-data-to-a-class)
- [Unwrapping Response Data](#unwrapping-response-data)
- [Using Different Data Transfer Formats](#using-different-data-transfer-formats)
- [File uploads with FormData](#file-uploads-with-form-data)
- [Using variable data formats](#using-variable-data-formats)
- [Extracting Metadata from the Response](#extracting-metadata-from-the-response)
- [Custom request rejections](#custom-request-rejections)
@ -252,6 +253,26 @@ var file = m.request({
---
### File uploads with FormData
To use the HTML5 FormData object as the payload for a request, you need to override the `serialize` option. By default, `serialize` converts an object to JSON, but in the case of a FormData payload, you want to pass the object intact.
```javascript
//assume the file comes from an HTML5 drag-n-drop event
var file = e.dataTransfer.files[0]
var data = new FormData();
data.append("file", file)
m.request({
method: "POST",
url: "/upload",
serialize: function(data) {return data}
})
```
---
### Using variable data formats
By default, Mithril assumes both success and error responses are in JSON format, but some servers may not return JSON responses when returning HTTP error codes (e.g. 404)

View file

@ -1,6 +0,0 @@
## Refactoring
Below are some common refactoring patterns:
### Porting legacy code to Mithril