├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── browsers.js ├── karma.conf.js ├── lib └── routehandler.js ├── logo.png ├── package.json ├── routehandler-logo.svg ├── src └── routehandler.tag └── test ├── routehandler.coffee └── samplepages.tag /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .DS_Store -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.2.3" 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | script: 10 | npm test 11 | 12 | after_success: 13 | - 'cat ./coverage/report-lcov/lcov.info | ./node_modules/.bin/coveralls' 14 | 15 | sudo: false 16 | 17 | before_install: 18 | npm install 19 | 20 | notifications: 21 | email: false 22 | 23 | addons: 24 | sauce_connect:true -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.2.2] 4 | ### Bugfix 5 | * Tags not remounting after middlware (issue #4) 6 | 7 | ## [1.2.1] 8 | ### Added 9 | * Change Middleware callback parameters to (ctx,next,page) 10 | * Allowed `use` and `tag` to be used together on the same route declaration 11 | 12 | ## [1.2.0] 13 | ### Added 14 | * Middleware, tag can now be replace with use within route, which accepts a callback 15 | 16 | ## [1.1.0] 17 | ### Added 18 | * Default routes using '/', allowing subroutes to autoload when parent routehandler changes -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | logo 2 | 3 | [![Build Status](https://travis-ci.org/crisward/riot-routehandler.svg?branch=master)](https://travis-ci.org/crisward/riot-routehandler) 4 | [![Coverage Status](https://coveralls.io/repos/crisward/riot-routehandler/badge.svg?branch=master&service=github)](https://coveralls.io/github/crisward/riot-routehandler?branch=master) 5 | [![NPM Downloads](https://img.shields.io/npm/dm/riot-routehandler.svg)](https://www.npmjs.com/package/riot-routehandler) 6 | [![NPM Downloads](https://img.shields.io/npm/v/riot-routehandler.svg)](https://www.npmjs.com/package/riot-routehandler) 7 | 8 | ## Installing 9 | 10 | ```bash 11 | npm install riot-routehandler@1.2.3 # for riot 2 12 | 13 | npm install riot-routehandler # for riot 3 14 | ``` 15 | 16 | ## Versions 17 | 18 | This branch is for Riot v3 19 | I've forked a branch for Riot v2 and will maintain that if neccessary going forward. 20 | 21 | ## Demo 22 | 23 | Basic demo [here](http://codepen.io/crisward/pen/xwGJpM?editors=101) 24 | 25 | ## Usage 26 | 27 | This has been designed to be used with a common js compiler. I mainly use 'Browserify', though it should work fine 28 | with webpack. You'll also need something like `riotify` to require your tags in. 29 | 30 | First you'll need to setup your routes and tag files. Each route can be assigned 31 | a tag. When the route is navigated to your tag will be loaded into the routehandler tag. 32 | Mount will be called when your tag is added and unmount will be called when it is navigated away from. 33 | 34 | ```javascript 35 | //app.js 36 | var riot = require('riot'); 37 | require('riot-routehandler'); 38 | require('home.tag') 39 | require('about.tag') 40 | require('settings.tag') 41 | require('settings1.tag') 42 | require('settings2.tag') 43 | 44 | var routes = [ 45 | {route:"/",tag:"home"}, 46 | {route:"/about/",tag:"about"}, 47 | {route:"/settings/",tag:"settings",routes:[ 48 | {route:"setting1/",tag:"settings1"}, 49 | {route:"setting2/:name?",tag:"settings2"}, 50 | ]} 51 | ]; 52 | 53 | var app = riot.mount('routehandler',{routes:routes,options:{hashbang:true}}); 54 | ``` 55 | 56 | You'll also need to add the routehandler to your html file. 57 | If you add links to your page which match those in your router, they'll be 58 | intercepted to use your client side routes. 59 | 60 | ```html 61 | 62 | 63 | 64 | Sample Doc 65 | 66 | 67 | Home 68 | About 69 | Settings 70 | 71 | 72 | 73 | 74 | 75 | ``` 76 | 77 | ### Subroutes 78 | 79 | In the example above the settings section has two sub-routes, `/settings/setting1/` and 80 | `/settings/setting2/:name?`. 81 | 82 | For these to work, the settings tag will also need a routehandler. 83 | 84 | ```html 85 | 86 |

Settings Panel

