rework api

This commit is contained in:
Leo Horie 2015-04-09 22:44:45 -04:00
parent 6abb868c6b
commit 9e8dc6998d
14 changed files with 830 additions and 345 deletions

View file

@ -41,7 +41,7 @@ Here, we've defined a class called `Contact`. A contact has an id, a name and an
One way of organizing components is to use component parameter lists to send data downstream, and to define events to bubble data back upstream to a centralized module who is responsible for interfacing with the model layer.
```javascript
var ContactsWidget = m.component({
var ContactsWidget = {
controller: function update() {
this.contacts = Contact.list()
this.save = function(contact) {
@ -50,13 +50,13 @@ var ContactsWidget = m.component({
},
view: function(ctrl) {
return [
ContactForm({onsave: ctrl.save}),
ContactList({contacts: ctrl.contacts})
m.component(ContactForm, {onsave: ctrl.save}),
m.component(ContactList, {contacts: ctrl.contacts})
]
}
})
}
var ContactForm = m.component({
var ContactForm = {
controller: function(args) {
this.contact = m.prop(args.contact || new Contact())
},
@ -73,9 +73,9 @@ var ContactForm = m.component({
m("button[type=button]", {onclick: args.onsave.bind(this, contact)}, "Save")
])
}
})
}
var ContactList = m.component({
var ContactList = {
view: function(ctrl, args) {
return m("table", [
args.contacts().map(function(contact) {
@ -87,9 +87,9 @@ var ContactList = m.component({
})
])
}
})
}
m.module(document.body, ContactsWidget())
m.mount(document.body, ContactsWidget)
```
In the example above, there are 3 components. `ContactsWidget` is the top level module being rendered to `document.body`, and it is the module that has the responsibility of talking to our Model entity `Contact`, which we defined earlier.
@ -123,7 +123,7 @@ Another way of organizing code is to distribute concrete responsibilities across
Here's a refactored version of the sample app above to illustrate:
```javascript
var ContactForm = m.component({
var ContactForm = {
controller: function() {
this.contact = m.prop(new Contact())
this.save = function(contact) {
@ -143,7 +143,7 @@ var ContactForm = m.component({
m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
])
}
})
}
var ContactList = {
controller: function() {
@ -163,8 +163,8 @@ var ContactList = {
}
m.route(document.body, "/", {
"/list": ContactList(),
"/create": ContactForm()
"/list": ContactList,
"/create": ContactForm
})
```
@ -200,16 +200,16 @@ var Observable = function() {
}.call()
var ContactsWidget = m.component({
var ContactsWidget = {
view: function(ctrl) {
return [
m.module(ContactForm),
m.module(ContactList)
ContactForm,
ContactList
]
}
})
}
var ContactForm = m.component({
var ContactForm = {
controller: function() {
this.contact = m.prop(new Contact())
this.save = function(contact) {
@ -229,9 +229,9 @@ var ContactForm = m.component({
m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
])
}
})
}
var ContactList = m.component({
var ContactList = {
controller: Observable.register(function() {
this.contacts = Contact.list()
}),
@ -246,9 +246,9 @@ var ContactList = m.component({
})
])
}
})
}
m.module(document.body, ContactsWidget())
m.module(document.body, ContactsWidget)
```
In this iteration, both the `ContactForm` and `ContactList` components are now children of the `ContactsWidget` component and they appear simultaneously on the same page.
@ -311,19 +311,19 @@ It's of course possible to use both aggregation of responsibility and the observ
The example below shows a variation of the contacts app where `ContactForm` is responsible for saving.
```javascript
var ContactsWidget = m.component({
var ContactsWidget = {
controller: Observable.register(["updateContact"], function() {
this.contacts = Contact.list()
}),
view: function(ctrl) {
return [
ContactForm(),
ContactList({contacts: ctrl.contacts})
m.component(ContactForm),
m.component(ContactList, {contacts: ctrl.contacts})
]
}
})
}
var ContactForm = m.component({
var ContactForm = {
controller: function(args) {
this.contact = m.prop(new Contact())
this.save = function(contact) {
@ -343,9 +343,9 @@ var ContactForm = m.component({
m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
])
}
})
}
var ContactList = m.component({
var ContactList = {
view: function(ctrl, args) {
return m("table", [
args.contacts().map(function(contact) {
@ -357,9 +357,9 @@ var ContactList = m.component({
})
])
}
})
}
m.mount(document.body, ContactsWidget())
m.mount(document.body, ContactsWidget)
```
Here, the data fetching is still centralized in the top-level component, so that we can avoid duplicate AJAX requests when fetching data.
@ -379,20 +379,20 @@ Observable.on(["saveContact"], function(data) {
})
//ContactsWidget is the same as before
var ContactsWidget = m.component({
var ContactsWidget = {
controller: Observable.register(["updateContact"], function() {
this.contacts = Contact.list()
}),
view: function(ctrl) {
return [
ContactForm(),
ContactList({contacts: ctrl.contacts})
m.component(ContactForm),
m.component(ContactList, {contacts: ctrl.contacts})
]
}
})
}
//ContactList no longer calls `Contact.save`
var ContactForm = m.component({
var ContactForm = {
controller: function(args) {
this.contact = m.prop(new Contact())
this.save = function(contact) {
@ -412,10 +412,10 @@ var ContactForm = m.component({
m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
])
}
})
}
//ContactList is the same as before
var ContactList = m.component({
var ContactList = {
view: function(ctrl, args) {
return m("table", [
args.contacts().map(function(contact) {
@ -427,9 +427,9 @@ var ContactList = m.component({
})
])
}
})
}
m.mount(document.body, ContactsWidget())
m.mount(document.body, ContactsWidget)
```
Here we've moved `Contact.save(contact).then(Observable.broadcast("updateContact"))` out of the `ContactForm` component and into the model layer. In its place, `ContactForm` merely emits an action, which is then handled by this model layer observer.
@ -445,7 +445,35 @@ Here's an example of a not-so-trivial component: a drag-n-drop file uploader. In
These two functions are here to illustrate the ability to expose APIs to component consumers that complement the component's user interface. By bundling model methods in the component, we avoid hard-coding how files are handled once they're dropped in, and instead, we provide a useful library of functions that can be consumed flexibly to meet the demands on an application.
```javascript
var Uploader = m.component({
var Uploader = {
upload: function(options) {
var formData = new FormData
for (var key in options.data) {
for (var i = 0; i < options.data[key].length; i++) {
formData.append(key, files[i])
}
}
//simply pass the FormData object intact to the underlying XMLHttpRequest, instead of JSON.stringify'ing it
options.serialize = function(value) {return value}
options.data = formData
return m.request(options)
},
serialize: function(files) {
var promises = Array.prototype.slice.call(files).map(function(file) {
var deferred = m.deferred()
var reader = new FileReader
reader.readAsDataURL()
reader.onloadend = function(e) {
deferred.resolve(e.result)
}
reader.onerror = deferred.reject
return deferred
})
return m.sync(promises)
},
controller: function(args) {
this.noop = function(e) {
e.preventDefault()
@ -460,34 +488,6 @@ var Uploader = m.component({
view: function(ctrl, args) {
return m(".uploader", {ondragover: ctrl.noop, ondrop: ctrl.update})
}
})
Uploader.upload = function(options) {
var formData = new FormData
for (var key in options.data) {
for (var i = 0; i < options.data[key].length; i++) {
formData.append(key, files[i])
}
}
//simply pass the FormData object intact to the underlying XMLHttpRequest, instead of JSON.stringify'ing it
options.serialize = function(value) {return value}
options.data = formData
return m.request(options)
}
Uploader.serialize = function(files) {
var promises = Array.prototype.slice.call(files).map(function(file) {
var deferred = m.deferred()
var reader = new FileReader
reader.readAsDataURL()
reader.onloadend = function(e) {
deferred.resolve(e.result)
}
reader.onerror = deferred.reject
return deferred
})
return m.sync(promises)
}
```
@ -506,7 +506,7 @@ var Demo1 = {
view: function(ctrl) {
return [
m("h1", "Uploader demo"),
Uploader({onchange: ctrl.upload})
m.component(Uploader, {onchange: ctrl.upload})
]
}
}
@ -534,7 +534,7 @@ var Demo2 = {
return [
m("h1", "Uploader demo"),
m("form", [
Uploader({onchange: ctrl.files}),
m.component(Uploader, {onchange: ctrl.files}),
m("button[type=button]", {onclick: ctrl.save})
])
]