Comparing version 0.1.2 to 0.1.3
/** @jsx React.DOM */ | ||
var _ = require('underscore'); | ||
var React = require('react'); | ||
/** | ||
* An Application is created by passing in a hash of names -> stores. | ||
* Stores may expose functionality to the Application. | ||
* At minimum, each store will be available to components via it's name. | ||
*/ | ||
var Application = require('../../index.js').Application; | ||
var Application = require('../../index.js').Application; | ||
var ViewMixin = require('../../index.js').ViewMixin; | ||
/** | ||
* The root react view(s) must use this mixin. | ||
* In doing so, they will be wired into all of the store's | ||
* on('change') callbacks. And will forceUpdate (repaint) when stores change. | ||
*/ | ||
var RootViewMixin = require('../../index.js').RootViewMixin; | ||
/** | ||
* All other views must use the ViewMixin. | ||
* In doing so they can resove variables in their scope: | ||
* var myPerson = this.resolve('person'), | ||
* they can extend the scope with new properties to pass into a child: | ||
* var scopeForChile = this.scope({newPropForChild : someValue}); | ||
* and they can dispatch events to the stores: | ||
* this.dispatch('CREATE_FOO', {fooProperty: fooValue}); | ||
*/ | ||
var ViewMixin = require('../../index.js').ViewMixin; | ||
var _ = require('underscore'); | ||
var React = require('react/addons'); | ||
/** | ||
* Clock and Phonebook are plain javascript objects. | ||
* Importantly, they implement on(event, callback), and will call | ||
* on('change', callback) when their data changes. | ||
*/ | ||
var Clock = require('./clock.js').Clock; | ||
@@ -14,2 +41,10 @@ var PhoneBook = require('./phoneBook.js').PhoneBook; | ||
/** | ||
* This is used for animations. | ||
*/ | ||
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; | ||
/** | ||
* We can use this hook to log events. | ||
*/ | ||
Application.prototype.beforeDispatch = function(event_name, payload){ | ||
@@ -24,2 +59,6 @@ console.log(event_name, payload); | ||
/** | ||
* Create a clockStore. | ||
* Registering for an event simply passes the call to the clock object. | ||
*/ | ||
var clockStore = { | ||
@@ -32,2 +71,18 @@ clock : clock, | ||
/** | ||
* Create a phoneBookStore. | ||
* As a convetions, actions are UPPER CASE. | ||
* When a view calls this.dispatch('EVENT_NAME', paramsMap), | ||
* all stores who have a method called EVENT_NAME will have that method invoked. | ||
* These methods should return promises to make used of the fact that the dispatcher will | ||
* wrap all results in a Promise.all so that the callee can respond asynchronously to | ||
* success or failure. | ||
* | ||
* Any function whose key-name starts with a '/' will be added to the routes table. | ||
* An alias for a defined route can be added by declaring a key value pair as such: | ||
* '/alias' : '/definedRoute' | ||
* | ||
* RootViewMixin instances will be told to update after the route functions are done. | ||
* So the route functions can simply set some state that will be used during render. | ||
*/ | ||
var phoneBookStore = { | ||
@@ -41,4 +96,12 @@ phoneBook : phoneBook, | ||
ADD_CONTACT : function(payload){ | ||
/** | ||
* We emulate lag here, but return a promise. In the UI, we make use of this to | ||
* disable/reneable features accordingly. | ||
*/ | ||
return this.phoneBook.addContact_with_lag(payload); | ||
}, | ||
DELETE_CONTACT : function(payload){ | ||
return this.phoneBook.deleteContact(payload); | ||
}, | ||
@@ -75,2 +138,8 @@ //-- ROUTES ----------------------- | ||
/** | ||
* Create the application. | ||
* Routes are rigged up now, so don't dynamically add them later. | ||
* Make sure to call application.initRoute('/'); or similar to start the router. | ||
* See the end of this file. | ||
*/ | ||
var application = new Application( | ||
@@ -86,8 +155,16 @@ { | ||
render: function(){ | ||
var time = this.resolve('clockStore').clock.time(); | ||
var viewData = this.resolve('phoneBookStore').viewData; | ||
if (viewData) { | ||
//our main content is driven by URL, so URL is a fine key to use for | ||
//React's key functionality that tells it when something has changed. | ||
var key = document.location.toString(); | ||
return <div> | ||
<div>Time : {time}</div> | ||
<viewData.viewClass scope={viewData.scope}/> | ||
<div className="time">Time : {time}</div> | ||
<hr/> | ||
<ReactCSSTransitionGroup transitionName="slide"> | ||
<viewData.viewClass key={key} scope={viewData.scope}/> | ||
</ReactCSSTransitionGroup> | ||
</div>; | ||
@@ -106,2 +183,6 @@ } else { | ||
var contactViews = _.map(phoneBook.contacts, function(contact){ | ||
/** | ||
* Here we create a new scope for the sub-component. It will have access to | ||
* values in parent views that have not been shadowed. | ||
*/ | ||
var scope = self.scope({contact: contact}); | ||
@@ -113,3 +194,5 @@ return <li key={contact.id}><ContactView scope={scope}/></li>; | ||
<ul> | ||
{contactViews} | ||
<ReactCSSTransitionGroup transitionName="example"> | ||
{contactViews} | ||
</ReactCSSTransitionGroup> | ||
</ul> | ||
@@ -160,2 +243,6 @@ <br/> | ||
mixins: [ViewMixin], | ||
deleteSelf : function(){ | ||
this.dispatch('DELETE_CONTACT', this.resolve('contact').id); | ||
return false; | ||
}, | ||
render : function(){ | ||
@@ -172,5 +259,9 @@ var self = this; | ||
return <div className='bordered'> | ||
<a href={"#/contact/" + contact.id}> | ||
[<a href="" onClick={this.deleteSelf}> | ||
X | ||
</a>] | ||
<a cssStyle="float:left" href={"#/contact/" + contact.id}> | ||
{contact.name} | ||
</a> | ||
<br/> | ||
@@ -177,0 +268,0 @@ Phones: |
@@ -12,2 +12,9 @@ var _ = require('underscore'); | ||
}, | ||
deleteContact : function(id){ | ||
var index = _.indexOf(this.contacts, this.getContact(id)); | ||
if (index > -1){ | ||
this.contacts.splice(index, 1); | ||
} | ||
this.trigger("change"); | ||
}, | ||
@@ -14,0 +21,0 @@ addContact_with_lag: function(properties){ |
{ | ||
"name": "fluir", | ||
"version": "0.1.2", | ||
"version": "0.1.3", | ||
"description": "Dispatcher, router, store helper for React apps.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
190
README.md
@@ -6,190 +6,6 @@ fluir | ||
Example app (see https://github.com/z5h/fluir/tree/master/example): | ||
``` | ||
/** @jsx React.DOM */ | ||
See a live example [here](http://z5h.github.io/fluir/). | ||
var _ = require('underscore'); | ||
var React = require('react'); | ||
Right now the best way to learn is to read through the example code [here](https://github.com/z5h/fluir/tree/master/example). | ||
var Application = require('../../index.js').Application; | ||
var ViewMixin = require('../../index.js').ViewMixin; | ||
var RootViewMixin = require('../../index.js').RootViewMixin; | ||
var Clock = require('./clock.js').Clock; | ||
var PhoneBook = require('./phoneBook.js').PhoneBook; | ||
var loadExampleData = require('./phoneBook.js').loadExampleData; | ||
Application.prototype.beforeDispatch = function(event_name, payload){ | ||
console.log(event_name, payload); | ||
}; | ||
var phoneBook = new PhoneBook(); | ||
loadExampleData(phoneBook); | ||
var clock = new Clock(); | ||
var clockStore = { | ||
clock : clock, | ||
on : function(event, f){ | ||
this.clock.on(event, f); | ||
} | ||
}; | ||
var phoneBookStore = { | ||
phoneBook : phoneBook, | ||
on : function(event, f){ | ||
this.phoneBook.on(event, f); | ||
}, | ||
//-- ACTIONS ----------------------- | ||
ADD_CONTACT : function(payload){ | ||
this.phoneBook.addContact(payload); | ||
}, | ||
//-- ROUTES ----------------------- | ||
'/': '/phonebook', | ||
'/phonebook' : function(){ | ||
this.viewData = { | ||
viewClass : PhoneBookView, | ||
scope : this.scope({phoneBook : this.phoneBook}) | ||
}; | ||
}, | ||
'/contact/:id' : function(id){ | ||
var contact = this.phoneBook.getContact(id); | ||
console.log('contact',contact); | ||
this.viewData = { | ||
viewClass : ContactView, | ||
scope : this.scope({ | ||
contact : contact | ||
}) | ||
}; | ||
}, | ||
'/contact/:id/:phoneIndex' : function(id, phoneKey){ | ||
var contact = this.phoneBook.getContact(id); | ||
this.viewData = { | ||
viewClass : PhoneView, | ||
scope : this.scope({ | ||
contact : contact, | ||
phoneKey : phoneKey | ||
}) | ||
}; | ||
} | ||
}; | ||
var application = new Application( | ||
{ | ||
phoneBookStore: phoneBookStore, | ||
clockStore : clockStore | ||
} | ||
); | ||
var RootAppView = React.createClass({ | ||
mixins: [RootViewMixin], | ||
render: function(){ | ||
console.log('render'); | ||
var time = this.resolve('clockStore').clock.time(); | ||
var viewData = this.resolve('phoneBookStore').viewData; | ||
if (viewData) { | ||
return <div> | ||
<div>Time : {time}</div> | ||
<viewData.viewClass scope={viewData.scope}/> | ||
</div>; | ||
} else { | ||
return <div>Time: {time}</div>; | ||
} | ||
} | ||
}); | ||
var PhoneBookView = React.createClass({ | ||
mixins: [ViewMixin], | ||
render : function(){ | ||
var self = this; | ||
var phoneBook = this.resolve('phoneBook'); | ||
var contactViews = _.map(phoneBook.contacts, function(contact){ | ||
var scope = self.scope({contact: contact}); | ||
return <li key={contact.id}><ContactView scope={scope}/></li>; | ||
}); | ||
return <div> | ||
<div>PhoneBook</div> | ||
<ul> | ||
{contactViews} | ||
</ul> | ||
<br/> | ||
<CreateContactView scope={this.scope()}/> | ||
</div>; | ||
} | ||
}); | ||
var CreateContactView = React.createClass({ | ||
mixins: [ViewMixin], | ||
handleClick : function(){ | ||
var phoneNumbers = {}; | ||
phoneNumbers[this.refs.phoneType.getDOMNode().value] = | ||
this.refs.phoneNumber.getDOMNode().value; | ||
this.dispatch('ADD_CONTACT', { | ||
name : this.refs.name.getDOMNode().value, | ||
phoneNumbers : phoneNumbers | ||
}); | ||
}, | ||
render : function(){ | ||
return <div className='bordered pure-form pure-form-stacked'> | ||
<div>New User:</div> | ||
<label for="name">Name</label> | ||
<input id='name' ref='name'/> | ||
<label for="phoneType">Phone Type</label> | ||
<input id='phoneType' ref='phoneType'/> | ||
<label for="phoneNumber">Phone Number</label> | ||
<input id='phoneNumber' ref='phoneNumber'/> | ||
<button onClick={this.handleClick}>Add</button> | ||
</div>; | ||
} | ||
}); | ||
var ContactView = React.createClass({ | ||
mixins: [ViewMixin], | ||
render : function(){ | ||
var self = this; | ||
var contact = this.resolve('contact'); | ||
var phoneViews = _.map(contact.phoneNumbers, function(phoneNumber, key){ | ||
var scope = self.scope({ | ||
contact : contact, | ||
phoneKey : key | ||
}); | ||
return <li key={key}><PhoneView scope={scope}/></li>; | ||
}); | ||
return <div className='bordered'> | ||
<a href={"#/contact/" + contact.id}> | ||
{contact.name} | ||
</a> | ||
<br/> | ||
Phones: | ||
<ul> | ||
{phoneViews} | ||
</ul> | ||
</div> | ||
} | ||
}); | ||
var PhoneView = React.createClass({ | ||
mixins: [ViewMixin], | ||
render : function(){ | ||
var contact = this.resolve('contact'); | ||
var phoneKey = this.resolve('phoneKey'); | ||
var phoneNumber = contact.phoneNumbers[phoneKey]; | ||
return <span> | ||
<a href={"#/contact/" + contact.id + "/" + phoneKey}> | ||
{phoneKey} | ||
</a> | ||
: {phoneNumber} | ||
</span>; | ||
} | ||
}); | ||
React.renderComponent( | ||
<RootAppView scope={application} />, | ||
document.getElementById('app') | ||
); | ||
application.initRoute('/'); | ||
``` | ||
Or just look at the implementation. It's only [one file](https://github.com/z5h/fluir/blob/master/index.js). |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
26
503
78286
11