├── .gitignore ├── LICENSE ├── README.md ├── moria.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Barney Carroll 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | moria 2 | ===== 3 | 4 | A routing system for [Mithril](http://lhorie.github.io/mithril/). 5 | 6 | Mithril has a beautiful lightweight routing paradigm, but as [legends recount](http://en.wikipedia.org/wiki/Durin), sometimes you need to [dig deep](http://en.wikipedia.org/wiki/Moria_(Middle-earth)) to get at the good bits: it's difficult to express the routing of a complex application when [m.route( rootElement, defaultRoute, routesHash )](http://lhorie.github.io/mithril/mithril.route.html#defining-routes) can only be invoked once and the routes hash has to be flat. 7 | 8 | Moria aims to solve these problems by producing a Mithril-compatible route hash from nested hierarchies. 9 | 10 | # Features 11 | 12 | * Nested routes 13 | * Setup functions to run when a route is matched 14 | * Redirects 15 | 16 | # Roadmap 17 | 18 | * Nested modules (currently, each route endpointmust specify the complete module structure you want to render) 19 | * ... 20 | 21 | # Install 22 | 23 | Currently only works as a CommonJS module, and therefore requires Node (and npm if you want command-line installation). I use [browserify](http://browserify.org/) to compile my CommonJS JavaScript for use on the front-end. 24 | 25 | ``` 26 | npm install --save moria 27 | ``` 28 | 29 | # Use 30 | 31 | ```javascript 32 | var moria = require( 'moria' ); 33 | 34 | var routeHash = moria( { 35 | '' : loginModule, // Results in '/loginModule', 36 | 'search' : '../shop/search', // Redirect '/search' to '/shop/search' 37 | 'shop' : { 38 | '' : browseModule, // Results in '/shop' 39 | 'search' : searchModule, 40 | 'checkout' : { 41 | 'payment' : foo, // Results in '/shop/checkout/payment' 42 | 'delivery' : bar, 43 | 'confirm' : baz 44 | } 45 | }, 46 | ':userName' : [ // When '/:userName' is matched... 47 | function(){ // This function will be executed... 48 | initUserModel(); 49 | }, 50 | profileModule // And this module will render 51 | ], 52 | 'admin' : [ 53 | initAdminModel, // Another setup function 54 | { 55 | '' : 'users', // Redirect '/admin' to '/admin/users' 56 | 'users' : [ // When route matches '/admin/users'... 57 | initUsersModel, // Run initAdminModel && initUserModel... 58 | { 59 | '' : usersListModule,// When we render usersListModule... 60 | ':userName' : editUserModule // And when we render editUserModule 61 | } 62 | ] 63 | } 64 | ] 65 | } ); 66 | 67 | m.route( document.body, '/', routeHash ); 68 | ``` 69 | -------------------------------------------------------------------------------- /moria.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require( 'lodash' ); 4 | var m = require( 'mithril' ); 5 | 6 | var empty = ''; 7 | var slash = '/'; 8 | 9 | module.exports = function buildRouteHash( routeMap ){ 10 | var hash = {}; 11 | 12 | void function buildRouteLevel( routeMap, tail, before ){ 13 | _.each( routeMap, function buildRoute( value, key ){ 14 | var props = routeProps( value, key, tail, before ); 15 | 16 | if( props.redirect ){ 17 | hash[ props.path ] = redirect( props.path, props.redirect ); 18 | } 19 | else if( props.module ){ 20 | if( props.setup.length ){ 21 | hash[ props.path ] = decorateModule( props.module, props.setup ); 22 | } 23 | else { 24 | hash[ props.path ] = props.module; 25 | } 26 | } 27 | else if( props.subMap ){ 28 | buildRouteLevel( props.subMap, props.path, props.setup ); 29 | } 30 | } ); 31 | }( routeMap, empty, [] ); 32 | 33 | return hash; 34 | }; 35 | 36 | function routeProps( value, key, tail, before ){ 37 | var output = {}; 38 | 39 | var prefix = ( key || !tail ) ? slash : empty; 40 | var segment = prefix + key; 41 | var outcome = _.isArray( value ) && value.pop() || value; 42 | 43 | output.path = tail + segment; 44 | output.setup = _.isArray( value ) ? before.concat( value ) : before; 45 | output.module = isModule( outcome ) && outcome; 46 | output.subMap = !output.module && _.isPlainObject( outcome ) && outcome; 47 | output.redirect = _.isString( outcome ) && outcome; 48 | 49 | return output; 50 | } 51 | 52 | var redirect = ( function redirectScope(){ 53 | var absolute = /^\//; 54 | var ascend = /^\.\.\//; 55 | var tail = /[^\/]+\/?$/; 56 | var paramToken = /:([^\/]+)(\.\.\.)?/g; 57 | var emptyView = function(){}; 58 | 59 | return function redirect( from, to ){ 60 | if( !absolute.test( to ) ){ 61 | while( ascend.test( to ) ){ 62 | to.replace( ascend. empty ); 63 | 64 | from = from.replace( tail, empty ); 65 | } 66 | 67 | to = from + slash + to; 68 | } 69 | 70 | return { 71 | controller : function redirection(){ 72 | var endpoint = to.replace( paramToken, function insertParam( token, param ){ 73 | return m.route.param( param ); 74 | } ); 75 | 76 | m.startComputation(); 77 | 78 | m.route( endpoint ); 79 | 80 | m.endComputation(); 81 | }, 82 | view : emptyView 83 | }; 84 | }; 85 | }() ); 86 | 87 | function decorateModule( module, setup ){ 88 | return { 89 | controller : function controllerDecorator(){ 90 | var args = _.toArray( args ); 91 | 92 | _.each( setup, function executeSetup( fn ){ 93 | fn.apply( module, args ); 94 | } ); 95 | 96 | return construct( module.controller, args ); 97 | }, 98 | view : module.view 99 | }; 100 | } 101 | 102 | var isModule = ( function propsContainer(){ 103 | var props = [ 'controller', 'view' ]; 104 | 105 | return function isModule( x ){ 106 | return _.isPlainObject( x ) && _( x ).omit( props ).isEmpty() && _( x ).pick( props ).every( _.isFunction ); 107 | }; 108 | }() ); 109 | 110 | var construct = ( function metaConstructorFacade(){ 111 | var bind = Function.prototype.bind; 112 | 113 | return bind ? function( Constructor, args ){ 114 | return new ( bind.apply( 115 | Constructor, 116 | _( args ) 117 | .concat( Constructor ) 118 | .reverse() 119 | .valueOf() 120 | ) )(); 121 | } : function( Constructor, args ){ 122 | function Reconstruction( args ){ 123 | return Constructor.apply( this, args ); 124 | } 125 | 126 | Reconstruction.prototype = Constructor.prototype; 127 | 128 | return new Reconstruction( args ); 129 | }; 130 | }() ); 131 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moria", 3 | "version": "0.0.5", 4 | "description": "A routing system for Mithril", 5 | "main": "moria.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/barneycarroll/moria.git" 12 | }, 13 | "keywords": [ 14 | "mithril", 15 | "router", 16 | "routing" 17 | ], 18 | "author": "barney.carroll@gmail.com", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/barneycarroll/moria/issues" 22 | }, 23 | "homepage": "https://github.com/barneycarroll/moria", 24 | "dependencies": { 25 | "lodash": "^2.4.1", 26 | "mithril": "^0.1.22" 27 | } 28 | } 29 | --------------------------------------------------------------------------------