├── .npmignore ├── .coveralls.yml ├── .jshintignore ├── .gitignore ├── .jshintrc ├── index.js ├── .travis.yml ├── CONTRIBUTING.md ├── package.json ├── LICENSE.md ├── README.md ├── lib └── routr-plugin.js └── tests └── unit └── lib └── routr-plugin.js /.npmignore: -------------------------------------------------------------------------------- 1 | /artifacts/ 2 | /tests/ 3 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /artifacts 3 | *.log 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | 4 | "curly": true, 5 | "latedef": true, 6 | "quotmark": true, 7 | "undef": true, 8 | "unused": true, 9 | "trailing": true 10 | } 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | module.exports = require('./lib/routr-plugin'); 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | matrix: 3 | allow_failures: 4 | - node_js: "0.13" 5 | node_js: 6 | - "iojs" 7 | - "0.13" 8 | - "0.12" 9 | - "0.10" 10 | after_success: 11 | - "npm run cover" 12 | - "cat artifacts/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing Code to `fluxible-plugin-routr` 2 | ------------------------------- 3 | 4 | Please be sure to sign our [CLA][] before you submit pull requests or otherwise contribute to `fluxible-plugin-routr`. This protects developers, who rely on [BSD license][]. 5 | 6 | [BSD license]: https://github.com/yahoo/fluxible-plugin-routr/blob/master/LICENSE.md 7 | [CLA]: https://yahoocla.herokuapp.com/ 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluxible-plugin-routr", 3 | "version": "0.4.0", 4 | "description": "A plugin for Fluxible applications to provide routing methods", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/yahoo/fluxible-plugin-routr.git" 9 | }, 10 | "scripts": { 11 | "test": "mocha tests/unit/ --recursive --reporter spec", 12 | "cover": "istanbul cover --dir artifacts -- ./node_modules/mocha/bin/_mocha tests/unit/ --recursive --reporter spec", 13 | "lint": "jshint ." 14 | }, 15 | "dependencies": { 16 | "debug": "^2.0.0", 17 | "routr": "^0.1.0" 18 | }, 19 | "devDependencies": { 20 | "chai": "^1.10.0", 21 | "coveralls": "^2.11.1", 22 | "fluxible": "^0.1.0", 23 | "grunt": "^0.4.5", 24 | "istanbul": "^0.3.2", 25 | "jshint": "^2.5.5", 26 | "mocha": "^2.0.1", 27 | "mockery": "^1.4.0", 28 | "precommit-hook": "1.0.x" 29 | }, 30 | "author": "Michael Ridgway ", 31 | "contributors": [ 32 | { 33 | "name": "Alex Grant", 34 | "email": "alex@localnerve.com" 35 | } 36 | ], 37 | "licenses": [ 38 | { 39 | "type": "BSD", 40 | "url": "https://github.com/yahoo/fluxible-plugin-routr/blob/master/LICENSE.md" 41 | } 42 | ], 43 | "keywords": [ 44 | "yahoo", 45 | "flux", 46 | "react", 47 | "fluxible", 48 | "routr" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | ======================================== 3 | 4 | Copyright (c) 2014, Yahoo! Inc. All rights reserved. 5 | ---------------------------------------------------- 6 | 7 | Redistribution and use of this software in source and binary forms, with or 8 | without modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | * Neither the name of Yahoo! Inc. nor the names of YUI's contributors may be 17 | used to endorse or promote products derived from this software without 18 | specific prior written permission of Yahoo! Inc. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Routr Plugin for Fluxible 2 | 3 | [![npm version](https://badge.fury.io/js/fluxible-plugin-routr.svg)](http://badge.fury.io/js/fluxible-plugin-routr) 4 | [![Build Status](https://travis-ci.org/yahoo/fluxible-plugin-routr.svg?branch=master)](https://travis-ci.org/yahoo/fluxible-plugin-routr) 5 | [![Dependency Status](https://david-dm.org/yahoo/fluxible-plugin-routr.svg)](https://david-dm.org/yahoo/fluxible-plugin-routr) 6 | [![devDependency Status](https://david-dm.org/yahoo/fluxible-plugin-routr/dev-status.svg)](https://david-dm.org/yahoo/fluxible-plugin-routr#info=devDependencies) 7 | [![Coverage Status](https://coveralls.io/repos/yahoo/fluxible-plugin-routr/badge.png?branch=master)](https://coveralls.io/r/yahoo/fluxible-plugin-routr?branch=master) 8 | 9 | Provides routing methods to your [Fluxible](https://github.com/yahoo/fluxible) application using [routr](https://github.com/yahoo/routr). 10 | 11 | ## Static Routes Usage 12 | 13 | ```js 14 | var Fluxible = require('fluxible'); 15 | var routrPlugin = require('fluxible-plugin-routr'); 16 | var app = new Fluxible(); 17 | 18 | var pluginInstance = routrPlugin({ 19 | routes: { 20 | user: { 21 | path: '/user/:id', 22 | method: 'get', 23 | // flux-router-component uses this action when the route is matched 24 | action: function (actionContext, payload, done) { 25 | // ... 26 | done(); 27 | } 28 | } 29 | } 30 | }); 31 | 32 | app.plug(pluginInstance); 33 | ``` 34 | 35 | ## Dynamic Routes Usage 36 | 37 | ```js 38 | var Fluxible = require('fluxible'); 39 | var routrPlugin = require('fluxible-plugin-routr'); 40 | var app = new Fluxible(); 41 | 42 | var pluginInstance = routrPlugin({ 43 | storeName: 'RoutesStore', // storeName of the Store event source 44 | storeEvent: 'change' // Any event from that Store 45 | }); 46 | 47 | app.plug(pluginInstance); 48 | ``` 49 | [//]: # (API_START) 50 | ## Routr Plugin API 51 | 52 | ### Constructor(options) 53 | 54 | Creates a new routr plugin instance with the following parameters: 55 | 56 | * `options`: An object containing the plugin settings 57 | * `options.routes` (required, static routes): Stores your routes configuration 58 | * `options.storeName` (required, dynamic routes): The storeName of the Store 59 | * `options.storeEvent` (required, dynamic routes): The name of the event from the Store 60 | * `options.dehydrateRoutes` (optional, dynamic routes): A function to transform from fluxible routes to JSON 61 | * `options.rehydrateRoutes` (optional, dynamic routes): A function to transform from JSON to fluxible routes 62 | 63 | ### Instance Methods 64 | 65 | #### getRoutes 66 | 67 | getter for the `routes` option passed into the constructor. 68 | 69 | ``` 70 | pluginInstance.getRoutes(); // returns the full routes object passed to factory 71 | ``` 72 | 73 | ### actionContext Methods 74 | 75 | Provides full access to the routr instance. See [routr docs](https://github.com/yahoo/routr) for more information. 76 | 77 | * `actionContext.router.makePath(routeName, routeParams)`: Create a URL based on route name and params 78 | * `actionContext.router.getRoute(path)`: Returns matched route 79 | [//]: # (API_STOP) 80 | 81 | ## License 82 | 83 | This software is free to use under the Yahoo! Inc. BSD license. 84 | See the [LICENSE file][] for license text and copyright information. 85 | 86 | [LICENSE file]: https://github.com/yahoo/fluxible-plugin-routr/blob/master/LICENSE.md 87 | -------------------------------------------------------------------------------- /lib/routr-plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | var debug = require('debug')('RoutrPlugin'); 8 | var Router = require('routr'); 9 | 10 | var passThru = function(routes) { return routes; }; 11 | 12 | module.exports = function routrPlugin(options) { 13 | options = options || {}; 14 | 15 | var staticRoutes = options.routes; 16 | var storeName = options.storeName; 17 | var storeEvent = options.storeEvent; 18 | var dehydrateRoutes = options.dehydrateRoutes || passThru; 19 | var rehydrateRoutes = options.rehydrateRoutes || passThru; 20 | 21 | var routes = staticRoutes; 22 | 23 | /** 24 | * @class RoutrPlugin 25 | */ 26 | return { 27 | name: 'RoutrPlugin', 28 | /** 29 | * Called to plug the FluxContext 30 | * @method plugContext 31 | * @returns {Object} 32 | */ 33 | plugContext: function plugContext() { 34 | debug('new plug context'); 35 | var router, actionContext, componentContext; 36 | 37 | if (staticRoutes) { 38 | router = new Router(routes); 39 | } 40 | 41 | /** 42 | * Dynamically update the routes, make a new Router, fix references. 43 | * 44 | * @param {Object} params 45 | * @param {Object} params.routes The new routes to supply Routr 46 | */ 47 | var updateRoutes = function updateRoutes(params) { 48 | debug('updating routes'); 49 | routes = params.routes; 50 | router = new Router(routes); 51 | if (actionContext) { 52 | actionContext.router = router; 53 | } 54 | if (componentContext) { 55 | componentContext.makePath = router.makePath.bind(router); 56 | } 57 | }; 58 | 59 | var pluginContext = { 60 | /** 61 | * Provides full access to the router in the action context 62 | * @param {Object} actionContext 63 | */ 64 | plugActionContext: function plugActionContext(context) { 65 | debug('plug action context, router = '+router); 66 | actionContext = context; 67 | actionContext.router = router; 68 | 69 | if (!staticRoutes) { 70 | // Update on a store event within the single round. 71 | // Could be 'change' from dedicated route store, or 72 | // a dedicated change event within an application store. 73 | actionContext.getStore(storeName) 74 | .removeListener(storeEvent, updateRoutes) 75 | .on(storeEvent, updateRoutes); 76 | } 77 | }, 78 | /** 79 | * Provides access to create paths by name 80 | * @param {Object} componentContext 81 | */ 82 | plugComponentContext: function plugComponentContext(context) { 83 | componentContext = context; 84 | if (router) { 85 | componentContext.makePath = router.makePath.bind(router); 86 | } 87 | } 88 | }; 89 | 90 | if (!staticRoutes) { 91 | // Allows context plugin settings to be persisted between server and client. 92 | // Called on server to send data down to the client 93 | pluginContext.dehydrate = function dehydrate() { 94 | return { routes: dehydrateRoutes(routes) }; 95 | }; 96 | 97 | // Called on client to rehydrate the context plugin settings 98 | pluginContext.rehydrate = function rehydrate(state) { 99 | updateRoutes({ routes: rehydrateRoutes(state.routes) }); 100 | }; 101 | } 102 | 103 | return pluginContext; 104 | }, 105 | /** 106 | * @method getRoutes 107 | * @returns {Object} 108 | */ 109 | getRoutes: function getRoutes() { 110 | return routes; 111 | } 112 | }; 113 | }; 114 | -------------------------------------------------------------------------------- /tests/unit/lib/routr-plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | /*globals describe,it,beforeEach */ 6 | 'use strict'; 7 | 8 | var expect = require('chai').expect; 9 | var routrPlugin = require('../../../lib/routr-plugin'); 10 | var createStore = require('fluxible/utils/createStore'); 11 | var Fluxible = require('fluxible'); 12 | 13 | var RoutesStore = createStore({ 14 | storeName: 'RoutesStore', 15 | handlers: { 16 | 'RECEIVE_ROUTES': 'receiveRoutes' 17 | }, 18 | initialize: function() { 19 | this.routes = {}; 20 | }, 21 | receiveRoutes: function(routes) { 22 | this.routes = routes; 23 | this.emitChange(); 24 | } 25 | }); 26 | 27 | describe('fetchrPlugin', function () { 28 | var app, 29 | pluginInstance, 30 | context, 31 | routes1 = { 32 | view_user: { 33 | path: '/user/:id', 34 | method: 'get', 35 | foo: { 36 | bar: 'baz' 37 | } 38 | }, 39 | view_user_post: { 40 | path: '/user/:id/post/:post', 41 | method: 'get' 42 | } 43 | }, 44 | routes2 = { 45 | view_second: { 46 | path: '/second/:id', 47 | method: 'get', 48 | foo: { 49 | bar: 'all' 50 | } 51 | } 52 | }, 53 | pluginOptionsDynRoutes = { 54 | storeName: RoutesStore.storeName, 55 | storeEvent: 'change', 56 | dehydrateRoutes: function(routes) { return routes; }, 57 | rehydrateRoutes: function(routes) { return routes; } 58 | }; 59 | 60 | function actionContextRoutes1(actionContext) { 61 | expect(actionContext.router).to.be.an('object'); 62 | expect(actionContext.router.makePath).to.be.a('function'); 63 | expect(actionContext.router.getRoute).to.be.a('function'); 64 | expect(actionContext.router.makePath('view_user', {id: 1})).to.equal('/user/1'); 65 | } 66 | function componentContextRoutes1(componentContext) { 67 | expect(componentContext.makePath).to.be.a('function'); 68 | expect(componentContext.makePath('view_user', {id: 1})).to.equal('/user/1'); 69 | } 70 | function actionContextRoutes2(actionContext) { 71 | expect(actionContext.router).to.be.an('object'); 72 | expect(actionContext.router.makePath).to.be.a('function'); 73 | expect(actionContext.router.getRoute).to.be.a('function'); 74 | expect(actionContext.router.makePath('view_second', {id: 2})).to.equal('/second/2'); 75 | } 76 | function componentContextRoutes2(componentContext) { 77 | expect(componentContext.makePath).to.be.a('function'); 78 | expect(componentContext.makePath('view_second', {id: 2})).to.equal('/second/2'); 79 | } 80 | 81 | beforeEach(function () { 82 | app = new Fluxible(); 83 | }); 84 | 85 | describe('static routes', function() { 86 | beforeEach(function() { 87 | pluginInstance = routrPlugin({ routes: routes1 }); 88 | app.plug(pluginInstance); 89 | context = app.createContext(); 90 | }); 91 | 92 | describe('factory', function () { 93 | it('should accept static routes option', function () { 94 | expect(pluginInstance.getRoutes()).to.deep.equal(routes1); 95 | }); 96 | }); 97 | 98 | describe('actionContext', function () { 99 | var actionContext; 100 | beforeEach(function () { 101 | actionContext = context.getActionContext(); 102 | }); 103 | describe('router', function () { 104 | it('should have a router access', function () { 105 | actionContextRoutes1(actionContext); 106 | }); 107 | }); 108 | }); 109 | 110 | describe('componentContext', function () { 111 | var componentContext; 112 | beforeEach(function () { 113 | componentContext = context.getComponentContext(); 114 | }); 115 | describe('router', function () { 116 | it('should have a router access', function () { 117 | componentContextRoutes1(componentContext); 118 | }); 119 | }); 120 | }); 121 | 122 | describe('rehydrate/dehydrate', function() { 123 | it('should not have plugin rehydrate/dehydrate', function() { 124 | expect(pluginInstance.dehydrate).to.be.an('undefined'); 125 | expect(pluginInstance.rehydrate).to.be.an('undefined'); 126 | }); 127 | it('should not have a context rehydrate/dehydrate', function() { 128 | var contextPlug = pluginInstance.plugContext(); 129 | expect(contextPlug.dehydrate).to.be.an('undefined'); 130 | expect(contextPlug.rehydrate).to.be.an('undefined'); 131 | }); 132 | }); 133 | }); 134 | 135 | describe('dynamic routes', function() { 136 | beforeEach(function() { 137 | pluginInstance = routrPlugin(pluginOptionsDynRoutes); 138 | app.plug(pluginInstance); 139 | app.registerStore(RoutesStore); 140 | context = app.createContext(); 141 | }); 142 | 143 | describe('factory', function () { 144 | it('should not have any routes defined', function () { 145 | expect(pluginInstance.getRoutes()).to.be.an('undefined'); 146 | }); 147 | }); 148 | 149 | describe('dehydrate/rehydrate', function() { 150 | var dehydratedState = { 151 | routes: pluginOptionsDynRoutes.dehydrateRoutes(routes1) 152 | }; 153 | it('should have context rehydrate/dehydrate', function() { 154 | var contextPlug = pluginInstance.plugContext(); 155 | expect(contextPlug.dehydrate).to.be.a('function'); 156 | expect(contextPlug.rehydrate).to.be.a('function'); 157 | }); 158 | describe('rehydrate', function() { 159 | beforeEach(function() { 160 | context.rehydrate({ 161 | plugins: { 162 | RoutrPlugin: dehydratedState 163 | } 164 | }); 165 | }); 166 | it('should have routes', function() { 167 | expect(pluginInstance.getRoutes()).to.eql(routes1); 168 | }); 169 | describe('actionContext', function() { 170 | var actionContext; 171 | beforeEach(function() { 172 | actionContext = context.getActionContext(); 173 | }); 174 | it('should have a router access', function () { 175 | actionContextRoutes1(actionContext); 176 | }); 177 | }); 178 | describe('componentContext', function() { 179 | var componentContext; 180 | beforeEach(function() { 181 | componentContext = context.getComponentContext(); 182 | }); 183 | it('should have a router access', function () { 184 | componentContextRoutes1(componentContext); 185 | }); 186 | }); 187 | }); 188 | describe('dehydrate', function() { 189 | var contextPlug; 190 | beforeEach(function() { 191 | contextPlug = pluginInstance.plugContext(); 192 | contextPlug.rehydrate(dehydratedState); 193 | }); 194 | it('should dehydrate using dehydrateRoutes', function() { 195 | expect(contextPlug.dehydrate(routes1)).to.eql(dehydratedState); 196 | }); 197 | }); 198 | }); 199 | 200 | describe('updateRoutes', function() { 201 | var actionContext; 202 | beforeEach(function() { 203 | actionContext = context.getActionContext(); 204 | }); 205 | it('should update with routes1 when store updates', function() { 206 | actionContext.dispatch('RECEIVE_ROUTES', routes1); 207 | expect(pluginInstance.getRoutes()).to.eql(routes1); 208 | }); 209 | it('should update with routes2 when store updates', function() { 210 | actionContext.dispatch('RECEIVE_ROUTES', routes2); 211 | expect(pluginInstance.getRoutes()).to.eql(routes2); 212 | }); 213 | describe('context updates', function() { 214 | describe('routes1', function() { 215 | describe('actionContext', function() { 216 | it('should have a router access', function () { 217 | actionContext.dispatch('RECEIVE_ROUTES', routes1); 218 | actionContextRoutes1(actionContext); 219 | }); 220 | }); 221 | describe('componentContext', function() { 222 | var componentContext; 223 | beforeEach(function() { 224 | componentContext = context.getComponentContext(); 225 | }); 226 | it('should have a router access', function () { 227 | actionContext.dispatch('RECEIVE_ROUTES', routes1); 228 | componentContextRoutes1(componentContext); 229 | }); 230 | }); 231 | }); 232 | describe('routes2', function() { 233 | describe('actionContext', function() { 234 | it('should have a router access', function (){ 235 | actionContext.dispatch('RECEIVE_ROUTES', routes2); 236 | actionContextRoutes2(actionContext); 237 | }); 238 | }); 239 | describe('componentContext', function() { 240 | var componentContext; 241 | beforeEach(function() { 242 | componentContext = context.getComponentContext(); 243 | }); 244 | it('should have a router access', function () { 245 | actionContext.dispatch('RECEIVE_ROUTES', routes2); 246 | componentContextRoutes2(componentContext); 247 | }); 248 | }); 249 | }); 250 | }); 251 | }); 252 | }); 253 | }); 254 | --------------------------------------------------------------------------------