87 | 88 |
89 | ``` 90 | 91 | ### Parameters 92 | 93 | Any parameters passed into routes are made available on your tag as opts.params. 94 | So in the example above, the name parameter would be accessed as... 95 | 96 | ```html 97 | 98 |

Hello {opts.params.name}

99 |
100 | ``` 101 | 102 | ### Passing in data 103 | 104 | Any properties passed into the top level routehandler will be passed into 105 | all sub-routehandlers too. This can be useful for passing down 'stores'. 106 | 107 | `app = riot.mount('routehandler',{routes:routes,options:{hashbang:true},stores:stores})` 108 | 109 | ### Options 110 | 111 | Any options your want to pass into the [page.js](https://github.com/visionmedia/page.js) 112 | system can be done via `options`. The above example is using hashbang routing. 113 | Other options can be [found here](https://github.com/visionmedia/page.js#pageoptions) 114 | 115 | ### Navigation 116 | 117 | Navigation can be done programmatically via opts.page. Riot-routehandler 118 | uses [page.js](https://github.com/visionmedia/page.js), so naviating is done with this 119 | passed in function. ie to go to about page 120 | 121 | `opts.page('/about/')` 122 | 123 | ### Default Routes 124 | 125 | It is possible to have a default subroute. This can be done by simply using the `/` 126 | in your subroutes. With the following routes, `subpage` tag will be shown by default 127 | if you navigate to `/page/`. 128 | 129 | ```html 130 | routes = [ 131 | {route:"/",tag:"home"}, 132 | {route:"/page/",tag:"settings",routes:[ 133 | {route:"/",tag:"subpage"}, 134 | {route:"/another/",tag:"subpage2"}, 135 | ]} 136 | ]; 137 | ``` 138 | 139 | ### Middleware 140 | 141 | It is sometimes useful to run code before a tag is displayed, perhaps for permission 142 | checking or even animation (ie changing classes on the body tag) 143 | 144 | This is possible by passing functions into the router in the form of middleware. 145 | Each middleware function will be passed a context object, a next callback and a reference 146 | to the `page` instance so you have access to the various page methods. eg. 147 | 148 | ```javascript 149 | var auth = function(ctx,next,page){ 150 | if(loggedin) return next(); 151 | page.redirect('/login') 152 | } 153 | 154 | routes = [ 155 | {route:"/",tag:"home"}, 156 | {route:"/admin/",use:auth}, 157 | {route:"/admin/",tag:"adminpanel",routes:[ 158 | {route:"/",tag:"mainadmin"}, 159 | {route:"/user/",tag:"useradmin"}, 160 | ]} 161 | ]; 162 | 163 | ``` 164 | 165 | 166 | 167 | 168 | ## About 169 | 170 | Couldn't find anything for riot which gave this ui-router type functionality. 171 | I also wanted a familiar path syntax, so used page.js as it uses the same route 172 | matching as express.js. Also thought this would aid in making this work server 173 | side eventually. Finally I wanted something to be small and simple in the spirit of riot. 174 | Angular ui-router does a ton of stuff I never use, so I've kept this to do 175 | exaclty what I need and no more. Page.js is apparently 1200bytes and this library 176 | is <60 lines of code. 177 | 178 | 179 | ## Running Tests 180 | 181 | ``` 182 | npm install 183 | npm test 184 | ``` 185 | 186 | ## Compatibility 187 | 188 | Internet Explorer versions 9 and below don't support the pushstate api, so require the addition of a browser shim to make this work. Page.js recommends . This is available via a cdn, and if 189 | included with a conditional comment, would only be loaded in IE9 and below. 190 | 191 | ```html 192 | 195 | ``` 196 | 197 | 198 | ## Todo 199 | 200 | * Add an examples folder and a pretty site. 201 | * Maybe add a demo video. 202 | 203 | 204 | ## Credit 205 | 206 | Thanks to vision media for [page.js](https://github.com/visionmedia/page.js) which this depends on and the 207 | other routers which inspired this including [angular-ui-router](https://github.com/angular-ui/ui-router) 208 | 209 | 210 | Thanks also to [coderofsalvation](https://github.com/coderofsalvation) for his middleware suggestion 211 | and the [Noun Project](https://thenounproject.com/) for the logo. 212 | 213 | ## License 214 | 215 | (The MIT License) 216 | 217 | Copyright (c) 2015 Cris Ward 218 | 219 | 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: 220 | 221 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 222 | 223 | 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. 224 | 225 | -------------------------------------------------------------------------------- /browsers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | browsers: { 3 | slsafari: { 4 | base: 'SauceLabs', 5 | browserName: 'safari', 6 | platform: 'OS X 10.10', 7 | group: 0 8 | }, 9 | //slIE9: { 10 | // base: 'SauceLabs', 11 | // browserName: 'internet explorer', 12 | // platform: 'Windows 7', 13 | // version: '9', 14 | // group: 1 15 | //}, 16 | slIE10: { 17 | base: 'SauceLabs', 18 | browserName: 'internet explorer', 19 | platform: 'Windows 8', 20 | version: '10', 21 | group: 1 22 | }, 23 | slIOS8: { 24 | base: 'SauceLabs', 25 | browserName: 'iphone', 26 | platform: 'OS X 10.10', 27 | version: '8.4', 28 | group: 1 29 | }, 30 | slIE11: { 31 | base: 'SauceLabs', 32 | browserName: 'internet explorer', 33 | platform: 'Windows 8.1', 34 | version: '11', 35 | group: 1 36 | }, 37 | slandroid5: { 38 | base: 'SauceLabs', 39 | browserName: 'android', 40 | version: '5.1', 41 | group: 2 42 | }, 43 | slchrome: { 44 | base: 'SauceLabs', 45 | browserName: 'chrome', 46 | group: 2 47 | }, 48 | slfirefox: { 49 | base: 'SauceLabs', 50 | browserName: 'firefox', 51 | group: 2 52 | }, 53 | slandroid4: { 54 | base: 'SauceLabs', 55 | browserName: 'android', 56 | version: '4.0', 57 | group: 3 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | 'use strict'; 3 | var saucelabsBrowsers = require('./browsers').browsers 4 | var browsers = ['PhantomJS'] 5 | if (process.env.SAUCE_USERNAME) { 6 | browsers = Object.keys(saucelabsBrowsers) 7 | } 8 | 9 | config.set({ 10 | basePath: '', 11 | frameworks: ['browserify','mocha', 'chai','sinon'], 12 | files: [ 13 | './node_modules/simulant/dist/simulant.js', 14 | './test/*.coffee' 15 | ], 16 | preprocessors: { 17 | './test/*.coffee': [ 'browserify'], 18 | './lib/*.js': [ 'browserify'] 19 | }, 20 | "browserify": { 21 | "debug": true, 22 | "transform": ["browserify-istanbul"], 23 | extensions: ['.js', '.tag', '.coffee'] 24 | }, 25 | reporters: ['spec', "coverage",'saucelabs'], 26 | //hostname:'192.168.1.7', 27 | port: 9876, 28 | colors: true, 29 | autoWatch: true, 30 | singleRun: true, 31 | 32 | // level of logging 33 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 34 | logLevel: config.LOG_INFO, 35 | 36 | browsers: browsers, 37 | coverageReporter: { 38 | dir: 'coverage/', 39 | reporters: [{type:'lcov',subdir: 'report-lcov'},{type:'text-summary'}] 40 | }, 41 | sauceLabs: { 42 | build: 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')', 43 | tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER, 44 | testName: 'riot-routehandler', 45 | connectOptions: { 46 | port: 5757, 47 | logfile: 'sauce_connect.log' 48 | }, 49 | startConnect: true, 50 | recordVideo: false, 51 | recordScreenshots: false, 52 | }, 53 | captureTimeout: 120000,// Increase timeout in case connection in CI is slow 54 | customLaunchers: saucelabsBrowsers 55 | }); 56 | }; -------------------------------------------------------------------------------- /lib/routehandler.js: -------------------------------------------------------------------------------- 1 | 2 | riot.tag2('routehandler', '
', '', '', function(opts) { 3 | var page; 4 | 5 | page = null; 6 | 7 | if (typeof exports === "object" && (typeof exports !== "undefined" && exports !== null)) { 8 | page = require('page'); 9 | } else if (window.page == null) { 10 | return console.log('Page.js not found - please check it has been npm installed or included in your page'); 11 | } else { 12 | page = window.page; 13 | } 14 | 15 | this.on('mount', (function(_this) { 16 | return function() { 17 | var basepath, ref; 18 | _this.tagstack = []; 19 | if (opts.routes) { 20 | _this.mountRoutes({ 21 | handler: _this 22 | }, opts.routes); 23 | if ((ref = opts.options) != null ? ref.base : void 0) { 24 | basepath = opts.options.base; 25 | page.base(basepath); 26 | delete opts.options.base; 27 | } 28 | return page(opts.options); 29 | } 30 | }; 31 | })(this)); 32 | 33 | this.mountRoutes = (function(_this) { 34 | return function(parent, routes) { 35 | var route; 36 | return route = _this.findRoute(null, routes, function(tree, req) { 37 | var i, idx, len, nexttag, ref, ref1, ref2, ref3, removeTag, results, routeopts, tag; 38 | delete opts.routes; 39 | routeopts = opts; 40 | routeopts.page = page; 41 | routeopts.params = req.params; 42 | tag = _this; 43 | for (idx = i = 0, len = tree.length; i < len; idx = ++i) { 44 | route = tree[idx]; 45 | if (_this.tagstack[idx] && _this.tagstack[idx].tagname === route.tag) { 46 | nexttag = _this.tagstack[idx].nexttag; 47 | riot.update(); 48 | } else { 49 | if (tag && (route != null ? route.tag : void 0)) { 50 | nexttag = tag.setTag(route.tag, routeopts); 51 | } 52 | } 53 | _this.tagstack[idx] = { 54 | tagname: route.tag, 55 | nexttag: nexttag, 56 | tag: tag 57 | }; 58 | tag = (nexttag != null ? (ref = nexttag[0]) != null ? ref.tags.routehandler : void 0 : void 0) || (nexttag != null ? (ref1 = nexttag[0]) != null ? (ref2 = ref1.root.querySelector('routehandler')) != null ? ref2._tag : void 0 : void 0 : void 0); 59 | } 60 | results = []; 61 | while (idx < _this.tagstack.length) { 62 | removeTag = _this.tagstack.pop(); 63 | results.push((ref3 = removeTag.nexttag[0]) != null ? ref3.unmount(true) : void 0); 64 | } 65 | return results; 66 | }); 67 | }; 68 | })(this); 69 | 70 | this.setTag = (function(_this) { 71 | return function(tagname, routeopts) { 72 | var tag; 73 | _this.root.childNodes[0].setAttribute("data-is", tagname); 74 | _this.tags[tagname]; 75 | tag = riot.mount(tagname, routeopts); 76 | tag[0].opts = routeopts; 77 | return tag; 78 | }; 79 | })(this); 80 | 81 | this.findRoute = (function(_this) { 82 | return function(parents, routes, cback) { 83 | var i, len, parentpath, results, route, subparents; 84 | parentpath = parents ? parents.map(function(ob) { 85 | return ob.route; 86 | }).join("").replace(/\/\//g, '/') : ""; 87 | results = []; 88 | for (i = 0, len = routes.length; i < len; i++) { 89 | route = routes[i]; 90 | if ((route.use != null) && typeof route.use === "function") { 91 | (function(route) { 92 | var mainroute; 93 | mainroute = (parentpath + route.route).replace(/\/\//g, '/'); 94 | return page(mainroute, function(ctx, next) { 95 | if (mainroute !== "*") { 96 | cback([route], ctx); 97 | } 98 | return route.use(ctx, next, page); 99 | }); 100 | })(route); 101 | } 102 | if (route.tag != null) { 103 | subparents = parents ? parents.slice() : []; 104 | subparents.push(route); 105 | (function(subparents) { 106 | var mainroute, thisroute; 107 | thisroute = route; 108 | mainroute = (parentpath + route.route).replace(/\/\//g, '/'); 109 | return page(mainroute, function(req, next) { 110 | var ref; 111 | cback(subparents, req); 112 | if ((ref = thisroute.routes) != null ? ref.filter(function(route) { 113 | return route.route === "/"; 114 | }).length : void 0) { 115 | return next(); 116 | } 117 | }); 118 | })(subparents); 119 | } 120 | if (route.routes) { 121 | results.push(_this.findRoute(subparents, route.routes, cback)); 122 | } else { 123 | results.push(void 0); 124 | } 125 | } 126 | return results; 127 | }; 128 | })(this); 129 | }); -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crisward/riot-routehandler/b61e2926a8bb2e2e2b2b3773f8cb91577915d503/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "riot-routehandler", 3 | "version": "3.0.2", 4 | "description": "A angular-ui-router inspired router handler for riot.js", 5 | "main": "lib/routehandler.js", 6 | "repository": "crisward/riot-routehandler", 7 | "scripts": { 8 | "test": "karma start", 9 | "tdd": "npm run watch & karma start --reporters=spec --browsers=Chrome --singleRun=false", 10 | "watch": "riot -w src lib --template pug", 11 | "build": "riot src lib --template pug" 12 | }, 13 | "directories": { 14 | "lib": "lib" 15 | }, 16 | "browserify": { 17 | "transform": [ 18 | [ 19 | "riotify", 20 | { 21 | "expr": false, 22 | "type": "coffee", 23 | "template": "pug", 24 | "extension": ".tag" 25 | } 26 | ], 27 | [ 28 | "coffeeify", 29 | { 30 | "extension": ".coffee" 31 | } 32 | ] 33 | ] 34 | }, 35 | "author": "cris ward", 36 | "license": "ISC", 37 | "dependencies": { 38 | "page": "^1.6.3" 39 | }, 40 | "publishConfig": { 41 | "registry": "http://registry.npmjs.org" 42 | }, 43 | "devDependencies": { 44 | "browserify-istanbul": "0.2.1", 45 | "chai": "3.0.0", 46 | "coffee-script": "1.9.3", 47 | "coffeeify": "1.1.0", 48 | "coveralls": "2.11.4", 49 | "istanbul": "0.3.20", 50 | "karma": "0.13.9", 51 | "karma-browserify": "4.3.0", 52 | "karma-chai": "0.1.0", 53 | "karma-chrome-launcher": "0.2.1", 54 | "karma-coverage": "0.5.0", 55 | "karma-mocha": "0.1", 56 | "karma-phantomjs-launcher": "0.1", 57 | "karma-sauce-launcher": "0.2.14", 58 | "karma-sinon": "1.0.4", 59 | "karma-spec-reporter": "0.0.20", 60 | "mocha": "2.0.1", 61 | "pug": "0.1.0", 62 | "riot": "3.0.2", 63 | "riotify": "0.1.2", 64 | "simulant": "0.1.5", 65 | "sinon": "1.12.2", 66 | "sinon-chai": "2.8.0", 67 | "watchify": "^3.2.1" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /routehandler-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | riot 7 | 8 | 9 | routehandler 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/routehandler.tag: -------------------------------------------------------------------------------- 1 | routehandler 2 | div(data-is="{tagname}") 3 | 4 | script(type='text/coffeescript'). 5 | page = null 6 | if typeof exports == "object" && exports? 7 | page = require 'page' 8 | else if !window.page? 9 | return console.log 'Page.js not found - please check it has been npm installed or included in your page' 10 | else 11 | page = window.page 12 | @on 'mount',=> 13 | @tagstack = [] 14 | if opts.routes 15 | @mountRoutes({handler:@},opts.routes) 16 | if opts.options?.base 17 | basepath = opts.options.base 18 | page.base(basepath) 19 | delete opts.options.base 20 | page(opts.options) 21 | 22 | @mountRoutes = (parent,routes)=> 23 | route = @findRoute null,routes,(tree,req)=> 24 | delete opts.routes 25 | routeopts = opts 26 | routeopts.page = page 27 | routeopts.params = req.params 28 | tag = @ 29 | for route,idx in tree 30 | if @tagstack[idx] && @tagstack[idx].tagname == route.tag 31 | nexttag = @tagstack[idx].nexttag 32 | riot.update() 33 | else 34 | nexttag = tag.setTag(route.tag,routeopts) if tag && route?.tag #dont mount middlware 35 | @tagstack[idx] = {tagname:route.tag,nexttag:nexttag,tag:tag} 36 | tag = nexttag?[0]?.tags.routehandler || nexttag?[0]?.root.querySelector('routehandler')?._tag 37 | while idx < @tagstack.length 38 | removeTag = @tagstack.pop() 39 | removeTag.nexttag[0]?.unmount(true) 40 | 41 | @setTag = (tagname,routeopts)=> 42 | @root.childNodes[0].setAttribute("data-is",tagname) 43 | @tags[tagname] 44 | tag = riot.mount(tagname,routeopts) 45 | tag[0].opts = routeopts 46 | tag 47 | 48 | @findRoute = (parents,routes,cback)=> 49 | parentpath = if parents then parents.map((ob)->ob.route).join("").replace(/\/\//g,'/') else "" 50 | for route in routes 51 | if route.use? && typeof route.use == "function" 52 | do (route)-> 53 | mainroute = (parentpath+route.route).replace(/\/\//g,'/') 54 | page mainroute,(ctx,next)-> 55 | cback([route],ctx) if mainroute !="*" #dont call unmount with wild 56 | route.use(ctx,next,page) 57 | 58 | if route.tag? 59 | subparents = if parents then parents.slice() else [] 60 | subparents.push(route) 61 | do (subparents)-> 62 | thisroute = route 63 | mainroute = (parentpath+route.route).replace(/\/\//g,'/') 64 | page mainroute, (req,next)-> 65 | cback(subparents,req) 66 | next() if thisroute.routes?.filter((route)-> route.route=="/").length 67 | 68 | 69 | @findRoute(subparents,route.routes,cback) if route.routes -------------------------------------------------------------------------------- /test/routehandler.coffee: -------------------------------------------------------------------------------- 1 | window.riot = require 'riot' 2 | routehandler = require '../lib/routehandler.js' 3 | simulant = require 'simulant' 4 | page = require 'page' 5 | require './samplepages.tag' 6 | spyclick = null 7 | test = {} 8 | test = 9 | middleware1:(ctx,next)-> 10 | window.middleran1 = true 11 | next() 12 | middleware2:(ctx,next,page)-> 13 | window.middleran2 = true 14 | page.redirect('/page1/') 15 | middleware3:(ctx,next,page)-> 16 | window.middleran3 = true 17 | middleware4:(ctx,next,page)-> 18 | window.middleran4 = true 19 | router = document.querySelector("routehandler"); 20 | router._tag.setTag("middleware"); 21 | middleware5:(ctx,next,page)-> 22 | window.middleran5 = true 23 | next() 24 | 25 | routes = [ 26 | {route:"*",use:test.middleware1} 27 | {route:"/",tag:"home"} 28 | {route:"/page100/",use:test.middleware2} 29 | {route:"/page101/",use:test.middleware3,tag:"page1"} 30 | {route:"/page102/",use:test.middleware4} 31 | {route:"/page1/",tag:"page1"} 32 | {route:"/page1/:name",tag:"page1"} 33 | {route:"/page2/",tag:"page2",routes:[ 34 | {route:"/",tag:"page4"} 35 | {route:"/sub/:name?",tag:"page2sub"} 36 | ]} 37 | {route:"/page3/",tag:"page2",routes:[ 38 | {route:"sub/",tag:"page3sub",routes:[ 39 | {route:"three/",tag:"page3subsub"} 40 | {route:"/",tag:"page5"} 41 | {route:"four/",tag:"page2sub"} 42 | {route:"five/",use:test.middleware5} 43 | {route:"five/",tag:"page6"} 44 | 45 | ]} 46 | ]} 47 | {route:"/page103/",tag:"hiddensub",routes:[ 48 | {route:"/page104",tag:"page4"} 49 | ]} 50 | ] 51 | 52 | describe 'routehandler',-> 53 | 54 | before -> 55 | @domnode = document.createElement('app') 56 | @node = document.body.appendChild(@domnode) 57 | @tag = riot.mount(@domnode,'app',{options:{hashbang:false,base:'/test'},routes,test:'Cheese'})[0] 58 | 59 | after -> 60 | @domnode = '' 61 | @tag.unmount() 62 | 63 | afterEach -> 64 | delete window.middleran5 65 | 66 | it "should exist on the page",-> 67 | expect(document.querySelectorAll('routehandler').length).to.equal(1) 68 | 69 | it "should show home page",-> 70 | page('/') 71 | expect(document.body.textContent).to.contain('Home Page') 72 | 73 | it "should show page2 and default page",-> 74 | page('/page2/') 75 | expect(document.body.textContent).to.contain('Page 2') 76 | expect(document.body.textContent).to.not.contain('subpage') 77 | expect(document.body.textContent).to.contain('Default Page') 78 | 79 | it "should show sub page",-> 80 | page('/page2/sub/') 81 | expect(document.body.textContent).to.contain('subpage') 82 | 83 | it "should allow deep routes, event if previous route was not a parent",-> 84 | page('/page2/sub/') 85 | expect(document.body.textContent).to.contain('subpage') 86 | 87 | it "should show sub page inside page 2",-> 88 | page('/page2/sub/') 89 | expect(document.body.textContent).to.contain('Page 2') 90 | expect(document.body.textContent).to.contain('subpage') 91 | 92 | it "should show route with parameter",-> 93 | page('/page1/cris') 94 | expect(document.body.textContent).to.contain('cris') 95 | 96 | it "should show subroute with parameter",-> 97 | page('/page2/sub/cris/') 98 | expect(document.body.textContent).to.contain('cris') 99 | 100 | it "should have access to root routehandler opts at all levels",-> 101 | page('/page2/sub/cris') 102 | expect(document.body.textContent).to.contain('Cheese') 103 | 104 | it "should load default page at the third level",-> 105 | page('/page3/sub/') 106 | expect(document.body.textContent).to.contain('third level default') 107 | 108 | it "should not mount a tag if it's already mounted",-> 109 | window.mountcount = 0 110 | page('/page1/') 111 | expect(window.mountcount).to.equal(1) 112 | page('/page1/') 113 | expect(window.mountcount).to.equal(1) 114 | page('/page1/') 115 | expect(window.mountcount).to.equal(1) 116 | 117 | it "should unmount a subtag, if it's unmounted",-> 118 | window.submountcount = 0 119 | page('/page2/sub/test') 120 | expect(window.submountcount).to.equal(1) 121 | page('/page2/sub/test') 122 | expect(window.submountcount).to.equal(1) 123 | page('/page2/') 124 | expect(window.submountcount).to.equal(0) 125 | 126 | it "should not unmount a subtag, if it's child is unmounted",-> 127 | window.submountcount = 0 128 | window.subsubmountcount = 0 129 | page('/page3/sub/three') 130 | expect(window.submountcount).to.equal(1) 131 | expect(window.subsubmountcount).to.equal(1) 132 | page('/page3/sub/three') 133 | expect(window.submountcount).to.equal(1) 134 | expect(window.subsubmountcount).to.equal(1) 135 | page('/page3/sub/') 136 | expect(window.submountcount).to.equal(1) 137 | expect(window.subsubmountcount).to.equal(0) 138 | page('/page3') 139 | expect(window.submountcount).to.equal(0) 140 | expect(window.subsubmountcount).to.equal(0) 141 | 142 | it "should unmount a tag when overwritten",-> 143 | window.mountcount = 0 144 | page('/page1/') 145 | expect(window.mountcount).to.equal(1) 146 | page('/page2/') 147 | expect(window.mountcount).to.equal(0) 148 | 149 | it "should use routehandlers in yielded tags",-> 150 | page('/page103/') 151 | expect(document.body.textContent).to.contain('hello hidden sub') 152 | page('/page103/page104') 153 | expect(document.body.textContent).to.contain('hello hidden sub') 154 | expect(document.body.textContent).to.contain('Default Page') 155 | 156 | it "should have access to sub sub sub document",-> 157 | page('/page3/sub/three/') 158 | expect(document.body.textContent).to.contain('subsub') 159 | 160 | it "should show page five with submiddleware with next",-> 161 | page('/page3/sub/five/') 162 | expect(@domnode.textContent).to.contain("run after submiddle") 163 | 164 | 165 | it "should switch subsubtags",-> 166 | page('/page3/sub/three/') 167 | expect(document.body.textContent).to.contain('subsub') 168 | page('/page3/sub/four/') 169 | expect(document.body.textContent).to.contain("I'm a subpage") 170 | 171 | it "should update properties when path changes",-> 172 | page('/page1/') 173 | expect(document.body.textContent).not.to.contain('cris') 174 | expect(document.body.textContent).to.contain("hello I'm page 1") 175 | page('/page1/cris') 176 | expect(document.body.textContent).to.contain('cris') 177 | 178 | describe "Middleware",-> 179 | 180 | it "should call middleware on base route",-> 181 | window.middleran1 = false 182 | page('/') 183 | expect(window.middleran1).to.be.true 184 | 185 | it "should redirect from middleware",(done)-> 186 | window.middleran2 = false 187 | page('/page100/') 188 | expect(window.middleran2).to.be.true 189 | setTimeout -> 190 | expect(document.body.textContent).to.contain("hello I'm page 1") 191 | done() 192 | 193 | it "should change route after middleware",-> 194 | # https://github.com/crisward/riot-routehandler/issues/4 195 | simulant.fire( document.querySelector('a[href="/test/"]'), 'click' ) 196 | expect(@domnode.textContent).to.contain('Home Page') 197 | 198 | simulant.fire( document.querySelector('a[href="/test/page102/"]'), 'click' ) 199 | expect(document.body.textContent).to.contain('hello middleware') 200 | 201 | simulant.fire( document.querySelector('a[href="/test/page1/"]'), 'click' ) 202 | expect(document.body.textContent).to.contain("hello I'm page 1") 203 | 204 | simulant.fire( document.querySelector('a[href="/test/page102/"]'), 'click' ) 205 | expect(document.body.textContent).to.contain('hello middleware') 206 | 207 | simulant.fire( document.querySelector('a[href="/test/page1/"]'), 'click' ) 208 | expect(document.body.textContent).to.contain("hello I'm page 1") 209 | 210 | simulant.fire( document.querySelector('a[href="/test/page102/"]'), 'click' ) 211 | expect(document.body.textContent).to.contain('hello middleware') 212 | 213 | simulant.fire( document.querySelector('a[href="/test/page1/"]'), 'click' ) 214 | expect(document.body.textContent).to.contain("hello I'm page 1") 215 | 216 | it "should run middleware when declaired on same line as tag",-> 217 | window.middleran3 = false 218 | page('/page101/') 219 | expect(window.middleran3).to.be.true 220 | 221 | it "should run middleware on subroute",-> 222 | expect(window.middleran5).to.be.undefined 223 | page('/page3/sub/five/') 224 | expect(window.middleran5).to.be.true 225 | 226 | -------------------------------------------------------------------------------- /test/samplepages.tag: -------------------------------------------------------------------------------- 1 | app 2 | a(href="/test/") home 3 | a(href="/test/page1/") page1 4 | a(href="/test/page102/") middleware 5 | 6 | routehandler(options="{opts.options}",routes="{opts.routes}",test="{opts.test}") 7 | 8 | hiddenhandler 9 | 10 | 11 | hiddensub 12 | p hello hidden sub 13 | hiddenhandler 14 | routehandler 15 | 16 | home 17 | p hello I'm a Home Page 18 | 19 | page4 20 | p Default Page 21 | 22 | page5 23 | p third level default 24 | 25 | page6 26 | p run after submiddle 27 | 28 | middleware 29 | h3 hello middleware 30 | 31 | page1 32 | p hello I'm page 1 33 | p your name is {opts.params ? opts.params.name : ''} 34 | 35 | script(type='text/coffeescript'). 36 | @on 'mount',-> 37 | window.mountcount++ if window.mountcount? 38 | @on 'unmount',-> 39 | window.mountcount-- if window.mountcount? 40 | 41 | page2 42 | p hello I'm Page 2 43 | 44 | routehandler 45 | 46 | page2sub 47 | p(if="{opts.params}") I'm a subpage {opts.params.name} 48 | p Mmm {opts.test} 49 | 50 | script(type='text/coffeescript'). 51 | @on 'mount',-> 52 | window.submountcount++ if window.submountcount? 53 | @on 'unmount',-> 54 | window.submountcount-- if window.submountcount? 55 | 56 | page3sub 57 | p hello I'm Page 3 sub 58 | 59 | script(type='text/coffeescript'). 60 | @on 'mount',-> 61 | window.submountcount++ if window.submountcount? 62 | @on 'unmount',-> 63 | window.submountcount-- if window.submountcount? 64 | 65 | routehandler 66 | 67 | page3subsub 68 | p hello I'm Page 3 subsub 69 | 70 | script(type='text/coffeescript'). 71 | @on 'mount',-> 72 | window.subsubmountcount++ if window.subsubmountcount? 73 | @on 'unmount',-> 74 | window.subsubmountcount-- if window.subsubmountcount? 75 | 76 | 77 | 78 | 79 | 80 | --------------------------------------------------------------------------------