ff-stream-web

git clone git://archive.git.mtrnord.blog/MTRNord/ff-stream-web.git
Log | Files | Refs | README | LICENSE

commit baf9395cadaeeb4eb76470049f8f454709a040bf
parent 1493b0e8bbc4d997d284d6a3cecd1142e29389b5
Author: Konstantin Tarkus <hello@tarkus.me>
Date:   Tue, 11 Nov 2014 21:30:46 +0300

Add PageActions, PageStore; specify layouts by using static properties

Diffstat:
Mgulpfile.js | 50++++++++++++++++++++++++++++++++++++++++++++------
Mpackage.json | 2+-
Msrc/AppDispatcher.js | 15+++++++++++----
Asrc/actions/PageActions.js | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/actions/RouteActions.js | 10+++++++++-
Msrc/app.js | 53++++++++++++++++++++++++++++++++---------------------
Msrc/components/Link.js | 8++++++++
Msrc/components/Navbar.js | 8++++++++
Msrc/components/__tests__/Navbar-test.js | 8++++++++
Msrc/constants/ActionTypes.js | 15++++++++++++++-
Msrc/constants/PayloadSources.js | 10++++++++++
Asrc/constants/Settings.js | 15+++++++++++++++
Msrc/layouts/DefaultLayout.js | 52+++++++++++++++++++++++++++++++++++++++++-----------
Msrc/pages/Index.js | 23++++++++++++++++++-----
Msrc/pages/Privacy.js | 37+++++++++++++++++++++++++------------
Asrc/stores/PageStore.js | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
16 files changed, 376 insertions(+), 62 deletions(-)

