├── .gitignore ├── README.md ├── datetime ├── .gitignore ├── README.md ├── client.js ├── package.json ├── public │ └── js │ │ └── bundle.js ├── server.js ├── src │ ├── actions │ │ └── TimeActions.js │ ├── alt.js │ ├── components │ │ ├── AltIsomorphicElement.js │ │ └── DateTimeComponent.js │ └── stores │ │ └── TimeStore.js └── templates │ └── layout.jade ├── image-swipe ├── README.md ├── client.js ├── package.json ├── public │ ├── css │ │ ├── components │ │ │ ├── images.css │ │ │ └── swipr.css │ │ └── main.css │ ├── favicon.ico │ ├── img │ │ ├── cool.gif │ │ ├── react-black.png │ │ ├── react-white.png │ │ └── wow.png │ └── js │ │ └── bundle.js ├── server.js ├── src │ ├── actions │ │ └── ImageActions.js │ ├── alt.js │ ├── components │ │ ├── AltIsomorphicElement.js │ │ └── controller-views │ │ │ └── Images.js │ └── stores │ │ └── ImageStore.js └── templates │ └── layout.jade ├── locations ├── .gitignore ├── README.md ├── client.js ├── package.json ├── public │ └── js │ │ └── bundle.js ├── server.js ├── src │ ├── actions │ │ └── LocationActions.js │ ├── alt.js │ ├── api │ │ └── Locations.js │ ├── components │ │ ├── AltIsomorphicElement.js │ │ ├── Favorites.js │ │ └── Locations.js │ └── stores │ │ ├── FavoritesStore.js │ │ └── LocationStore.js └── templates │ └── layout.jade ├── product-search ├── README.md ├── client.js ├── package.json ├── public │ ├── css │ │ └── styles.css │ ├── favicon.ico │ └── js │ │ └── bundle.js ├── server.js ├── src │ ├── actions │ │ └── ProductActions.js │ ├── alt.js │ ├── components │ │ ├── AltIsomorphicElement.js │ │ ├── SearchBar.js │ │ └── controller-views │ │ │ └── ProductList.js │ └── stores │ │ └── ProductStore.js └── templates │ └── layout.jade ├── react-router ├── react-router-jsx │ ├── .gitignore │ ├── README.md │ ├── client.js │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── js │ │ │ └── bundle.js │ ├── server.js │ ├── src │ │ ├── alt.js │ │ ├── components │ │ │ ├── App.jsx │ │ │ ├── Hello.jsx │ │ │ └── Time.jsx │ │ ├── routes.jsx │ │ └── stores │ │ │ ├── HelloStore.js │ │ │ └── TimeStore.js │ └── templates │ │ └── layout.jade ├── react-router-movies │ ├── .gitignore │ ├── README.md │ ├── app.js │ ├── client.js │ ├── package.json │ ├── public │ │ ├── css │ │ │ ├── pure-min.css │ │ │ └── styles.css │ │ ├── favicon.ico │ │ ├── img │ │ │ ├── airborne.jpg │ │ │ ├── billteds.jpg │ │ │ ├── gleamingthecube.jpg │ │ │ ├── karatekid.jpg │ │ │ └── kungfury.jpg │ │ └── js │ │ │ └── bundle.js │ ├── server.js │ ├── src │ │ ├── actions │ │ │ └── MovieActions.js │ │ ├── alt.js │ │ ├── api │ │ │ └── movies.js │ │ ├── components │ │ │ ├── MovieLinks.js │ │ │ ├── MovieRow.js │ │ │ └── controller-views │ │ │ │ ├── App.js │ │ │ │ ├── Movie.js │ │ │ │ └── Movies.js │ │ ├── routes │ │ │ ├── routes.express.js │ │ │ └── routes.react.js │ │ └── stores │ │ │ └── MovieStore.js │ └── templates │ │ └── layout.jade ├── react-router-pets │ ├── .gitignore │ ├── README.md │ ├── client.js │ ├── package.json │ ├── public │ │ ├── css │ │ │ ├── pure-min.css │ │ │ └── styles.css │ │ ├── favicon.ico │ │ ├── img │ │ │ ├── bennie.png │ │ │ ├── chaplin.png │ │ │ ├── darkdog.png │ │ │ └── winston.png │ │ └── js │ │ │ └── bundle.js │ ├── server.js │ ├── src │ │ ├── actions │ │ │ └── PetActions.js │ │ ├── alt.js │ │ ├── api │ │ │ └── pets.js │ │ ├── components │ │ │ ├── App.js │ │ │ ├── PetLinks.js │ │ │ ├── PetList.js │ │ │ ├── PetRow.js │ │ │ └── PetView.js │ │ ├── routes.js │ │ └── stores │ │ │ └── PetStore.js │ └── templates │ │ └── layout.jade └── react-router-time │ ├── .gitignore │ ├── README.md │ ├── client.js │ ├── package.json │ ├── public │ ├── css │ │ ├── pure-min.css │ │ └── styles.css │ ├── favicon.ico │ └── js │ │ └── bundle.js │ ├── server.js │ ├── src │ ├── actions │ │ └── HelloActions.js │ ├── alt.js │ ├── components │ │ ├── App.js │ │ ├── Hello.js │ │ └── Time.js │ ├── routes.js │ └── stores │ │ ├── HelloStore.js │ │ └── TimeStore.js │ └── templates │ └── layout.jade └── todos ├── README.md ├── client.js ├── package.json ├── public └── js │ └── bundle.js ├── server.js ├── src ├── actions │ └── TodoActions.js ├── alt.js ├── components │ ├── AltIsomorphicElement.js │ ├── controller-views │ │ └── TodoList.js │ └── todo-item.js └── stores │ └── TodoStore.js └── templates └── layout.jade /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | pids 4 | *.pid 5 | *.seed 6 | lib-cov 7 | coverage 8 | .lock-wscript 9 | build/Release 10 | node_modules 11 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # isomorphic-react-examples 2 | Many examples of rendering React server-side 3 | -------------------------------------------------------------------------------- /datetime/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /datetime/README.md: -------------------------------------------------------------------------------- 1 | # datetime-flux-iso 2 | 3 | > Isomorphic react application using flux. 4 | 5 | ## Running This 6 | 7 | ```sh 8 | npm install; npm run build; npm start 9 | ``` 10 | 11 | Then open your browser to `localhost:8080` and enjoy. 12 | 13 | ## What 14 | 15 | This is a simple ismorphic application which renders the current time and a random number sent from the server. 16 | 17 | The purpose of this application is to show how an isomorphic react application using flux works with [iso](https://github.com/goatslacker/iso). 18 | 19 | One of the challenges with using flux isomorphically is how flux is structured. In flux, since the data only flows one way, all data changes start via the action triggers. This presents an issue on the server since actions are meant to be fire-and-forget and you can only dispatch one at a time. What this means is that an action has no callback and it's difficult to set off a chain of actions and know when they all completed. 20 | 21 | Store's themselves have listeners and you could theoretically use a store's listener along with `waitFor` to get close, but the React components usually rely on these store listeners in order to set their internal state which kicks off DOM diffing, thus making them unsuitable for usage on the server side. 22 | 23 | Another challenge is that flux stores are singletons. Pairing singleton data stores with concurrent requests is a recipe for disaster. One way of solving this dilemma is to create instances of these stores, but then the trade-off is that you're passing these instances around each component so they have a reference to the data and can use the appropriate store. This is both fragile and cumbersome. 24 | 25 | Fortunately, flux's stores work very well when they are synchronous. This means we can seed the stores with data, render our application, and then revert the stores to their previous virgin state. [Alt](https://github.com/goatslacker/alt) is a flux implementation that facilitates this. 26 | 27 | alt uses a method called `bootstrap` which seeds the stores with data on the server, and then initializes them when the application starts on the client. Turning `TimeComponent` into something that looks a lot like plain flux. 28 | 29 | ```js 30 | // yay, references and plain old require! 31 | var TimeStore = require('../stores/TimeStore') 32 | 33 | class TimeComponent extends React.Component { 34 | constructor() { 35 | this.state = TimeStore.getState() 36 | } 37 | 38 | render() { 39 | return
{this.state.time}
40 | } 41 | } 42 | ``` 43 | 44 | Actions then are meant to only be used on the client-side once the application starts. On the server you can perform all the necessary data gathering, and once complete you seed the data. 45 | 46 | In this example, the random number sent from the server is in order to test flux's singleton stores. Two concurrent requests won't interfere with each other and the store's data will never collide. The time component allows you to click in order to change the time via React's onClick, this proves that the application was initialized correctly on the client. 47 | 48 | ## License 49 | 50 | [MIT](http://josh.mit-license.org/) 51 | -------------------------------------------------------------------------------- /datetime/client.js: -------------------------------------------------------------------------------- 1 | import Iso from 'iso' 2 | import React from 'react' 3 | import AltIsomorphicElement from './src/components/AltIsomorphicElement' 4 | 5 | Iso.on('react', true, function (props, _, node) { 6 | React.render(React.createElement(AltIsomorphicElement, props), node) 7 | }) 8 | -------------------------------------------------------------------------------- /datetime/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datetime", 3 | "version": "1.0.0", 4 | "description": "An isomorphic date/time application example using alt and iso", 5 | "main": ".", 6 | "dependencies": { 7 | "express": "^4.11.2", 8 | "jade": "^1.9.2", 9 | "alt": "^0.16.5", 10 | "iso": "^4.1.0", 11 | "react": "^0.13.3" 12 | }, 13 | "devDependencies": { 14 | "babel": "^4.0.1", 15 | "babelify": "^5.0.3", 16 | "browserify": "^8.0.3" 17 | }, 18 | "scripts": { 19 | "test": "echo \"Error: no test specified\" && exit 1", 20 | "build": "browserify -t [babelify] client.js > public/js/bundle.js", 21 | "start": "babel-node server.js" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/goatslacker/isomorphic-react-examples" 26 | }, 27 | "keywords": [ 28 | "isomorphic", 29 | "javascript", 30 | "iso", 31 | "flux", 32 | "alt", 33 | "react" 34 | ], 35 | "author": "Josh Perez ", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/goatslacker/isomorphic-react-examples/issues" 39 | }, 40 | "homepage": "https://github.com/goatslacker/isomorphic-react-examples" 41 | } 42 | -------------------------------------------------------------------------------- /datetime/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import React from 'react' 3 | import AltIsomorphicElement from './src/components/AltIsomorphicElement' 4 | import Iso from 'iso' 5 | 6 | /* express boilerplate */ 7 | let app = express() 8 | let port = 8080; 9 | let ip = '127.0.0.1'; 10 | let path = require('path') 11 | app.set('view engine', 'jade') 12 | app.set('views', path.join(__dirname, 'templates')) 13 | app.use('/public', express.static(path.join(__dirname, 'public'))) 14 | 15 | 16 | /* Simulate an asynchronous event to retrieve the time from storage. */ 17 | function getTimeFromServer(cb) { 18 | setTimeout(function () { 19 | cb(Date.now()) 20 | }, 300) 21 | } 22 | 23 | /* 24 | Our simple route, we retrieve the time from our asynchronous system 25 | seed the stores with data and render the html using iso and jade. 26 | */ 27 | app.get('/', function (req, res) { 28 | getTimeFromServer(function (time) { 29 | let rand = Math.random() 30 | 31 | let data = { 32 | TimeStore: { 33 | time: time, 34 | asyncValue: rand 35 | } 36 | } 37 | 38 | let node = React.createElement(AltIsomorphicElement, { altStores: data }) 39 | let html = Iso.render(React.renderToString(node), { altStores: data }, { react: true }) 40 | res.render('layout', { html: html }) 41 | 42 | }) 43 | }) 44 | 45 | /* logging to the server */ 46 | app.listen(port,ip, function() { 47 | console.log("Go to " + ip + ":" + port); 48 | }); 49 | -------------------------------------------------------------------------------- /datetime/src/actions/TimeActions.js: -------------------------------------------------------------------------------- 1 | var alt = require('../alt') 2 | 3 | class TimeActions { 4 | constructor() { 5 | this.generateActions('updateTime', 'setAsync') 6 | } 7 | 8 | updateTime(n) { 9 | setTimeout(function () { 10 | this.dispatch(n) 11 | }.bind(this), 500) 12 | } 13 | } 14 | 15 | module.exports = alt.createActions(TimeActions) 16 | -------------------------------------------------------------------------------- /datetime/src/alt.js: -------------------------------------------------------------------------------- 1 | import Alt from 'alt' 2 | module.exports = new Alt() -------------------------------------------------------------------------------- /datetime/src/components/AltIsomorphicElement.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import alt from '../alt' 3 | import IsomorphicMixin from 'alt/mixins/IsomorphicMixin' 4 | import DateTimeComponent from './DateTimeComponent' 5 | 6 | module.exports = React.createClass({ 7 | mixins: [IsomorphicMixin.create(alt)], 8 | 9 | render: function () { 10 | return React.createElement( 11 | 'div', 12 | null, 13 | React.createElement(DateTimeComponent) 14 | ) 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /datetime/src/components/DateTimeComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TimeActions from '../actions/TimeActions' 3 | import TimeStore from '../stores/TimeStore' 4 | 5 | class DateTimeComponent extends React.Component { 6 | 7 | constructor() { 8 | this.state = TimeStore.getState() 9 | } 10 | 11 | componentDidMount() { 12 | TimeStore.listen(() => this.setState(TimeStore.getState())) 13 | } 14 | 15 | componentWillUnmount() { 16 | TimeStore.unlisten(() => this.setState(TimeStore.getState())) 17 | } 18 | 19 | updateTime() { 20 | TimeActions.updateTime(Date.now()) 21 | } 22 | 23 | render() { 24 | return ( 25 |
26 | Unique id from the server: 27 | { `${this.state.asyncValue}` } 28 |
29 | 30 | { `${this.state.time}` } 31 |
32 | ) 33 | } 34 | } 35 | 36 | module.exports = DateTimeComponent 37 | -------------------------------------------------------------------------------- /datetime/src/stores/TimeStore.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | import TimeActions from '../actions/TimeActions' 3 | 4 | class TimeStore { 5 | 6 | constructor() { 7 | this.bindActions(TimeActions) 8 | this.time = 0 9 | this.asyncValue = undefined 10 | } 11 | 12 | onUpdateTime(time) { 13 | this.time = time 14 | } 15 | 16 | onSetAsync(n) { 17 | this.asyncValue = n 18 | } 19 | } 20 | 21 | module.exports = alt.createStore(TimeStore, 'TimeStore') 22 | -------------------------------------------------------------------------------- /datetime/templates/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | body 4 | #app!= html 5 | script(src="/public/js/bundle.js") 6 | -------------------------------------------------------------------------------- /image-swipe/README.md: -------------------------------------------------------------------------------- 1 | # product-search-iso 2 | 3 | > Isomorphic react application using flux. 4 | 5 | ## Running This 6 | 7 | ```sh 8 | npm install; npm run build; npm start 9 | ``` 10 | 11 | Then open your browser to `localhost:8080` and enjoy. 12 | 13 | ## What 14 | 15 | The purpose of this application is to show how an isomorphic react application using flux works with [iso](https://github.com/goatslacker/iso). 16 | 17 | For this guide we’ll be creating an isomorphic image swipe app, using react-swipr. Let’s get started. 18 | 19 | ## License 20 | 21 | [MIT](http://josh.mit-license.org/) 22 | -------------------------------------------------------------------------------- /image-swipe/client.js: -------------------------------------------------------------------------------- 1 | let Iso = require('iso') 2 | let React = require('react') 3 | let AltIsomorphicElement = require('./src/components/AltIsomorphicElement') 4 | 5 | Iso.on('react', true, function (props, _, node) { 6 | React.render(React.createElement(AltIsomorphicElement, props), node) 7 | }) 8 | -------------------------------------------------------------------------------- /image-swipe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "image-swipe", 3 | "description": "An isomorphic image swipe example using alt and iso", 4 | "main": ".", 5 | "dependencies": { 6 | "express": "^4.11.2", 7 | "serve-favicon": "^2.2.0", 8 | "jade": "^1.9.2", 9 | "alt": "^0.16.5", 10 | "iso": "^4.1.0", 11 | "react": "^0.13.3", 12 | "react-swipr": "^1.0.10" 13 | }, 14 | "devDependencies": { 15 | "babel": "^4.0.1", 16 | "babelify": "^5.0.3", 17 | "browserify": "^8.0.3" 18 | }, 19 | "scripts": { 20 | "build": "browserify -t [babelify] client.js > public/js/bundle.js", 21 | "start": "babel-node server.js" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/steveniseki/react-flux-alt-iso" 26 | }, 27 | "keywords": [ 28 | "isomorphic", 29 | "javascript", 30 | "iso", 31 | "flux", 32 | "alt", 33 | "react" 34 | ], 35 | "author": "Steven Iseki ", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/goatslacker/isomorphic-react-examples/issues" 39 | }, 40 | "homepage": "https://github.com/goatslacker/isomorphic-react-examples" 41 | } 42 | -------------------------------------------------------------------------------- /image-swipe/public/css/components/images.css: -------------------------------------------------------------------------------- 1 | #images button { 2 | width: 100%; 3 | height: 2em; 4 | } 5 | 6 | #images .swipr_example { 7 | position: relative; 8 | width: 320px; 9 | margin: 0 auto 40px; 10 | } 11 | 12 | img { 13 | user-drag: none; 14 | -moz-user-select: none; 15 | -webkit-user-drag: none; 16 | } -------------------------------------------------------------------------------- /image-swipe/public/css/components/swipr.css: -------------------------------------------------------------------------------- 1 | /** 2 | * swipr styles 3 | */ 4 | 5 | .swipr { 6 | position: relative; 7 | width: 270px; 8 | margin: 0 auto; 9 | font-size: 0; 10 | line-height: 0; 11 | overflow: hidden; 12 | white-space: nowrap; 13 | -webkit-user-select: none; 14 | -moz-user-select: none; 15 | -o-user-select: none; 16 | user-select: none; 17 | } 18 | 19 | .swipr li { 20 | width: 270px; 21 | margin-right: 10px; 22 | position: relative; 23 | display: inline-block; 24 | font-family: sans-serif; 25 | padding-top: 75px; 26 | text-align: center; 27 | font-size: 14px; 28 | line-height: 30px; 29 | background: #fff; 30 | color: #fff; 31 | overflow: hidden; 32 | padding-bottom: 75px; 33 | } 34 | 35 | .swipr_slides { 36 | display: inline-block; 37 | } 38 | 39 | @media screen and (min-width: 640px ) { 40 | 41 | .swipr_example { 42 | width: 640px; 43 | margin: 0 auto 20px; 44 | } 45 | 46 | .swipr { 47 | width: 540px; 48 | } 49 | 50 | .swipr li { 51 | width: 580px; 52 | } 53 | 54 | } 55 | 56 | @media screen and (min-width: 980px ) { 57 | 58 | .swipr_example { 59 | width: 980px; 60 | margin: 0 auto 20px; 61 | } 62 | 63 | .swipr { 64 | width: 880px; 65 | } 66 | 67 | .swipr li { 68 | width: 880px; 69 | } 70 | } -------------------------------------------------------------------------------- /image-swipe/public/css/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin:0px; 3 | padding:0px; 4 | border:0px; 5 | } 6 | 7 | *, 8 | *:before, 9 | *:after { 10 | -moz-box-sizing:border-box; 11 | -webkit-box-sizing:border-box; 12 | box-sizing:border-box; 13 | } 14 | -------------------------------------------------------------------------------- /image-swipe/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/image-swipe/public/favicon.ico -------------------------------------------------------------------------------- /image-swipe/public/img/cool.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/image-swipe/public/img/cool.gif -------------------------------------------------------------------------------- /image-swipe/public/img/react-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/image-swipe/public/img/react-black.png -------------------------------------------------------------------------------- /image-swipe/public/img/react-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/image-swipe/public/img/react-white.png -------------------------------------------------------------------------------- /image-swipe/public/img/wow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/image-swipe/public/img/wow.png -------------------------------------------------------------------------------- /image-swipe/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import React from 'react' 3 | import AltIsomorphicElement from './src/components/AltIsomorphicElement' 4 | import Iso from 'iso' 5 | import favicon from 'serve-favicon' 6 | import path from 'path' 7 | 8 | let port = 8080 9 | let ip = '127.0.0.1' 10 | let app = express() 11 | 12 | app.use('/public', express.static(path.join(__dirname, 'public'))) 13 | app.use(favicon(__dirname + '/public/favicon.ico')); 14 | 15 | app.set('views', path.join(__dirname, 'templates')); 16 | app.set('view engine', 'jade') 17 | 18 | 19 | // Simulate an asynchronous event to get the initial images 20 | function getImagesFromServer(cb) { 21 | setTimeout(function () { 22 | let images = ['/public/img/react-black.png', '/public/img/react-white.png'] 23 | cb(images) 24 | }, 500) 25 | } 26 | 27 | 28 | /* just a simple route which displays the product search app */ 29 | app.get('/', function (req, res) { 30 | 31 | 32 | getImagesFromServer(function (images) { 33 | 34 | var data = { 35 | ImageStore: { 36 | images: images 37 | } 38 | } 39 | 40 | let node = React.createElement(AltIsomorphicElement, { altStores: data }) 41 | let html = Iso.render(React.renderToString(node), { altStores: data }, { react: true }) 42 | res.render('layout', { html: html }) 43 | }); 44 | 45 | }) 46 | 47 | 48 | /* logging to the server */ 49 | app.listen(port,ip, function() { 50 | console.log("Go to " + ip + ":" + port) 51 | }); 52 | -------------------------------------------------------------------------------- /image-swipe/src/actions/ImageActions.js: -------------------------------------------------------------------------------- 1 | var alt = require('../alt') 2 | 3 | class ImageActions { 4 | 5 | constructor() { 6 | this.generateActions('updateImages') 7 | } 8 | 9 | updateImages() { 10 | this.dispatch(images); 11 | } 12 | 13 | } 14 | 15 | module.exports = alt.createActions(ImageActions) 16 | -------------------------------------------------------------------------------- /image-swipe/src/alt.js: -------------------------------------------------------------------------------- 1 | let Alt = require('alt') 2 | module.exports = new Alt() -------------------------------------------------------------------------------- /image-swipe/src/components/AltIsomorphicElement.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import alt from '../alt' 3 | import IsomorphicMixin from 'alt/mixins/IsomorphicMixin' 4 | import AltContainer from 'alt/AltContainer' 5 | 6 | import Images from './controller-views/Images' 7 | import ImageStore from '../stores/ImageStore' 8 | 9 | module.exports = React.createClass({ 10 | mixins: [IsomorphicMixin.create(alt)], 11 | 12 | render: function () { 13 | 14 | return ( 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | }) 22 | -------------------------------------------------------------------------------- /image-swipe/src/components/controller-views/Images.js: -------------------------------------------------------------------------------- 1 | let React = require('react') 2 | let ImageActions = require('../../actions/ImageActions') 3 | let ImageStore = require('../../stores/ImageStore') 4 | 5 | // just a simple swiping component 6 | let Swipr = require('react-swipr') 7 | 8 | class Images extends React.Component { 9 | 10 | constructor() { 11 | this.state = ImageStore.getState() 12 | } 13 | 14 | componentDidMount() { 15 | ImageStore.listen(() => this.setState(ImageStore.getState())) 16 | } 17 | 18 | componentWillUnmount() { 19 | ImageStore.unlisten(() => this.setState(ImageStore.getState())) 20 | } 21 | 22 | updateImages() { 23 | ImageActions.updateImages() 24 | } 25 | 26 | render() { 27 | 28 | let rows = null; 29 | 30 | if(this.state.images !== undefined){ 31 | 32 | rows = this.state.images.map(function(image, i) { 33 | return ( 34 |
  • 35 | 36 |
  • 37 | ); 38 | }) 39 | } 40 | 41 | return ( 42 |
    43 | 44 | 45 | {rows} 46 | 47 |
    48 | ) 49 | } 50 | } 51 | 52 | module.exports = Images 53 | -------------------------------------------------------------------------------- /image-swipe/src/stores/ImageStore.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | import ImageActions from '../actions/ImageActions' 3 | 4 | class ImageStore { 5 | 6 | constructor() { 7 | this.images = [] 8 | this.imageType = 'react' 9 | 10 | this.bindActions(ImageActions) 11 | } 12 | 13 | onUpdateImages(images) { 14 | if(this.imageType === 'react'){ 15 | this.setSparklyImages() 16 | } else { 17 | this.setReactImages() 18 | } 19 | 20 | } 21 | 22 | setSparklyImages() { 23 | this.images = ['/public/img/cool.gif', '/public/img/wow.png'] 24 | this.imageType = 'sparkly' 25 | } 26 | 27 | setReactImages() { 28 | this.images = ['/public/img/react-black.png', '/public/img/react-white.png'] 29 | this.imageType = 'react' 30 | } 31 | 32 | } 33 | 34 | module.exports = alt.createStore(ImageStore, 'ImageStore') -------------------------------------------------------------------------------- /image-swipe/templates/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | link(rel='stylesheet', href='/public/css/main.css') 5 | link(rel='stylesheet', href='/public/css/components/images.css') 6 | link(rel='stylesheet', href='/public/css/components/swipr.css') 7 | 8 | body 9 | #app!= html 10 | 11 | script(src="/public/js/bundle.js") -------------------------------------------------------------------------------- /locations/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /locations/README.md: -------------------------------------------------------------------------------- 1 | # datetime-flux-iso 2 | 3 | > Isomorphic react application using flux. 4 | 5 | ## Running This 6 | 7 | ```sh 8 | npm install; npm run build; npm start 9 | ``` 10 | 11 | Then open your browser to `localhost:8080` and enjoy. 12 | 13 | ## What 14 | 15 | The purpose of this application is to show how an isomorphic react application using flux works with [iso](https://github.com/goatslacker/iso). 16 | 17 | For this guide we’ll be creating a very simple application which has a list of travel destinations and allows you to favorite which ones you’re interested in. Let’s get started. 18 | 19 | ## License 20 | 21 | [MIT](http://josh.mit-license.org/) 22 | -------------------------------------------------------------------------------- /locations/client.js: -------------------------------------------------------------------------------- 1 | let Iso = require('iso') 2 | let React = require('react') 3 | let AltIsomorphicElement = require('./src/components/AltIsomorphicElement') 4 | 5 | Iso.on('react', true, function (props, _, node) { 6 | React.render(React.createElement(AltIsomorphicElement, props), node) 7 | }) 8 | -------------------------------------------------------------------------------- /locations/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "locations", 3 | "version": "1.0.0", 4 | "description": "An isomorphic locations application example using alt and iso", 5 | "main": ".", 6 | "dependencies": { 7 | "alt": "^0.16.5", 8 | "iso": "^4.1.0", 9 | "express": "^4.11.2", 10 | "jade": "^1.9.2", 11 | "react": "^0.13.3" 12 | }, 13 | "devDependencies": { 14 | "babel": "^4.0.1", 15 | "babelify": "^5.0.3", 16 | "browserify": "^8.0.3" 17 | }, 18 | "scripts": { 19 | "test": "echo \"Error: no test specified\" && exit 1", 20 | "build": "browserify -t [babelify] client.js > public/js/bundle.js", 21 | "start": "babel-node server.js" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/goatslacker/isomorphic-react-examples" 26 | }, 27 | "keywords": [ 28 | "isomorphic", 29 | "javascript", 30 | "iso", 31 | "flux", 32 | "alt", 33 | "react" 34 | ], 35 | "author": "Josh Perez ", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/goatslacker/isomorphic-react-examples/issues" 39 | }, 40 | "homepage": "https://github.com/goatslacker/isomorphic-react-examples" 41 | } 42 | -------------------------------------------------------------------------------- /locations/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import React from 'react' 3 | import AltIsomorphicElement from './src/components/AltIsomorphicElement' 4 | import Iso from 'iso' 5 | import LocationApi from './src/api/Locations' 6 | 7 | /* express boilerplate */ 8 | let app = express() 9 | let port = 8080; 10 | let ip = '127.0.0.1'; 11 | let path = require('path') 12 | app.set('view engine', 'jade') 13 | app.set('views', path.join(__dirname, 'templates')) 14 | app.use('/public', express.static(path.join(__dirname, 'public'))) 15 | 16 | 17 | /* just the one simple route which for now displays our locations */ 18 | app.get('/', function (req, res) { 19 | 20 | /* simulate a call to an API to return location data */ 21 | LocationApi.fetch() 22 | .then((locations) => { 23 | console.log(locations) 24 | let data = { LocationStore: {locations: locations} } 25 | let node = React.createElement(AltIsomorphicElement, { altStores: data }) 26 | let html = Iso.render(React.renderToString(node), { altStores: data }, { react: true }) 27 | res.render('layout', { html: html }) 28 | 29 | }) 30 | 31 | }) 32 | 33 | /* logging to the server */ 34 | app.listen(port,ip, function() { 35 | console.log("Go to " + ip + ":" + port); 36 | }); 37 | -------------------------------------------------------------------------------- /locations/src/actions/LocationActions.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | 3 | class LocationActions { 4 | 5 | updateLocations(locations) { 6 | this.dispatch(locations); 7 | } 8 | 9 | fetchLocations() { 10 | this.dispatch(); 11 | } 12 | 13 | locationsFailed(errorMessage) { 14 | this.dispatch(errorMessage); 15 | } 16 | 17 | favoriteLocation(location) { 18 | /* simulating a call to ajax method here in an action */ 19 | setTimeout(function () { 20 | this.dispatch(location); 21 | console.log('you set a new favorite location') 22 | }.bind(this), 500) 23 | } 24 | } 25 | 26 | module.exports = alt.createActions(LocationActions); 27 | -------------------------------------------------------------------------------- /locations/src/alt.js: -------------------------------------------------------------------------------- 1 | let Alt = require('alt') 2 | module.exports = new Alt() -------------------------------------------------------------------------------- /locations/src/api/Locations.js: -------------------------------------------------------------------------------- 1 | let locationData = [ 2 | { id: 0, name: 'Abu Dhabi' }, 3 | { id: 1, name: 'Berlin' }, 4 | { id: 3, name: 'Buenos Aires' }, 5 | { id: 4, name: 'Cairo' }, 6 | { id: 6, name: 'Lima' }, 7 | { id: 7, name: 'London' }, 8 | { id: 8, name: 'Miami' }, 9 | { id: 11, name: 'Paris' }, 10 | { id: 12, name: 'San Francisco' } 11 | ]; 12 | 13 | let LocationFetcher = { 14 | fetch: function(){ 15 | return new Promise(function (resolve, reject) { 16 | 17 | console.log('returning promise') 18 | if (true) { 19 | resolve(locationData) 20 | } else { 21 | reject({ status: 'error: things have broken' }) 22 | } 23 | 24 | /* 25 | setTimeout(function () { 26 | }, 500); 27 | */ 28 | 29 | }); 30 | } 31 | }; 32 | 33 | 34 | module.exports = LocationFetcher; 35 | -------------------------------------------------------------------------------- /locations/src/components/AltIsomorphicElement.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import alt from '../alt' 3 | import AltContainer from 'alt/AltContainer' 4 | import IsomorphicMixin from 'alt/mixins/IsomorphicMixin' 5 | 6 | import Locations from './Locations' 7 | import LocationStore from '../stores/LocationStore' 8 | import Favorites from './Favorites' 9 | import FavoritesStore from '../stores/FavoritesStore' 10 | 11 | 12 | module.exports = React.createClass({ 13 | mixins: [IsomorphicMixin.create(alt)], 14 | 15 | render: function () { 16 | 17 | return ( 18 |
    19 |

    Locations

    20 | 21 | 22 | 23 | 24 |

    Favorites

    25 | 26 | 27 | 28 |
    29 | ); 30 | } 31 | 32 | }) 33 | -------------------------------------------------------------------------------- /locations/src/components/Favorites.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | let Favorites = React.createClass({ 4 | render() { 5 | return ( 6 |
      7 | {this.props.locations.map((location, i) => { 8 | return ( 9 |
    • {location.name}
    • 10 | ); 11 | })} 12 |
    13 | ); 14 | } 15 | }); 16 | 17 | 18 | module.exports = Favorites; -------------------------------------------------------------------------------- /locations/src/components/Locations.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AltContainer from 'alt/AltContainer' 3 | import LocationStore from '../stores/LocationStore' 4 | import LocationActions from '../actions/LocationActions' 5 | 6 | let Locations = React.createClass({ 7 | 8 | addFave(e) { 9 | let location = LocationStore.getLocation( Number(e.target.getAttribute('data-id'))); 10 | LocationActions.favoriteLocation(location); 11 | }, 12 | 13 | render() { 14 | 15 | return ( 16 |
      17 | {this.props.locations.map((location, i) => { 18 | var faveButton = ( 19 | 22 | ); 23 | 24 | return ( 25 |
    • 26 | {location.name} {location.has_favorite ? '<3' : faveButton} 27 |
    • 28 | ); 29 | })} 30 |
    31 | ); 32 | } 33 | }); 34 | 35 | module.exports = Locations; 36 | -------------------------------------------------------------------------------- /locations/src/stores/FavoritesStore.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | import LocationActions from '../actions/LocationActions' 3 | 4 | class FavoritesStore { 5 | 6 | constructor() { 7 | this.locations = []; 8 | 9 | this.bindListeners({ 10 | addFavoriteLocation: LocationActions.FAVORITE_LOCATION 11 | }); 12 | } 13 | 14 | addFavoriteLocation(location) { 15 | this.locations.push(location); 16 | } 17 | } 18 | 19 | module.exports = alt.createStore(FavoritesStore, 'FavoritesStore'); 20 | -------------------------------------------------------------------------------- /locations/src/stores/LocationStore.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | import LocationActions from '../actions/LocationActions' 3 | import FavoritesStore from './FavoritesStore' 4 | 5 | class LocationStore { 6 | 7 | constructor() { 8 | this.locations = []; 9 | 10 | this.bindListeners({ 11 | handleUpdateLocations: LocationActions.UPDATE_LOCATIONS, 12 | handleFetchLocations: LocationActions.FETCH_LOCATIONS, 13 | setFavorites: LocationActions.FAVORITE_LOCATION 14 | }); 15 | 16 | this.exportPublicMethods({ 17 | getLocation: this.getLocation 18 | }); 19 | } 20 | 21 | handleUpdateLocations(locations) { 22 | this.locations = locations 23 | } 24 | 25 | handleFetchLocations() { 26 | this.locations = [] 27 | } 28 | 29 | resetAllFavorites() { 30 | this.locations = this.locations.map((location) => { 31 | return { 32 | id: location.id, 33 | name: location.name, 34 | has_favorite: false 35 | }; 36 | }); 37 | } 38 | 39 | setFavorites(location) { 40 | this.waitFor(FavoritesStore) 41 | let favoritedLocations = FavoritesStore.getState().locations 42 | this.resetAllFavorites() 43 | 44 | favoritedLocations.forEach((location) => { 45 | // find each location in the array and set favorite to true 46 | for (let i = 0; i < this.locations.length; i += 1) { 47 | if (this.locations[i].id === location.id) { 48 | this.locations[i].has_favorite = true 49 | break 50 | } 51 | } 52 | }); 53 | } 54 | 55 | getLocation(id) { 56 | let { locations } = this.getState(); 57 | for (let i = 0; i < locations.length; i += 1) { 58 | if (locations[i].id === id) { 59 | return locations[i] 60 | } 61 | } 62 | return null; 63 | } 64 | } 65 | 66 | module.exports = alt.createStore(LocationStore, 'LocationStore'); -------------------------------------------------------------------------------- /locations/templates/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | body 4 | #app!= html 5 | script(src="/public/js/bundle.js") 6 | -------------------------------------------------------------------------------- /product-search/README.md: -------------------------------------------------------------------------------- 1 | # product-search-iso 2 | 3 | > Isomorphic react application using flux. 4 | 5 | ## Running This 6 | 7 | ```sh 8 | npm install; npm run build; npm start 9 | ``` 10 | 11 | Then open your browser to `localhost:8080` and enjoy. 12 | 13 | ## What 14 | 15 | The purpose of this application is to show how an isomorphic react application using flux works with [iso](https://github.com/goatslacker/iso). 16 | 17 | For this guide we’ll be creating an isomorphic product search app, using react-table-view and a simple search bar. Let’s get started. 18 | 19 | ## License 20 | 21 | [MIT](http://josh.mit-license.org/) 22 | -------------------------------------------------------------------------------- /product-search/client.js: -------------------------------------------------------------------------------- 1 | let Iso = require('iso') 2 | let React = require('react') 3 | let AltIsomorphicElement = require('./src/components/AltIsomorphicElement') 4 | 5 | Iso.on('react', true, function (props, _, node) { 6 | React.render(React.createElement(AltIsomorphicElement, props), node) 7 | }) 8 | -------------------------------------------------------------------------------- /product-search/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "product-search", 3 | "description": "An isomorphic product search example using alt and iso", 4 | "main": ".", 5 | "dependencies": { 6 | "express": "^4.11.2", 7 | "body-parser": "^1.12.2", 8 | "cookie-parser": "^1.3.4", 9 | "serve-favicon": "^2.2.0", 10 | "jade": "^1.9.2", 11 | "alt": "^0.16.5", 12 | "iso": "^4.1.0", 13 | "react": "^0.13.3", 14 | "react-search": "^0.0.10", 15 | "react-table-view": "^0.0.9" 16 | }, 17 | "devDependencies": { 18 | "babel": "^4.0.1", 19 | "babelify": "^5.0.3", 20 | "browserify": "^8.0.3" 21 | }, 22 | "scripts": { 23 | "test": "echo \"Error: no test specified\" && exit 1", 24 | "build": "browserify --debug -t [babelify] client.js > public/js/bundle.js", 25 | "start": "babel-node server.js" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/steveniseki/react-flux-alt-iso" 30 | }, 31 | "keywords": [ 32 | "isomorphic", 33 | "javascript", 34 | "iso", 35 | "flux", 36 | "alt", 37 | "react" 38 | ], 39 | "author": "Steven Iseki ", 40 | "license": "MIT", 41 | "bugs": { 42 | "url": "https://github.com/goatslacker/isomorphic-react-examples/issues" 43 | }, 44 | "homepage": "https://github.com/goatslacker/isomorphic-react-examples" 45 | } 46 | -------------------------------------------------------------------------------- /product-search/public/css/styles.css: -------------------------------------------------------------------------------- 1 | /* search */ 2 | 3 | .react-search { 4 | font-weight: 200; 5 | line-height: 1.5; 6 | } 7 | 8 | .react-search .input-text { 9 | color: #666; 10 | width: 100%; 11 | padding: 1em; 12 | } 13 | 14 | .menu { 15 | display: block; 16 | line-height: 1.5em; 17 | text-decoration: none; 18 | white-space: nowrap; 19 | padding: 1em; 20 | } 21 | 22 | .menu ul { 23 | list-style-type: none; 24 | padding: 0em; 25 | margin: 0em; 26 | } 27 | 28 | .menu a:hover, .menu a:visited { 29 | font-weight: bold; 30 | } 31 | 32 | .menu-open { 33 | visibility: visible; 34 | z-index: 2; 35 | background: #fff; 36 | border: 1px solid #b7b7b7; 37 | } 38 | 39 | .menu-hidden { 40 | visibility: hidden; 41 | } 42 | 43 | 44 | /* table-view */ 45 | 46 | .react-table-view { 47 | margin: 0; 48 | font-size: 150%; 49 | font-weight: 300; 50 | line-height: 1.5; 51 | } 52 | 53 | .react-table-view .sort-up { 54 | display: inline-block; 55 | margin-left: 0.2em; 56 | border-left: 0.2em solid transparent; 57 | border-right: 0.2em solid transparent; 58 | border-bottom: 0.2em solid #ABEAFC; 59 | } 60 | 61 | .react-table-view .sort-down { 62 | display: inline-block; 63 | margin-left: 0.2em; 64 | border-left: 0.2em solid transparent; 65 | border-right: 0.2em solid transparent; 66 | border-top: 0.2em solid #ABEAFC; 67 | } 68 | 69 | .react-table-view table { 70 | border-collapse: collapse; 71 | border: 1px solid #cbcbcb; 72 | color:#666; 73 | text-shadow: 1px 1px 0px #fff; 74 | background: #eaebec; 75 | margin: 0.5em; 76 | } 77 | 78 | .react-table-view table th { 79 | padding: 0.5em; 80 | border-top: 1px solid #fafafa; 81 | border-bottom: 1px solid #e0e0e0; 82 | } 83 | 84 | .react-table-view table tr { 85 | text-align: center; 86 | padding-left: 1em; 87 | } 88 | .react-table-view table td { 89 | padding: 0.2em; 90 | background: #fafafa; 91 | } -------------------------------------------------------------------------------- /product-search/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/product-search/public/favicon.ico -------------------------------------------------------------------------------- /product-search/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import React from 'react' 3 | import AltIsomorphicElement from './src/components/AltIsomorphicElement' 4 | import Iso from 'iso' 5 | import bodyParser from 'body-parser' 6 | import cookieParser from 'cookie-parser' 7 | import favicon from 'serve-favicon' 8 | import path from 'path' 9 | 10 | let port = 8080 11 | let ip = '127.0.0.1' 12 | let app = express() 13 | 14 | app.use(bodyParser.json()); 15 | app.use(bodyParser.urlencoded({ extended: false })); 16 | app.use(cookieParser()); 17 | app.use('/public', express.static(path.join(__dirname, 'public'))) 18 | app.use(favicon(__dirname + '/public/favicon.ico')); 19 | 20 | app.set('views', path.join(__dirname, 'templates')); 21 | app.set('view engine', 'jade') 22 | 23 | 24 | /* Simulate an asynchronous event to retrieve products from storage. */ 25 | function getProductsFromServer(cb) { 26 | setTimeout(function () { 27 | 28 | var mockData = [ 29 | {name: 'Football', category: 'Sporting Goods', price: '$49.99', stocked: true, visible: true}, 30 | {name: 'Basketball', category: 'Sporting Goods', price: '$29.99', stocked: false, visible: true}, 31 | {name: 'iPhone 5', category: 'Electronics', price: '$399.99', stocked: false, visible: true}, 32 | {name: 'Nexus 7', category: 'Electronics', price: '$199.99', stocked: true, visible: true} 33 | ]; 34 | 35 | cb(mockData) 36 | 37 | }, 500) 38 | } 39 | 40 | 41 | /* just a simple route which displays the product search app */ 42 | app.get('/', function (req, res) { 43 | 44 | getProductsFromServer( function (products) { 45 | 46 | var data = { 47 | ProductStore: { 48 | products: products 49 | } 50 | } 51 | 52 | var node = React.createElement(AltIsomorphicElement, { altStores: data }) 53 | var html = Iso.render(React.renderToString(node), { altStores: data }, { react: true }) 54 | res.render('layout', { html: html }) 55 | }); 56 | 57 | }) 58 | 59 | 60 | /* logging to the server */ 61 | app.listen(port,ip, function() { 62 | console.log("Go to " + ip + ":" + port) 63 | }); 64 | -------------------------------------------------------------------------------- /product-search/src/actions/ProductActions.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | 3 | class ProductActions { 4 | 5 | filterProducts(text) { 6 | this.dispatch(text); 7 | } 8 | 9 | } 10 | 11 | module.exports = alt.createActions(ProductActions); -------------------------------------------------------------------------------- /product-search/src/alt.js: -------------------------------------------------------------------------------- 1 | let Alt = require('alt') 2 | module.exports = new Alt() -------------------------------------------------------------------------------- /product-search/src/components/AltIsomorphicElement.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import alt from '../alt' 3 | import IsomorphicMixin from 'alt/mixins/IsomorphicMixin' 4 | import AltContainer from 'alt/AltContainer' 5 | 6 | import ProductList from './controller-views/ProductList' 7 | import ProductStore from '../stores/ProductStore' 8 | 9 | module.exports = React.createClass({ 10 | mixins: [IsomorphicMixin.create(alt)], 11 | 12 | render: function () { 13 | 14 | return ( 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | }) 22 | -------------------------------------------------------------------------------- /product-search/src/components/SearchBar.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | import React from 'react'; 3 | import ProductActions from '../actions/ProductActions' 4 | import Search from 'react-search' 5 | 6 | let SearchBar = React.createClass({ 7 | 8 | propTypes: { 9 | names: React.PropTypes.array 10 | }, 11 | 12 | filter(e) { 13 | ProductActions.filterProducts(e.target.value) 14 | }, 15 | 16 | render() { 17 | 18 | return ( 19 |
    20 | 21 | 22 | ); 23 | } 24 | 25 | }); 26 | 27 | module.exports = SearchBar; -------------------------------------------------------------------------------- /product-search/src/components/controller-views/ProductList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AltContainer from 'alt/AltContainer' 3 | 4 | import ProductStore from '../../stores/ProductStore' 5 | import ProductActions from '../../actions/ProductActions' 6 | 7 | import TableView from 'react-table-view' 8 | import SearchBar from '../SearchBar.js' 9 | 10 | 11 | var ProductList = React.createClass({ 12 | 13 | filterProducts(filterText) { 14 | ProductActions.filterProducts(); 15 | }, 16 | 17 | render() { 18 | 19 | // define the display for each column for react-table-view 20 | let columns = { 21 | name: function(data) { 22 | if(data.visible){ 23 | let style = { 24 | color: data.stocked ? "red" : "black" 25 | }; 26 | return {data.name}; 27 | } 28 | }, 29 | category: function(data) { 30 | if(data.visible){ 31 | return {data.category} 32 | } 33 | }, 34 | price: function(data) { 35 | if(data.visible){ 36 | return {data.price} 37 | } 38 | }, 39 | visible: function(data) { 40 | return null; 41 | } 42 | } 43 | 44 | // define the names for the search autocomplete 45 | let names = this.props.products.map(function (product){ 46 | return product.name; 47 | }) 48 | 49 | return ( 50 |
    51 | 52 | 53 | 54 | 55 | 56 |
    57 | ); 58 | } 59 | 60 | }); 61 | 62 | module.exports = ProductList; -------------------------------------------------------------------------------- /product-search/src/stores/ProductStore.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | import ProductActions from '../actions/ProductActions' 3 | 4 | class ProductStore { 5 | 6 | constructor() { 7 | 8 | this.products = [] 9 | this.filterText = '' 10 | 11 | this.bindListeners({ 12 | filterProducts: ProductActions.FILTER_PRODUCTS 13 | }); 14 | 15 | } 16 | 17 | 18 | filterProducts(text) { 19 | 20 | this.filterText = text 21 | 22 | this.products.map(function(product) { 23 | if( product.name.toLowerCase().includes(this.filterText.toLowerCase()) ){ 24 | product.visible = true 25 | } else { 26 | product.visible = false 27 | } 28 | }.bind(this)); 29 | 30 | } 31 | 32 | } 33 | 34 | module.exports = alt.createStore(ProductStore, 'ProductStore'); -------------------------------------------------------------------------------- /product-search/templates/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | link(rel='stylesheet', href='/public/css/styles.css') 5 | 6 | body 7 | #app!= html 8 | 9 | script(src="/public/js/bundle.js") -------------------------------------------------------------------------------- /react-router/react-router-jsx/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /react-router/react-router-jsx/README.md: -------------------------------------------------------------------------------- 1 | # iso-react-router-flux 2 | 3 | > Isomorphic react application using flux and react-router 4 | 5 | ## Running This 6 | 7 | ```sh 8 | npm install; npm run build; npm start 9 | ``` 10 | 11 | Then open your browser to `localhost:8080` and enjoy. 12 | 13 | There are a few routes you can visit directly: 14 | 15 | `localhost:8080/hello` and `localhost:8080/time`. 16 | 17 | Hello will display a hello message whereas time will display the current time. 18 | 19 | For funsies you can include your own name as a parameter to hello: eg `localhost:8080/hello/jane` This will be seeded on the server and bootstrapped on the client. 20 | -------------------------------------------------------------------------------- /react-router/react-router-jsx/client.js: -------------------------------------------------------------------------------- 1 | var Iso = require('iso') 2 | var Router = require('react-router') 3 | var React = require('react') 4 | 5 | var routes = require('./src/routes.jsx') 6 | var alt = require('./src/alt') 7 | 8 | /* 9 | Once we bootstrap the stores, we run react-router using Router.HistoryLocation 10 | The element is created and we just render it into the container 11 | */ 12 | 13 | Iso.bootstrap(function (state, _, container) { 14 | alt.bootstrap(state) 15 | 16 | Router.run(routes, Router.HistoryLocation, function (Handler) { 17 | var node = React.createElement(Handler) 18 | React.render(node, container) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /react-router/react-router-jsx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iso-react-router-flux", 3 | "version": "1.0.0", 4 | "description": "Isomorphic date/time application with react-router and flux", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "browserify -t [reactify --harmony] client.js > public/js/bundle.js", 8 | "start": "node server.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "Josh Perez ", 12 | "license": "MIT", 13 | "dependencies": { 14 | "express": "^4.11.2", 15 | "serve-favicon": "^2.2.0", 16 | "jade": "^1.9.2", 17 | "alt": "^0.16.5", 18 | "iso": "^4.1.0", 19 | "react": "^0.13.3", 20 | "react-router": "^0.13.3", 21 | "node-jsx": "^0.12.4" 22 | }, 23 | "devDependencies": { 24 | "browserify": "^8.0.3", 25 | "reactify": "^0.17.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /react-router/react-router-jsx/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-jsx/public/favicon.ico -------------------------------------------------------------------------------- /react-router/react-router-jsx/server.js: -------------------------------------------------------------------------------- 1 | require('node-jsx').install({ extension: '.jsx', harmony: true }) 2 | 3 | var Router = require('react-router') 4 | var React = require('react') 5 | var express = require('express') 6 | var Iso = require('iso') 7 | var favicon = require('serve-favicon') 8 | var path = require('path') 9 | 10 | var routes = require('./src/routes') 11 | var alt = require('./src/alt') 12 | 13 | var port = 8080 14 | var ip = '127.0.0.1' 15 | var app = express() 16 | 17 | app.set('view engine', 'jade') 18 | app.set('views', path.join(__dirname, 'templates')) 19 | app.use('/public', express.static(path.join(__dirname, 'public'))) 20 | app.use(favicon(__dirname + '/public/favicon.ico')); 21 | 22 | 23 | /* Simulate an asynchronous event that takes 300ms to complete */ 24 | function getNameFromServer(cb) { 25 | setTimeout(function () { 26 | cb('Server') 27 | }, 300) 28 | } 29 | 30 | /* 31 | Prior to running react-router we setup this route in order to handle data fetching. 32 | We can pass data fetched via express' locals. 33 | */ 34 | app.get('/hello/:name?', function (req, res, next) { 35 | if (req.params.name) { 36 | res.locals.data = { HelloStore: { name: req.params.name } } 37 | next() 38 | } else { 39 | getNameFromServer(function (name) { 40 | res.locals.data = { 41 | HelloStore: { name: name } 42 | } 43 | next() 44 | }) 45 | } 46 | }) 47 | 48 | app.get('/time', function (req, res, next) { 49 | res.locals.data = { 50 | TimeStore: { time: Date.now() } 51 | } 52 | next() 53 | }) 54 | 55 | /* 56 | This is where the magic happens... 57 | */ 58 | app.use(function (req, res) { 59 | 60 | // We take the locals data we have fetched and seed our stores with data 61 | alt.bootstrap(JSON.stringify(res.locals.data || {})) 62 | var iso = new Iso() 63 | 64 | // We use react-router to run the URL that is provided in routes.jsx 65 | 66 | Router.run(routes, req.url, function (Handler) { 67 | var content = React.renderToString(React.createElement(Handler)) 68 | 69 | // Use iso to render, it picks back up on the client side and bootstraps the stores. 70 | 71 | iso.add(content, alt.flush()) 72 | res.render('layout', { html: iso.render() }) 73 | 74 | }) 75 | }) 76 | 77 | 78 | /* logging to the server */ 79 | app.listen(port,ip, function() { 80 | console.log("Go to " + ip + ":" + port) 81 | }); 82 | -------------------------------------------------------------------------------- /react-router/react-router-jsx/src/alt.js: -------------------------------------------------------------------------------- 1 | var Alt = require('alt') 2 | module.exports = new Alt() 3 | -------------------------------------------------------------------------------- /react-router/react-router-jsx/src/components/App.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react') 2 | var { RouteHandler, Link } = require('react-router') 3 | 4 | var App = React.createClass({ 5 | render() { 6 | return ( 7 |
    8 | Say hi 9 |
    10 | What time is it? 11 |
    12 | 13 |
    14 | ) 15 | } 16 | }) 17 | 18 | module.exports = App 19 | -------------------------------------------------------------------------------- /react-router/react-router-jsx/src/components/Hello.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react') 2 | 3 | var HelloStore = require('../stores/HelloStore') 4 | 5 | var App = React.createClass({ 6 | getInitialState() { 7 | return HelloStore.getState() 8 | }, 9 | 10 | render() { 11 | return
    {`Hello, ${this.state.name}`}
    12 | } 13 | }) 14 | 15 | module.exports = App 16 | -------------------------------------------------------------------------------- /react-router/react-router-jsx/src/components/Time.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react') 2 | 3 | var TimeStore = require('../stores/TimeStore') 4 | 5 | var App = React.createClass({ 6 | getInitialState() { 7 | return TimeStore.getState() 8 | }, 9 | 10 | render() { 11 | return
    {`The time is now: ${this.state.time}`}
    12 | } 13 | }) 14 | 15 | module.exports = App 16 | -------------------------------------------------------------------------------- /react-router/react-router-jsx/src/routes.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react') 2 | var Route = require('react-router').Route 3 | 4 | var App = require('./components/App.jsx') 5 | var Hello = require('./components/Hello.jsx') 6 | var Time = require('./components/Time.jsx') 7 | 8 | var routes = ( 9 | 10 | 11 | 12 | 13 | ) 14 | 15 | module.exports = routes 16 | -------------------------------------------------------------------------------- /react-router/react-router-jsx/src/stores/HelloStore.js: -------------------------------------------------------------------------------- 1 | var alt = require('../alt') 2 | 3 | function HelloStore() { 4 | this.name = 'Nobody' 5 | } 6 | 7 | module.exports = alt.createStore(HelloStore, 'HelloStore') 8 | -------------------------------------------------------------------------------- /react-router/react-router-jsx/src/stores/TimeStore.js: -------------------------------------------------------------------------------- 1 | var alt = require('../alt') 2 | 3 | function TimeStore() { 4 | this.time = Date.now() 5 | } 6 | 7 | module.exports = alt.createStore(TimeStore, 'TimeStore') 8 | -------------------------------------------------------------------------------- /react-router/react-router-jsx/templates/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | body 4 | #app!= html 5 | script(src="/public/js/bundle.js") 6 | -------------------------------------------------------------------------------- /react-router/react-router-movies/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /react-router/react-router-movies/README.md: -------------------------------------------------------------------------------- 1 | # iso-react-router-flux-movies 2 | 3 | > Isomorphic react application using flux and react-router 4 | 5 | ## Running This 6 | 7 | ```sh 8 | npm install; npm run build; npm start 9 | ``` 10 | 11 | Then open your browser to `localhost:8080` and enjoy. 12 | 13 | There are a few routes you can visit directly: 14 | 15 | `localhost:8080/movies` and `localhost:8080/movie/`. 16 | 17 | Movies will display a list of the movies whereas movie will display all the info for an individual movie... e.g. Gleaming the Cube 18 | 19 | ![](public/img/gleamingthecube.jpg) -------------------------------------------------------------------------------- /react-router/react-router-movies/app.js: -------------------------------------------------------------------------------- 1 | import Router from 'react-router' 2 | import React from 'react' 3 | import express from 'express' 4 | import Iso from 'iso' 5 | import favicon from 'serve-favicon' 6 | import path from 'path' 7 | 8 | import expressRoutes from './src/routes/routes.express' 9 | import reactRoutes from './src/routes/routes.react' 10 | import alt from './src/alt' 11 | 12 | let port = 8080 13 | let ip = '127.0.0.1' 14 | let app = express() 15 | 16 | app.set('view engine', 'jade') 17 | app.set('views', path.join(__dirname, 'templates')) 18 | app.use('/public', express.static(path.join(__dirname, 'public'))) 19 | app.use(favicon(__dirname + '/public/favicon.ico')); 20 | 21 | 22 | /* routing from express, we pass data fetched via express' locals */ 23 | expressRoutes(app); 24 | 25 | 26 | /* Kick off react routing */ 27 | app.use(function (req, res) { 28 | 29 | // take the locals data we have fetched and seed our stores with data 30 | alt.bootstrap(JSON.stringify(res.locals.data || {})) 31 | let iso = new Iso() 32 | 33 | // react-router runs the URL that is provided in reactRoutes 34 | 35 | Router.run(reactRoutes, req.url, function (Handler) { 36 | // Use iso to render, picks back up on the client side and bootstraps the stores. 37 | let node = React.renderToString(React.createElement(Handler)) 38 | iso.add(node, alt.flush()) 39 | res.render('layout', { html: iso.render() }) 40 | 41 | }) 42 | }) 43 | 44 | 45 | /* logging to the server */ 46 | app.listen(port,ip, function() { 47 | console.log("Go to " + ip + ":" + port) 48 | }); -------------------------------------------------------------------------------- /react-router/react-router-movies/client.js: -------------------------------------------------------------------------------- 1 | import Iso from 'iso' 2 | import Router from 'react-router' 3 | import React from 'react' 4 | import reactRoutes from './src/routes/routes.react' 5 | import alt from './src/alt' 6 | 7 | /* 8 | Once we bootstrap the stores, we run react-router using Router.HistoryLocation 9 | The element is created and we just render it into the container 10 | */ 11 | 12 | Iso.bootstrap(function (state, _, container) { 13 | 14 | // bootstrap the state from the server 15 | alt.bootstrap(state) 16 | 17 | Router.run(reactRoutes, Router.HistoryLocation, function (Handler, req) { 18 | 19 | // req.params are passed in as props to the component 20 | let node = React.createElement(Handler) 21 | React.render(node, container) 22 | }) 23 | }) -------------------------------------------------------------------------------- /react-router/react-router-movies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iso-react-router-flux-movies", 3 | "version": "1.0.0", 4 | "description": "Isomorphic movie application with react-router and flux", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "browserify --debug -t [babelify] client.js > public/js/bundle.js", 9 | "start": "node server.js" 10 | }, 11 | "author": "Steven Iseki ", 12 | "license": "MIT", 13 | "dependencies": { 14 | "express": "^4.11.2", 15 | "serve-favicon": "^2.2.0", 16 | "jade": "^1.9.2", 17 | "alt": "^0.16.5", 18 | "iso": "^4.1.0", 19 | "react": "^0.13.3", 20 | "react-router": "^0.13.3" 21 | }, 22 | "devDependencies": { 23 | "babel": "^4.0.1", 24 | "babelify": "^5.0.3", 25 | "browserify": "^8.0.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /react-router/react-router-movies/public/css/pure-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.6.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v^3.0 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-link,.pure-menu-heading{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-disabled,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} -------------------------------------------------------------------------------- /react-router/react-router-movies/public/css/styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-movies/public/css/styles.css -------------------------------------------------------------------------------- /react-router/react-router-movies/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-movies/public/favicon.ico -------------------------------------------------------------------------------- /react-router/react-router-movies/public/img/airborne.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-movies/public/img/airborne.jpg -------------------------------------------------------------------------------- /react-router/react-router-movies/public/img/billteds.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-movies/public/img/billteds.jpg -------------------------------------------------------------------------------- /react-router/react-router-movies/public/img/gleamingthecube.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-movies/public/img/gleamingthecube.jpg -------------------------------------------------------------------------------- /react-router/react-router-movies/public/img/karatekid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-movies/public/img/karatekid.jpg -------------------------------------------------------------------------------- /react-router/react-router-movies/public/img/kungfury.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-movies/public/img/kungfury.jpg -------------------------------------------------------------------------------- /react-router/react-router-movies/server.js: -------------------------------------------------------------------------------- 1 | // only ES5 is allowed in this file 2 | require("babel/register"); 3 | 4 | // other babel configuration, if necessary 5 | 6 | // load your app 7 | var app = require("./app"); -------------------------------------------------------------------------------- /react-router/react-router-movies/src/actions/MovieActions.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | import MovieApi from '../api/movies' 3 | 4 | class MovieActions { 5 | 6 | sayHi(name) { 7 | this.dispatch(name); 8 | } 9 | 10 | setMovie(id) { 11 | 12 | // call out to your movie api in the action 13 | MovieApi.findMovie(id) 14 | .then ( function (movie) { 15 | 16 | let data = { MovieStore: { movie: movie } } 17 | 18 | /* not sure if it is best to bootstrap here or just send to the store */ 19 | alt.bootstrap(JSON.stringify(data || {})) 20 | this.dispatch(movie); 21 | 22 | }.bind(this), function(error) { 23 | console.error("Failed!", error); 24 | }); 25 | 26 | } 27 | 28 | } 29 | 30 | module.exports = alt.createActions(MovieActions); -------------------------------------------------------------------------------- /react-router/react-router-movies/src/alt.js: -------------------------------------------------------------------------------- 1 | import Alt from 'alt' 2 | 3 | module.exports = new Alt() -------------------------------------------------------------------------------- /react-router/react-router-movies/src/api/movies.js: -------------------------------------------------------------------------------- 1 | /* 2 | Simulate an asynchronous event that takes 500ms to complete 3 | Could return a promiseee... 4 | */ 5 | 6 | let movieData = [ 7 | { 8 | id: '1011', 9 | name: 'Gleaming the cube', 10 | year: "1989", 11 | description: "A California skateboarder (Christian Slater) solves and avenges the death of his adopted Vietnamese brother.", 12 | image: '/public/img/gleamingthecube.jpg' 13 | }, 14 | { 15 | id: "1012", 16 | name: "Airborne", 17 | year: "1993", 18 | description: "When his parents go to Australia for six months to work on a zoology project, young Mitchell (Shane McDermott) is sent to live with his Aunt Irene (Edie McClurg) and Uncle Louie (Patrick Thomas O'Brien) in Cincinnati. Initially dismayed by the cold climate, Mitchell's unhappiness increases when he's taunted by the high school's popular kids. As he strikes up a relationship with Nikki (Brittney Powell), Mitchell begins to win over his bullies by using his Rollerblading skills in street hockey games.", 19 | image: "/public/img/airborne.jpg" 20 | }, 21 | { 22 | id: "1013", 23 | name: "Kung Fury", 24 | year: "2015", 25 | description: "A martial artist who makes his living as a syndicate hit man finds himself the quarry of both the police and the mob.", 26 | image: "/public/img/kungfury.jpg" 27 | }, 28 | { 29 | id: "1014", 30 | name: "The Karate Kid", 31 | year: "1984", 32 | description: "Daniel (Ralph Macchio) moves to Southern California with his mother, Lucille (Randee Heller), but quickly finds himself the target of a group of bullies who study karate at the Cobra Kai dojo. Fortunately, Daniel befriends Mr. Miyagi (Noriyuki Morita), an unassuming repairman who just happens to be a martial arts master himself. Miyagi takes Daniel under his wing, training him in a more compassionate form of karate and preparing him to compete against the brutal Cobra Kai.", 33 | image: "/public/img/karatekid.jpg" 34 | }, 35 | { 36 | id: "1015", 37 | year: "1989", 38 | name: "Bill & Teds Excellent Adventure", 39 | description: "Bill (Alex Winter) and Ted (Keanu Reeves) are high school buddies starting a band. However, they are about to fail their history class, which means Ted would be sent to military school. They receive help from Rufus (George Carlin), a traveler from a future where their band is the foundation for a perfect society. With the use of Rufus time machine, Bill and Ted travel to various points in history, returning with important figures to help them complete their final history presentation.", 40 | image: "/public/img/billteds.jpg" 41 | } 42 | ] 43 | 44 | let movies = { 45 | 46 | findMovie: function(id) { 47 | 48 | return new Promise(function(resolve, reject) { 49 | 50 | 51 | let movie = ''; 52 | 53 | if(id === undefined){ 54 | reject(Error('incorrect movie id')); 55 | } 56 | 57 | 58 | for (let m of movieData) { 59 | if (m.id.toLowerCase() === id.toLowerCase()) { 60 | movie = m 61 | break; 62 | } 63 | } 64 | 65 | 66 | setTimeout(function () { 67 | // resolve movies after, using setTimeout as a delay 68 | resolve(movie); 69 | }.bind(this), 300) 70 | 71 | }); 72 | 73 | }, 74 | 75 | findAllMovies: function() { 76 | 77 | return new Promise(function(resolve, reject) { 78 | 79 | if(movieData === undefined){ 80 | reject(Error('Could not find any movies')); 81 | } 82 | 83 | setTimeout(function () { 84 | // resolve movies after, using setTimeout as a delay 85 | resolve(movieData); 86 | }.bind(this), 300) 87 | 88 | }); 89 | 90 | } 91 | 92 | } 93 | 94 | module.exports = movies -------------------------------------------------------------------------------- /react-router/react-router-movies/src/components/MovieLinks.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | let { RouteHandler, Link } = require('react-router') 3 | 4 | let MovieLinks = React.createClass({ 5 | 6 | propTypes: { 7 | movies: React.PropTypes.array 8 | }, 9 | 10 | render() { 11 | 12 | let movieLinks = this.props.movies.map(function (m, i) { 13 | 14 | return ( 15 |
  • 16 | 17 | {`View the movie: ${m.name}`} 18 | 19 |
  • 20 | ); 21 | 22 | }.bind(this)); 23 | 24 | return ( 25 |
    26 |
      {movieLinks}
    27 |
    28 | ); 29 | 30 | } 31 | }) 32 | 33 | module.exports = MovieLinks -------------------------------------------------------------------------------- /react-router/react-router-movies/src/components/MovieRow.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | let { RouteHandler, Link } = require('react-router') 4 | 5 | let MovieRow = React.createClass({ 6 | 7 | propTypes: { 8 | movie: React.PropTypes.object 9 | }, 10 | 11 | render() { 12 | 13 | return ( 14 |
    15 |

    {this.props.movie.name}

    16 | {this.props.movie.description} 17 | 18 | 19 | 20 | 21 | 22 |
    23 | ); 24 | 25 | } 26 | }) 27 | 28 | module.exports = MovieRow -------------------------------------------------------------------------------- /react-router/react-router-movies/src/components/controller-views/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MovieStore from '../../stores/MovieStore' 3 | import MovieLinks from '../MovieLinks' 4 | 5 | let { RouteHandler, Link } = require('react-router') 6 | 7 | let App = React.createClass({ 8 | 9 | getInitialState() { 10 | return MovieStore.getState() 11 | }, 12 | 13 | render() { 14 | 15 | return ( 16 |
    17 | 18 | {/* links */} 19 | All movies 20 | 21 | 22 | 23 | {/* route */} 24 | 25 | 26 |
    27 | ) 28 | } 29 | }) 30 | 31 | module.exports = App -------------------------------------------------------------------------------- /react-router/react-router-movies/src/components/controller-views/Movie.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MovieStore from '../../stores/MovieStore' 3 | import MovieActions from '../../actions/MovieActions' 4 | import MovieRow from '../MovieRow' 5 | 6 | let MovieView = React.createClass({ 7 | 8 | getInitialState() { 9 | return MovieStore.getState() 10 | }, 11 | 12 | initMovie (id) { 13 | MovieActions.setMovie(id) 14 | }, 15 | 16 | componentDidMount() { 17 | this.initMovie(this.props.params.id) 18 | MovieStore.listen(() => this.setState(MovieStore.getState())) 19 | }, 20 | 21 | componentWillUnmount() { 22 | MovieStore.unlisten(() => this.setState(MovieStore.getState())) 23 | }, 24 | 25 | componentWillReceiveProps(nextProps) { 26 | /* 27 | When changing to a new route with the same controller view 28 | the component does not re-mount. We need to detect the new params here 29 | */ 30 | this.initMovie(nextProps.params.id) 31 | MovieStore.listen(() => this.setState(MovieStore.getState())) 32 | }, 33 | 34 | sayHi() { 35 | MovieActions.sayHi('Hello from you favorite movie: ') 36 | }, 37 | 38 | render() { 39 | return ( 40 | 41 |
    42 | 43 |

    {`Hello , ${this.state.movie.name}`}

    44 | 45 | 46 | { /* TODO Rate movie component ... */} 47 | 48 | 49 |
    50 | ); 51 | 52 | } 53 | }) 54 | 55 | module.exports = MovieView -------------------------------------------------------------------------------- /react-router/react-router-movies/src/components/controller-views/Movies.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MovieStore from '../../stores/MovieStore' 3 | import MovieRow from '../MovieRow' 4 | 5 | var MovieList = React.createClass({ 6 | 7 | getInitialState() { 8 | return MovieStore.getState() 9 | }, 10 | 11 | render() { 12 | 13 | var movieList = this.state.movies.map(function (m, i) { 14 | 15 | return ( 16 | 17 | ); 18 | 19 | }.bind(this)); 20 | 21 | return ( 22 |
    23 | 24 |

    Movie List

    25 | {movieList} 26 |
    27 | ); 28 | } 29 | 30 | }) 31 | 32 | module.exports = MovieList 33 | -------------------------------------------------------------------------------- /react-router/react-router-movies/src/routes/routes.express.js: -------------------------------------------------------------------------------- 1 | import MovieApi from '../api/movies' 2 | 3 | module.exports = function(app) { 4 | 5 | /* home route */ 6 | app.get('/', function (req, res, next) { 7 | 8 | console.log('home route loaded from express') 9 | 10 | // call to pre load movies ... 11 | MovieApi.findAllMovies() 12 | .then( function (movies){ 13 | res.locals.data = { MovieStore: { movies: movies } } 14 | next() 15 | }) 16 | }) 17 | 18 | 19 | /* movie list route */ 20 | 21 | app.get('/movies', function (req, res, next) { 22 | 23 | console.log('movie list route loaded from express') 24 | 25 | // call to pre load movies ... 26 | MovieApi.findAllMovies() 27 | .then( function (movies){ 28 | res.locals.data = { MovieStore: { movies: movies } } 29 | next() 30 | }) 31 | }) 32 | 33 | 34 | /* movie route */ 35 | app.get('/movie/:id', function (req, res, next) { 36 | 37 | console.log('movie route loaded from express') 38 | 39 | // movie name 40 | if (!req.params.id) { 41 | next() 42 | } 43 | 44 | /* 45 | call to pre load movies ... 46 | then call to bring detailed information on an individual movie 47 | */ 48 | let data = { MovieStore: { movie: {}, movies: [] } } 49 | 50 | MovieApi.findAllMovies() 51 | .then( function (movies){ 52 | data.MovieStore.movies = movies 53 | return MovieApi.findMovie(req.params.id) 54 | }).then(function(movie){ 55 | data.MovieStore.movie = movie 56 | res.locals.data = data 57 | next() 58 | }).catch(function(error) { 59 | console.log('got to error') 60 | next() 61 | }); 62 | 63 | }) 64 | 65 | }; -------------------------------------------------------------------------------- /react-router/react-router-movies/src/routes/routes.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactRouter from 'react-router' 3 | 4 | import App from '../components/controller-views/App' 5 | import Movies from '../components/controller-views/Movies' 6 | import Movie from '../components/controller-views/Movie' 7 | 8 | let Route = ReactRouter.Route 9 | 10 | let routes = ( 11 | 12 | 13 | 14 | 15 | ) 16 | 17 | module.exports = routes -------------------------------------------------------------------------------- /react-router/react-router-movies/src/stores/MovieStore.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | import MovieActions from '../actions/MovieActions' 3 | 4 | class MovieStore { 5 | 6 | constructor() { 7 | this.movies = [] 8 | this.movie = {} 9 | 10 | this.bindListeners({ 11 | sayHi: MovieActions.SAY_HI, 12 | setMovie: MovieActions.SET_MOVIE 13 | }); 14 | 15 | } 16 | 17 | sayHi(name) { 18 | /* not sure what this method does... could update a message */ 19 | } 20 | 21 | setMovie(movie) { 22 | /* maybe update the current pet... */ 23 | } 24 | 25 | 26 | } 27 | 28 | module.exports = alt.createStore(MovieStore, 'MovieStore') -------------------------------------------------------------------------------- /react-router/react-router-movies/templates/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | link(rel='stylesheet', href='/public/css/pure-min.css') 5 | link(rel='stylesheet', href='/public/css/styles.css') 6 | body 7 | #app!= html 8 | script(src="/public/js/bundle.js") 9 | -------------------------------------------------------------------------------- /react-router/react-router-pets/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /react-router/react-router-pets/README.md: -------------------------------------------------------------------------------- 1 | # iso-react-router-flux-pets 2 | 3 | > Isomorphic react application using flux and react-router 4 | 5 | ## Running This 6 | 7 | ```sh 8 | npm install; npm run build; npm start 9 | ``` 10 | 11 | Then open your browser to `localhost:8080` and enjoy. 12 | 13 | There are a few routes you can visit directly: 14 | 15 | `localhost:8080/pets` and `localhost:8080/pet`. 16 | 17 | Pets will display a list of the pets whereas pet will display all the info for a specific pet... e.g. /pet/darkdog 18 | 19 | ![](public/img/darkdog.png) -------------------------------------------------------------------------------- /react-router/react-router-pets/client.js: -------------------------------------------------------------------------------- 1 | import Iso from 'iso' 2 | import Router from 'react-router' 3 | import React from 'react' 4 | import routes from './src/routes' 5 | import alt from './src/alt' 6 | 7 | /* 8 | Once we bootstrap the stores, we run react-router using Router.HistoryLocation 9 | The element is created and we just render it into the container 10 | */ 11 | 12 | Iso.bootstrap(function (state, _, container) { 13 | 14 | // bootstrap the state from the server 15 | alt.bootstrap(state) 16 | 17 | Router.run(routes, Router.HistoryLocation, function (Handler, req) { 18 | 19 | // req.params are passed in as props to the component 20 | let node = React.createElement(Handler) 21 | React.render(node, container) 22 | }) 23 | }) -------------------------------------------------------------------------------- /react-router/react-router-pets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iso-react-router-flux-pets", 3 | "version": "1.0.0", 4 | "description": "Isomorphic pet application with react-router and flux", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "browserify --debug -t [babelify] client.js > public/js/bundle.js", 9 | "start": "babel-node server.js" 10 | }, 11 | "author": "Steven Iseki ", 12 | "license": "MIT", 13 | "dependencies": { 14 | "express": "^4.11.2", 15 | "serve-favicon": "^2.2.0", 16 | "jade": "^1.9.2", 17 | "alt": "^0.16.5", 18 | "iso": "^4.1.0", 19 | "react": "^0.13.3", 20 | "react-router": "^0.13.3" 21 | }, 22 | "devDependencies": { 23 | "babel": "^4.0.1", 24 | "babelify": "^5.0.3", 25 | "browserify": "^8.0.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /react-router/react-router-pets/public/css/pure-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.6.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v^3.0 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-link,.pure-menu-heading{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-disabled,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} -------------------------------------------------------------------------------- /react-router/react-router-pets/public/css/styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-pets/public/css/styles.css -------------------------------------------------------------------------------- /react-router/react-router-pets/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-pets/public/favicon.ico -------------------------------------------------------------------------------- /react-router/react-router-pets/public/img/bennie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-pets/public/img/bennie.png -------------------------------------------------------------------------------- /react-router/react-router-pets/public/img/chaplin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-pets/public/img/chaplin.png -------------------------------------------------------------------------------- /react-router/react-router-pets/public/img/darkdog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-pets/public/img/darkdog.png -------------------------------------------------------------------------------- /react-router/react-router-pets/public/img/winston.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-pets/public/img/winston.png -------------------------------------------------------------------------------- /react-router/react-router-pets/server.js: -------------------------------------------------------------------------------- 1 | import Router from 'react-router' 2 | import React from 'react' 3 | import express from 'express' 4 | import Iso from 'iso' 5 | import favicon from 'serve-favicon' 6 | import path from 'path' 7 | 8 | 9 | import routes from './src/routes' 10 | import alt from './src/alt' 11 | import PetApi from './src/api/pets' 12 | 13 | let port = 8080 14 | let ip = '127.0.0.1' 15 | let app = express() 16 | 17 | app.set('view engine', 'jade') 18 | app.set('views', path.join(__dirname, 'templates')) 19 | app.use('/public', express.static(path.join(__dirname, 'public'))) 20 | app.use(favicon(__dirname + '/public/favicon.ico')); 21 | 22 | 23 | 24 | /* 25 | Prior to running react-router we setup this route in order to handle data fetching. 26 | We can pass data fetched via express' locals. 27 | */ 28 | 29 | 30 | app.get('/', function (req, res, next) { 31 | 32 | console.log('home loaded from express') 33 | 34 | // call to pre load pets ... 35 | PetApi.findAllPets(function (pets){ 36 | res.locals.data = { PetStore: { pets: pets } } 37 | next() 38 | }) 39 | 40 | }) 41 | 42 | 43 | 44 | app.get('/pets/:name?', function (req, res, next) { 45 | 46 | console.log('pets loaded from express') 47 | 48 | // pet names 49 | if (req.params.name) { 50 | 51 | // call to pre load pets ... 52 | PetApi.findAllPets(function (pets){ 53 | 54 | // call to bring detailed information on individual pet 55 | PetApi.findPet(req.params.name, function (pet) { 56 | res.locals.data = { PetStore: { pet: pet, pets: pets } } 57 | next() 58 | }) 59 | }) 60 | 61 | } else { 62 | 63 | // call to pre load pets ... 64 | PetApi.findAllPets(function (pets){ 65 | res.locals.data = { PetStore: { pets: pets } } 66 | next() 67 | }) 68 | 69 | } 70 | 71 | }) 72 | 73 | 74 | /* 75 | This is where the magic happens... 76 | */ 77 | app.use(function (req, res) { 78 | 79 | /* 80 | We take the locals data we have fetched and seed our stores with data 81 | Makes sure that your components have the proper data 82 | */ 83 | alt.bootstrap(JSON.stringify(res.locals.data || {})) 84 | let iso = new Iso() 85 | 86 | // Use react-router to run the URL that is provided in routes.js 87 | 88 | Router.run(routes, req.url, function (Handler) { 89 | let node = React.renderToString(React.createElement(Handler)) 90 | 91 | /* 92 | alt.flush() once the view markup has been created. 93 | resets your stores so they are ready for the next request. 94 | */ 95 | iso.add(node, alt.flush()) 96 | 97 | // Use iso to render, picks back up on the client side and bootstraps the stores. 98 | res.render('layout', { html: iso.render() }) 99 | 100 | }) 101 | }) 102 | 103 | 104 | /* logging to the server */ 105 | app.listen(port,ip, function() { 106 | console.log("Go to " + ip + ":" + port) 107 | }); -------------------------------------------------------------------------------- /react-router/react-router-pets/src/actions/PetActions.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | import PetApi from '../api/pets' 3 | 4 | class PetActions { 5 | 6 | sayHi(name) { 7 | this.dispatch(name); 8 | } 9 | 10 | 11 | setPet(name) { 12 | 13 | // call out to your pet api in the action 14 | PetApi.findPet(name, function (pet) { 15 | let data = { PetStore: { pet: pet } } 16 | 17 | /* not sure if it is best to bootstrap here or just send to the store */ 18 | alt.bootstrap(JSON.stringify(data || {})) 19 | this.dispatch(pet); 20 | 21 | }.bind(this)) 22 | 23 | } 24 | 25 | } 26 | 27 | module.exports = alt.createActions(PetActions); -------------------------------------------------------------------------------- /react-router/react-router-pets/src/alt.js: -------------------------------------------------------------------------------- 1 | import Alt from 'alt' 2 | 3 | module.exports = new Alt() -------------------------------------------------------------------------------- /react-router/react-router-pets/src/api/pets.js: -------------------------------------------------------------------------------- 1 | /* 2 | Simulate an asynchronous event that takes 500ms to complete 3 | Could return a promiseee... 4 | */ 5 | 6 | let petData = [ 7 | {name: 'darkdog', description: ' is a very scary dog', image: '/public/img/darkdog.png'}, 8 | {name: 'winston', description: ' is a fluffy dog', image: '/public/img/winston.png'}, 9 | {name: 'chaplin', description: ' is a red dog', image: '/public/img/chaplin.png'}, 10 | {name: 'bennie', description: ' is a cool dog', image: '/public/img/bennie.png'} 11 | ] 12 | 13 | let pets = { 14 | 15 | findPet: function(petName, cb) { 16 | let pet = ''; 17 | 18 | for (let p of petData) { 19 | if (p.name.toLowerCase() === petName.toLowerCase()) { 20 | this.pet = p 21 | break; 22 | } 23 | } 24 | 25 | setTimeout(function () { 26 | cb(this.pet) 27 | }.bind(this), 200) 28 | 29 | }, 30 | 31 | findAllPets: function(cb) { 32 | setTimeout(function () { 33 | cb(petData) 34 | }.bind(this), 200) 35 | 36 | } 37 | 38 | } 39 | 40 | module.exports = pets -------------------------------------------------------------------------------- /react-router/react-router-pets/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PetLinks from './PetLinks' 3 | import PetStore from '../stores/PetStore' 4 | 5 | let { RouteHandler, Link } = require('react-router') 6 | 7 | let App = React.createClass({ 8 | 9 | getInitialState() { 10 | return PetStore.getState() 11 | }, 12 | 13 | render() { 14 | 15 | console.log(this.state) 16 | 17 | return ( 18 |
    19 | 20 | {/* links */} 21 | All Pet details 22 | 23 | 24 | 25 | {/* route */} 26 | 27 | 28 |
    29 | ) 30 | } 31 | }) 32 | 33 | module.exports = App -------------------------------------------------------------------------------- /react-router/react-router-pets/src/components/PetLinks.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PetRow from './PetRow' 3 | 4 | let { RouteHandler, Link } = require('react-router') 5 | 6 | var PetLinks = React.createClass({ 7 | 8 | render() { 9 | 10 | var petLinks = this.props.pets.map(function (p, i) { 11 | 12 | let name = p.name 13 | return ( 14 |
  • 15 | 16 | {`View the awesome: ${name}`} 17 | 18 |
  • 19 | ); 20 | 21 | }.bind(this)); 22 | 23 | return ( 24 |
    25 |
      {petLinks}
    26 |
    27 | ); 28 | 29 | 30 | } 31 | }) 32 | 33 | module.exports = PetLinks 34 | -------------------------------------------------------------------------------- /react-router/react-router-pets/src/components/PetList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PetStore from '../stores/PetStore' 3 | import PetRow from './PetRow' 4 | 5 | var PetList = React.createClass({ 6 | 7 | getInitialState() { 8 | return PetStore.getState() 9 | }, 10 | 11 | 12 | render() { 13 | 14 | var petList = this.state.pets.map(function (p, i) { 15 | 16 | return ( 17 | 18 | ); 19 | 20 | }.bind(this)); 21 | 22 | return ( 23 |
    24 |

    Pet List

    25 | {petList} 26 |
    27 | ); 28 | } 29 | 30 | }) 31 | 32 | module.exports = PetList 33 | -------------------------------------------------------------------------------- /react-router/react-router-pets/src/components/PetRow.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | let PetRow = React.createClass({ 4 | 5 | propTypes: { 6 | pet: React.PropTypes.object 7 | }, 8 | 9 | render() { 10 | 11 | return ( 12 |
    13 |

    {this.props.pet.name}

    14 | {this.props.pet.description} 15 | 16 |
    17 | ); 18 | 19 | } 20 | }) 21 | 22 | module.exports = PetRow -------------------------------------------------------------------------------- /react-router/react-router-pets/src/components/PetView.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PetStore from '../stores/PetStore' 3 | import PetActions from '../actions/PetActions' 4 | import PetRow from './PetRow' 5 | 6 | let PetView = React.createClass({ 7 | 8 | getInitialState() { 9 | return PetStore.getState() 10 | }, 11 | 12 | initializePet (petName) { 13 | PetActions.setPet(petName) 14 | }, 15 | 16 | componentDidMount() { 17 | // pet name params passed through fine on component mount 18 | this.initializePet(this.props.params.name) 19 | PetStore.listen(() => this.setState(PetStore.getState())) 20 | }, 21 | 22 | componentWillUnmount() { 23 | PetStore.unlisten(() => this.setState(PetStore.getState())) 24 | }, 25 | 26 | componentWillReceiveProps(nextProps) { 27 | /* 28 | When changing to a new route with the same controller view 29 | the component does not re-mount. We need to detect the new params here 30 | */ 31 | this.initializePet(nextProps.params.name) 32 | PetStore.listen(() => this.setState(PetStore.getState())) 33 | }, 34 | 35 | sayHiPet() { 36 | PetActions.sayHi('Hello from you favorite pet: ') 37 | }, 38 | 39 | render() { 40 | return ( 41 |
    42 |

    {`Hello , ${this.state.pet.name}`}

    43 | 44 | 45 |
    46 | ); 47 | 48 | } 49 | }) 50 | 51 | module.exports = PetView -------------------------------------------------------------------------------- /react-router/react-router-pets/src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactRouter from 'react-router' 3 | 4 | import App from './components/App' 5 | import PetList from './components/PetList' 6 | import PetView from './components/PetView' 7 | 8 | let Route = ReactRouter.Route 9 | 10 | let routes = ( 11 | 12 | 13 | 14 | 15 | ) 16 | 17 | module.exports = routes 18 | -------------------------------------------------------------------------------- /react-router/react-router-pets/src/stores/PetStore.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | import PetActions from '../actions/PetActions' 3 | 4 | class PetStore { 5 | 6 | constructor() { 7 | this.pets = [] 8 | this.pet = {name: 'NO DOG', description: 'No dog description'} 9 | 10 | this.bindListeners({ 11 | sayHi: PetActions.SAY_HI, 12 | setPet: PetActions.SET_PET 13 | }); 14 | 15 | } 16 | 17 | sayHi(name) { 18 | /* not sure what this method does... could update a message */ 19 | } 20 | 21 | setPet(pet) { 22 | /* maybe update the current pet... */ 23 | } 24 | 25 | 26 | } 27 | 28 | module.exports = alt.createStore(PetStore, 'PetStore') -------------------------------------------------------------------------------- /react-router/react-router-pets/templates/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | link(rel='stylesheet', href='/public/css/pure-min.css') 5 | link(rel='stylesheet', href='/public/css/styles.css') 6 | body 7 | #app!= html 8 | script(src="/public/js/bundle.js") 9 | -------------------------------------------------------------------------------- /react-router/react-router-time/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /react-router/react-router-time/README.md: -------------------------------------------------------------------------------- 1 | # iso-react-router-flux 2 | 3 | > Isomorphic react application using flux and react-router 4 | 5 | ## Running This 6 | 7 | ```sh 8 | npm install; npm run build; npm start 9 | ``` 10 | 11 | Then open your browser to `localhost:8080` and enjoy. 12 | 13 | There are a few routes you can visit directly: 14 | 15 | `localhost:8080/hello` and `localhost:8080/time`. 16 | 17 | Hello will display a hello message whereas time will display the current time. 18 | 19 | For funsies you can include your own name as a parameter to hello: eg `localhost:8080/hello/jane` This will be seeded on the server and bootstrapped on the client. 20 | -------------------------------------------------------------------------------- /react-router/react-router-time/client.js: -------------------------------------------------------------------------------- 1 | import Iso from 'iso' 2 | import Router from 'react-router' 3 | import React from 'react' 4 | import routes from './src/routes' 5 | import alt from './src/alt' 6 | 7 | /* 8 | Once we bootstrap the stores, we run react-router using Router.HistoryLocation 9 | The element is created and we just render it into the container 10 | */ 11 | 12 | Iso.bootstrap(function (state, _, container) { 13 | alt.bootstrap(state) 14 | 15 | Router.run(routes, Router.HistoryLocation, function (Handler) { 16 | let node = React.createElement(Handler) 17 | React.render(node, container) 18 | }) 19 | }) -------------------------------------------------------------------------------- /react-router/react-router-time/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iso-react-router-flux", 3 | "version": "1.0.0", 4 | "description": "Isomorphic date/time application with react-router and flux", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "browserify --debug -t [babelify] client.js > public/js/bundle.js", 9 | "start": "babel-node server.js" 10 | }, 11 | "author": "Steven Iseki ", 12 | "license": "MIT", 13 | "dependencies": { 14 | "express": "^4.11.2", 15 | "serve-favicon": "^2.2.0", 16 | "jade": "^1.9.2", 17 | "alt": "^0.16.5", 18 | "iso": "^4.1.0", 19 | "react": "^0.13.3", 20 | "react-router": "^0.13.3" 21 | }, 22 | "devDependencies": { 23 | "babel": "^4.0.1", 24 | "babelify": "^5.0.3", 25 | "browserify": "^8.0.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /react-router/react-router-time/public/css/pure-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.6.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v^3.0 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-link,.pure-menu-heading{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-disabled,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} -------------------------------------------------------------------------------- /react-router/react-router-time/public/css/styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-time/public/css/styles.css -------------------------------------------------------------------------------- /react-router/react-router-time/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatslacker/isomorphic-react-examples/66167810249d7f4531179cf63144643fc09a5cae/react-router/react-router-time/public/favicon.ico -------------------------------------------------------------------------------- /react-router/react-router-time/server.js: -------------------------------------------------------------------------------- 1 | import Router from 'react-router' 2 | import React from 'react' 3 | import express from 'express' 4 | import Iso from 'iso' 5 | import favicon from 'serve-favicon' 6 | import path from 'path' 7 | import routes from './src/routes' 8 | import alt from './src/alt' 9 | 10 | let port = 8080 11 | let ip = '127.0.0.1' 12 | let app = express() 13 | 14 | app.set('view engine', 'jade') 15 | app.set('views', path.join(__dirname, 'templates')) 16 | app.use('/public', express.static(path.join(__dirname, 'public'))) 17 | app.use(favicon(__dirname + '/public/favicon.ico')); 18 | 19 | 20 | /* Simulate an asynchronous event that takes 300ms to complete */ 21 | function getNameFromServer(cb) { 22 | setTimeout(function () { 23 | cb('Server') 24 | }, 300) 25 | } 26 | 27 | 28 | /* 29 | Prior to running react-router we setup this route in order to handle data fetching. 30 | We can pass data fetched via express' locals. 31 | */ 32 | app.get('/hello/:name?', function (req, res, next) { 33 | if (req.params.name) { 34 | res.locals.data = { HelloStore: { name: req.params.name } } 35 | next() 36 | } else { 37 | getNameFromServer(function (name) { 38 | res.locals.data = { HelloStore: { name: name } } 39 | next() 40 | }) 41 | } 42 | }) 43 | 44 | app.get('/time', function (req, res, next) { 45 | res.locals.data = { TimeStore: { time: Date.now() } } 46 | next() 47 | }) 48 | 49 | 50 | /* 51 | This is where the magic happens... 52 | */ 53 | app.use(function (req, res) { 54 | 55 | // We take the locals data we have fetched and seed our stores with data 56 | alt.bootstrap(JSON.stringify(res.locals.data || {})) 57 | let iso = new Iso() 58 | 59 | // We use react-router to run the URL that is provided in routes.jsx 60 | 61 | Router.run(routes, req.url, function (Handler) { 62 | let content = React.renderToString(React.createElement(Handler)) 63 | 64 | // Use iso to render, picks back up on the client side and bootstraps the stores. 65 | iso.add(content, alt.flush()) 66 | res.render('layout', { html: iso.render() }) 67 | 68 | }) 69 | }) 70 | 71 | 72 | /* logging to the server */ 73 | app.listen(port,ip, function() { 74 | console.log("Go to " + ip + ":" + port) 75 | }); -------------------------------------------------------------------------------- /react-router/react-router-time/src/actions/HelloActions.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | 3 | class HelloActions { 4 | 5 | sayHi(name) { 6 | this.dispatch(name); 7 | } 8 | 9 | } 10 | 11 | module.exports = alt.createActions(HelloActions); -------------------------------------------------------------------------------- /react-router/react-router-time/src/alt.js: -------------------------------------------------------------------------------- 1 | import Alt from 'alt' 2 | 3 | module.exports = new Alt() -------------------------------------------------------------------------------- /react-router/react-router-time/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | let { RouteHandler, Link } = require('react-router') 3 | 4 | let App = React.createClass({ 5 | render() { 6 | return ( 7 |
    8 | Say hi 9 |
    10 | What time is it? 11 |
    12 | 13 |
    14 | ) 15 | } 16 | }) 17 | 18 | module.exports = App -------------------------------------------------------------------------------- /react-router/react-router-time/src/components/Hello.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import HelloStore from '../stores/HelloStore' 3 | import HelloActions from '../actions/HelloActions' 4 | 5 | let App = React.createClass({ 6 | 7 | getInitialState() { 8 | return HelloStore.getState() 9 | }, 10 | 11 | sayHi() { 12 | HelloActions.sayHi(' to the console') 13 | }, 14 | 15 | render() { 16 | return ( 17 |
    18 | {`Hello, ${this.state.name}`} 19 | 20 |
    21 | ); 22 | 23 | } 24 | }) 25 | 26 | module.exports = App 27 | -------------------------------------------------------------------------------- /react-router/react-router-time/src/components/Time.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TimeStore from '../stores/TimeStore' 3 | 4 | var App = React.createClass({ 5 | 6 | getInitialState() { 7 | return TimeStore.getState() 8 | }, 9 | 10 | render() { 11 | return
    {`The time is now: ${this.state.time}`}
    12 | } 13 | }) 14 | 15 | module.exports = App 16 | -------------------------------------------------------------------------------- /react-router/react-router-time/src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactRouter from 'react-router' 3 | import App from './components/App' 4 | import Hello from './components/Hello' 5 | import Time from './components/Time' 6 | 7 | let Route = ReactRouter.Route 8 | 9 | let routes = ( 10 | 11 | 12 | 13 | 14 | ) 15 | 16 | module.exports = routes 17 | -------------------------------------------------------------------------------- /react-router/react-router-time/src/stores/HelloStore.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | import HelloActions from '../actions/HelloActions' 3 | 4 | class HelloStore { 5 | 6 | constructor() { 7 | 8 | this.name = 'Nobody' 9 | 10 | this.bindListeners({ 11 | sayHi: HelloActions.SAY_HI 12 | }); 13 | 14 | } 15 | 16 | sayHi(text) { 17 | console.log('hey there ' + this.name + text) 18 | } 19 | 20 | } 21 | 22 | module.exports = alt.createStore(HelloStore, 'HelloStore') 23 | 24 | -------------------------------------------------------------------------------- /react-router/react-router-time/src/stores/TimeStore.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | 3 | class TimeStore { 4 | 5 | constructor() { 6 | this.time = Date.now() 7 | } 8 | 9 | } 10 | 11 | module.exports = alt.createStore(TimeStore, 'TimeStore') -------------------------------------------------------------------------------- /react-router/react-router-time/templates/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | link(rel='stylesheet', href='/public/css/pure-min.css') 5 | link(rel='stylesheet', href='/public/css/styles.css') 6 | body 7 | #app!= html 8 | script(src="/public/js/bundle.js") 9 | -------------------------------------------------------------------------------- /todos/README.md: -------------------------------------------------------------------------------- 1 | # todos-flux-iso 2 | 3 | > Isomorphic react application using flux. 4 | 5 | ## Running This 6 | 7 | ```sh 8 | npm install; npm run build; npm start 9 | ``` 10 | 11 | Then open your browser to `localhost:8080` and enjoy. 12 | 13 | ## What 14 | 15 | The purpose of this application is to show how an isomorphic react application using flux works with [iso](https://github.com/goatslacker/iso). 16 | 17 | For this guide we’ll be creating a very simple todo application. Let’s get started. 18 | 19 | ## License 20 | 21 | [MIT](http://josh.mit-license.org/) 22 | -------------------------------------------------------------------------------- /todos/client.js: -------------------------------------------------------------------------------- 1 | let Iso = require('iso') 2 | let React = require('react') 3 | let AltIsomorphicElement = require('./src/components/AltIsomorphicElement') 4 | 5 | Iso.on('react', true, function (props, _, node) { 6 | React.render(React.createElement(AltIsomorphicElement, props), node) 7 | }) 8 | -------------------------------------------------------------------------------- /todos/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todos", 3 | "description": "An isomorphic todo list example using alt and iso", 4 | "main": ".", 5 | "dependencies": { 6 | "express": "^4.11.2", 7 | "jade": "^1.9.2", 8 | "react": "^0.13.3", 9 | "alt": "^0.16.5", 10 | "iso": "^4.1.0" 11 | }, 12 | "devDependencies": { 13 | "babel": "^4.0.1", 14 | "babelify": "^5.0.3", 15 | "browserify": "^8.0.3" 16 | }, 17 | "scripts": { 18 | "test": "echo \"Error: no test specified\" && exit 1", 19 | "build": "browserify --debug -t [babelify] client.js > public/js/bundle.js", 20 | "start": "babel-node server.js" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/goatslacker/isomorphic-react-examples" 25 | }, 26 | "keywords": [ 27 | "isomorphic", 28 | "javascript", 29 | "iso", 30 | "flux", 31 | "alt", 32 | "react" 33 | ], 34 | "author": "Steven Iseki ", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/goatslacker/isomorphic-react-examples/issues" 38 | }, 39 | "homepage": "https://github.com/goatslacker/isomorphic-react-examples" 40 | } 41 | -------------------------------------------------------------------------------- /todos/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import React from 'react' 3 | import AltIsomorphicElement from './src/components/AltIsomorphicElement' 4 | import Iso from 'iso' 5 | 6 | let port = 8080 7 | let ip = '127.0.0.1' 8 | let app = express() 9 | let path = require('path') 10 | app.use('/public', express.static(path.join(__dirname, 'public'))) 11 | app.set('views', path.join(__dirname, 'templates')); 12 | app.set('view engine', 'jade') 13 | 14 | 15 | /* Simulate an asynchronous event to retrieve products from storage. */ 16 | function getTodosFromServer(cb) { 17 | setTimeout(function () { 18 | 19 | let mockData = [ 20 | {'text':'Todo 1', 'complete': true}, 21 | {'text': 'Todo 2', 'complete': false} 22 | ]; 23 | 24 | cb(mockData) 25 | 26 | }, 500) 27 | } 28 | 29 | 30 | /* just the one simple route which displays a todo list */ 31 | app.get('/', function (req, res) { 32 | 33 | getTodosFromServer( function (todos) { 34 | 35 | let data = { 36 | TodoStore: { 37 | todos: todos 38 | } 39 | } 40 | 41 | let node = React.createElement(AltIsomorphicElement, { altStores: data }) 42 | 43 | res.render('layout', { 44 | html: Iso.render(React.renderToString(node), { altStores: data }, { react: true }) 45 | }) 46 | 47 | }); 48 | 49 | }) 50 | 51 | /* logging to the server */ 52 | app.listen(port,ip, function() { 53 | console.log("Go to " + ip + ":" + port); 54 | }); 55 | -------------------------------------------------------------------------------- /todos/src/actions/TodoActions.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | 3 | class TodoActions { 4 | 5 | changeTodoText(text) { 6 | this.dispatch(text); 7 | } 8 | 9 | addTodo() { 10 | this.dispatch(); 11 | } 12 | 13 | resetTodo() { 14 | this.dispatch(); 15 | } 16 | 17 | toggleTodo(todo) { 18 | this.dispatch(todo); 19 | } 20 | 21 | clearCompleteTodos() { 22 | this.dispatch(); 23 | } 24 | 25 | } 26 | 27 | module.exports = alt.createActions(TodoActions); -------------------------------------------------------------------------------- /todos/src/alt.js: -------------------------------------------------------------------------------- 1 | let Alt = require('alt') 2 | module.exports = new Alt() -------------------------------------------------------------------------------- /todos/src/components/AltIsomorphicElement.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import alt from '../alt' 3 | import IsomorphicMixin from 'alt/mixins/IsomorphicMixin' 4 | import AltContainer from 'alt/AltContainer' 5 | 6 | import TodoList from './controller-views/TodoList' 7 | import TodoStore from '../stores/TodoStore' 8 | 9 | module.exports = React.createClass({ 10 | mixins: [IsomorphicMixin.create(alt)], 11 | 12 | render: function () { 13 | 14 | return ( 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | }) 22 | -------------------------------------------------------------------------------- /todos/src/components/controller-views/TodoList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TodoStore from '../../stores/TodoStore' 3 | import TodoActions from '../../actions/TodoActions' 4 | import TodoItem from '../todo-item' 5 | 6 | 7 | let TodoList = React.createClass({ 8 | 9 | propTypes: { 10 | todos: React.PropTypes.array.isRequired 11 | }, 12 | 13 | changeTodoText(e) { 14 | TodoActions.changeTodoText(e.target.value); 15 | }, 16 | 17 | submitTodo(e) { 18 | e.preventDefault(); 19 | 20 | if (this.props.newTodoText.trim()) { 21 | TodoActions.addTodo() 22 | TodoActions.resetTodo() 23 | } 24 | }, 25 | 26 | clearCompleteTodos(e) { 27 | TodoActions.clearCompleteTodos(location) 28 | }, 29 | 30 | render() { 31 | 32 | return ( 33 | 34 |
    35 | 36 |

    Todos

    37 | 38 |
      39 | { this.props.todos.map(function(todo, i) { 40 | return ( 41 |
    • 42 | 43 |
    • 44 | ); 45 | }) 46 | } 47 |
    48 | 49 |
    50 | 53 | 54 | 55 | 56 |
    57 | 58 | 59 | 60 |
    61 | 62 | ); 63 | } 64 | }); 65 | 66 | module.exports = TodoList; -------------------------------------------------------------------------------- /todos/src/components/todo-item.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | import React from 'react'; 3 | import TodoActions from '../actions/TodoActions' 4 | 5 | let TodoItem = React.createClass({ 6 | 7 | propTypes: { 8 | todo: React.PropTypes.object.isRequired 9 | }, 10 | 11 | render: function() { 12 | var style = { 13 | textDecoration: this.props.todo.complete ? "line-through" : "none", 14 | color: this.props.todo.complete ? "red" : "black" 15 | }; 16 | 17 | return {this.props.todo.text}; 18 | }, 19 | 20 | onClick: function() { 21 | TodoActions.toggleTodo(this.props.todo) 22 | } 23 | }); 24 | 25 | module.exports = TodoItem; -------------------------------------------------------------------------------- /todos/src/stores/TodoStore.js: -------------------------------------------------------------------------------- 1 | import alt from '../alt' 2 | import TodoActions from '../actions/TodoActions' 3 | 4 | class TodoStore { 5 | 6 | constructor() { 7 | 8 | this.todos = [] 9 | this.newTodoText = '' 10 | 11 | this.bindListeners({ 12 | changeTodoText: TodoActions.CHANGE_TODO_TEXT, 13 | addTodo: TodoActions.ADD_TODO, 14 | resetTodo: TodoActions.RESET_TODO, 15 | toggleTodo: TodoActions.TOGGLE_TODO, 16 | clearCompleteTodos: TodoActions.CLEAR_COMPLETE_TODOS 17 | }); 18 | 19 | } 20 | 21 | changeTodoText(text) { 22 | this.newTodoText = text; 23 | } 24 | 25 | addTodo() { 26 | this.todos.push({text: this.newTodoText, complete: false}); 27 | } 28 | 29 | resetTodo() { 30 | this.newTodoText = ''; 31 | } 32 | 33 | toggleTodo(todo) { 34 | todo.complete = !todo.complete; 35 | } 36 | 37 | clearCompleteTodos() { 38 | this.todos = this.todos.filter(function(todo) { 39 | return !todo.complete; 40 | }); 41 | } 42 | 43 | } 44 | 45 | module.exports = alt.createStore(TodoStore, 'TodoStore'); -------------------------------------------------------------------------------- /todos/templates/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | body 4 | #app!= html 5 | script(src="/public/js/bundle.js") 6 | --------------------------------------------------------------------------------