From 2e586b87d630529ece3f5ebab68f734d2519e5c1 Mon Sep 17 00:00:00 2001 From: Neil Fenton Date: Sat, 22 Aug 2015 11:17:32 -0400 Subject: [PATCH] initial commit --- .editorconfig | 13 ++++ .eslintrc | 53 +++++++++++++++++ .gitignore | 82 ++++++++++++++++++++++++++ example/index.html | 11 ++++ example/index.js | 114 ++++++++++++++++++++++++++++++++++++ example/package.json | 25 ++++++++ example/server.js | 18 ++++++ example/styles.css | 30 ++++++++++ example/webpack.config.js | 37 ++++++++++++ notes.md | 5 ++ package.json | 30 ++++++++++ src/action-types.js | 17 ++++++ src/index.js | 15 +++++ src/router-actions.js | 21 +++++++ src/router-listener.js | 14 +++++ src/router-middleware.js | 45 ++++++++++++++ src/router-state-reducer.js | 23 ++++++++ src/state-change-start.js | 26 ++++++++ src/state-change-success.js | 26 ++++++++ src/state-go.js | 19 ++++++ src/state-reload.js | 17 ++++++ src/state-transition-to.js | 19 ++++++ 22 files changed, 660 insertions(+) create mode 100644 .editorconfig create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 example/index.html create mode 100644 example/index.js create mode 100644 example/package.json create mode 100644 example/server.js create mode 100644 example/styles.css create mode 100644 example/webpack.config.js create mode 100644 notes.md create mode 100644 package.json create mode 100644 src/action-types.js create mode 100644 src/index.js create mode 100644 src/router-actions.js create mode 100644 src/router-listener.js create mode 100644 src/router-middleware.js create mode 100644 src/router-state-reducer.js create mode 100644 src/state-change-start.js create mode 100644 src/state-change-success.js create mode 100644 src/state-go.js create mode 100644 src/state-reload.js create mode 100644 src/state-transition-to.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fa9a819 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +charset = utf-8 +max_line_length = 80 +indent_brace_style = 1TBS +spaces_around_operators = true +quote_type = auto diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..8c49fa2 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,53 @@ +{ + "env": { + "browser": true, + "node": true, + "mocha": true, + "es6": true + }, + "globals": { + "angular": true, + "moment": true, + "R": true, + "inject": true, + "Q": true, + "sinon": true, + "getService": true, + "expect": true, + "xit": true, + "dealoc": true, + "require": true + }, + "rules": { + "camelcase": 2, + "curly": 2, + "brace-style": [2, "1tbs"], + "quotes": [2, "single"], + "semi": [2, "always"], + "object-curly-spacing": [2, "never"], + "array-bracket-spacing": [2, "never"], + "computed-property-spacing": [2, "never"], + "space-infix-ops": 2, + "space-after-keywords": [2, "always"], + "dot-notation": 2, + "eqeqeq": [2, "smart"], + "no-use-before-define": 2, + "no-redeclare": 2, + "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], + "no-floating-decimal": 2, + "no-spaced-func": 2, + "no-extra-parens": 1, + "no-underscore-dangle": 0, + "valid-jsdoc": 1, + "space-after-keywords": 2, + "max-len": [2, 120], + "no-use-before-define": [2, "nofunc"], + "no-warning-comments": 0, + "strict": 0, + "eol-last": 0, + "semi": 2 + }, + "ecmaFeatures": { + "modules": true + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f05e53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,82 @@ +# Logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Sass cache folder +.sass-cache + +# Users Environment Variables +.lock-wscript +.idea/ +*~ +\#*# +.#* +*.keystore +*.sw* +.DS_Store +._* +Thumbs.db +.cache +*.sublime-project +*.sublime-workspace +*swp +*swo +*swn +build +dist +temp +.tmp +node_modules +bower_components +jspm_packages +.test +www + +keys.md +.env + +# Cordova +platforms/ +plugins/ + +# emacs +.tern-port +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ diff --git a/example/index.html b/example/index.html new file mode 100644 index 0000000..6c70de7 --- /dev/null +++ b/example/index.html @@ -0,0 +1,11 @@ + + + + + ng-redux-ui-router + + +
+ + + diff --git a/example/index.js b/example/index.js new file mode 100644 index 0000000..daa394e --- /dev/null +++ b/example/index.js @@ -0,0 +1,114 @@ +import angular from 'angular'; +import uiRouter from 'angular-ui-router'; + +import ngUiRouterMiddleware from '../src'; +import ngRedux from 'ng-redux'; +import * as redux from 'redux'; +import createLogger from 'redux-logger'; + +const logger = createLogger({ + level: 'info', + collapsed: true +}); + +import uiRouterState from '../src/router-state-reducer'; + +const reducers = redux.combineReducers({ + uiRouterState +}); + +import thunk from 'redux-thunk'; + +export default angular + .module('app', [ + uiRouter, + ngRedux, + ngUiRouterMiddleware + ]) + .config(($urlRouterProvider, $stateProvider) => { + $urlRouterProvider.otherwise('/app'); + + $stateProvider + .state('app', { + url: '/app', + views: { + main: { + template: ` +
{{ globalState | json }}
+
+

Main View

+ + +
+
+ `, + controller: ($scope, $ngRedux) => { + $scope.globalState = {}; + + $ngRedux.connect(state => state, state => $scope.globalState = state); + } + } + } + }) + .state('app.child1', { + url: '/child1?hello?optional', + views: { + child: { + controller: ($scope, ngUiRouterActions) => { + $scope.goto = () => { + ngUiRouterActions.stateGo('app.child2'); + } + }, + template: ` +
+

Child View 1

+ +
+ ` + } + } + }) + .state('app.child2', { + url: '/child2', + views: { + child: { + controller: ($scope, ngUiRouterActions) => { + $scope.goto = () => { + ngUiRouterActions.stateGo('app.child1'); + } + + $scope.goWithReload = () => { + ngUiRouterActions.stateReload('app.child1'); + } + + $scope.goWithParams = () => { + ngUiRouterActions.stateGo('app.child1', { + hello: 'world', + optional: true + }); + } + }, + template: ` +
+

Child View 2

+ + + +
+ ` + } + } + }) + }) + .config(($ngReduxProvider) => { + $ngReduxProvider.createStoreWith(reducers, ['ngUiRouterMiddleware', logger, thunk]); + }) + .name; diff --git a/example/package.json b/example/package.json new file mode 100644 index 0000000..a165226 --- /dev/null +++ b/example/package.json @@ -0,0 +1,25 @@ +{ + "name": "angular-ui-router-redux", + "version": "0.1.0", + "main": "index.js", + "scripts": { + "start": "node server.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "devDependencies": { + "babel-core": "5.6.15", + "babel-loader": "^5.1.4", + "react-hot-loader": "^1.2.7", + "redux-batched-updates": "^0.1.0", + "webpack": "^1.9.11", + "webpack-dev-server": "^1.9.0" + }, + "license": "MIT", + "dependencies": { + "react-pure-component": "^0.1.0", + "react-redux": "^0.2.1", + "redux": "^1.0.0-alpha", + "redux-logger": "^1.0.4", + "redux-thunk": "^0.1.0" + } +} diff --git a/example/server.js b/example/server.js new file mode 100644 index 0000000..627cbb3 --- /dev/null +++ b/example/server.js @@ -0,0 +1,18 @@ +var webpack = require('webpack'); +var WebpackDevServer = require('webpack-dev-server'); +var config = require('./webpack.config'); + +new WebpackDevServer(webpack(config), { + publicPath: config.output.publicPath, + hot: true, + stats: { + colors: true + } +}) +.listen(3000, 'localhost', function (err) { + if (err) { + console.log(err); + } + + console.log('http://localhost:3000'); +}); diff --git a/example/styles.css b/example/styles.css new file mode 100644 index 0000000..36dd9a1 --- /dev/null +++ b/example/styles.css @@ -0,0 +1,30 @@ +html, +body { + padding: 0; + margin: 0; +} + +main { + background-color: lightgray; +} + +pre { + position: fixed; + top: 0; + left: 0; + width: 300px; + bottom: 0; + background-color: black; + color: white; +} + +.mainView { + margin-left: 400px; + min-height: 100vh; +} + +.child-view { + background-color: white; + padding: 15px; + margin: 15px; +} diff --git a/example/webpack.config.js b/example/webpack.config.js new file mode 100644 index 0000000..f89fc58 --- /dev/null +++ b/example/webpack.config.js @@ -0,0 +1,37 @@ +var path = require('path'); +var webpack = require('webpack'); + +module.exports = { + devtool: 'eval', + entry: [ + 'webpack-dev-server/client?http://localhost:3000', + 'webpack/hot/dev-server', + './index' + ], + output: { + path: path.join(__dirname, 'dist'), + filename: 'bundle.js', + publicPath: '/static/' + }, + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new webpack.NoErrorsPlugin() + ], + resolve: { + extensions: ['', '.js'], + alias: { + 'react': path.join(__dirname, '..', '..', 'node_modules', 'react') + } + }, + module: { + loaders: [{ + test: /\.js$/, + loaders: ['babel'], + exclude: /node_modules/ + }, { + test: /\.css?$/, + loaders: ['style', 'raw'], + exclude: /node_modules/ + }] + } +}; diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..2bb446c --- /dev/null +++ b/notes.md @@ -0,0 +1,5 @@ +- Wondering if `toState` and `toParams` doesn't make sense in the context of the router state? + - `toParams` implies that the router is still going to a state + - Possibly rename to `currentParams` and `currentState` + - `fromState` and `fromParams` would become `prevState` and `prevParams` + diff --git a/package.json b/package.json new file mode 100644 index 0000000..77bff7d --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "redux-ui-router-middleware", + "version": "0.1.0", + "description": "Redux middleware for use with Angular UI Router", + "main": "src/index.js", + "scripts": { + "lint": "node node_modules/.bin/eslint src", + "clean": "rm -rf lib", + "test": "npm run lint; NODE_ENV=test node node_modules/.bin/mocha --compilers js:babel/register --recursive --require src/__tests__/index.js src/**/*.test.js", + "test:live": "npm run lint; NODE_ENV=test node node_modules/.bin/mocha --compilers js:babel/register --recursive --require src/__tests__/index.js -w src/**/*.test.js", + "build": "node node_modules/.bin/babel src --out-dir lib" + }, + "author": "Neil Fenton (neilff)", + "license": "ISC", + "dependencies": { + "angular": "^1.4.4", + "angular-ui-router": "^0.2.15" + }, + "devDependencies": { + "babel-core": "5.6.15", + "babel-eslint": "^3.1.20", + "babel-loader": "^5.3.1", + "chai": "^3.0.0", + "eslint": "^0.24.0", + "mocha": "^2.2.5", + "node-libs-browser": "^0.5.2", + "redux": "1.0.0-alpha", + "sinon": "^1.15.4" + } +} diff --git a/src/action-types.js b/src/action-types.js new file mode 100644 index 0000000..425bdab --- /dev/null +++ b/src/action-types.js @@ -0,0 +1,17 @@ +// Exports the constants used for triggering transitions using Angular UI Router +// +// STATE_GO: Action for triggering a $state.go +// STATE_RELOAD: Action for triggering a $state.reload +// STATE_TRANSITION_TO: Action for triggering a $state.transitionTo +// +export const STATE_GO = '@@reduxUiRouter/stateGo'; +export const STATE_RELOAD = '@@reduxUiRouter/stateReload'; +export const STATE_TRANSITION_TO = '@@reduxUiRouter/transitionTo'; + +// UI Router Events +export const STATE_CHANGE_START = '@@reduxUiRouter/$stateChangeStart'; +export const STATE_CHANGE_SUCCESS = '@@reduxUiRouter/$stateChangeSuccess'; +export const STATE_CHANGE_ERROR = '@@reduxUiRouter/$stateChangeError'; +export const STATE_NOT_FOUND = '@@reduxUiRouter/$stateNotFound'; +export const VIEW_CONTENT_LOADING = '@@reduxUiRouter/$viewContentLoading'; +export const VIEW_CONTENT_LOADED = '@@reduxUiRouter/$viewContentLoaded'; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..2c196cf --- /dev/null +++ b/src/index.js @@ -0,0 +1,15 @@ +import angular from 'angular'; +import uiRouter from 'angular-ui-router'; + +import routerMiddleware from './router-middleware'; +import routerActions from './router-actions'; +import uiRouterListener from './router-listener'; + +export default angular + .module('ng-ui-router-middleware', [ + uiRouter + ]) + .factory('ngUiRouterActions', routerActions) + .factory('ngUiRouterMiddleware', routerMiddleware) + .run(uiRouterListener) + .name; diff --git a/src/router-actions.js b/src/router-actions.js new file mode 100644 index 0000000..29bfb8a --- /dev/null +++ b/src/router-actions.js @@ -0,0 +1,21 @@ +import onStateChangeStart from './state-change-start'; +import onStateChangeSuccess from './state-change-success'; +import stateGo from './state-go'; +import stateReload from './state-reload'; +import stateTransitionTo from './state-transition-to'; + +import * as redux from 'redux'; + +export default function routerActions($ngRedux) { + let actionCreator = { + onStateChangeStart, + onStateChangeSuccess, + stateGo, + stateReload, + stateTransitionTo + }; + + return redux.bindActionCreators(actionCreator, $ngRedux.getStore().dispatch); +} + +routerActions.$inject = ['$ngRedux']; diff --git a/src/router-listener.js b/src/router-listener.js new file mode 100644 index 0000000..60d6f1d --- /dev/null +++ b/src/router-listener.js @@ -0,0 +1,14 @@ +/** + * Listens for events emitted from Angular UI Router and fires redux events + * + * @param {object} $rootScope Dependency + * @param {object} ngUiRouterActions Dependency + * @param {object} accountSelectActions Dependency + * @return {undefined} undefined + */ +export default function RouterListener($rootScope, ngUiRouterActions) { + $rootScope.$on('$stateChangeStart', ngUiRouterActions.onStateChangeStart); + $rootScope.$on('$stateChangeSuccess', ngUiRouterActions.onStateChangeSuccess); +} + +RouterListener.$inject = ['$rootScope', 'ngUiRouterActions']; diff --git a/src/router-middleware.js b/src/router-middleware.js new file mode 100644 index 0000000..05eb5ba --- /dev/null +++ b/src/router-middleware.js @@ -0,0 +1,45 @@ +import { + STATE_GO, + STATE_RELOAD, + STATE_TRANSITION_TO, + STATE_CHANGE_START, + STATE_CHANGE_SUCCESS, + STATE_CHANGE_ERROR, + STATE_NOT_FOUND, + VIEW_CONTENT_LOADING, + VIEW_CONTENT_LOADED, +} from './action-types'; + +export default function routerMiddleware($state) { + return store => next => action => { + if (action.type === STATE_GO) { + let { + payload: { + to: to, + params: params, + options: options, + } + } = action; + + return $state.go(to, params, options); + } else if (action.type === STATE_RELOAD) { + let { payload } = action; + + return $state.reload(payload); + } else if (action.type === STATE_TRANSITION_TO) { + let { + payload: { + to: to, + params: params, + options: options, + } + } = action; + + return $state.transitionTo(to, params, options); + } else { + next(action); + } + }; +} + +routerMiddleware.$inject = ['$state']; diff --git a/src/router-state-reducer.js b/src/router-state-reducer.js new file mode 100644 index 0000000..466a031 --- /dev/null +++ b/src/router-state-reducer.js @@ -0,0 +1,23 @@ +import { STATE_CHANGE_SUCCESS } from './action-types'; + +const INITIAL_STATE = { + event: null, + toState: null, + toParams: null, + fromState: null, + fromParams: null +}; + +/** + * Reducer of STATE_CHANGE_SUCCESS actions. Returns a state object + * with { toState, toParams, fromState, fromParams } + * + * @param {Object} state - Previous state + * @param {Object} action - Action + * @return {Object} New state + */ +export default function routerStateReducer(state = INITIAL_STATE, action) { + return action.type === STATE_CHANGE_SUCCESS + ? action.payload + : state; +} diff --git a/src/state-change-start.js b/src/state-change-start.js new file mode 100644 index 0000000..0779347 --- /dev/null +++ b/src/state-change-start.js @@ -0,0 +1,26 @@ +import { STATE_CHANGE_START } from './action-types'; + +/** + * This action is triggered when a $stateChangeStart event is broadcast. + * Accepts a payload which matches the UI Router $stateChangeStart event. + * + * http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state + * + * @param {Object} evt event details + * @param {Object} toState To state definition + * @param {Object} toParams To params object + * @param {Object} fromState From state definition + * @param {Object} fromParams From params object + * @return {Object} Action object + */ +export default function onStateChangeStart(evt, toState, toParams, fromState, fromParams) { + return { + type: STATE_CHANGE_START, + payload: { + toState, + toParams, + fromState, + fromParams + } + }; +} diff --git a/src/state-change-success.js b/src/state-change-success.js new file mode 100644 index 0000000..bd26004 --- /dev/null +++ b/src/state-change-success.js @@ -0,0 +1,26 @@ +import { STATE_CHANGE_SUCCESS } from './action-types'; + +/** + * This action is triggered when a $stateChangeSuccess event is broadcast. + * Accepts a payload which matches the UI Router $stateChangeSuccess event. + * + * http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state + * + * @param {Object} evt event details + * @param {Object} toState To state definition + * @param {Object} toParams To params object + * @param {Object} fromState From state definition + * @param {Object} fromParams From params object + * @return {Object} Action object + */ +export default function onStateChangeSuccess(evt, toState, toParams, fromState, fromParams) { + return { + type: STATE_CHANGE_SUCCESS, + payload: { + toState, + toParams, + fromState, + fromParams + } + }; +} diff --git a/src/state-go.js b/src/state-go.js new file mode 100644 index 0000000..0f530e6 --- /dev/null +++ b/src/state-go.js @@ -0,0 +1,19 @@ +import { STATE_GO } from './action-types'; + +/** + * This action create will trigger a $state.go in the UiRouter Middleware. + * Accepts a payload which matches the UI Router $state.go function. + * + * http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state + * + * @param {String} to - State name + * @param {Object} params - (optional) Parameters to send with state + * @param {Object} options - (optional) Options object + * @return {Object} Action object + */ +export default function stateGo(to, params, options) { + return { + type: STATE_GO, + payload: { to, params, options } + }; +} diff --git a/src/state-reload.js b/src/state-reload.js new file mode 100644 index 0000000..1f7fe37 --- /dev/null +++ b/src/state-reload.js @@ -0,0 +1,17 @@ +import { STATE_RELOAD } from './action-types'; + +/** + * This action create will trigger a $state.reload in the UiRouter Middleware. + * Accepts a payload which matches the UI Router $state.reload function. + * + * http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state + * + * @param {String} state - (optional) Root of the resolves to be re-resolved + * @return {Object} Action object + */ +export default function stateReload(state) { + return { + type: STATE_RELOAD, + payload: state + }; +} diff --git a/src/state-transition-to.js b/src/state-transition-to.js new file mode 100644 index 0000000..966f3fa --- /dev/null +++ b/src/state-transition-to.js @@ -0,0 +1,19 @@ +import { STATE_TRANSITION_TO } from './action-types'; + +/** + * This action create will trigger a $state.transitionTo in the UiRouter Middleware. + * Accepts a payload which matches the UI Router $state.transitionTo function. + * + * http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state + * + * @param {String} to - State name + * @param {Object} toParams - (optional) Parameters to send with state + * @param {Object} options - (optional) Options object + * @return {Object} Action object + */ +export default function stateTransitionTo(to, toParams, options) { + return { + type: STATE_TRANSITION_TO, + payload: { to, toParams, options } + }; +}