diff --git a/gulpfile.js b/gulpfile.js @@ -1,6 +1,9 @@ -/*! - * Facebook React Starter Kit | https://github.com/kriasoft/react-starter-kit - * Copyright (c) KriaSoft, LLC. All rights reserved. See LICENSE.txt +/* + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. */ 'use strict'; @@ -16,6 +19,9 @@ var runSequence = require('run-sequence'); var webpack = require('webpack'); var browserSync = require('browser-sync'); var pagespeed = require('psi'); +var fs = require('fs'); +var url = require('url'); +var ReactTools = require('react-tools'); var argv = require('minimist')(process.argv.slice(2)); // Settings @@ -47,6 +53,20 @@ var pkgs = (function() { return pkgs; }()); +// Configure JSX Harmony transform in order to be able +// require .js files with JSX (see 'pages' task) +var originalJsTransform = require.extensions['.js']; +var reactTransform = function(module, filename) { + if (filename.indexOf('node_modules') === -1) { + var src = fs.readFileSync(filename, {encoding: 'utf8'}); + src = ReactTools.transform(src, {harmony: true}); + module._compile(src, filename); + } else { + originalJsTransform(module, filename); + } +}; +require.extensions['.js'] = reactTransform; + // The default task gulp.task('default', ['serve']); @@ -88,8 +108,28 @@ gulp.task('images', function() { // HTML pages gulp.task('pages', function() { src.pages = ['src/pages/**/*.js', 'src/pages/404.html']; - var render = $.render({template: './src/pages/_template.html'}) + + var data = {}; + var ActionTypes = require('./src/constants/ActionTypes'); + var AppDispatcher = require('./src/AppDispatcher'); + + // Capture document.title changes + AppDispatcher.register(function(payload) { + switch (payload.action.actionType) + { + case ActionTypes.SET_PAGE_TITLE: + data.title = payload.action.text; + break; + } + return true; + }); + + var render = $.render({ + template: './src/pages/_template.html', + data: function() { return data; } + }) .on('error', function(err) { console.log(err); render.end(); }); + return gulp.src(src.pages) .pipe($.changed(DEST, {extension: '.html'})) .pipe($.if('*.js', render)) @@ -154,8 +194,6 @@ gulp.task('build', ['clean'], function(cb) { // Launch a lightweight HTTP Server gulp.task('serve', function(cb) { - var url = require('url'); - var fs = require('fs'); watch = true; runSequence('build', function() { diff --git a/package.json b/package.json @@ -30,7 +30,7 @@ "gulp-load-plugins": "^0.7.1", "gulp-minify-css": "^0.3.11", "gulp-plumber": "^0.6.6", - "gulp-render": "^0.1.1", + "gulp-render": "^0.2.0", "gulp-replace": "^0.5.0", "gulp-size": "^1.1.0", "gulp-uglify": "^1.0.1", diff --git a/src/AppDispatcher.js b/src/AppDispatcher.js @@ -1,6 +1,9 @@ /* - * A singleton that operates as the central hub for application updates. - * For more information visit https://facebook.github.io/flux/ + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. */ 'use strict'; @@ -9,6 +12,10 @@ var {Dispatcher} = require('flux'); var PayloadSources = require('./constants/PayloadSources'); var assign = require('object-assign'); +/** + * A singleton that operates as the central hub for application updates. + * For more information visit https://facebook.github.io/flux/ + */ var AppDispatcher = assign(new Dispatcher(), { /** @@ -18,7 +25,7 @@ var AppDispatcher = assign(new Dispatcher(), { handleServerAction(action) { var payload = { source: PayloadSources.SERVER_ACTION, - action + action: action }; this.dispatch(payload); }, @@ -30,7 +37,7 @@ var AppDispatcher = assign(new Dispatcher(), { handleViewAction(action) { var payload = { source: PayloadSources.VIEW_ACTION, - action + action: action }; this.dispatch(payload); } diff --git a/src/actions/PageActions.js b/src/actions/PageActions.js @@ -0,0 +1,51 @@ +/* + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + +'use strict'; + +var AppDispatcher = require('../AppDispatcher'); +var ActionTypes = require('../constants/ActionTypes'); + +var PageActions = { + + /** + * Set a title for the current page. + * @param {string} text The text to be set as a page title. + */ + setTitle(text) { + AppDispatcher.handleViewAction({ + actionType: ActionTypes.SET_PAGE_TITLE, + text: text + }); + }, + + /** + * Set description for the current page. + * @param {string} text The text to be set as a page description. + */ + setDescription(text) { + AppDispatcher.handleViewAction({ + actionType: ActionTypes.SET_PAGE_DESC, + text: text + }); + }, + + /** + * Set keywords for the current page. + * @param {string} text The text to be set as page keywords. + */ + setKeywords(text) { + AppDispatcher.handleViewAction({ + actionType: ActionTypes.SET_PAGE_KEYWORDS, + text: text + }); + } + +}; + +module.exports = PageActions; diff --git a/src/actions/RouteActions.js b/src/actions/RouteActions.js @@ -1,3 +1,11 @@ +/* + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + 'use strict'; var AppDispatcher = require('../AppDispatcher'); @@ -12,7 +20,7 @@ var AppActions = { setRoute(route) { AppDispatcher.handleViewAction({ actionType: ActionTypes.SET_CURRENT_ROUTE, - route + route: route }); } diff --git a/src/app.js b/src/app.js @@ -1,32 +1,54 @@ -/*! - * Facebook React Starter Kit | https://github.com/kriasoft/react-starter-kit - * Copyright (c) KriaSoft, LLC. All rights reserved. See LICENSE.txt +/* + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. */ 'use strict'; var React = require('react'); +var ExecutionEnvironment = require('react/lib/ExecutionEnvironment'); var {Router} = require('director'); var AppDispatcher = require('./AppDispatcher'); var ActionTypes = require('./constants/ActionTypes'); -var assign = require('object-assign'); +var router; // Export React so the dev tools can find it (window !== window.top ? window.top : window).React = React; +AppDispatcher.register((payload) => { + + var action = payload.action; + + switch (action.actionType) + { + case ActionTypes.SET_CURRENT_ROUTE: + router.setRoute(action.route); + break; + + case ActionTypes.SET_PAGE_TITLE: + if (ExecutionEnvironment.canUseDOM) { + document.title = action.text; + } + break; + } + + return true; // No errors. Needed by promise in Dispatcher. +}); + /** * Check if Page component has a layout property; and if yes, wrap the page * into the specified layout, then mount to document.body. */ function render(page) { - var child = null, props = {}; - while (page.defaultProps && page.defaultProps.layout) { + var layout = null, child = null, props = {}; + while ((layout = page.type.layout || (page.defaultProps && page.defaultProps.layout))) { child = React.createElement(page, props, child); - assign(props, page.defaultProps); - page = page.defaultProps.layout; + page = layout; } React.render(React.createElement(page, props, child), document.body); - document.title = props.title; } // Define URL routes @@ -37,15 +59,4 @@ var routes = { }; // Initialize a router -var router = new Router(routes).configure({html5history: true}).init(); - -AppDispatcher.register((payload) => { - - var action = payload.action; - - if (action.actionType === ActionTypes.SET_CURRENT_ROUTE) { - router.setRoute(action.route); - } - - return true; // No errors. Needed by promise in Dispatcher. -}); +router = new Router(routes).configure({html5history: true}).init(); diff --git a/src/components/Link.js b/src/components/Link.js @@ -1,3 +1,11 @@ +/* + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + 'use strict'; var React = require('react'); diff --git a/src/components/Navbar.js b/src/components/Navbar.js @@ -1,3 +1,11 @@ +/* + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + 'use strict'; var React = require('react'); diff --git a/src/components/__tests__/Navbar-test.js b/src/components/__tests__/Navbar-test.js @@ -1,3 +1,11 @@ +/* + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + /* global jest, describe, it, expect */ 'use strict'; diff --git a/src/constants/ActionTypes.js b/src/constants/ActionTypes.js @@ -1,6 +1,19 @@ +/* + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + module.exports = { // Route action types - SET_CURRENT_ROUTE: 'SET_CURRENT_ROUTE' + SET_CURRENT_ROUTE: 'SET_CURRENT_ROUTE', + + // Page action types + SET_PAGE_TITLE: 'SET_PAGE_TITLE', + SET_PAGE_DESC: 'SET_PAGE_DESC', + SET_PAGE_KEYWORDS: 'SET_PAGE_KEYWORDS' }; diff --git a/src/constants/PayloadSources.js b/src/constants/PayloadSources.js @@ -1,4 +1,14 @@ +/* + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + module.exports = { + VIEW_ACTION: 'VIEW_ACTION', SERVER_ACTION: 'SERVER_ACTION' + }; diff --git a/src/constants/Settings.js b/src/constants/Settings.js @@ -0,0 +1,15 @@ +/* + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + +module.exports = { + + title: 'React.js Starter Kit', + description: 'A skeleton for an isomorphic web application (SPA) built with React.js and Flux', + keywords: null + +}; diff --git a/src/layouts/DefaultLayout.js b/src/layouts/DefaultLayout.js @@ -1,25 +1,48 @@ +/* + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + 'use strict'; var React = require('react'); +var PageStore = require('../stores/PageStore'); var Link = require('../components/Link'); var Navbar = require('../components/Navbar'); +/** + * Retrieve the current page metadata from the PageStore. + * @returns {{title: string}} + */ +function getState() { + return { + title: PageStore.getCurrentPage().title + }; +} + var DefaultLayout = React.createClass({ - propTypes: { - title: React.PropTypes.string, - breadcrumb: React.PropTypes.element + + getInitialState() { + return getState(); }, - getDefaultProps() { - return { - title: 'React.js Starter Kit', - description: 'A skeleton for an isomorphic web application (SPA) built with React.js and Flux' - }; + + componentDidMount() { + PageStore.addEventListener(this._onChange); + this._onChange(); }, + + componentWillUnmount() { + PageStore.removeEventListener(this._onChange); + }, + render() { - var header = this.props.breadcrumb ? ( + var header = this.props.children.type.breadcrumb ? ( <div className="container"> - <h2>{this.props.title}</h2> - {this.props.breadcrumb} + <h2>{this.state.title}</h2> + {this.props.children.type.breadcrumb} </div> ) : ( <div className="jumbotron"> @@ -46,6 +69,13 @@ var DefaultLayout = React.createClass({ </div> </div> ); + }, + + /** + * Event handler for 'change' events coming from the PageStore + */ + _onChange() { + this.setState(getState()); } }); diff --git a/src/pages/Index.js b/src/pages/Index.js @@ -1,15 +1,27 @@ +/* + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + 'use strict'; var React = require('react'); +var PageActions = require('../actions/PageActions'); var DefaultLayout = require('../layouts/DefaultLayout'); var HomePage = React.createClass({ - getDefaultProps() { - return { - title: 'React.js Starter Kit', - layout: DefaultLayout - }; + + statics: { + layout: DefaultLayout + }, + + componentWillMount() { + PageActions.setTitle('React.js Starter Kit'); }, + render() { return ( <div className="container"> @@ -44,6 +56,7 @@ var HomePage = React.createClass({ </div> ); } + }); module.exports = HomePage; diff --git a/src/pages/Privacy.js b/src/pages/Privacy.js @@ -1,22 +1,34 @@ +/* + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + 'use strict'; var React = require('react'); -var Link = require('../components/Link'); +var PageActions = require('../actions/PageActions'); var DefaultLayout = require('../layouts/DefaultLayout'); +var Link = require('../components/Link'); var PrivacyPage = React.createClass({ - getDefaultProps() { - return { - title: 'Privacy Policy', - layout: DefaultLayout, - breadcrumb: ( - <ol className="breadcrumb"> - <li><Link to="/">Home</Link></li> - <li className="active">Privacy</li> - </ol> - ) - }; + + statics: { + layout: DefaultLayout, + breadcrumb: ( + <ol className="breadcrumb"> + <li><Link to="/">Home</Link></li> + <li className="active">Privacy</li> + </ol> + ) }, + + componentWillMount() { + PageActions.setTitle('Privacy Policy'); + }, + render() { return ( <div className="container"> @@ -63,6 +75,7 @@ var PrivacyPage = React.createClass({ </div> ); } + }); module.exports = PrivacyPage; diff --git a/src/stores/PageStore.js b/src/stores/PageStore.js @@ -0,0 +1,81 @@ +/* + * React.js Starter Kit + * Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + +'use strict'; + +var AppDispatcher = require('../AppDispatcher'); +var ActionTypes = require('../constants/ActionTypes'); +var Settings = require('../constants/Settings'); +var EventEmitter = require('events').EventEmitter; +var assign = require('object-assign'); + +var CHANGE_EVENT = 'change'; + +var page = { + title: null, + description: null, + keywords: null +}; + +var PageStore = assign({}, EventEmitter.prototype, { + + /** + * Get metadata associated with the current page. + * @returns {object} + */ + getCurrentPage() { + return { + title: page.title || Settings.title, + description: page.description || Settings.description, + keywords: page.keywords || Settings.keywords + }; + }, + + emitChange() { + this.emit(CHANGE_EVENT); + }, + + addEventListener(callback) { + this.on(CHANGE_EVENT, callback); + }, + + removeEventListener(callback) { + this.removeListener(CHANGE_EVENT, callback); + } + +}); + + +AppDispatcher.register(function(payload) { + + var action = payload.action; + + switch (action.actionType) + { + case ActionTypes.SET_PAGE_TITLE: + page.title = action.text; + break; + + case ActionTypes.SET_PAGE_DESC: + page.description = action.description; + break; + + case ActionTypes.SET_PAGE_KEYWORDS: + page.keywords = action.keywords; + break; + + default: + return true; + } + + PageStore.emitChange(); + + return true; // No errors. Needed by promise in Dispatcher. +}); + +module.exports = PageStore;