├── public
├── javascripts
│ ├── noop.js
│ ├── common
│ │ ├── string-utils.js
│ │ ├── http-error.js
│ │ ├── number-utils.js
│ │ ├── ajax.js
│ │ └── metered-request.js
│ ├── services
│ │ ├── index.js
│ │ └── api-subscriptions.js
│ ├── mixins
│ │ ├── navigation-utils.js
│ │ ├── store-change.js
│ │ └── overlay.js
│ ├── components
│ │ ├── route-not-found.jsx
│ │ ├── app.jsx
│ │ ├── overlays
│ │ │ ├── overlay-manager.jsx
│ │ │ ├── alert.jsx
│ │ │ ├── confirm.jsx
│ │ │ └── item-form.jsx
│ │ ├── nav-bar.jsx
│ │ ├── server-time.jsx
│ │ └── items.jsx
│ ├── stores
│ │ ├── index.js
│ │ ├── items.js
│ │ ├── overlays.js
│ │ ├── server-time.js
│ │ ├── base.js
│ │ └── crud-base.js
│ ├── routes.jsx
│ ├── dispatcher.js
│ ├── constants
│ │ ├── states.js
│ │ └── actions.js
│ ├── actions
│ │ ├── items.js
│ │ ├── server-time.js
│ │ ├── overlays.js
│ │ ├── base.js
│ │ └── crud-base.js
│ ├── main.jsx
│ └── vendor
│ │ └── flux
│ │ ├── invariant.js
│ │ └── Dispatcher.js
├── images
│ └── favicon.ico
├── fonts
│ └── bootstrap
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.ttf
│ │ ├── glyphicons-halflings-regular.woff
│ │ ├── glyphicons-halflings-regular.woff2
│ │ └── glyphicons-halflings-regular.svg
└── less
│ └── styles.less
├── views
├── error.ejs
└── index.ejs
├── gulp
├── tasks
│ ├── default.js
│ ├── build.js
│ ├── browserify-main.js
│ ├── browserify-vendor.js
│ ├── bootstrap.js
│ ├── watch.js
│ └── less.js
├── util
│ ├── handleErrors.js
│ ├── bundleLogger.js
│ └── bundler.js
└── config.js
├── .gitignore
├── gulpfile.js
├── routes
├── index.js
└── api.js
├── LICENSE
├── README.md
├── app.js
├── package.json
├── bin
└── www
├── .jshintrc
└── pubsub-bridge.js
/public/javascripts/noop.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyric/react-flux-starter/master/public/images/favicon.ico
--------------------------------------------------------------------------------
/views/error.ejs:
--------------------------------------------------------------------------------
1 |
<%= message %>
2 | <%= error.status %>
3 | <%= error.stack %>
4 |
--------------------------------------------------------------------------------
/gulp/tasks/default.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 |
5 | gulp.task('default', ['watch']);
6 |
--------------------------------------------------------------------------------
/gulp/tasks/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 |
5 | gulp.task('build', ['main.js', 'vendor.js', 'less']);
6 |
--------------------------------------------------------------------------------
/public/fonts/bootstrap/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyric/react-flux-starter/master/public/fonts/bootstrap/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/public/fonts/bootstrap/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyric/react-flux-starter/master/public/fonts/bootstrap/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/public/fonts/bootstrap/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyric/react-flux-starter/master/public/fonts/bootstrap/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/public/fonts/bootstrap/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyric/react-flux-starter/master/public/fonts/bootstrap/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/public/javascripts/common/string-utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var validator = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i;
4 |
5 | module.exports = {
6 | isValidEmail: function (s) {
7 | return validator.test(s);
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/public/javascripts/services/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var apiSubscriptionSrvc = require('./api-subscriptions');
4 |
5 | exports.initialize = function (accessToken) {
6 | exports.apiSubscriptions = apiSubscriptionSrvc(accessToken);
7 | };
8 |
--------------------------------------------------------------------------------
/public/less/styles.less:
--------------------------------------------------------------------------------
1 | // Define any overrides of bootstrap before importing
2 | //
3 | @icon-font-path: "../fonts/bootstrap/";
4 |
5 | // include bootstrap
6 | @import 'node_modules/bootstrap/less/bootstrap.less';
7 |
8 | // place your app CSS here or in a separate .less file
9 |
--------------------------------------------------------------------------------
/gulp/tasks/browserify-main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 | var config = require('../config').browserify;
5 | var bundler = require('../util/bundler');
6 |
7 | gulp.task('main.js', function(callback) {
8 | bundler(config.bundleConfigs.main)
9 | .on('end', callback);
10 | });
11 |
--------------------------------------------------------------------------------
/gulp/tasks/browserify-vendor.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 | var config = require('../config').browserify;
5 | var bundler = require('../util/bundler');
6 |
7 | gulp.task('vendor.js', function(callback) {
8 | bundler(config.bundleConfigs.vendor)
9 | .on('end', callback);
10 | });
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Mr Developer
2 | .DS_Store
3 |
4 | #Emacs cruft
5 | *~
6 |
7 | # node modules
8 | node_modules/
9 |
10 | # npm debug log
11 | npm-debug.log
12 |
13 | # app specific files that are generated as part of the asset pipeline
14 | #public/fonts/bootstrap
15 | #public/javascripts/bundles/
16 | #public/stylesheets/
17 |
--------------------------------------------------------------------------------
/public/javascripts/mixins/navigation-utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var History = require('react-router').History;
4 |
5 | module.exports = {
6 |
7 | backOrTransitionTo: function (routeName, params) {
8 | if (History.length > 1) {
9 | this.goBack();
10 | }
11 | else {
12 | this.transitionTo(routeName, params);
13 | }
14 | }
15 |
16 | };
17 |
--------------------------------------------------------------------------------
/gulp/util/handleErrors.js:
--------------------------------------------------------------------------------
1 | var notify = require("gulp-notify");
2 |
3 | module.exports = function() {
4 |
5 | var args = Array.prototype.slice.call(arguments);
6 |
7 | // Send error to notification center with gulp-notify
8 | notify.onError({
9 | title: "Compile Error",
10 | message: "<%= error.message %>"
11 | }).apply(this, args);
12 |
13 | // Keep gulp from hanging on this task
14 | this.emit('end');
15 | };
16 |
--------------------------------------------------------------------------------
/public/javascripts/components/route-not-found.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react/addons');
4 |
5 | module.exports = React.createClass({
6 | render: function () {
7 | return (
8 |
9 |
10 |
11 | Route Not Found
12 |
13 |
14 |
15 | );
16 | }
17 | });
18 |
--------------------------------------------------------------------------------
/gulp/tasks/bootstrap.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp'),
4 | path = require('path'),
5 | config = require('../config').bootstrap;
6 |
7 |
8 | // copy bootstrap fonts into the public folder
9 | gulp.task('bootstrap', function () {
10 |
11 | /* BOOTSTRAP */
12 |
13 | // copy over all the fonts
14 | gulp.src(path.join(config.bootstrapHome, 'fonts/**/*'))
15 | .pipe(gulp.dest(config.fonts));
16 |
17 | });
18 |
--------------------------------------------------------------------------------
/public/javascripts/common/http-error.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | class HTTPError extends Error {
4 | constructor(url, xhr, status, err, response) {
5 | this.url = url;
6 | this.xhr = xhr;
7 | this.status = status;
8 | this.error = err;
9 | this.response = response;
10 | }
11 |
12 | toString() {
13 | return `${this.constructor.name} (status=${this.xhr.status}, url=${this.url})`;
14 | }
15 | }
16 |
17 | module.exports = HTTPError;
18 |
--------------------------------------------------------------------------------
/gulp/tasks/watch.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* Notes: ??? NEEDS TO BE UPDATED TO USE WATCHIFY
4 | - gulp/tasks/browserify.js handles js recompiling with watchify
5 | - gulp/tasks/browserSync.js watches and reloads compiled files
6 | */
7 |
8 | var gulp = require('gulp');
9 | var config = require('../config');
10 |
11 | gulp.task('watch', ['build'], function() {
12 | gulp.watch(config.less.watch, ['less']);
13 | gulp.watch(config.browserify.bundleConfigs.main.watch, ['main.js']);
14 | });
15 |
--------------------------------------------------------------------------------
/public/javascripts/stores/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Dispatcher = require('../dispatcher');
4 |
5 | var ItemsStore = require('./items'),
6 | ServerTimeStore = require('./server-time'),
7 | OverlaysStore = require('./overlays');
8 |
9 | exports.initialize = function () {
10 |
11 | exports.ItemsStore = new ItemsStore(Dispatcher);
12 | exports.ServerTimeStore = new ServerTimeStore(Dispatcher);
13 |
14 | exports.OverlaysStore = new OverlaysStore(Dispatcher);
15 |
16 | return this;
17 | };
18 |
--------------------------------------------------------------------------------
/gulp/tasks/less.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp'),
4 | less = require('gulp-less'),
5 | autoprefixer = require('gulp-autoprefixer'),
6 | sourcemaps = require('gulp-sourcemaps'),
7 | handleErrors = require('../util/handleErrors'),
8 | config = require('../config').less;
9 |
10 | gulp.task('less', function() {
11 | return gulp.src(config.src)
12 | .pipe(sourcemaps.init())
13 | .pipe(less())
14 | .on('error', handleErrors)
15 | .pipe(autoprefixer({cascade: false, browsers: ['last 2 versions']}))
16 | .pipe(sourcemaps.write())
17 | .pipe(gulp.dest(config.dest));
18 | });
19 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /*
2 | gulpfile.js
3 | ===========
4 | Rather than manage one giant configuration file responsible
5 | for creating multiple tasks, each task has been broken out into
6 | its own file in gulp/tasks. Any files in that directory get
7 | automatically required below.
8 | To add a new task, simply add a new task file in that directory.
9 | gulp/tasks/default.js specifies the default set of tasks to run
10 | when you run `gulp`.
11 | */
12 |
13 | var requireDir = require('require-dir');
14 |
15 | // Require all tasks in gulp/tasks, including subfolders
16 | requireDir('./gulp/tasks', { recurse: true });
17 |
--------------------------------------------------------------------------------
/public/javascripts/routes.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react/addons');
4 |
5 | var Router = require('react-router'),
6 | Route = Router.Route,
7 | NotFoundRoute = Router.NotFoundRoute,
8 | DefaultRoute = Router.Route;
9 |
10 | module.exports = (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
--------------------------------------------------------------------------------
/gulp/util/bundleLogger.js:
--------------------------------------------------------------------------------
1 | /* bundleLogger
2 | ------------
3 | Provides gulp style logs to the bundle method in browserify.js
4 | */
5 |
6 | var gutil = require('gulp-util');
7 | var prettyHrtime = require('pretty-hrtime');
8 | var startTime;
9 |
10 | module.exports = {
11 | start: function(filepath) {
12 | startTime = process.hrtime();
13 | gutil.log('Bundling', gutil.colors.green(filepath) + '...');
14 | },
15 |
16 | end: function(filepath) {
17 | var taskTime = process.hrtime(startTime);
18 | var prettyTime = prettyHrtime(taskTime);
19 | gutil.log('Bundled', gutil.colors.green(filepath), 'in', gutil.colors.magenta(prettyTime));
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/public/javascripts/components/app.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // bootstrap initialization
4 | var $ = require('jquery');
5 | window.jQuery = $;
6 | require('bootstrap');
7 |
8 | var React = require('react/addons');
9 |
10 | var Router = require('react-router'),
11 | RouteHandler = Router.RouteHandler;
12 |
13 | var NavBar = require('./nav-bar.jsx'),
14 | OverlayManager = require('./overlays/overlay-manager.jsx');
15 |
16 | module.exports = React.createClass({
17 |
18 | mixins: [Router.State],
19 |
20 | render: function () {
21 | return (
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 | });
30 |
--------------------------------------------------------------------------------
/public/javascripts/stores/items.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 |
5 | var CRUDBase = require('./crud-base');
6 |
7 | var ItemActions = require('../actions/items');
8 |
9 | /**
10 | * Basic CRUD store for a RESTful JSON "resource". Overriding "getAll" to add
11 | * sort order to resources lists.
12 | */
13 | class ItemsStore extends CRUDBase {
14 |
15 | // specify the action instance and action object identifier (and dispatcher)
16 | constructor(dispatcher) {
17 | super(dispatcher, ItemActions, 'ITEM');
18 | }
19 |
20 | getAll() {
21 | return _.sortBy(super.getAll(), item => item.data.last + item.data.first);
22 | }
23 |
24 | }
25 |
26 | module.exports = ItemsStore;
27 |
--------------------------------------------------------------------------------
/public/javascripts/common/number-utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var re = /\B(?=(\d{3})+(?!\d))/g;
4 |
5 | module.exports = {
6 | numberWithCommas: function (x, defaultValue='-') {
7 | if (!x) { return defaultValue; }
8 | return x.toString().replace(re, ",");
9 | },
10 |
11 | formatTimeForInterval: function (timestamp, interval="hour") {
12 | var result;
13 | switch (interval) {
14 | case "day":
15 | result = timestamp.format("ddd, MMM D, YYYY");
16 | break;
17 | case "hour":
18 | result = timestamp.format("ddd, MMM D, YYYY, h:mm A");
19 | break;
20 | default:
21 | // default is "minute"
22 | result = timestamp.format("ddd, MMM D, YYYY, h:mm:ss A");
23 | }
24 | return result;
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/public/javascripts/components/overlays/overlay-manager.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react/addons');
4 |
5 | var Stores = require('../../stores'),
6 | storeChangeMixin = require('../../mixins/store-change');
7 |
8 | /**
9 | * Basic manager for displaying overlays
10 | *
11 | * Only 1 overlay at a time can be displayed.
12 | *
13 | * Overlays are responsible for their own popping
14 | *
15 | */
16 |
17 | module.exports = React.createClass({
18 |
19 | mixins: [storeChangeMixin(Stores.OverlaysStore)],
20 |
21 | storeChange: function () {
22 | this.forceUpdate();
23 | },
24 |
25 | render: function () {
26 | var overlay = Stores.OverlaysStore.getTopOverlay();
27 | if (!overlay) { return null; }
28 | return overlay;
29 | }
30 |
31 | });
32 |
--------------------------------------------------------------------------------
/public/javascripts/dispatcher.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var Dispatcher = require('./vendor/flux/Dispatcher');
5 |
6 | module.exports = _.extend(new Dispatcher(), {
7 |
8 | /**
9 | * A bridge function between the views and the dispatcher, marking the action
10 | * as a view action.
11 | * @param {object} action The data coming from the view.
12 | */
13 | handleViewAction: function(action) {
14 | this.dispatch({
15 | source: 'VIEW_ACTION',
16 | action: action
17 | });
18 | },
19 |
20 | /**
21 | * A bridge function between the server and the dispatcher, marking the action
22 | * as a server action.
23 | * @param {object} action The data coming from the view.
24 | */
25 | handleServerAction: function(action) {
26 | this.dispatch({
27 | source: 'SERVER_ACTION',
28 | action: action
29 | });
30 | }
31 |
32 | });
33 |
--------------------------------------------------------------------------------
/public/javascripts/common/ajax.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Wrapper for $.ajax() that returns ES6 promises instead
3 | * of jQuery promises.
4 | * @module common/ajax
5 | */
6 |
7 | 'use strict';
8 |
9 | var $ = require('jquery');
10 |
11 | var HTTPError = require('./http-error');
12 |
13 |
14 | module.exports = function(opts) {
15 | var promise = new Promise(function(resolve, reject) {
16 | $.ajax(opts)
17 | .done(function(data) {
18 | resolve(data);
19 | })
20 | .fail(function(xhr, status, err) {
21 | var response;
22 | if (xhr.status === 0 && xhr.responseText === undefined) {
23 | response = {detail:'Possible CORS error; check your browser console for further details'};
24 | }
25 | else {
26 | response = xhr.responseJSON;
27 | }
28 |
29 | reject(new HTTPError(opts.url, xhr, status, err, response));
30 | });
31 | });
32 |
33 | return promise;
34 | };
35 |
--------------------------------------------------------------------------------
/public/javascripts/constants/states.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var keyMirror = require('react/lib/keyMirror');
4 |
5 | /**
6 | * Store entities need the concept of "new", "dirty", "deleted" (i.e. isNew, isDirty, isDelete) which
7 | * when combined with state (synced, request, errored) provides components good detail on how to
8 | * render
9 | *
10 | */
11 |
12 | module.exports = keyMirror({
13 |
14 | /* entity states */
15 | SYNCED: null, // entity is in sync with backend
16 | LOADING: null, // entity is in-process of being fetched from backend (implies GET)
17 | NEW: null, // entity is new and in-process of syncing with backend
18 | SAVING: null, // entity is dirty and in-process of syncing with backend
19 | DELETING: null, // entity has been deleted and in-process of syncing with backend
20 | ERRORED: null // entity is in an error state and potentially out-of-sync with server
21 |
22 | });
23 |
--------------------------------------------------------------------------------
/public/javascripts/stores/overlays.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 |
5 | var kActions = require('../constants/actions');
6 |
7 | var BaseStore = require('./base');
8 |
9 | var _handlers = _.zipObject([
10 | [kActions.OVERLAY_PUSH, '_onOverlayPush'],
11 | [kActions.OVERLAY_POP, '_onOverlayPop']
12 | ]);
13 |
14 |
15 | class OverlaysStore extends BaseStore {
16 |
17 | initialize() {
18 | this._overlays = [];
19 | }
20 |
21 | _getActions(){
22 | return _handlers;
23 | }
24 |
25 | getTopOverlay() {
26 | return this._overlays.length ? this._overlays[this._overlays.length - 1] : null;
27 | }
28 |
29 | // action handlers
30 | _onOverlayPush(payload) {
31 | this._overlays.push(payload.data.component);
32 | this.emitChange();
33 | }
34 |
35 | _onOverlayPop() {
36 | this._overlays.pop();
37 | this.emitChange();
38 | }
39 |
40 | }
41 |
42 | module.exports = OverlaysStore;
43 |
--------------------------------------------------------------------------------
/public/javascripts/actions/items.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var CRUDBase = require('./crud-base');
4 |
5 | /**
6 | * Basic CRUD actions for a RESTful JSON "resource". Overriding "post" and "put"
7 | * to create JSON payload that the endpoint expects.
8 | */
9 |
10 | class ItemActions extends CRUDBase {
11 |
12 | // specify the baseURL and action object identifier for dispatches
13 | constructor() {
14 | super('/api/items', 'ITEM');
15 | }
16 |
17 | // define "create" json payload appropriate for resource
18 | post (first, last) {
19 | var data = {
20 | first: first,
21 | last: last
22 | };
23 | return super.post(data);
24 | }
25 |
26 | // define "update" json payload appropriate for resource
27 | put (id, first, last) {
28 | var data = {
29 | id: id,
30 | first: first,
31 | last: last
32 | };
33 | super.put(id, data);
34 | }
35 |
36 | }
37 |
38 | module.exports = new ItemActions();
39 |
--------------------------------------------------------------------------------
/routes/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var express = require('express');
4 |
5 | var router = express.Router();
6 |
7 | /**
8 | * Standard handler for returning index template
9 | * @function
10 | */
11 | function indexRouteHandler (req, res) {
12 | res.render('index', {
13 | title: 'Example',
14 | token: req['heroku-bouncer'] && req['heroku-bouncer'].token || '',
15 | herokuId: req['heroku-bouncer'] && req['heroku-bouncer'].id || ''
16 | });
17 | }
18 |
19 | router.get('/api/users/me', function (req, res) {
20 | res.json(req['heroku-bouncer']);
21 | });
22 |
23 | router.get('/api/items', function (req, res) {
24 | res.json([
25 | {id: 1, first: 'Howard', last: 'Burrows'},
26 | {id: 2, first: 'David', last: 'Gouldin'},
27 | {id: 3, first: 'Scott', last: 'Persinger'}
28 | ]);
29 | });
30 |
31 | router.get('/api/servertime', function (req, res) {
32 | res.json(new Date().toString());
33 | });
34 |
35 | /* GET home page. */
36 | router.get('/*', indexRouteHandler);
37 |
38 | module.exports = router;
39 |
--------------------------------------------------------------------------------
/public/javascripts/mixins/store-change.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 |
5 | var StoreChangeMixin = function() {
6 | var args = Array.prototype.slice.call(arguments);
7 |
8 | return {
9 |
10 | componentWillMount: function() {
11 | if (!_.isFunction(this.storeChange)) {
12 | throw new Error('StoreChangeMixin requires storeChange handler');
13 | }
14 |
15 | _.each(args, function(store) {
16 | store.addChangeListener(this.storeChange);
17 | }, this);
18 | },
19 |
20 | componentWillUnmount: function() {
21 | _.each(args, function(store) {
22 | store.removeChangeListener(this.storeChange);
23 | }, this);
24 | }
25 |
26 | };
27 | };
28 |
29 | StoreChangeMixin.componentWillMount = function() {
30 | throw new Error("StoreChangeMixin is a function that takes one or more " +
31 | "store names as parameters and returns the mixin, e.g.: " +
32 | "mixins[StoreChangeMixin(store1, store2)]");
33 | };
34 |
35 | module.exports = StoreChangeMixin;
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License:
2 |
3 | Copyright (C) 2015 Heroku, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/public/javascripts/main.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var $ = require('jquery');
4 |
5 | /**
6 | * Main entry-point
7 | */
8 | $(function () {
9 |
10 | var React = require('react/addons');
11 |
12 | var Router = require('react-router');
13 |
14 | // for developer tools
15 | window.React = React;
16 |
17 | React.initializeTouchEvents(true);
18 |
19 | // services initialization
20 | var Services = require('./services');
21 | Services.initialize(window.EX.const.apiAccessToken);
22 |
23 | // store initialization -- needs to be done before any component references
24 | var Stores = require('./stores');
25 | Stores.initialize();
26 |
27 | // for debugging - allows you to query the stores from the browser console
28 | window._stores = Stores;
29 |
30 | var Routes = require('./routes.jsx');
31 |
32 | var router = Router.create({
33 | routes: Routes,
34 | location: Router.HistoryLocation,
35 | onError: function () {
36 | alert('unexpected error in Router');
37 | }
38 | });
39 |
40 | router.run(function (Handler) {
41 | React.render(, document.body);
42 | });
43 |
44 | });
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-flux-starter
2 |
3 | An application template for a single page webapp backed by Express that uses React with a Flux architecture with support for Actions, Stores, message dispatching, and real-time updates using web sockets. Uses react-router for page routing.
4 |
5 | Uses Bootstrap and LESS for CSS, Browserify for Javascript bundling, ES6 syntax via Traceur, and Gulp for managing all the front-end asset build and packaging.
6 |
7 | To use:
8 |
9 | Clone this repository the execute the following commands:
10 |
11 |
12 | 1. `npm install`
13 | 1. `./node_modules/gulp/bin/gulp.js bootstrap`
14 | 1. `./node_modules/gulp/bin/gulp.js build`
15 | 1. `npm start`
16 | 1. Goto http://localhost:3000 in your browser
17 |
18 | If you want to develop further and make modifications:
19 |
20 |
21 | 1. In one console window run `./node_modules/gulp/bin/gulp.js`. This will run Gulp in "watch" mode and automatially rebuild your frontend assets on change.
22 | 1. In a second console window run `npm start`. If you're going to modify the Express code use nodemon (`nodemon ./bin/www`) which will automatically reload Express after you make a change.
23 |
24 | Enjoy!!
25 |
--------------------------------------------------------------------------------
/public/javascripts/stores/server-time.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 |
5 | var BaseStore = require('./base');
6 |
7 | var kActions = require('../constants/actions'),
8 | ServerActions = require('../actions/server-time');
9 |
10 | var _actions = _.zipObject([
11 | [kActions.SERVERTIME_GET, 'handleSet'],
12 | [kActions.SERVERTIME_PUT, 'handleSet']
13 | ]);
14 |
15 | class ServerTimeStore extends BaseStore {
16 |
17 | constructor(dispatcher) {
18 | super(dispatcher);
19 | this._serverTime = undefined;
20 | }
21 |
22 | _getActions() {
23 | return _actions;
24 | }
25 |
26 | _load() {
27 | ServerActions.getTime();
28 | return undefined;
29 | }
30 |
31 | _getTime() {
32 | return this._serverTime !== undefined ? this._serverTime : this._load();
33 | }
34 |
35 | getServerTime() {
36 | return this._getTime();
37 | }
38 |
39 |
40 | /*
41 | *
42 | * Action Handlers
43 | *
44 | */
45 |
46 | handleSet(payload) {
47 | console.debug(`${this.getStoreName()}:handleSet state=${payload.syncState}`);
48 | this._serverTime = this.makeStatefulEntry(payload.syncState, payload.data);
49 | this.emitChange();
50 | }
51 |
52 | }
53 |
54 | module.exports = ServerTimeStore;
55 |
--------------------------------------------------------------------------------
/views/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= title %>
9 |
10 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/public/javascripts/mixins/overlay.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var $ = require('jquery');
4 |
5 | var Overlays = require('../actions/overlays');
6 |
7 |
8 | module.exports = {
9 |
10 | componentDidMount: function() {
11 | var $node = $(this.getDOMNode());
12 |
13 | $node.on('shown.bs.modal', this._handleModalShown);
14 | $node.on('hidden.bs.modal', this._handleModalHidden);
15 | $(document).on('keyup', this._handleKeyUp);
16 |
17 | $node.modal({backdrop: 'static', keyboard: true});
18 | },
19 |
20 | componentWillUnmount: function() {
21 | var $node = $(this.getDOMNode());
22 | $node.off('hidden.bs.modal', this._handleModalHidden);
23 | $node.off('hidden.bs.modal', this._handleModalShown);
24 | $(document).off('keyup', this._handleKeyUp);
25 | },
26 |
27 | hideModal: function () {
28 | $(this.getDOMNode()).modal('hide');
29 | },
30 |
31 | _handleModalShown: function () {
32 | if (this.handleModalShown) {
33 | this.handleModalShown();
34 | }
35 | },
36 |
37 | _handleModalHidden: function() {
38 | if (this.handleModalHidden) {
39 | this.handleModalHidden();
40 | }
41 | Overlays.pop();
42 | },
43 |
44 | _handleKeyUp: function (e) {
45 | if (e.keyCode === 27) {
46 | this.hideModal();
47 | }
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/public/javascripts/constants/actions.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var keyMirror = require('react/lib/keyMirror');
4 |
5 | /**
6 | * Action type constants. Should follow the format:
7 | *