├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── dist ├── angular-detour.amd.js ├── angular-detour.amd.min.js ├── angular-detour.js └── angular-detour.min.js ├── package.json ├── samples ├── 1-ui-router-compatibility │ ├── css │ │ └── bootstrap.min.css │ ├── index.html │ ├── js │ │ ├── app.js │ │ └── main.js │ ├── partials │ │ ├── contacts.detail.html │ │ ├── contacts.detail.item.edit.html │ │ ├── contacts.detail.item.html │ │ ├── contacts.html │ │ ├── contacts.list.html │ │ └── fourOhfour.html │ └── server.js ├── 2-lazy-components-runtime │ ├── css │ │ └── bootstrap.min.css │ ├── index.html │ ├── js │ │ ├── app.js │ │ ├── controllers │ │ │ ├── contactsController.js │ │ │ ├── contactsDetailController.js │ │ │ ├── contactsDetailItemController.js │ │ │ ├── contactsDetailItemEditController.js │ │ │ └── homeController.js │ │ ├── detourService.js │ │ └── main.js │ ├── partials │ │ ├── contacts.detail.html │ │ ├── contacts.detail.item.edit.html │ │ ├── contacts.detail.item.html │ │ ├── contacts.html │ │ ├── contacts.list.html │ │ ├── fourOhfour.html │ │ └── home.html │ └── server.js ├── 3-json-config │ ├── css │ │ └── bootstrap.min.css │ ├── index.html │ ├── js │ │ ├── app.js │ │ ├── controllers │ │ │ ├── contactsController.js │ │ │ ├── contactsDetailController.js │ │ │ ├── contactsDetailItemController.js │ │ │ ├── contactsDetailItemEditController.js │ │ │ └── homeController.js │ │ ├── detourService.js │ │ ├── main.js │ │ └── services │ │ │ ├── getContactIdFromParams.js │ │ │ ├── getContactIdHtml.js │ │ │ └── getHelloWorld.js │ ├── partials │ │ ├── contacts.detail.html │ │ ├── contacts.detail.item.edit.html │ │ ├── contacts.detail.item.html │ │ ├── contacts.html │ │ ├── contacts.list.html │ │ ├── fourOhfour.html │ │ └── home.html │ └── server.js ├── 4-lazy-routing │ ├── css │ │ └── bootstrap.min.css │ ├── index.html │ ├── js │ │ ├── app.js │ │ ├── detourService.js │ │ ├── lazy │ │ │ ├── controllers │ │ │ │ ├── contactsController.js │ │ │ │ ├── contactsDetailController.js │ │ │ │ ├── contactsDetailItemController.js │ │ │ │ ├── contactsDetailItemEditController.js │ │ │ │ └── homeController.js │ │ │ └── services │ │ │ │ ├── getContactIdFromParams.js │ │ │ │ ├── getContactIdHtml.js │ │ │ │ └── getHelloWorld.js │ │ └── main.js │ ├── partials │ │ ├── contacts.detail.html │ │ ├── contacts.detail.item.edit.html │ │ ├── contacts.detail.item.html │ │ ├── contacts.html │ │ ├── contacts.list.html │ │ ├── fourOhfour.html │ │ └── home.html │ ├── server-files │ │ ├── sample-routes.json │ │ └── urlMatcher.js │ └── server.js └── README.md └── src ├── StateBase.js ├── UrlMatcher.js ├── angular-detour.js ├── bst.js.hide ├── common.js ├── detourModule.js ├── detourProvider.js ├── getRoute.txt ├── templateFactory.js └── viewDirective.js /.gitignore: -------------------------------------------------------------------------------- 1 | dist/dependencies 2 | bower_components 3 | lib 4 | node_modules 5 | 6 | test 7 | core_test 8 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "quotmark": true, 4 | "laxcomma" : true, 5 | "laxbreak": true, 6 | "curly":true, 7 | "eqeqeq":true, 8 | "immed":true, 9 | "latedef":true, 10 | "newcap":true, 11 | "noarg":true, 12 | "sub":true, 13 | "eqnull":true, 14 | 15 | "globals" : { 16 | "angular" : false, 17 | "requirejs" :false 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.loadNpmTasks('grunt-bower-task'); 4 | grunt.renameTask('bower', 'bowerTask'); 5 | grunt.loadNpmTasks('grunt-bower'); 6 | grunt.renameTask('bower', 'gruntBower'); 7 | grunt.loadNpmTasks('grunt-requirejs'); 8 | grunt.loadNpmTasks('grunt-contrib-jshint'); 9 | grunt.loadNpmTasks('grunt-contrib-clean'); 10 | // grunt.loadNpmTasks('grunt-contrib-concat'); 11 | // grunt.loadNpmTasks('grunt-contrib-uglify'); 12 | grunt.loadNpmTasks('grunt-contrib-watch'); 13 | grunt.renameTask('watch', 'gruntWatch'); 14 | 15 | // Default task. 16 | grunt.registerTask('bower', ['bowerTask', 'gruntBower']); 17 | grunt.registerTask('default', ['build']); 18 | grunt.registerTask('build', ['jshint', 'clean', 'bower', 'requirejs:detourDev', 'requirejs:detourDevAmd']); 19 | grunt.registerTask('rebuild', ['requirejs:detourDev', 'requirejs:detourDevAmd']); 20 | grunt.registerTask('release', ['build','requirejs:detourMin', 'requirejs:detourMinAmd','jshint']); 21 | grunt.registerTask('watch', ['build', 'gruntWatch']); 22 | 23 | // Print a timestamp (useful for when watching) 24 | grunt.registerTask('timestamp', function() { 25 | grunt.log.subhead(Date()); 26 | }); 27 | 28 | // Project configuration. 29 | grunt.initConfig({ 30 | dirs: { 31 | dist: 'dist', 32 | components: 'components', 33 | lib: 'lib', 34 | src: { 35 | js: ['src/**/*.js'] 36 | } 37 | }, 38 | pkg: grunt.file.readJSON('package.json'), 39 | banner: 40 | '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + 41 | '<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' + 42 | ' * Copyright (c) <%= grunt.template.today(\'yyyy\') %> <%= pkg.author.name %>;\n' + 43 | ' * Based on and uses software code found at https://github.com/angular-ui/ui-router which is \n' + 44 | ' * Copyright (c) 2013, Karsten Sperling\n' + 45 | ' * Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n */\n', 46 | header: 47 | '(function() {\n' + 48 | '\'use strict\';\n' + 49 | '//ECMAScript 5 rules apply.\n' + 50 | '//Self-invoking anonymous function keeps global scope clean.\n', 51 | footer: 52 | '//close self-invoking anonymous function\n' + 53 | '}());\n', 54 | clean: ['<%= dirs.dist %>/*', '<%= dirs.components %>/*', '<%= dirs.lib %>/*'], 55 | gruntBower: { 56 | dev: { 57 | dest: '<%= dirs.dist %>/dependencies' 58 | } 59 | }, 60 | gruntWatch:{ 61 | files:['<%= dirs.src.js %>'], 62 | tasks:['rebuild'] 63 | }, 64 | bowerTask: { 65 | install: { 66 | options: { 67 | copy: false 68 | } 69 | } 70 | }, 71 | requirejs: { 72 | options: { 73 | wrap: { 74 | start: '<%= banner %>(function() {', 75 | end: '}());' 76 | }, 77 | baseUrl: 'src', 78 | paths: { 79 | 'couchPotato': '../dist/dependencies/angular-couch-potato', 80 | 'angular': 'empty:' 81 | }, 82 | include: ['angular-detour'] 83 | }, 84 | detourDev: { 85 | options: { 86 | almond: true, 87 | out: '<%= dirs.dist %>/<%= pkg.name %>.js', 88 | optimize: 'none', 89 | insertRequire: ['angular-detour'] 90 | } 91 | }, 92 | detourDevAmd: { 93 | options: { 94 | out: '<%= dirs.dist %>/<%= pkg.name %>.amd.js' 95 | , optimize: 'none' 96 | } 97 | }, 98 | detourMin: { 99 | options: { 100 | almond: true, 101 | out: '<%= dirs.dist %>/<%= pkg.name %>.min.js', 102 | insertRequire: ['angular-detour'] 103 | } 104 | }, 105 | detourMinAmd: { 106 | options: { 107 | out: '<%= dirs.dist %>/<%= pkg.name %>.amd.min.js' 108 | } 109 | } 110 | }, 111 | concat:{ 112 | dist: { 113 | options: { 114 | banner: '<%= banner + header %>', 115 | footer: '<%= footer %>', 116 | stripBanners: true, 117 | process: function(src, filepath) { 118 | return '// Source: ' + filepath + '\n' + 119 | src.replace(/(^|\n)[ \t]*('use strict'|"use strict");?\s*/g, '$1'); 120 | } 121 | }, 122 | src: [ 123 | 'lib/angular-couch-potato/angular-couch-potato.js', 124 | 'src/common.js', 125 | 'src/templateFactory.js', 126 | 'src/urlMatcherFactory.js', 127 | 'src/stateLoader.js', 128 | 'src/stateBase.js', 129 | 'src/detour.js', 130 | 'src/viewDirective.js' 131 | ], 132 | dest:'<%= dirs.dist %>/<%= pkg.name %>.js' 133 | } 134 | }, 135 | uglify:{ 136 | dist: { 137 | options: { 138 | banner: '<%= banner %>' 139 | }, 140 | src:'<%= dirs.dist %>/<%= pkg.name %>.js', 141 | dest:'<%= dirs.dist %>/<%= pkg.name %>.min.js' 142 | } 143 | }, 144 | jshint:{ 145 | files:['Gruntfile.js', '<%= dirs.src.js %>'], 146 | options: { 147 | jshintrc: '.jshintrc' 148 | } 149 | } 150 | }); 151 | }; 152 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 Stuart Salsbury 4 | Based on, and includes substantial portions of angular-ui/ui-router 5 | (http://github.com/angular-ui/ui-router) which is Copyright (c) 2013 6 | Karsten Sperling. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-detour 2 | 3 | * implements a StatesTree-version of ui-router 4 | * allows runtime editing of configuration 5 | * supports merging json representations of routing definitions as initial configurations and as updates 6 | * allows templateProviders and resolve functions to be specified as named services 7 | * supports lazy loading of components through angular-couchPotato 8 | * supports lazy-loading/updating definitions from a server through AJAX 9 | * derivation of [ui-router](https://github.com/angular-ui/ui-router) 10 | 11 | **See the README in the [samples directory](https://github.com/afterglowtech/angular-detour/tree/master/samples) for basic demonstrations.** 12 | 13 | #### Implementation 14 | 15 | Integration points with ui-router are up in the air as that project progresses. At this time, updates to ui-router are being tracked manually. 16 | 17 | ### History/Attribution 18 | 19 | detour is a derivation of ui-router (https://github.com/angular-ui/ui-router). 20 | 21 | ### License 22 | 23 | See the [LICENSE file](https://github.com/afterglowtech/angular-detour/blob/master/LICENSE). 24 | 25 | ### Questions/Comments/Concerns 26 | 27 | See the [Issues List](https://github.com/afterglowtech/angular-detour/issues). 28 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-detour", 3 | "version": "0.6.0", 4 | "main": [ 5 | "dist/angular-detour.js" 6 | ], 7 | "description": "Lazy-loaded/runtime-configurable/server-defined routing for AngularJS applications based on ui-router", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/afterglowtech/angular-detour.git" 11 | }, 12 | "ignore": [ 13 | "samples", 14 | "src", 15 | "./*.js", 16 | "./*.jshintrc", 17 | "./*.gitignore", 18 | "package.json" 19 | ], 20 | "dependencies": { 21 | }, 22 | "devDependencies": { 23 | "angular-couch-potato" : "stu-salsbury/angular-couch-potato#master", 24 | "angular": "~1.2.0", 25 | "requirejs": "~2.1.8" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /dist/angular-detour.amd.min.js: -------------------------------------------------------------------------------- 1 | /*! angular-detour - v0.6.0 - 2013-11-07 2 | * https://github.com/afterglowtech/angular-detour 3 | * Copyright (c) 2013 Stu Salsbury; 4 | * Based on and uses software code found at https://github.com/angular-ui/ui-router which is 5 | * Copyright (c) 2013, Karsten Sperling 6 | * Licensed MIT 7 | */ 8 | 9 | /*! angular-couch-potato - v0.1.0 - 2013-11-07 10 | * https://github.com/stu-salsbury/angular-couch-potato 11 | * Copyright (c) 2013 Stu Salsbury; 12 | * Uses software code originally found at https://github.com/szhanginrhythm/angular-require-lazyload 13 | * Licensed MIT 14 | */ 15 | 16 | (function(){define("common",[],function(){function e(){function e(e){return typeof e=="function"}function t(e){return!e||typeof e.length!="number"?!1:typeof e.hasOwnProperty!="function"&&typeof e.constructor!="function"?!0:typeof e=="JQLite"||typeof jQuery!="undefined"&&typeof e=="jQuery"||toString.call(e)!=="[object Object]"||typeof e.callee=="function"}function n(r,i,s){var o;if(r)if(e(r))for(o in r)o!=="prototype"&&o!=="length"&&o!=="name"&&r.hasOwnProperty(o)&&i.call(s,r[o],o);else if(r.forEach&&r.forEach!==n)r.forEach(i,s);else if(t(r))for(o=0;o=0)break;i+=l(p)+"("+h+")",f(c),u.push(p),s=n.lastIndex}p=t.substring(s);var d=p.indexOf("?");if(d>=0){var v=this.sourceSearch=p.substring(d);p=p.substring(0,d),this.sourcePath=t.substring(0,s+d),e.forEach(v.substring(1).split(/[&?]/),f)}else this.sourcePath=t,this.sourceSearch="";i+=l(p)+"$",u.push(p),this.regexp=new RegExp(i),this.prefix=u[0]}return t.prototype.concat=function(e){return new t(this.sourcePath+e+this.sourceSearch)},t.prototype.toString=function(){return this.source},t.prototype.exec=function(e,t){var n=this.regexp.exec(e);if(!n)return null;var r=this.params,i=r.length,s=this.segments.length-1,o={},u;for(u=0;u=0)throw new Error("Invalid local state name ("+t+")")},Object.defineProperty(r.prototype,"root",{get:function(){return this.parent.root}}),Object.defineProperty(r.prototype,"children",{get:function(){return this._children},set:function(e){this._children=e;if(this._children)for(var t in this._children)this._children[t].parent=this}}),r.prototype.resetPath=function(){this.path=this.parent.path.concat(this)},Object.defineProperty(r.prototype,"url",{get:function(){return this._url},set:function(e){this._url=e,this.needsInit=!0}}),Object.defineProperty(r.prototype,"aliases",{get:function(){return this._aliases},set:function(e){this._aliases=e,this.needsInit=!0}}),r.prototype.resetUrl=function(){this.preparedUrl=null;if(e.isString(this.url))this.url.charAt(0)==="^"?this.preparedUrl=new t(this.url.substring(1)):this.preparedUrl=(this.parent.navigable||this.root).preparedUrl.concat(this.url);else if(e.isObject(this.url)&&e.isFunction(this.url.exec)&&e.isFunction(this.url.format)&&e.isFunction(this.url.concat))this.preparedUrl=this.url;else if(this.url!=null)throw new Error("Invalid url "+this.url+" in state "+this)},Object.defineProperty(r.prototype,"params",{get:function(){return this._params},set:function(e){this._params=e,this.needsInit=!0}}),r.prototype.resetParams=function(){this.preparedParams=null;var t=this.params;if(t){if(!e.isArray(t))throw new Error("Invalid params in state '"+this+"'");if(this.preparedUrl)throw new Error("Both params and url specicified in state '"+this+"'");this.perparedParams=t}else this.preparedParams=this.preparedUrl?this.preparedUrl.parameters():this.parent.preparedParams;var n={};e.forEach(this.preparedParams,function(e){n[e]=!0});if(this.parent){var r=this;e.forEach(this.parent.preparedParams,function(e){if(!n[e])throw new Error("Missing required parameter '"+e+"' in state '"+r.name+"'");n[e]=!1});var i=this.ownParams=[];e.forEach(n,function(e,t){e&&i.push(t)})}else this.ownParams=this.preparedParams},r.prototype.resetNavigable=function(){this.navigable=this.url?this:this.parent?this.parent.navigable:null},Object.defineProperty(r.prototype,n,{get:function(){return this._abstract},set:function(e){this._abstract=e,this.needsInit=!0}}),r.prototype.resetIncludes=function(){this.includes=this.parent?e.extend({},this.parent.includes):{},this.includes[this.name]=!0},Object.defineProperty(r.prototype,"views",{get:function(){return this._views},set:function(e){this._views=e,this.needsInit=!0}}),r.prototype.resetViews=function(){var t=this,n={},r=this.views;e.forEach(e.isDefined(r)?r:{"":t},function(e,r){r.indexOf("@")<0&&(r=r+"@"+t.parent.name),n[r]=e}),this.preparedViews=n},r.prototype.resetHandlers=function(){throw new Error('not implemented: "resetHandlers"')},r.prototype.newInstance=function(){return new r},r.prototype.getChild=function(e){return this.children?this.children[e]:null},r.prototype.setChild=function(t,n){var r=this.newInstance();return e.extend(r,t),this.setChildState(r,n)},r.prototype.removeChild=function(e){return this.children[e]&&delete this.children[e],this},r.prototype.setChildState=function(e,t){if(!t){var n=this.getChild(e.localName),r=n?n.children:null;r&&(e._children=r)}return this.children[e.localName]=e,e.parent=this,this.needsInit=!0,e},r.prototype.updateChild=function(t){var n=this.getChild(t.localName);return n?(e.extend(n,t),this.setChildState(n,!1)):this.setChild(t,!0)},r.prototype.prepFlatGetParent=function(e){var t,n;if(e.parent)t=this.getState(e.parent),n=e.fullName;else{var r=e.fullName?e.fullName:e.name,i=/^(.*?)\.?([^\.]*)$/.exec(r),s=i[1];n=i[2],t=s?this.getState(s):this.root}return e.localName=n,delete e.name,delete e.fullName,delete e.parent,t},r.prototype.setState=function(e,t){var n=this.prepFlatGetParent(e);return n.setChild(e,t)},r.prototype.updateState=function(e){var t=this.prepFlatGetParent(e);return t.updateChild(e)},r.prototype.findState=function(e){var t=/^([^\.]+)(\.(.*))?$/.exec(e),n=t[1];if(this.localName===n){var r=t[3];return r?this.findStateChildren(r):this}return null},r.prototype.findStateChildren=function(e){if(this.children)for(var t in this.children){var n=this.children[t].findState(e);if(n)return n}return null},r.prototype.getState=function(t){return e.isString(t)?this.root.findStateChildren(t):this.root.findStateChildren(t.fullName)},r.prototype.getIntJson=function(e,t,n){return e[n]?parseInt(e[n],10):e[t]?parseInt(e[t],10):null},r.prototype.getObjJson=function(e,t,n){return e[n]?e[n]:e[t]?e[t]:null},r.prototype.expandDefinition=function(e){this.expandJson(e,"url","u"),this.expandJson(e,"dependencies","d"),this.expandJson(e,"resolveByService","r"),this.expandJson(e,"templateService","i"),this.expandJson(e,"aliases","s"),this.expandJson(e,"controller","c"),this.expandJson(e,"templateUrl","t"),this.expandJson(e,"template","l"),this.expandJson(e,"data","a"),this.expandJson(e,"abstract","b"),this.expandJson(e,"views","v")},r.prototype.expandView=function(e){this.expandJson(e,"url","u"),this.expandJson(e,"resolveByService","r"),this.expandJson(e,"templateService","i"),this.expandJson(e,"controller","c"),this.expandJson(e,"templateUrl","t"),this.expandJson(e,"template","l"),this.expandJson(e,"data","a")},r.prototype.expandJson=function(e,t,n){e[n]&&(e[t]=e[n],delete e[n])},r.prototype.mergeChild=function(e,t){var n=this.getObjJson(t,"delete","x");if(n)this.removeChild(e);else{var r=this.getObjJson(t,"definition","d");if(r){r.localName=e,this.expandDefinition(r);if(r.views)for(var i in r.views){var s=r.views[i];this.expandView(s)}this.updateChild(r)}var o=this.getObjJson(t,"children","c");if(o){var u=this.getChild(e);for(var a in o){var f=o[a];u.mergeChild(a,f)}}}return!0},Object.defineProperty(r.prototype,"knownStates",{get:function(){var e={};if(Object.keys(this.children).length>0){var t={};for(var n in this.children){var r=this.children[n];t[r.localName]=r.knownStates}e=t}return e}}),r}),function(){var e=function(e){function n(e,t,n,r){function s(e,t){n.value.apply(null,e),t&&i.$apply()}function o(e,t){n.factory.apply(null,e),t&&i.$apply()}function u(e,t){n.service.apply(null,e),t&&i.$apply()}function a(e,t){r.register.apply(null,e),t&&i.$apply()}function f(e,n){t.directive.apply(null,e),n&&i.$apply()}function l(t,n){e.register.apply(null,t),n&&i.$apply()}function c(e,t){n.decorator.apply(null,e),t&&i.$apply()}function h(e,t){n.provider.apply(null,e),t&&i.$apply()}function p(e,t,n){return e.dependencies?v(e,t,n):d(e,t,n)}function d(e,t,n){function r(r,i){var s=r.defer();return require(e,function(){var e=Array.prototype.slice(arguments),r;t===undefined?r=arguments[arguments.length-1]:(argForOut=arguments[t],n===undefined?r=argForOut:r=argForOut[n]),s.resolve(r),i.$apply()}),s.promise}return r.$inject=["$q","$rootScope"],r}function v(e){if(e.dependencies){var t=e,n=e.dependencies;return delete t.dependencies,t.resolve={},t.resolve.delay=d(n),t}return e}var i=null;this.resolve=p,this.resolveDependencies=d,this.resolveDependenciesProperty=v,this.$get=function(e){var t={};return i=e,t.registerValue=s,t.registerFactory=o,t.registerService=u,t.registerFilter=a,t.registerDirective=f,t.registerController=l,t.registerDecorator=c,t.registerProvider=h,t.resolveDependenciesProperty=v,t.resolveDependencies=d,t.resolve=p,t},this.$get.$inject=["$rootScope"]}var t=e.module("scs.couch-potato",["ng"]);n.$inject=["$controllerProvider","$compileProvider","$provide","$filterProvider"],t.provider("$couchPotato",n),this.configureApp=function(t){t.registerController=function(e,n){return t.lazy?t.lazy.registerController([e,n]):t.controller(e,n),t},t.registerFactory=function(e,n){return t.lazy?t.lazy.registerFactory([e,n]):t.factory(e,n),t},t.registerService=function(e,n){return t.lazy?t.lazy.registerService([e,n]):t.service(e,n),t},t.registerDirective=function(e,n){return t.lazy?t.lazy.registerDirective([e,n]):t.directive(e,n),t},t.registerDecorator=function(e,n){return t.lazy?t.lazy.registerDecorator([e,n]):t.decorator(e,n),t},t.registerProvider=function(e,n){return t.lazy?t.lazy.registerProvider([e,n]):t.provider(e,n),t},t.registerValue=function(e,n){return t.lazy?t.lazy.registerValue([e,n]):t.value(e,n),t},t.registerFilter=function(e,n){return t.lazy?t.lazy.registerFilter([e,n]):t.filter(e,n),t},t.extendInjectable=function(t,n){function r(t){if(e.isArray(t)){var n=t.slice(t.length-1)[0];return[n,t.slice(0,t.length-1)]}var r=t.$inject;return[t,r||[]]}function i(){var e=Array.prototype.slice.call(arguments);parentPieces[0].apply(this,e.slice(0,parentPieces[1].length)),childPieces[0].apply(this,e.slice(parentPieces[1].length))}function s(){}return parentPieces=r(t),childPieces=r(n),s.prototype=parentPieces[0].prototype,i.prototype=new s,i.$inject=[].concat(parentPieces[1]).concat(childPieces[1]),i}}};typeof define=="function"&&define.amd?define("couchPotato",["angular"],function(){return new e(window.angular)}):window.couchPotato=new e(angular)}(),define("detourModule",["couchPotato"],function(){return angular.module("agt.detour",["scs.couch-potato"])}),define("templateFactory",["./detourModule"],function(){function e(e,t,n){this.fromConfig=function(e,t,n){return angular.isDefined(e.template)?this.fromString(e.template,t):angular.isDefined(e.templateUrl)?this.fromUrl(e.templateUrl,t):angular.isDefined(e.templateProvider)?this.fromProvider(e.templateProvider,t,n):angular.isDefined(e.templateService)?this.fromService(e.templateService,t,n):null},this.fromString=function(e,t){return angular.isFunction(e)?e(t):e},this.fromUrl=function(n,r){return angular.isFunction(n)&&(n=n(r)),n==null?null:e.get(n,{cache:t}).then(function(e){return e.data})},this.fromProvider=function(e,t,r){return n.invoke(e,null,r||{params:t})},this.fromService=function(e,t,r){return n.invoke([e,function(e){return e.getTemplate(t,r)}])}}e.$inject=["$http","$templateCache","$injector"],angular.module("agt.detour").service("$templateFactory",e)}),define("detourProvider",["./common","UrlMatcher","StateBase","couchPotato","templateFactory","./detourModule"],function(e,t,n){function o(){function u(){this.children={}}function a(){this.locals={globals:{$stateParams:{}}},this.serial=0,this.resetAll()}function f(e){var t=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(e.source);return t!==null?t[1].replace(/\\(.)/g,"$1"):""}function l(e,t){return e.replace(/\$(\$|\d{1,2})/,function(e,n){return t[n==="$"?0:Number(n)]})}function c(t,n,r){if(!r)return!1;var i=t.invoke(n,n,{$match:r});return e.isDefined(i)?i:!0}function p(e,t){return t.fullName=e,h.setState(t),this}function d(e){return h.fallback=e,this}function v(){h.initialize()}function m(e){var t=this.getState(e);return t&&t.parent.removeChild(t.localName),this}function g(e){return h.setState(e)}function y(e){return h.updateState(e)}function b(e){return h.getState(e)}function w(e){return h.mergeJson(e)}function S(t,n,r,i,s,u,a,f){function T(e){var t=n.defer(),r=null;return E.crossDomain?(r=angular.copy(x,r),delete r.headers.common["X-Requested-With"],r.useXDomain=!0):r=x,f({method:"GET",url:e,config:r}).success(function(e,n,r,i){t.resolve(angular.fromJson(e))}).error(function(e,n,r,i){t.resolve(null)}),t.promise}function N(e){var t=E.lazy.routeUrl+"?"+E.lazy.routeParameter+"="+encodeURIComponent(e)+"&"+E.knownStatesParameter+"="+encodeURIComponent(angular.toJson(h.knownStates));for(var r in E.additionalParams)t+="&"+encodeURIComponent(r)+"="+encodeURIComponent(E.additionalParams[r]);var i=n.defer();return T(t).then(function(e){e&&h.mergeJson(e),i.resolve()}),i.promise}function C(e){var t=E.lazy.stateUrl+"?"+E.lazy.stateParameter+"="+encodeURIComponent(e)+"&"+E.knownStatesParameter+"="+encodeURIComponent(angular.toJson(h.knownStates));for(var r in E.additionalParams)t+="&"+encodeURIComponent(r)+"="+encodeURIComponent(E.additionalParams[r]);var i=n.defer();return T(t).then(function(e){e&&h.mergeJson(e),i.resolve()}),i.promise}function k(r,o,a,f){var p=h.getState(r);if(!p&&E.lazy.enabled&&!f)return C(r).then(function(){return k(r,o,a,!0)});r=p,e.isDefined(a)||(a=!0);if(r["abstract"])throw new Error("Cannot transition to abstract state '"+r+"'");var d=r.path,v=S.$current,m=S.params,g=v.path,y,b,w=h.locals,x=[];for(y=0,b=d[y];b&&b===g[y]&&O(o,m,b.ownParams);y++,b=d[y])w=x[y]=b.locals;if(r===v&&w===v.locals)return S.transition=null,n.when(S.current);o=A(r.preparedParams,o||{});if(t.$broadcast("$stateChangeStart",r.self,o,v.self,m).defaultPrevented)return c;var T=n.when(w);for(var N=y;N=y;e--)f=g[e],f.self.onExit&&i.invoke(f.self.onExit,f.self,f.locals.globals),f.locals=null;for(e=y;e").html(o.$template).contents(),v.enter(a,u)):(u.html(o.$template),a=u.contents());var f=n(a);l=i.$new();if(o.$$controller){o.$scope=l;var m=r(o.$$controller,o);u.children().data("$ngControllerController",m)}f(l),l.$emit("$viewContentLoaded"),l.$eval(d),s()}else c=null,y.state=null,e?v.enter(h,u):u.html(h)}var l,c,h=u.contents(),p=f[a.name]||f.name||"",d=f.onload||"",v=e.isDefined(o)&&o(i,f),g=u.parent().inheritedData("$uiView");p.indexOf("@")<0&&(p=p+"@"+(g?g.state.name:""));var y={name:p,state:null};u.data("$uiView",y),i.$on("$stateChangeSuccess",function(){m(!0)}),m(!1)}};return a}t.$inject=["$detour","$compile","$controller","$injector","$anchorScroll"],angular.module("agt.detour").directive("uiView",t)}),define("angular-detour",["./detourProvider","./viewDirective"],function(){})})(); -------------------------------------------------------------------------------- /dist/angular-detour.min.js: -------------------------------------------------------------------------------- 1 | /*! angular-detour - v0.6.0 - 2013-11-07 2 | * https://github.com/afterglowtech/angular-detour 3 | * Copyright (c) 2013 Stu Salsbury; 4 | * Based on and uses software code found at https://github.com/angular-ui/ui-router which is 5 | * Copyright (c) 2013, Karsten Sperling 6 | * Licensed MIT 7 | */ 8 | 9 | /** 10 | * almond 0.2.6 Copyright (c) 2011-2012, The Dojo Foundation All Rights Reserved. 11 | * Available via the MIT or new BSD license. 12 | * see: http://github.com/jrburke/almond for details 13 | */ 14 | 15 | /*! angular-couch-potato - v0.1.0 - 2013-11-07 16 | * https://github.com/stu-salsbury/angular-couch-potato 17 | * Copyright (c) 2013 Stu Salsbury; 18 | * Uses software code originally found at https://github.com/szhanginrhythm/angular-require-lazyload 19 | * Licensed MIT 20 | */ 21 | 22 | (function(){var e,t,n;(function(r){function d(e,t){return h.call(e,t)}function v(e,t){var n,r,i,s,o,u,a,f,c,h,p=t&&t.split("/"),d=l.map,v=d&&d["*"]||{};if(e&&e.charAt(0)===".")if(t){p=p.slice(0,p.length-1),e=p.concat(e.split("/"));for(f=0;f0&&(e.splice(f-1,2),f-=2)}}e=e.join("/")}else e.indexOf("./")===0&&(e=e.substring(2));if((p||v)&&d){n=e.split("/");for(f=n.length;f>0;f-=1){r=n.slice(0,f).join("/");if(p)for(c=p.length;c>0;c-=1){i=d[p.slice(0,c).join("/")];if(i){i=i[r];if(i){s=i,o=f;break}}}if(s)break;!u&&v&&v[r]&&(u=v[r],a=f)}!s&&u&&(s=u,o=a),s&&(n.splice(0,o,s),e=n.join("/"))}return e}function m(e,t){return function(){return s.apply(r,p.call(arguments,0).concat([e,t]))}}function g(e){return function(t){return v(t,e)}}function y(e){return function(t){a[e]=t}}function b(e){if(d(f,e)){var t=f[e];delete f[e],c[e]=!0,i.apply(r,t)}if(!d(a,e)&&!d(c,e))throw new Error("No "+e);return a[e]}function w(e){var t,n=e?e.indexOf("!"):-1;return n>-1&&(t=e.substring(0,n),e=e.substring(n+1,e.length)),[t,e]}function E(e){return function(){return l&&l.config&&l.config[e]||{}}}var i,s,o,u,a={},f={},l={},c={},h=Object.prototype.hasOwnProperty,p=[].slice;o=function(e,t){var n,r=w(e),i=r[0];return e=r[1],i&&(i=v(i,t),n=b(i)),i?n&&n.normalize?e=n.normalize(e,g(t)):e=v(e,t):(e=v(e,t),r=w(e),i=r[0],e=r[1],i&&(n=b(i))),{f:i?i+"!"+e:e,n:e,pr:i,p:n}},u={require:function(e){return m(e)},exports:function(e){var t=a[e];return typeof t!="undefined"?t:a[e]={}},module:function(e){return{id:e,uri:"",exports:a[e],config:E(e)}}},i=function(e,t,n,i){var s,l,h,p,v,g=[],w;i=i||e;if(typeof n=="function"){t=!t.length&&n.length?["require","exports","module"]:t;for(v=0;v=0)break;i+=l(p)+"("+h+")",f(c),u.push(p),s=n.lastIndex}p=t.substring(s);var d=p.indexOf("?");if(d>=0){var v=this.sourceSearch=p.substring(d);p=p.substring(0,d),this.sourcePath=t.substring(0,s+d),e.forEach(v.substring(1).split(/[&?]/),f)}else this.sourcePath=t,this.sourceSearch="";i+=l(p)+"$",u.push(p),this.regexp=new RegExp(i),this.prefix=u[0]}return t.prototype.concat=function(e){return new t(this.sourcePath+e+this.sourceSearch)},t.prototype.toString=function(){return this.source},t.prototype.exec=function(e,t){var n=this.regexp.exec(e);if(!n)return null;var r=this.params,i=r.length,s=this.segments.length-1,o={},u;for(u=0;u=0)throw new Error("Invalid local state name ("+t+")")},Object.defineProperty(r.prototype,"root",{get:function(){return this.parent.root}}),Object.defineProperty(r.prototype,"children",{get:function(){return this._children},set:function(e){this._children=e;if(this._children)for(var t in this._children)this._children[t].parent=this}}),r.prototype.resetPath=function(){this.path=this.parent.path.concat(this)},Object.defineProperty(r.prototype,"url",{get:function(){return this._url},set:function(e){this._url=e,this.needsInit=!0}}),Object.defineProperty(r.prototype,"aliases",{get:function(){return this._aliases},set:function(e){this._aliases=e,this.needsInit=!0}}),r.prototype.resetUrl=function(){this.preparedUrl=null;if(e.isString(this.url))this.url.charAt(0)==="^"?this.preparedUrl=new t(this.url.substring(1)):this.preparedUrl=(this.parent.navigable||this.root).preparedUrl.concat(this.url);else if(e.isObject(this.url)&&e.isFunction(this.url.exec)&&e.isFunction(this.url.format)&&e.isFunction(this.url.concat))this.preparedUrl=this.url;else if(this.url!=null)throw new Error("Invalid url "+this.url+" in state "+this)},Object.defineProperty(r.prototype,"params",{get:function(){return this._params},set:function(e){this._params=e,this.needsInit=!0}}),r.prototype.resetParams=function(){this.preparedParams=null;var t=this.params;if(t){if(!e.isArray(t))throw new Error("Invalid params in state '"+this+"'");if(this.preparedUrl)throw new Error("Both params and url specicified in state '"+this+"'");this.perparedParams=t}else this.preparedParams=this.preparedUrl?this.preparedUrl.parameters():this.parent.preparedParams;var n={};e.forEach(this.preparedParams,function(e){n[e]=!0});if(this.parent){var r=this;e.forEach(this.parent.preparedParams,function(e){if(!n[e])throw new Error("Missing required parameter '"+e+"' in state '"+r.name+"'");n[e]=!1});var i=this.ownParams=[];e.forEach(n,function(e,t){e&&i.push(t)})}else this.ownParams=this.preparedParams},r.prototype.resetNavigable=function(){this.navigable=this.url?this:this.parent?this.parent.navigable:null},Object.defineProperty(r.prototype,n,{get:function(){return this._abstract},set:function(e){this._abstract=e,this.needsInit=!0}}),r.prototype.resetIncludes=function(){this.includes=this.parent?e.extend({},this.parent.includes):{},this.includes[this.name]=!0},Object.defineProperty(r.prototype,"views",{get:function(){return this._views},set:function(e){this._views=e,this.needsInit=!0}}),r.prototype.resetViews=function(){var t=this,n={},r=this.views;e.forEach(e.isDefined(r)?r:{"":t},function(e,r){r.indexOf("@")<0&&(r=r+"@"+t.parent.name),n[r]=e}),this.preparedViews=n},r.prototype.resetHandlers=function(){throw new Error('not implemented: "resetHandlers"')},r.prototype.newInstance=function(){return new r},r.prototype.getChild=function(e){return this.children?this.children[e]:null},r.prototype.setChild=function(t,n){var r=this.newInstance();return e.extend(r,t),this.setChildState(r,n)},r.prototype.removeChild=function(e){return this.children[e]&&delete this.children[e],this},r.prototype.setChildState=function(e,t){if(!t){var n=this.getChild(e.localName),r=n?n.children:null;r&&(e._children=r)}return this.children[e.localName]=e,e.parent=this,this.needsInit=!0,e},r.prototype.updateChild=function(t){var n=this.getChild(t.localName);return n?(e.extend(n,t),this.setChildState(n,!1)):this.setChild(t,!0)},r.prototype.prepFlatGetParent=function(e){var t,n;if(e.parent)t=this.getState(e.parent),n=e.fullName;else{var r=e.fullName?e.fullName:e.name,i=/^(.*?)\.?([^\.]*)$/.exec(r),s=i[1];n=i[2],t=s?this.getState(s):this.root}return e.localName=n,delete e.name,delete e.fullName,delete e.parent,t},r.prototype.setState=function(e,t){var n=this.prepFlatGetParent(e);return n.setChild(e,t)},r.prototype.updateState=function(e){var t=this.prepFlatGetParent(e);return t.updateChild(e)},r.prototype.findState=function(e){var t=/^([^\.]+)(\.(.*))?$/.exec(e),n=t[1];if(this.localName===n){var r=t[3];return r?this.findStateChildren(r):this}return null},r.prototype.findStateChildren=function(e){if(this.children)for(var t in this.children){var n=this.children[t].findState(e);if(n)return n}return null},r.prototype.getState=function(t){return e.isString(t)?this.root.findStateChildren(t):this.root.findStateChildren(t.fullName)},r.prototype.getIntJson=function(e,t,n){return e[n]?parseInt(e[n],10):e[t]?parseInt(e[t],10):null},r.prototype.getObjJson=function(e,t,n){return e[n]?e[n]:e[t]?e[t]:null},r.prototype.expandDefinition=function(e){this.expandJson(e,"url","u"),this.expandJson(e,"dependencies","d"),this.expandJson(e,"resolveByService","r"),this.expandJson(e,"templateService","i"),this.expandJson(e,"aliases","s"),this.expandJson(e,"controller","c"),this.expandJson(e,"templateUrl","t"),this.expandJson(e,"template","l"),this.expandJson(e,"data","a"),this.expandJson(e,"abstract","b"),this.expandJson(e,"views","v")},r.prototype.expandView=function(e){this.expandJson(e,"url","u"),this.expandJson(e,"resolveByService","r"),this.expandJson(e,"templateService","i"),this.expandJson(e,"controller","c"),this.expandJson(e,"templateUrl","t"),this.expandJson(e,"template","l"),this.expandJson(e,"data","a")},r.prototype.expandJson=function(e,t,n){e[n]&&(e[t]=e[n],delete e[n])},r.prototype.mergeChild=function(e,t){var n=this.getObjJson(t,"delete","x");if(n)this.removeChild(e);else{var r=this.getObjJson(t,"definition","d");if(r){r.localName=e,this.expandDefinition(r);if(r.views)for(var i in r.views){var s=r.views[i];this.expandView(s)}this.updateChild(r)}var o=this.getObjJson(t,"children","c");if(o){var u=this.getChild(e);for(var a in o){var f=o[a];u.mergeChild(a,f)}}}return!0},Object.defineProperty(r.prototype,"knownStates",{get:function(){var e={};if(Object.keys(this.children).length>0){var t={};for(var n in this.children){var r=this.children[n];t[r.localName]=r.knownStates}e=t}return e}}),r}),function(){var e=function(e){function r(e,n,r,i){function o(e,t){r.value.apply(null,e),t&&s.$apply()}function u(e,t){r.factory.apply(null,e),t&&s.$apply()}function a(e,t){r.service.apply(null,e),t&&s.$apply()}function f(e,t){i.register.apply(null,e),t&&s.$apply()}function l(e,t){n.directive.apply(null,e),t&&s.$apply()}function c(t,n){e.register.apply(null,t),n&&s.$apply()}function h(e,t){r.decorator.apply(null,e),t&&s.$apply()}function p(e,t){r.provider.apply(null,e),t&&s.$apply()}function d(e,t,n){return e.dependencies?m(e,t,n):v(e,t,n)}function v(e,n,r){function i(i,s){var o=i.defer();return t(e,function(){var e=Array.prototype.slice(arguments),t;n===undefined?t=arguments[arguments.length-1]:(argForOut=arguments[n],r===undefined?t=argForOut:t=argForOut[r]),o.resolve(t),s.$apply()}),o.promise}return i.$inject=["$q","$rootScope"],i}function m(e){if(e.dependencies){var t=e,n=e.dependencies;return delete t.dependencies,t.resolve={},t.resolve.delay=v(n),t}return e}var s=null;this.resolve=d,this.resolveDependencies=v,this.resolveDependenciesProperty=m,this.$get=function(e){var t={};return s=e,t.registerValue=o,t.registerFactory=u,t.registerService=a,t.registerFilter=f,t.registerDirective=l,t.registerController=c,t.registerDecorator=h,t.registerProvider=p,t.resolveDependenciesProperty=m,t.resolveDependencies=v,t.resolve=d,t},this.$get.$inject=["$rootScope"]}var n=e.module("scs.couch-potato",["ng"]);r.$inject=["$controllerProvider","$compileProvider","$provide","$filterProvider"],n.provider("$couchPotato",r),this.configureApp=function(t){t.registerController=function(e,n){return t.lazy?t.lazy.registerController([e,n]):t.controller(e,n),t},t.registerFactory=function(e,n){return t.lazy?t.lazy.registerFactory([e,n]):t.factory(e,n),t},t.registerService=function(e,n){return t.lazy?t.lazy.registerService([e,n]):t.service(e,n),t},t.registerDirective=function(e,n){return t.lazy?t.lazy.registerDirective([e,n]):t.directive(e,n),t},t.registerDecorator=function(e,n){return t.lazy?t.lazy.registerDecorator([e,n]):t.decorator(e,n),t},t.registerProvider=function(e,n){return t.lazy?t.lazy.registerProvider([e,n]):t.provider(e,n),t},t.registerValue=function(e,n){return t.lazy?t.lazy.registerValue([e,n]):t.value(e,n),t},t.registerFilter=function(e,n){return t.lazy?t.lazy.registerFilter([e,n]):t.filter(e,n),t},t.extendInjectable=function(t,n){function r(t){if(e.isArray(t)){var n=t.slice(t.length-1)[0];return[n,t.slice(0,t.length-1)]}var r=t.$inject;return[t,r||[]]}function i(){var e=Array.prototype.slice.call(arguments);parentPieces[0].apply(this,e.slice(0,parentPieces[1].length)),childPieces[0].apply(this,e.slice(parentPieces[1].length))}function s(){}return parentPieces=r(t),childPieces=r(n),s.prototype=parentPieces[0].prototype,i.prototype=new s,i.$inject=[].concat(parentPieces[1]).concat(childPieces[1]),i}}};typeof n=="function"&&n.amd?n("couchPotato",["angular"],function(){return new e(window.angular)}):window.couchPotato=new e(angular)}(),n("detourModule",["couchPotato"],function(){return angular.module("agt.detour",["scs.couch-potato"])}),n("templateFactory",["./detourModule"],function(){function e(e,t,n){this.fromConfig=function(e,t,n){return angular.isDefined(e.template)?this.fromString(e.template,t):angular.isDefined(e.templateUrl)?this.fromUrl(e.templateUrl,t):angular.isDefined(e.templateProvider)?this.fromProvider(e.templateProvider,t,n):angular.isDefined(e.templateService)?this.fromService(e.templateService,t,n):null},this.fromString=function(e,t){return angular.isFunction(e)?e(t):e},this.fromUrl=function(n,r){return angular.isFunction(n)&&(n=n(r)),n==null?null:e.get(n,{cache:t}).then(function(e){return e.data})},this.fromProvider=function(e,t,r){return n.invoke(e,null,r||{params:t})},this.fromService=function(e,t,r){return n.invoke([e,function(e){return e.getTemplate(t,r)}])}}e.$inject=["$http","$templateCache","$injector"],angular.module("agt.detour").service("$templateFactory",e)}),n("detourProvider",["./common","UrlMatcher","StateBase","couchPotato","templateFactory","./detourModule"],function(e,t,n){function o(){function u(){this.children={}}function a(){this.locals={globals:{$stateParams:{}}},this.serial=0,this.resetAll()}function f(e){var t=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(e.source);return t!==null?t[1].replace(/\\(.)/g,"$1"):""}function l(e,t){return e.replace(/\$(\$|\d{1,2})/,function(e,n){return t[n==="$"?0:Number(n)]})}function c(t,n,r){if(!r)return!1;var i=t.invoke(n,n,{$match:r});return e.isDefined(i)?i:!0}function p(e,t){return t.fullName=e,h.setState(t),this}function d(e){return h.fallback=e,this}function v(){h.initialize()}function m(e){var t=this.getState(e);return t&&t.parent.removeChild(t.localName),this}function g(e){return h.setState(e)}function y(e){return h.updateState(e)}function b(e){return h.getState(e)}function w(e){return h.mergeJson(e)}function S(t,n,r,i,s,u,a,f){function T(e){var t=n.defer(),r=null;return E.crossDomain?(r=angular.copy(x,r),delete r.headers.common["X-Requested-With"],r.useXDomain=!0):r=x,f({method:"GET",url:e,config:r}).success(function(e,n,r,i){t.resolve(angular.fromJson(e))}).error(function(e,n,r,i){t.resolve(null)}),t.promise}function N(e){var t=E.lazy.routeUrl+"?"+E.lazy.routeParameter+"="+encodeURIComponent(e)+"&"+E.knownStatesParameter+"="+encodeURIComponent(angular.toJson(h.knownStates));for(var r in E.additionalParams)t+="&"+encodeURIComponent(r)+"="+encodeURIComponent(E.additionalParams[r]);var i=n.defer();return T(t).then(function(e){e&&h.mergeJson(e),i.resolve()}),i.promise}function C(e){var t=E.lazy.stateUrl+"?"+E.lazy.stateParameter+"="+encodeURIComponent(e)+"&"+E.knownStatesParameter+"="+encodeURIComponent(angular.toJson(h.knownStates));for(var r in E.additionalParams)t+="&"+encodeURIComponent(r)+"="+encodeURIComponent(E.additionalParams[r]);var i=n.defer();return T(t).then(function(e){e&&h.mergeJson(e),i.resolve()}),i.promise}function k(r,o,a,f){var p=h.getState(r);if(!p&&E.lazy.enabled&&!f)return C(r).then(function(){return k(r,o,a,!0)});r=p,e.isDefined(a)||(a=!0);if(r["abstract"])throw new Error("Cannot transition to abstract state '"+r+"'");var d=r.path,v=S.$current,m=S.params,g=v.path,y,b,w=h.locals,x=[];for(y=0,b=d[y];b&&b===g[y]&&O(o,m,b.ownParams);y++,b=d[y])w=x[y]=b.locals;if(r===v&&w===v.locals)return S.transition=null,n.when(S.current);o=A(r.preparedParams,o||{});if(t.$broadcast("$stateChangeStart",r.self,o,v.self,m).defaultPrevented)return c;var T=n.when(w);for(var N=y;N=y;e--)f=g[e],f.self.onExit&&i.invoke(f.self.onExit,f.self,f.locals.globals),f.locals=null;for(e=y;e").html(o.$template).contents(),v.enter(a,u)):(u.html(o.$template),a=u.contents());var f=n(a);l=i.$new();if(o.$$controller){o.$scope=l;var m=r(o.$$controller,o);u.children().data("$ngControllerController",m)}f(l),l.$emit("$viewContentLoaded"),l.$eval(d),s()}else c=null,y.state=null,e?v.enter(h,u):u.html(h)}var l,c,h=u.contents(),p=f[a.name]||f.name||"",d=f.onload||"",v=e.isDefined(o)&&o(i,f),g=u.parent().inheritedData("$uiView");p.indexOf("@")<0&&(p=p+"@"+(g?g.state.name:""));var y={name:p,state:null};u.data("$uiView",y),i.$on("$stateChangeSuccess",function(){m(!0)}),m(!1)}};return a}t.$inject=["$detour","$compile","$controller","$injector","$anchorScroll"],angular.module("agt.detour").directive("uiView",t)}),n("angular-detour",["./detourProvider","./viewDirective"],function(){}),t(["angular-detour"])})(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-detour", 3 | "description": "Lazy-loaded/runtime-configurable/server-defined routing for AngularJS applications", 4 | "version": "0.6.0", 5 | "homepage": "https://github.com/afterglowtech/angular-detour", 6 | "author": { 7 | "name": "Stu Salsbury", 8 | "url": "https://github.com/stu-salsbury" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/afterglowtech/angular-detour.git" 13 | }, 14 | "licenses": [ 15 | { 16 | "type": "MIT", 17 | "url": "https://github.com/afterglowtech/angular-detour/blob/master/LICENSE" 18 | } 19 | ], 20 | "dependencies": {}, 21 | "devDependencies": { 22 | "express": "~3.3.4", 23 | "grunt": "~0.4.1", 24 | "grunt-bower": "~0.7.0", 25 | "grunt-bower-task": "~0.3.2", 26 | "grunt-contrib-jshint": "~0.4.3", 27 | "grunt-contrib-clean": "~0.4.0", 28 | "grunt-contrib-watch": "~0.3.1", 29 | "grunt-requirejs": "~0.4.0", 30 | "requirejs": "~2.1.6" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/1-ui-router-compatibility/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | agt.detour/ui-router compatibility 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 29 |
30 |
31 |
32 |   $detour = {{$detour.current.name}}
33 |   $stateParams = {{$stateParams}}
34 | 
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /samples/1-ui-router-compatibility/js/app.js: -------------------------------------------------------------------------------- 1 | define(['angular-detour'], function () { 2 | 'use strict'; 3 | 4 | var app = angular.module('app', ['agt.detour']); 5 | 6 | app.config(['$locationProvider', '$provide', '$detourProvider', 7 | function ($locationProvider, $provide, $detourProvider) { 8 | //comment out the decorator function for html5mode 9 | //uncomment the decorator function for forced hash(bang) mode 10 | // $provide.decorator('$sniffer', function($delegate) { 11 | // $delegate.history = false; 12 | // return $delegate; 13 | // }); 14 | $locationProvider.html5Mode(true); 15 | 16 | //demonstrates ui-router style syntax 17 | //plus: 18 | // otherwise function (on $detourProvider instead of $urlRouterProvider) 19 | // aliases (instead of $urlRouterProvider when functions) 20 | // dependencies (lazy-loading controllers) 21 | 22 | //otherwise is defined in detour instead of urlRouterProvider, 23 | //unlike ui-router 24 | $detourProvider.otherwise('/404') 25 | 26 | .state('404', { 27 | url: '/404', 28 | templateUrl: 'partials/fourOhfour.html' 29 | }) 30 | 31 | .state('home', { 32 | url: '/', 33 | 34 | //aliases is like urlRouterProvider.when 35 | aliases: {'': '^/'}, 36 | 37 | template: '

Welcome to the agt.detour ui-router compatibility sample<' + '/p>

Use the menu above to navigate<' + '/p>' + 38 | '

Look at Alice<' + '/a> or Bob<' + '/a> to see a URL with a ' + 39 | 'redirect in action.<' + '/p>

Just don\'t get lost<' + '/a>!<' + '/p>' 40 | }) 41 | 42 | .state('contacts', { 43 | url: '/contacts', 44 | abstract: true, 45 | templateUrl: 'partials/contacts.html', 46 | controller: [ '$scope', '$detour', 47 | function ($scope, $detour) { 48 | $scope.contacts = [{ 49 | id: 1, 50 | name: 'Alice', 51 | items: [{ 52 | id: 'a', 53 | type: 'phone number', 54 | value: '555-1234-1234' 55 | },{ 56 | id: 'b', 57 | type: 'email', 58 | value: 'alice@mailinator.com' 59 | }] 60 | }, { 61 | id: 42, 62 | name: 'Bob', 63 | items: [{ 64 | id: 'a', 65 | type: 'blog', 66 | value: 'http://bob.blogger.com' 67 | },{ 68 | id: 'b', 69 | type: 'fax', 70 | value: '555-999-9999' 71 | }] 72 | }, { 73 | id: 123, 74 | name: 'Eve', 75 | items: [{ 76 | id: 'a', 77 | type: 'full name', 78 | value: 'Eve Adamsdottir' 79 | }] 80 | }]; 81 | 82 | $scope.goToRandom = function () { 83 | /*jshint eqeqeq:false */ 84 | var contacts = $scope.contacts, id; 85 | do { 86 | id = contacts[Math.floor(contacts.length * Math.random())].id; 87 | } while (id == $detour.params.contactId); 88 | $detour.transitionTo('contacts.detail', { contactId: id }); 89 | }; 90 | } 91 | ] 92 | }) 93 | 94 | .state('contacts.list', { 95 | url: '', 96 | templateUrl: 'partials/contacts.list.html' 97 | }) 98 | 99 | .state('detail', { 100 | parent: 'contacts', 101 | url: '/{contactId}', 102 | aliases: {'/c?id': '/:id', '/user/{id}': '/:id'}, 103 | resolve: { 104 | something: 105 | [ '$timeout', '$stateParams', 106 | function ($timeout, $stateParams) { 107 | return $timeout(function () { return 'Asynchronously resolved data (' + $stateParams.contactId + ')'; }, 10); 108 | }] 109 | }, 110 | views: { 111 | '': { 112 | templateUrl: 'partials/contacts.detail.html', 113 | controller: [ '$scope', '$stateParams', 'something', 114 | function ($scope, $stateParams, something) { 115 | $scope.something = something; 116 | $scope.contact = findById($scope.contacts, $stateParams.contactId); 117 | } 118 | ] 119 | }, 120 | 'hint@': { 121 | template: 'This is contacts.detail populating the view "hint@"' 122 | }, 123 | 'menu': { 124 | templateProvider: 125 | [ '$stateParams', 126 | function ($stateParams){ 127 | // This is just to demonstrate that $stateParams injection works for templateProvider 128 | // $stateParams are the parameters for the new state we're transitioning to, even 129 | // though the global '$stateParams' has not been updated yet. 130 | return '


Contact ID: ' + $stateParams.contactId + '<' + '/small>'; 131 | }] 132 | } 133 | } 134 | }) 135 | 136 | .state('item', { 137 | parent: 'contacts.detail', 138 | url: '/item/:itemId', 139 | views: { 140 | '': { 141 | templateUrl: 'partials/contacts.detail.item.html', 142 | controller: [ '$scope', '$stateParams', '$detour', 143 | function ($scope, $stateParams, $detour) { 144 | $scope.item = findById($scope.contact.items, $stateParams.itemId); 145 | $scope.edit = function () { 146 | $detour.transitionTo('contacts.detail.item.edit', $stateParams); 147 | }; 148 | } 149 | ] 150 | }, 151 | 'hint@': { 152 | template: 'Overriding the view "hint@"' 153 | } 154 | } 155 | }) 156 | 157 | .state('contacts.detail.item.edit', { 158 | views: { 159 | '@contacts.detail': { 160 | templateUrl: 'partials/contacts.detail.item.edit.html', 161 | controller: [ '$scope', '$stateParams', '$detour', 162 | function ($scope, $stateParams, $detour) { 163 | $scope.item = findById($scope.contact.items, $stateParams.itemId); 164 | $scope.done = function () { 165 | $detour.transitionTo('contacts.detail.item', $stateParams); 166 | }; 167 | } 168 | ] 169 | } 170 | } 171 | }) 172 | 173 | .state('about', { 174 | url: '/about', 175 | templateProvider: 176 | [ '$timeout', 177 | function ($timeout) { 178 | return $timeout(function () { return 'Hello world'; }, 100); 179 | }] 180 | }) 181 | 182 | // must call initialize, unlike ui-router 183 | .initialize(); 184 | } 185 | ]); 186 | 187 | app.run([ '$rootScope', '$detour', '$stateParams', 188 | function($rootScope, $detour, $stateParams) { 189 | //"cheating" so that detour is available in requirejs 190 | //define modules -- we want run-time registration of components 191 | //to take place within those modules because it allows 192 | //for them to have their own dependencies also be lazy-loaded. 193 | //this is what requirejs is good at. 194 | 195 | //if not using any dependencies properties in detour states, 196 | //then this is not necessary 197 | app.detour = $detour; 198 | 199 | 200 | //the sample reads from the current $detour.state 201 | //and $stateParams in its templates 202 | //that it the only reason this is necessary 203 | $rootScope.$detour = $detour; 204 | $rootScope.$stateParams = $stateParams; 205 | } 206 | ]); 207 | 208 | return app; 209 | 210 | }); 211 | -------------------------------------------------------------------------------- /samples/1-ui-router-compatibility/js/main.js: -------------------------------------------------------------------------------- 1 | require.config({ 2 | baseUrl: 'js', 3 | paths: { 4 | 'angular-detour': '/dist/angular-detour.amd', 5 | 'angular': '/dist/dependencies/angular' 6 | }, 7 | shim: { 8 | 'angular-detour': { 9 | deps: ['angular'] 10 | } 11 | } 12 | }); 13 | 14 | require( [ 15 | 'app' 16 | ], function(app) { 17 | 'use strict'; 18 | angular.element(document).ready(function() { 19 | angular.bootstrap(document, [app['name'], function(){ 20 | }]); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /samples/1-ui-router-compatibility/partials/contacts.detail.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /samples/1-ui-router-compatibility/partials/contacts.detail.item.edit.html: -------------------------------------------------------------------------------- 1 |
2 |

{{item.type}}

3 |
4 | -------------------------------------------------------------------------------- /samples/1-ui-router-compatibility/partials/contacts.detail.item.html: -------------------------------------------------------------------------------- 1 |
2 |

{{item.type}}

3 |
{{item.value}}
4 | -------------------------------------------------------------------------------- /samples/1-ui-router-compatibility/partials/contacts.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 12 |
13 | 14 |
15 |
16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /samples/1-ui-router-compatibility/partials/contacts.list.html: -------------------------------------------------------------------------------- 1 |

All Contacts

2 | 7 | -------------------------------------------------------------------------------- /samples/1-ui-router-compatibility/partials/fourOhfour.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | You got lost on a detour. Maybe you should find your way home. 5 |
6 |
7 |
8 |
9 | -------------------------------------------------------------------------------- /samples/1-ui-router-compatibility/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | app.use('/js', express.static(__dirname + '/js')); 5 | app.use('/dist', express.static(__dirname + '/../../dist')); 6 | app.use('/css', express.static(__dirname + '/css')); 7 | app.use('/partials', express.static(__dirname + '/partials')); 8 | 9 | app.all('/*', function(req, res, next) { 10 | // Just send the index.html for other files to support HTML5Mode 11 | res.sendfile('index.html', { root: __dirname }); 12 | }); 13 | 14 | app.listen(3006); //the port you want to use 15 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | agt.detour/runtime config 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 32 |
33 |
34 |
35 |   $detour = {{$detour.current.name}}
36 |   $stateParams = {{$stateParams}}
37 | 
38 | 39 | 40 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/js/app.js: -------------------------------------------------------------------------------- 1 | define(['angular-detour'], function () { 2 | 'use strict'; 3 | 4 | var app = angular.module('app', ['agt.detour']); 5 | 6 | app.config([ '$locationProvider', '$provide', 7 | function($locationProvider, $provide) { 8 | 9 | //comment out the decorator function for html5mode 10 | //uncomment the decorator function for forced hash(bang) mode 11 | // $provide.decorator('$sniffer', function($delegate) { 12 | // $delegate.history = false; 13 | // return $delegate; 14 | // }); 15 | $locationProvider.html5Mode(true); 16 | } 17 | ]); 18 | 19 | app.run([ '$rootScope', '$detour', '$stateParams', 20 | function($rootScope, $detour, $stateParams) { 21 | $detour.otherwise('/404'); 22 | $detour.state('404', { 23 | url: '/404', 24 | templateUrl: '/partials/fourOhfour.html' 25 | }); 26 | 27 | 28 | $detour.state('home', { 29 | url: '/', 30 | aliases: {'': '^/'}, 31 | templateUrl: '/partials/home.html', 32 | controller: 'homeController', 33 | dependencies: ['controllers/homeController'] 34 | }); 35 | 36 | var contacts = $detour.setState({ 37 | name: 'contacts', 38 | url: '/contacts', 39 | abstract: true, 40 | templateUrl: '/partials/contacts.html', 41 | controller: 'contactsController', 42 | dependencies: ['controllers/contactsController'] 43 | }); 44 | 45 | contacts.setChild({ 46 | localName: 'list', 47 | url: '', 48 | templateUrl: '/partials/contacts.list.html' 49 | }); 50 | 51 | var detail = contacts.setChild({ 52 | localName: 'detail', 53 | // parent: 'contacts', 54 | url: '/{contactId}', 55 | aliases: {'/c?id': '/:id', '/user/{id}': '/:id'}, 56 | resolve: { 57 | something: 58 | [ '$timeout', '$stateParams', 59 | function ($timeout, $stateParams) { 60 | return $timeout(function () { return 'Asynchronously resolved data (' + $stateParams.contactId + ')'; }, 10); 61 | }] 62 | }, 63 | dependencies: ['controllers/contactsDetailController'], 64 | views: { 65 | '': { 66 | templateUrl: '/partials/contacts.detail.html', 67 | controller: 'contactsDetailController' 68 | }, 69 | 'hint@': { 70 | template: 'This is contacts.detail populating the view "hint@"' 71 | }, 72 | 'menu': { 73 | templateProvider: 74 | [ '$stateParams', 75 | function ($stateParams){ 76 | // This is just to demonstrate that $stateParams injection works for templateProvider 77 | // $stateParams are the parameters for the new state we're transitioning to, even 78 | // though the global '$stateParams' has not been updated yet. 79 | return '
Contact ID: ' + $stateParams.contactId + '<' + '/small>'; 80 | }] 81 | } 82 | } 83 | }); 84 | 85 | var detailItem = detail.setChild({ 86 | localName: 'item', 87 | // parent: 'contacts.detail', 88 | url: '/item/:itemId', 89 | dependencies: ['controllers/contactsDetailItemController'], 90 | views: { 91 | '': { 92 | templateUrl: '/partials/contacts.detail.item.html', 93 | controller: 'contactsDetailItemController' 94 | }, 95 | 'hint@': { 96 | template: 'Overriding the view "hint@"' 97 | } 98 | } 99 | }); 100 | 101 | detailItem.setChild({ 102 | localName: 'edit', 103 | dependencies: ['controllers/contactsDetailItemEditController'], 104 | views: { 105 | '@contacts.detail': { 106 | templateUrl: '/partials/contacts.detail.item.edit.html', 107 | controller: 'contactsDetailItemEditController' 108 | } 109 | } 110 | }); 111 | 112 | $detour.setState({ 113 | name: 'about', 114 | url: '/about', 115 | templateProvider: 116 | [ '$timeout', 117 | function ($timeout) { 118 | return $timeout(function () { return 'Hello world'; }, 100); 119 | }] 120 | }); 121 | 122 | $detour.initialize(); 123 | 124 | //"cheating" so that detour is available in requirejs 125 | //define modules -- we want run-time registration of components 126 | //to take place within those modules because it allows 127 | //for them to have their own dependencies also be lazy-loaded. 128 | //this is what requirejs is good at. 129 | 130 | //if not using any dependencies properties in detour states, 131 | //then this is not necessary 132 | app.detour = $detour; 133 | 134 | 135 | //the sample reads from the current $detour.state 136 | //and $stateParams in its templates 137 | //that it the only reason this is necessary 138 | $rootScope.$detour = $detour; 139 | $rootScope.$stateParams = $stateParams; 140 | } 141 | ]); 142 | 143 | return app; 144 | 145 | }); 146 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/js/controllers/contactsController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'contactsController', 4 | [ '$scope', '$detour', 5 | function ($scope, $detour) { 6 | $scope.contacts = [{ 7 | id: 1, 8 | name: 'Alice', 9 | items: [{ 10 | id: 'a', 11 | type: 'phone number', 12 | value: '555-1234-1234' 13 | },{ 14 | id: 'b', 15 | type: 'email', 16 | value: 'alice@mailinator.com' 17 | }] 18 | }, { 19 | id: 42, 20 | name: 'Bob', 21 | items: [{ 22 | id: 'a', 23 | type: 'blog', 24 | value: 'http://bob.blogger.com' 25 | },{ 26 | id: 'b', 27 | type: 'fax', 28 | value: '555-999-9999' 29 | }] 30 | }, { 31 | id: 123, 32 | name: 'Eve', 33 | items: [{ 34 | id: 'a', 35 | type: 'full name', 36 | value: 'Eve Adamsdottir' 37 | }] 38 | }]; 39 | 40 | $scope.goToRandom = function () { 41 | /*jshint eqeqeq:false */ 42 | var contacts = $scope.contacts, id; 43 | do { 44 | id = contacts[Math.floor(contacts.length * Math.random())].id; 45 | } while (id == $detour.params.contactId); 46 | $detour.transitionTo('contacts.detail', { contactId: id }); 47 | }; 48 | }] 49 | ]); 50 | }); 51 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/js/controllers/contactsDetailController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'contactsDetailController', 4 | [ '$scope', '$stateParams', 'something', 5 | function ($scope, $stateParams, something) { 6 | $scope.something = something; 7 | $scope.contact = findById($scope.contacts, $stateParams.contactId); 8 | }] 9 | ]); 10 | }); 11 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/js/controllers/contactsDetailItemController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'contactsDetailItemController', 4 | [ '$scope', '$stateParams', '$detour', 5 | function ($scope, $stateParams, $detour) { 6 | $scope.item = findById($scope.contact.items, $stateParams.itemId); 7 | $scope.edit = function () { 8 | $detour.transitionTo('contacts.detail.item.edit', $stateParams); 9 | }; 10 | }] 11 | ]); 12 | }); 13 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/js/controllers/contactsDetailItemEditController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'contactsDetailItemEditController', 4 | [ '$scope', '$stateParams', '$detour', 5 | function ($scope, $stateParams, $detour) { 6 | $scope.item = findById($scope.contact.items, $stateParams.itemId); 7 | $scope.done = function () { 8 | $detour.transitionTo('contacts.detail.item', $stateParams); 9 | }; 10 | }] 11 | ]); 12 | }); 13 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/js/controllers/homeController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'homeController', 4 | [ '$scope', '$detour', 5 | function ($scope, $detour) { 6 | $scope.removeAbout = function() { 7 | $detour.removeState('about'); 8 | $detour.initialize(); 9 | }; 10 | 11 | $scope.addAbout = function() { 12 | $detour.setState({ 13 | name: 'about', 14 | url: '/about', 15 | templateProvider: 16 | [ '$timeout', 17 | function ($timeout) { 18 | return $timeout(function () { return 'Hello world'; }, 100); 19 | }] 20 | }); 21 | $detour.initialize(); 22 | }; 23 | }] 24 | ]); 25 | }); 26 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/js/detourService.js: -------------------------------------------------------------------------------- 1 | define(['app'], function(app) { 2 | return app.detour; 3 | }); 4 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/js/main.js: -------------------------------------------------------------------------------- 1 | require.config({ 2 | baseUrl: 'js', 3 | paths: { 4 | 'angular-detour': '/dist/angular-detour.amd', 5 | 'angular': '/dist/dependencies/angular' 6 | } 7 | }); 8 | 9 | require( [ 10 | 'app' 11 | ], function(app) { 12 | 'use strict'; 13 | angular.element(document).ready(function() { 14 | angular.bootstrap(document, [app['name'], function(){ 15 | }]); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/partials/contacts.detail.html: -------------------------------------------------------------------------------- 1 |
2 |

{{contact.name}}

3 |

{{something}}

4 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/partials/contacts.detail.item.edit.html: -------------------------------------------------------------------------------- 1 |
2 |

{{item.type}}

3 |
4 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/partials/contacts.detail.item.html: -------------------------------------------------------------------------------- 1 |
2 |

{{item.type}}

3 |
{{item.value}}
4 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/partials/contacts.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 12 |
13 | 14 |
15 |
16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/partials/contacts.list.html: -------------------------------------------------------------------------------- 1 |

All Contacts

2 | 7 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/partials/fourOhfour.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | You got lost on a detour. Maybe you should find your way home. 5 |
6 |
7 |
8 |
9 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/partials/home.html: -------------------------------------------------------------------------------- 1 |

Welcome to the agt.detour runtime config sample

Use the menu above to navigate

2 |

Look at Alice or Bob to see a URL with a 3 | redirect in action.

Just don't get lost!

4 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /samples/2-lazy-components-runtime/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | app.use('/js', express.static(__dirname + '/js')); 5 | app.use('/dist', express.static(__dirname + '/../../dist')); 6 | app.use('/css', express.static(__dirname + '/css')); 7 | app.use('/partials', express.static(__dirname + '/partials')); 8 | 9 | app.all('/*', function(req, res, next) { 10 | // Just send the index.html for other files to support HTML5Mode 11 | res.sendfile('index.html', { root: __dirname }); 12 | }); 13 | 14 | app.listen(3006); //the port you want to use 15 | -------------------------------------------------------------------------------- /samples/3-json-config/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | agt.detour/json-config 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 32 |
33 |
34 |
35 |   $detour = {{$detour.current.name}}
36 |   $stateParams = {{$stateParams}}
37 | 
38 | 39 | 40 | -------------------------------------------------------------------------------- /samples/3-json-config/js/app.js: -------------------------------------------------------------------------------- 1 | define(['angular-detour'], function () { 2 | 'use strict'; 3 | 4 | var app = angular.module('app', ['agt.detour']); 5 | 6 | app.config([ '$locationProvider', '$provide', 7 | function($locationProvider, $provide) { 8 | 9 | //comment out the decorator function for html5mode 10 | //uncomment the decorator function for forced hash(bang) mode 11 | // $provide.decorator('$sniffer', function($delegate) { 12 | // $delegate.history = false; 13 | // return $delegate; 14 | // }); 15 | $locationProvider.html5Mode(true); 16 | } 17 | ]); 18 | 19 | app.run([ '$rootScope', '$detour', '$stateParams', 20 | function($rootScope, $detour, $stateParams) { 21 | 22 | $detour.mergeJson({ 23 | f: '/404', 24 | t: { 25 | '404': { 26 | d: { 27 | u: '/404', 28 | t: 'partials/fourOhfour.html' 29 | } 30 | }, 31 | 'home': { 32 | definition: { 33 | url: '/', 34 | aliases: {'': '^/'}, 35 | templateUrl: '/partials/home.html', 36 | controller: 'homeController', 37 | dependencies: ['controllers/homeController'] 38 | } 39 | }, 40 | 'contacts': { 41 | definition: { 42 | url: '/contacts', 43 | abstract: true, 44 | templateUrl: '/partials/contacts.html', 45 | controller: 'contactsController', 46 | dependencies: ['controllers/contactsController'] 47 | }, 48 | children: { 49 | 'list': { 50 | definition: { 51 | url: '', 52 | templateUrl: '/partials/contacts.list.html' 53 | } 54 | }, 55 | 'detail': { 56 | definition: { 57 | url: '/{contactId}', 58 | aliases: {'/c?id': '/:id', '/user/{id}': '/:id'}, 59 | resolveByService: { 60 | something: 'getContactIdFromParams' 61 | }, 62 | dependencies: ['controllers/contactsDetailController', 'services/getContactIdFromParams', 'services/getContactIdHtml'], 63 | views: { 64 | '': { 65 | templateUrl: '/partials/contacts.detail.html', 66 | controller: 'contactsDetailController' 67 | }, 68 | 'hint@': { 69 | template: 'This is contacts.detail populating the view "hint@"' 70 | }, 71 | 'menu': { 72 | templateService: 'getContactIdHtml' 73 | } 74 | } 75 | }, 76 | children: { 77 | 'item': { 78 | definition: { 79 | url: '/item/:itemId', 80 | dependencies: ['controllers/contactsDetailItemController'], 81 | views: { 82 | '': { 83 | templateUrl: '/partials/contacts.detail.item.html', 84 | controller: 'contactsDetailItemController' 85 | }, 86 | 'hint@': { 87 | template: 'Overriding the view "hint@"' 88 | } 89 | } 90 | }, 91 | children: { 92 | 'edit': { 93 | definition: { 94 | dependencies: ['controllers/contactsDetailItemEditController'], 95 | views: { 96 | '@contacts.detail': { 97 | templateUrl: '/partials/contacts.detail.item.edit.html', 98 | controller: 'contactsDetailItemEditController' 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | } 107 | } 108 | }, 109 | 'about': { 110 | definition: { 111 | url: '/about', 112 | dependencies: ['services/getHelloWorld'], 113 | i: 'getHelloWorld' 114 | } 115 | } 116 | } 117 | }); 118 | 119 | //"cheating" so that detour is available in requirejs 120 | //define modules -- we want run-time registration of components 121 | //to take place within those modules because it allows 122 | //for them to have their own dependencies also be lazy-loaded. 123 | //this is what requirejs is good at. 124 | 125 | //if not using any dependencies properties in detour states, 126 | //then this is not necessary 127 | app.detour = $detour; 128 | 129 | 130 | //the sample reads from the current $detour.state 131 | //and $stateParams in its templates 132 | //that it the only reason this is necessary 133 | $rootScope.$detour = $detour; 134 | $rootScope.$stateParams = $stateParams; 135 | } 136 | ]); 137 | 138 | return app; 139 | 140 | }); 141 | -------------------------------------------------------------------------------- /samples/3-json-config/js/controllers/contactsController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'contactsController', 4 | [ '$scope', '$detour', 5 | function ($scope, $detour) { 6 | $scope.contacts = [{ 7 | id: 1, 8 | name: 'Alice', 9 | items: [{ 10 | id: 'a', 11 | type: 'phone number', 12 | value: '555-1234-1234' 13 | },{ 14 | id: 'b', 15 | type: 'email', 16 | value: 'alice@mailinator.com' 17 | }] 18 | }, { 19 | id: 42, 20 | name: 'Bob', 21 | items: [{ 22 | id: 'a', 23 | type: 'blog', 24 | value: 'http://bob.blogger.com' 25 | },{ 26 | id: 'b', 27 | type: 'fax', 28 | value: '555-999-9999' 29 | }] 30 | }, { 31 | id: 123, 32 | name: 'Eve', 33 | items: [{ 34 | id: 'a', 35 | type: 'full name', 36 | value: 'Eve Adamsdottir' 37 | }] 38 | }]; 39 | 40 | $scope.goToRandom = function () { 41 | /*jshint eqeqeq:false */ 42 | var contacts = $scope.contacts, id; 43 | do { 44 | id = contacts[Math.floor(contacts.length * Math.random())].id; 45 | } while (id == $detour.params.contactId); 46 | $detour.transitionTo('contacts.detail', { contactId: id }); 47 | }; 48 | }] 49 | ]); 50 | }); 51 | -------------------------------------------------------------------------------- /samples/3-json-config/js/controllers/contactsDetailController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'contactsDetailController', 4 | [ '$scope', '$stateParams', 'something', 5 | function ($scope, $stateParams, something) { 6 | $scope.something = something; 7 | $scope.contact = findById($scope.contacts, $stateParams.contactId); 8 | }] 9 | ]); 10 | }); 11 | -------------------------------------------------------------------------------- /samples/3-json-config/js/controllers/contactsDetailItemController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'contactsDetailItemController', 4 | [ '$scope', '$stateParams', '$detour', 5 | function ($scope, $stateParams, $detour) { 6 | $scope.item = findById($scope.contact.items, $stateParams.itemId); 7 | $scope.edit = function () { 8 | $detour.transitionTo('contacts.detail.item.edit', $stateParams); 9 | }; 10 | }] 11 | ]); 12 | }); 13 | -------------------------------------------------------------------------------- /samples/3-json-config/js/controllers/contactsDetailItemEditController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'contactsDetailItemEditController', 4 | [ '$scope', '$stateParams', '$detour', 5 | function ($scope, $stateParams, $detour) { 6 | $scope.item = findById($scope.contact.items, $stateParams.itemId); 7 | $scope.done = function () { 8 | $detour.transitionTo('contacts.detail.item', $stateParams); 9 | }; 10 | }] 11 | ]); 12 | }); 13 | -------------------------------------------------------------------------------- /samples/3-json-config/js/controllers/homeController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'homeController', 4 | [ '$scope', '$detour', 5 | function ($scope, $detour) { 6 | $scope.removeAbout = function() { 7 | $detour.mergeJson({ 8 | t: { 9 | 'about': {'delete': true} 10 | } 11 | }); 12 | }; 13 | 14 | $scope.addAbout = function() { 15 | $detour.mergeJson({ 16 | t: { 17 | 'about': { 18 | definition: { 19 | url: '/about', 20 | dependencies: ['services/getHelloWorld'], 21 | i: 'getHelloWorld' 22 | } 23 | } 24 | } 25 | }); 26 | }; 27 | }] 28 | ]); 29 | }); 30 | -------------------------------------------------------------------------------- /samples/3-json-config/js/detourService.js: -------------------------------------------------------------------------------- 1 | define(['app'], function(app) { 2 | return app.detour; 3 | }); 4 | -------------------------------------------------------------------------------- /samples/3-json-config/js/main.js: -------------------------------------------------------------------------------- 1 | require.config({ 2 | baseUrl: 'js', 3 | paths: { 4 | 'angular-detour': '/dist/angular-detour.amd', 5 | 'angular': '/dist/dependencies/angular' 6 | } 7 | }); 8 | 9 | require( [ 10 | 'app' 11 | ], function(app) { 12 | 'use strict'; 13 | angular.element(document).ready(function() { 14 | angular.bootstrap(document, [app['name'], function(){ 15 | }]); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /samples/3-json-config/js/services/getContactIdFromParams.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerFactory([ 3 | 'getContactIdFromParams', 4 | [ '$timeout', 5 | function ($timeout) { 6 | var svc = {}; 7 | svc.resolve = function(stateParams, locals) { 8 | return $timeout(function () { return 'Asynchronously resolved data (' + stateParams.contactId + ')'; }, 10); 9 | }; 10 | return svc; 11 | }] 12 | ]); 13 | }); 14 | -------------------------------------------------------------------------------- /samples/3-json-config/js/services/getContactIdHtml.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerFactory([ 3 | 'getContactIdHtml', 4 | [ '$stateParams', 5 | function ($stateParams){ 6 | var svc = {}; 7 | svc.getTemplate = function(params, locals) { 8 | // This is just to demonstrate that $stateParams injection works for templateProvider 9 | // $stateParams are the parameters for the new state we're transitioning to, even 10 | // though the global '$stateParams' has not been updated yet. 11 | return '
Contact ID: ' + params.contactId + '<' + '/small>'; 12 | }; 13 | return svc; 14 | 15 | }] 16 | ]); 17 | }); 18 | -------------------------------------------------------------------------------- /samples/3-json-config/js/services/getHelloWorld.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerFactory([ 3 | 'getHelloWorld', 4 | [ '$timeout', 5 | function ($timeout) { 6 | var svc = {}; 7 | svc.getTemplate = function() { 8 | return $timeout(function () { return 'Hello world'; }, 100); 9 | }; 10 | return svc; 11 | }] 12 | ]); 13 | }); 14 | -------------------------------------------------------------------------------- /samples/3-json-config/partials/contacts.detail.html: -------------------------------------------------------------------------------- 1 |
2 |

{{contact.name}}

3 |

{{something}}

4 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /samples/3-json-config/partials/contacts.detail.item.edit.html: -------------------------------------------------------------------------------- 1 |
2 |

{{item.type}}

3 |
4 | -------------------------------------------------------------------------------- /samples/3-json-config/partials/contacts.detail.item.html: -------------------------------------------------------------------------------- 1 |
2 |

{{item.type}}

3 |
{{item.value}}
4 | -------------------------------------------------------------------------------- /samples/3-json-config/partials/contacts.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 12 |
13 | 14 |
15 |
16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /samples/3-json-config/partials/contacts.list.html: -------------------------------------------------------------------------------- 1 |

All Contacts

2 | 7 | -------------------------------------------------------------------------------- /samples/3-json-config/partials/fourOhfour.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | You got lost on a detour. Maybe you should find your way home. 5 |
6 |
7 |
8 |
9 | -------------------------------------------------------------------------------- /samples/3-json-config/partials/home.html: -------------------------------------------------------------------------------- 1 |

Welcome to the agt.detour json config sample

Use the menu above to navigate

2 |

Look at Alice or Bob to see a URL with a 3 | redirect in action.

Just don't get lost!

4 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /samples/3-json-config/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | app.use('/js', express.static(__dirname + '/js')); 5 | app.use('/dist', express.static(__dirname + '/../../dist')); 6 | app.use('/css', express.static(__dirname + '/css')); 7 | app.use('/partials', express.static(__dirname + '/partials')); 8 | 9 | app.all('/*', function(req, res, next) { 10 | // Just send the index.html for other files to support HTML5Mode 11 | res.sendfile('index.html', { root: __dirname }); 12 | }); 13 | 14 | app.listen(3006); //the port you want to use 15 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | agt.detour/lazy-routing 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 32 |
33 |
34 |
35 |   $detour = {{$detour.current.name}}
36 |   $stateParams = {{$stateParams}}
37 | 
38 | 39 | 40 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/js/app.js: -------------------------------------------------------------------------------- 1 | define(['angular-detour'], function () { 2 | 'use strict'; 3 | 4 | var app = angular.module('app', ['agt.detour']); 5 | 6 | app.config([ '$locationProvider', '$provide', '$detourProvider', 7 | function($locationProvider, $provide, $detourProvider) { 8 | //comment out the decorator function for html5mode 9 | //uncomment the decorator function for forced hash(bang) mode 10 | // $provide.decorator('$sniffer', function($delegate) { 11 | // $delegate.history = false; 12 | // return $delegate; 13 | // }); 14 | $locationProvider.html5Mode(true); 15 | 16 | //configure loader 17 | $detourProvider.loader = { 18 | lazy : { 19 | enabled: true, 20 | routeUrl: '/svc/getRoute', 21 | stateUrl: '/svc/getState' 22 | }, 23 | crossDomain: true, 24 | additionalParams: { 25 | myParam1: 'test', 26 | myParam2: { 27 | jsonSubObj: true 28 | } 29 | } 30 | }; 31 | } 32 | ]); 33 | 34 | app.run([ '$rootScope', '$detour', '$stateParams', 35 | function($rootScope, $detour, $stateParams) { 36 | 37 | //"cheating" so that detour is available in requirejs 38 | //define modules -- we want run-time registration of components 39 | //to take place within those modules because it allows 40 | //for them to have their own dependencies also be lazy-loaded. 41 | //this is what requirejs is good at. 42 | 43 | //if not using any dependencies properties in detour states, 44 | //then this is not necessary 45 | app.detour = $detour; 46 | 47 | 48 | //the sample reads from the current $detour.state 49 | //and $stateParams in its templates 50 | //that it the only reason this is necessary 51 | $rootScope.$detour = $detour; 52 | $rootScope.$stateParams = $stateParams; 53 | } 54 | ]); 55 | 56 | return app; 57 | 58 | }); 59 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/js/detourService.js: -------------------------------------------------------------------------------- 1 | define(['app'], function(app) { 2 | return app.detour; 3 | }); 4 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/js/lazy/controllers/contactsController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'contactsController', 4 | [ '$scope', '$detour', 5 | function ($scope, $detour) { 6 | $scope.contacts = [{ 7 | id: 1, 8 | name: 'Alice', 9 | items: [{ 10 | id: 'a', 11 | type: 'phone number', 12 | value: '555-1234-1234' 13 | },{ 14 | id: 'b', 15 | type: 'email', 16 | value: 'alice@mailinator.com' 17 | }] 18 | }, { 19 | id: 42, 20 | name: 'Bob', 21 | items: [{ 22 | id: 'a', 23 | type: 'blog', 24 | value: 'http://bob.blogger.com' 25 | },{ 26 | id: 'b', 27 | type: 'fax', 28 | value: '555-999-9999' 29 | }] 30 | }, { 31 | id: 123, 32 | name: 'Eve', 33 | items: [{ 34 | id: 'a', 35 | type: 'full name', 36 | value: 'Eve Adamsdottir' 37 | }] 38 | }]; 39 | 40 | $scope.goToRandom = function () { 41 | /*jshint eqeqeq:false */ 42 | var contacts = $scope.contacts, id; 43 | do { 44 | id = contacts[Math.floor(contacts.length * Math.random())].id; 45 | } while (id == $detour.params.contactId); 46 | $detour.transitionTo('contacts.detail', { contactId: id }); 47 | }; 48 | }] 49 | ]); 50 | }); 51 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/js/lazy/controllers/contactsDetailController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'contactsDetailController', 4 | [ '$scope', '$stateParams', 'something', 5 | function ($scope, $stateParams, something) { 6 | $scope.something = something; 7 | $scope.contact = findById($scope.contacts, $stateParams.contactId); 8 | }] 9 | ]); 10 | }); 11 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/js/lazy/controllers/contactsDetailItemController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'contactsDetailItemController', 4 | [ '$scope', '$stateParams', '$detour', 5 | function ($scope, $stateParams, $detour) { 6 | $scope.item = findById($scope.contact.items, $stateParams.itemId); 7 | $scope.edit = function () { 8 | $detour.transitionTo('contacts.detail.item.edit', $stateParams); 9 | }; 10 | }] 11 | ]); 12 | }); 13 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/js/lazy/controllers/contactsDetailItemEditController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'contactsDetailItemEditController', 4 | [ '$scope', '$stateParams', '$detour', 5 | function ($scope, $stateParams, $detour) { 6 | $scope.item = findById($scope.contact.items, $stateParams.itemId); 7 | $scope.done = function () { 8 | $detour.transitionTo('contacts.detail.item', $stateParams); 9 | }; 10 | }] 11 | ]); 12 | }); 13 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/js/lazy/controllers/homeController.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerController([ 3 | 'homeController', 4 | [ '$scope', '$detour', 5 | function ($scope, $detour) { 6 | $scope.removeAbout = function() { 7 | $detour.mergeJson({ 8 | t: { 9 | 'about': {'delete': true } 10 | } 11 | }); 12 | }; 13 | 14 | $scope.addAbout = function() { 15 | $detour.mergeJson({ 16 | t: { 17 | 'about': { 18 | definition: { 19 | url: '/about', 20 | dependencies: ['lazy/services/getHelloWorld'], 21 | i: 'getHelloWorld' 22 | } 23 | } 24 | } 25 | }); 26 | }; 27 | }] 28 | ]); 29 | }); 30 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/js/lazy/services/getContactIdFromParams.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerFactory([ 3 | 'getContactIdFromParams', 4 | [ '$timeout', 5 | function ($timeout) { 6 | var svc = {}; 7 | svc.resolve = function(stateParams, locals) { 8 | return $timeout(function () { return 'Asynchronously resolved data (' + stateParams.contactId + ')'; }, 10); 9 | }; 10 | return svc; 11 | }] 12 | ]); 13 | }); 14 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/js/lazy/services/getContactIdHtml.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerFactory([ 3 | 'getContactIdHtml', 4 | [ '$stateParams', 5 | function ($stateParams){ 6 | var svc = {}; 7 | svc.getTemplate = function(params, locals) { 8 | // This is just to demonstrate that $stateParams injection works for templateProvider 9 | // $stateParams are the parameters for the new state we're transitioning to, even 10 | // though the global '$stateParams' has not been updated yet. 11 | return '
Contact ID: ' + params.contactId + '<' + '/small>'; 12 | }; 13 | return svc; 14 | 15 | }] 16 | ]); 17 | }); 18 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/js/lazy/services/getHelloWorld.js: -------------------------------------------------------------------------------- 1 | define(['detourService'], function (detour) { 2 | detour.registerFactory([ 3 | 'getHelloWorld', 4 | [ '$timeout', 5 | function ($timeout) { 6 | var svc = {}; 7 | svc.getTemplate = function() { 8 | return $timeout(function () { return 'Hello world'; }, 100); 9 | }; 10 | return svc; 11 | }] 12 | ]); 13 | }); 14 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/js/main.js: -------------------------------------------------------------------------------- 1 | require.config({ 2 | baseUrl: 'js', 3 | paths: { 4 | 'angular-detour': '/dist/angular-detour.amd', 5 | 'angular': '/dist/dependencies/angular' 6 | } 7 | }); 8 | 9 | require( [ 10 | 'app' 11 | ], function(app) { 12 | 'use strict'; 13 | angular.element(document).ready(function() { 14 | angular.bootstrap(document, [app['name'], function(){ 15 | }]); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/partials/contacts.detail.html: -------------------------------------------------------------------------------- 1 |
2 |

{{contact.name}}

3 |

{{something}}

4 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/partials/contacts.detail.item.edit.html: -------------------------------------------------------------------------------- 1 |
2 |

{{item.type}}

3 |
4 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/partials/contacts.detail.item.html: -------------------------------------------------------------------------------- 1 |
2 |

{{item.type}}

3 |
{{item.value}}
4 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/partials/contacts.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 12 |
13 | 14 |
15 |
16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/partials/contacts.list.html: -------------------------------------------------------------------------------- 1 |

All Contacts

2 | 7 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/partials/fourOhfour.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | You got lost on a detour. Maybe you should find your way home. 5 |
6 |
7 |
8 |
9 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/partials/home.html: -------------------------------------------------------------------------------- 1 |

Welcome to the agt.detour lazy routing sample

Use the menu above to navigate

2 |

Look at Alice or Bob to see a URL with a 3 | redirect in action.

Just don't get lost!

4 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/server-files/sample-routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "f": "/404", 3 | "t": { 4 | "404": { 5 | "z": true, 6 | "d": { 7 | "u": "/404", 8 | "t": "partials/fourOhfour.html" 9 | } 10 | }, 11 | "home": { 12 | "z": true, "definition": { 13 | "url": "/", 14 | "aliases": {"": "^/"}, 15 | "templateUrl": "/partials/home.html", 16 | "controller": "homeController", 17 | "dependencies": ["lazy/controllers/homeController"] 18 | } 19 | }, 20 | "contacts": { 21 | "z": true, 22 | "definition": { 23 | "url": "/contacts", 24 | "abstract": true, 25 | "templateUrl": "/partials/contacts.html", 26 | "controller": "contactsController", 27 | "dependencies": ["lazy/controllers/contactsController"] 28 | }, 29 | "children": { 30 | "list": { 31 | "definition": { 32 | "url": "", 33 | "templateUrl": "/partials/contacts.list.html" 34 | } 35 | }, 36 | "detail": { 37 | "definition": { 38 | "url": "/{contactId}", 39 | "aliases": {"/c?id": "/:id", "/user/{id}": "/:id"}, 40 | "resolveByService": { 41 | "something": "getContactIdFromParams" 42 | }, 43 | "dependencies": ["lazy/controllers/contactsDetailController", "lazy/services/getContactIdFromParams", "lazy/services/getContactIdHtml"], 44 | "views": { 45 | "": { 46 | "templateUrl": "/partials/contacts.detail.html", 47 | "controller": "contactsDetailController" 48 | }, 49 | "hint@": { 50 | "template": "This is contacts.detail populating the view \"hint@\"" 51 | }, 52 | "menu": { 53 | "templateService": "getContactIdHtml" 54 | } 55 | } 56 | }, 57 | "children": { 58 | "item": { 59 | "definition": { 60 | "url": "/item/:itemId", 61 | "dependencies": ["lazy/controllers/contactsDetailItemController"], 62 | "views": { 63 | "": { 64 | "templateUrl": "/partials/contacts.detail.item.html", 65 | "controller": "contactsDetailItemController" 66 | }, 67 | "hint@": { 68 | "template": "Overriding the view \"hint@\"" 69 | } 70 | } 71 | }, 72 | "children": { 73 | "edit": { 74 | "definition": { 75 | "dependencies": ["lazy/controllers/contactsDetailItemEditController"], 76 | "views": { 77 | "@contacts.detail": { 78 | "templateUrl": "/partials/contacts.detail.item.edit.html", 79 | "controller": "contactsDetailItemEditController" 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | }, 90 | "about": { 91 | "z": true, 92 | "definition": { 93 | "url": "/about", 94 | "dependencies": ["lazy/services/getHelloWorld"], 95 | "i": "getHelloWorld" 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /samples/4-lazy-routing/server-files/urlMatcher.js: -------------------------------------------------------------------------------- 1 | module.exports = UrlMatcher; 2 | 3 | /** 4 | * Matches URLs against patterns and extracts named parameters from the path or the search 5 | * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list 6 | * of search parameters. Multiple search parameter names are separated by '&'. Search parameters 7 | * do not influence whether or not a URL is matched, but their values are passed through into 8 | * the matched parameters returned by {@link UrlMatcher#exec exec}. 9 | * 10 | * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace 11 | * syntax, which optionally allows a regular expression for the parameter to be specified: 12 | * 13 | * * ':' name - colon placeholder 14 | * * '*' name - catch-all placeholder 15 | * * '{' name '}' - curly placeholder 16 | * * '{' name ':' regexp '}' - curly placeholder with regexp. Should the regexp itself contain 17 | * curly braces, they must be in matched pairs or escaped with a backslash. 18 | * 19 | * Parameter names may contain only word characters (latin letters, digits, and underscore) and 20 | * must be unique within the pattern (across both path and search parameters). For colon 21 | * placeholders or curly placeholders without an explicit regexp, a path parameter matches any 22 | * number of characters other than '/'. For catch-all placeholders the path parameter matches 23 | * any number of characters. 24 | * 25 | * ### Examples 26 | * 27 | * * '/hello/' - Matches only if the path is exactly '/hello/'. There is no special treatment for 28 | * trailing slashes, and patterns have to match the entire path, not just a prefix. 29 | * * '/user/:id' - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or 30 | * '/user/bob/details'. The second path segment will be captured as the parameter 'id'. 31 | * * '/user/{id}' - Same as the previous example, but using curly brace syntax. 32 | * * '/user/{id:[^/]*}' - Same as the previous example. 33 | * * '/user/{id:[0-9a-fA-F]{1,8}}' - Similar to the previous example, but only matches if the id 34 | * parameter consists of 1 to 8 hex digits. 35 | * * '/files/{path:.*}' - Matches any URL starting with '/files/' and captures the rest of the 36 | * path into the parameter 'path'. 37 | * * '/files/*path' - ditto. 38 | * 39 | * @constructor 40 | * @param {string} pattern the pattern to compile into a matcher. 41 | * 42 | * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any 43 | * URL matching this matcher (i.e. any string for which {@link UrlMatcher#exec exec()} returns 44 | * non-null) will start with this prefix. 45 | */ 46 | function UrlMatcher(pattern, ignoreUrlParams) { 47 | 48 | // Find all placeholders and create a compiled pattern, using either classic or curly syntax: 49 | // '*' name 50 | // ':' name 51 | // '{' name '}' 52 | // '{' name ':' regexp '}' 53 | // The regular expression is somewhat complicated due to the need to allow curly braces 54 | // inside the regular expression. The placeholder regexp breaks down as follows: 55 | // ([:*])(\w+) classic placeholder ($1 / $2) 56 | // \{(\w+)(?:\:( ... ))?\} curly brace placeholder ($3) with optional regexp ... ($4) 57 | // (?: ... | ... | ... )+ the regexp consists of any number of atoms, an atom being either 58 | // [^{}\\]+ - anything other than curly braces or backslash 59 | // \\. - a backslash escape 60 | // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms 61 | var placeholder = /([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, 62 | names = {}, compiled = '^', last = 0, m, 63 | segments = this.segments = [], 64 | params = this.params = []; 65 | 66 | function addParameter(id) { 67 | if (!/^\w+$/.test(id)) { 68 | throw new Error('Invalid parameter name \'' + id + '\' in pattern \'' + pattern + '\''); 69 | } 70 | if (names[id]) { 71 | throw new Error('Duplicate parameter name \'' + id + '\' in pattern \'' + pattern + '\''); 72 | } 73 | names[id] = true; 74 | params.push(id); 75 | } 76 | 77 | function quoteRegExp(string) { 78 | return string.replace(/[\\\[\]\^$*+?.()|{}]/g, '\\$&'); 79 | } 80 | 81 | this.source = pattern; 82 | 83 | // Split into static segments separated by path parameter placeholders. 84 | // The number of segments is always 1 more than the number of parameters. 85 | var id, regexp, segment; 86 | while ((m = placeholder.exec(pattern))) { 87 | id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null 88 | regexp = m[4] || (m[1] === '*' ? '.*' : '[^/]*'); 89 | segment = pattern.substring(last, m.index); 90 | if (segment.indexOf('?') >= 0) { 91 | break; // we're into the search part 92 | } 93 | compiled += quoteRegExp(segment) + '(' + regexp + ')'; 94 | addParameter(id); 95 | segments.push(segment); 96 | last = placeholder.lastIndex; 97 | } 98 | segment = pattern.substring(last); 99 | 100 | // Find any search parameter names and remove them from the last segment 101 | var i = segment.indexOf('?'); 102 | if (i >= 0) { 103 | var search = this.sourceSearch = segment.substring(i); 104 | segment = segment.substring(0, i); 105 | this.sourcePath = pattern.substring(0, last+i); 106 | 107 | // Allow parameters to be separated by '?' as well as '&' to make concat() easier 108 | var searchRes = search.substring(1).split(/[&?]/); 109 | for (var j = 0; j < searchRes.length; j++) { 110 | addParameter(searchRes[j]); 111 | } 112 | } else { 113 | this.sourcePath = pattern; 114 | this.sourceSearch = ''; 115 | } 116 | 117 | if (ignoreUrlParams) { 118 | this.ignoreUrlParams = true; 119 | } 120 | 121 | compiled += quoteRegExp(segment) + '$'; 122 | segments.push(segment); 123 | this.regexp = new RegExp(compiled); 124 | this.prefix = segments[0]; 125 | } 126 | 127 | /** 128 | * Returns a new matcher for a pattern constructed by appending the path part and adding the 129 | * search parameters of the specified pattern to this pattern. The current pattern is not 130 | * modified. This can be understood as creating a pattern for URLs that are relative to (or 131 | * suffixes of) the current pattern. 132 | * 133 | * ### Example 134 | * The following two matchers are equivalent: 135 | * ``` 136 | * new UrlMatcher('/user/{id}?q').concat('/details?date'); 137 | * new UrlMatcher('/user/{id}/details?q&date'); 138 | * ``` 139 | * 140 | * @param {string} pattern The pattern to append. 141 | * @return {UrlMatcher} A matcher for the concatenated pattern. 142 | */ 143 | UrlMatcher.prototype.concat = function (pattern) { 144 | // Because order of search parameters is irrelevant, we can add our own search 145 | // parameters to the end of the new pattern. Parse the new pattern by itself 146 | // and then join the bits together, but it's much easier to do this on a string level. 147 | return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch); 148 | }; 149 | 150 | UrlMatcher.prototype.toString = function () { 151 | return this.source; 152 | }; 153 | 154 | /** 155 | * Tests the specified path against this matcher, and returns an object containing the captured 156 | * parameter values, or null if the path does not match. The returned object contains the values 157 | * of any search parameters that are mentioned in the pattern, but their value may be null if 158 | * they are not present in `searchParams`. This means that search parameters are always treated 159 | * as optional. 160 | * 161 | * ### Example 162 | * ``` 163 | * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', { x:'1', q:'hello' }); 164 | * // returns { id:'bob', q:'hello', r:null } 165 | * ``` 166 | * 167 | * @param {string} path The URL path to match, e.g. `$location.path()`. 168 | * @param {Object} searchParams URL search parameters, e.g. `$location.search()`. 169 | * @return {Object} The captured parameter values. 170 | */ 171 | UrlMatcher.prototype.exec = function (path, searchParams) { 172 | var m = this.regexp.exec(path); 173 | if (!m) { 174 | return null; 175 | } 176 | 177 | var params = this.params, nTotal = params.length, 178 | nPath = this.segments.length-1, 179 | values = {}, i; 180 | 181 | if (!this.ignoreUrlParams) { 182 | 183 | for (i=0; i} An array of parameter names. Must be treated as read-only. If the 197 | * pattern has no parameters, an empty array is returned. 198 | */ 199 | UrlMatcher.prototype.parameters = function () { 200 | return this.params; 201 | }; 202 | 203 | /** 204 | * Creates a URL that matches this pattern by substituting the specified values 205 | * for the path and search parameters. Null values for path parameters are 206 | * treated as empty strings. 207 | * 208 | * ### Example 209 | * ``` 210 | * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' }); 211 | * // returns '/user/bob?q=yes' 212 | * ``` 213 | * 214 | * @param {Object} values the values to substitute for the parameters in this pattern. 215 | * @return {string} the formatted URL (path and optionally search part). 216 | */ 217 | UrlMatcher.prototype.format = function (values) { 218 | var segments = this.segments, params = this.params; 219 | if (!values) { 220 | return segments.join(''); 221 | } 222 | 223 | var nPath = segments.length-1, nTotal = params.length, 224 | result = segments[0], i, search, value; 225 | 226 | for (i=0; i 0; 221 | 222 | //delete the states that are known but not defined 223 | for (var missingStateName in knownState) { 224 | response[missingStateName] = {x: true}; 225 | } 226 | 227 | if (state) { 228 | //at the very least include a summary if there are children to respond with 229 | var includeSummary = childrenResponseNonEmpty; 230 | //definition will be included if tests below indicate 231 | var includeDefinition = false; 232 | if (knownState && knownState[state.name]) { 233 | //client knows about this state -- is it up to date? 234 | serial = state.serial; 235 | var serialPush = 236 | serial && (serialMin || serialMax) //serial is required for updates 237 | && 238 | ( 239 | (!serialMin || (serial >= serialMin)) 240 | && 241 | (!serialMax || (serial <= serialMax)) 242 | ) 243 | ; 244 | 245 | includeDefinition = includeDefinition || serialPush; 246 | } 247 | else { 248 | //not known 249 | // include the definition if the state is not lazy 250 | // or there are children to send back 251 | // or this is the specific state 252 | includeDefinition = includeSummary || !state.lazy || state === specificState; 253 | } 254 | 255 | if (includeDefinition) { 256 | response[state.name] = state.littleState; 257 | if (childrenResponseNonEmpty) { 258 | response[state.name].c = childrenResponse; 259 | } 260 | } 261 | else { 262 | if (includeSummary) { 263 | response[state.name] = {}; 264 | response[state.name].c = childrenResponse; 265 | } 266 | } 267 | } 268 | else { 269 | for (var responseChildName in childrenResponse) { 270 | response[responseChildName] = childrenResponse[responseChildName]; 271 | } 272 | } 273 | } 274 | 275 | function getRoute(routeRequest, knownStates) { 276 | var response = { 277 | t: {}, 278 | s: littleStates.s 279 | }; 280 | 281 | if (knownStates.f !== littleStates.f) { 282 | response.f = littleStates.f; 283 | } 284 | //TODO: need to deal with the fallback somehow (or not) 285 | var matchState = getStateForRoute(statesDefinition.tree, routeRequest); 286 | 287 | buildResponse(matchState, null, statesDefinition.tree, knownStates.t, null, null, response.t); 288 | 289 | return response; 290 | } 291 | 292 | function getState(stateName, knownStates) { 293 | var response = { 294 | t: {}, 295 | f: littleStates.f, 296 | s: littleStates.s 297 | }; 298 | //TODO: need to deal with the fallback somehow (or not) 299 | var matchState = statesByName[stateName]; 300 | 301 | buildResponse(matchState, null, statesDefinition.tree, knownStates.t, null, null, response.t); 302 | 303 | return response; 304 | } 305 | 306 | loadStates(defaultStatesFile); 307 | 308 | app.use('/js', express.static(__dirname + '/js')); 309 | app.use('/dist', express.static(__dirname + '/../../dist')); 310 | app.use('/css', express.static(__dirname + '/css')); 311 | app.use('/partials', express.static(__dirname + '/partials')); 312 | 313 | app.get('/svc/getRoute', function(req, res, next) { 314 | var ks = JSON.parse(req.query.k); 315 | res.json(getRoute(req.query.r, ks)); 316 | }); 317 | 318 | app.get('/svc/getState', function(req, res, next) { 319 | var ks = JSON.parse(req.query.k); 320 | res.json(getState(req.query.s, ks)); 321 | }); 322 | 323 | app.all('/*', function(req, res, next) { 324 | // Just send the index.html for other files to support HTML5Mode 325 | res.sendfile('index.html', { root: __dirname }); 326 | }); 327 | 328 | app.listen(3006); //the port you want to use 329 | -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | To run the samples: 2 | * clone the repository 3 | * npm install 4 | * grunt bower 5 | * grunt 6 | * cd to one of 7 | * 1-ui-router-compatibility 8 | * 2-lazy-components-runtime 9 | * 3-json-config 10 | * 4-lazy-routing 11 | * node server.js 12 | * visit localhost:3006 13 | 14 | The samples work in html5 mode and hashbang mode -- see js/app.js to force hashbang mode. 15 | -------------------------------------------------------------------------------- /src/StateBase.js: -------------------------------------------------------------------------------- 1 | define(['./common', 'UrlMatcher'], function(common, UrlMatcher) { 2 | var abstractVar = 'abstract' 3 | ; 4 | 5 | function StateBase() { 6 | this.children = {}; 7 | } 8 | 9 | 10 | //********************************************* 11 | // initialize 12 | //********************************************* 13 | Object.defineProperty(StateBase.prototype, 'self', { 14 | get: function() { return this; } 15 | }); 16 | 17 | StateBase.prototype.resetAll = function() { 18 | this.resetFullName(); 19 | this.resetUrl(); 20 | this.resetParams(); 21 | this.resetNavigable(); 22 | this.resetPath(); 23 | this.resetViews(); 24 | this.resetIncludes(); 25 | this.resetHandlers(); 26 | }; 27 | 28 | StateBase.prototype.initialize = function(forceInit) { 29 | if (this.needsInit || forceInit) { 30 | this.resetAll(); 31 | 32 | for (var child in this.children) { 33 | this.children[child].initialize(true); 34 | } 35 | this.needsInit = false; 36 | } 37 | }; 38 | 39 | //********************************************* 40 | // name/fullName/localName 41 | //********************************************* 42 | Object.defineProperty(StateBase.prototype, 'fullName', { 43 | get: function() { return this._fullName; } 44 | }); 45 | Object.defineProperty(StateBase.prototype, 'name', { 46 | get: function() { return this._fullName; } 47 | }); 48 | Object.defineProperty(StateBase.prototype, 'localName', { 49 | get: function() { return this._localName; }, 50 | set: function(val) { 51 | this.validateName(val); 52 | this._localName= val; 53 | this.needsInit = true; 54 | } 55 | }); 56 | 57 | StateBase.prototype.resetFullName = function() { 58 | this._fullName = (this.parent.fullName) 59 | ? this.parent.fullName + '.' + this.localName 60 | : this.localName; 61 | }; 62 | StateBase.prototype.toString = function() { return this.fullName; }; 63 | 64 | StateBase.prototype.validateName = function(localName) { 65 | if (!common.isString(localName) || localName.indexOf('@') >= 0) { 66 | throw new Error('Invalid local state name (' + localName + ')'); 67 | } 68 | 69 | // can't redefine if we throw this error here 70 | //not really useful, anyway 71 | // if (this.parent && this.parent.getChild(localName)) { 72 | // throw new Error('State ' + parent.fullName + ' already has child ' + localName); 73 | // } 74 | }; 75 | 76 | //********************************************* 77 | // root 78 | //********************************************* 79 | Object.defineProperty(StateBase.prototype, 'root', { 80 | get: function() { return this.parent.root; } 81 | }); 82 | 83 | //********************************************* 84 | // children 85 | //********************************************* 86 | Object.defineProperty(StateBase.prototype, 'children', { 87 | get: function() { return this._children; }, 88 | set: function(val) { 89 | this._children = val; 90 | if (this._children) { 91 | for (var child in this._children) { 92 | //assigning their parent takes care of resetting the children 93 | this._children[child].parent = this; 94 | } 95 | } 96 | } 97 | }); 98 | 99 | //********************************************* 100 | // path 101 | //********************************************* 102 | StateBase.prototype.resetPath = function() { 103 | // Keep a full path from the root down to this state as this is needed for state activation. 104 | this.path = this.parent.path.concat(this); // exclude root from path 105 | }; 106 | 107 | //********************************************* 108 | // url 109 | //********************************************* 110 | Object.defineProperty(StateBase.prototype, 'url', { 111 | get: function() { return this._url; }, 112 | set: function(val) { 113 | this._url= val; 114 | this.needsInit = true; 115 | } 116 | }); 117 | Object.defineProperty(StateBase.prototype, 'aliases', { 118 | get: function() { return this._aliases; }, 119 | set: function(val) { 120 | this._aliases= val; 121 | this.needsInit = true; 122 | } 123 | }); 124 | 125 | 126 | StateBase.prototype.resetUrl = function() { 127 | /*jshint eqeqeq:false */ 128 | this.preparedUrl = null; 129 | if (common.isString(this.url)) { 130 | if (this.url.charAt(0) === '^') { 131 | this.preparedUrl = new UrlMatcher(this.url.substring(1)); 132 | } else { 133 | this.preparedUrl = (this.parent.navigable || this.root).preparedUrl.concat(this.url); 134 | } 135 | } else if (common.isObject(this.url) && 136 | common.isFunction(this.url.exec) && common.isFunction(this.url.format) && common.isFunction(this.url.concat)) { 137 | this.preparedUrl = this.url; 138 | /* use UrlMatcher (or compatible object) as is */ 139 | } else if (this.url != null) { 140 | throw new Error('Invalid url ' + this.url + ' in state ' + this); 141 | } 142 | 143 | }; 144 | 145 | //********************************************* 146 | // params 147 | //********************************************* 148 | Object.defineProperty(StateBase.prototype, 'params', { 149 | get: function() { return this._params; }, 150 | set: function(val) { 151 | this._params= val; 152 | this.needsInit = true; 153 | } 154 | }); 155 | StateBase.prototype.resetParams = function() { 156 | // Derive parameters for this state and ensure they're a super-set of parent's parameters 157 | this.preparedParams = null; 158 | 159 | // Derive parameters for this state and ensure they're a super-set of parent's parameters 160 | var params = this.params; 161 | if (params) { 162 | if (!common.isArray(params)) { 163 | throw new Error('Invalid params in state \'' + this + '\''); 164 | } 165 | else { 166 | if (this.preparedUrl) { 167 | throw new Error('Both params and url specicified in state \'' + this + '\''); 168 | } 169 | else { 170 | this.perparedParams = params; 171 | } 172 | } 173 | } 174 | else { 175 | this.preparedParams = this.preparedUrl ? this.preparedUrl.parameters() : this.parent.preparedParams; 176 | } 177 | 178 | var paramNames = {}; 179 | common.forEach(this.preparedParams, function (p) { 180 | paramNames[p] = true; 181 | }); 182 | if (this.parent) { 183 | var that = this; 184 | common.forEach(this.parent.preparedParams, function (p) { 185 | if (!paramNames[p]) { 186 | throw new Error('Missing required parameter \'' + p + '\' in state \'' + that.name + '\''); 187 | } 188 | paramNames[p] = false; 189 | }); 190 | 191 | var ownParams = this.ownParams = []; 192 | common.forEach(paramNames, function (own, p) { 193 | if (own) { 194 | ownParams.push(p); 195 | } 196 | }); 197 | } else { 198 | this.ownParams = this.preparedParams; 199 | } 200 | }; 201 | 202 | //********************************************* 203 | // navigable 204 | //********************************************* 205 | StateBase.prototype.resetNavigable = function() { 206 | this.navigable = (this.url) 207 | ? this 208 | : (this.parent) 209 | ? this.parent.navigable 210 | : null; 211 | }; 212 | 213 | //********************************************* 214 | // abstract 215 | //********************************************* 216 | Object.defineProperty(StateBase.prototype, abstractVar, { 217 | get: function() { return this._abstract; }, 218 | set: function(val) { 219 | this._abstract= val; 220 | this.needsInit = true; 221 | } 222 | }); 223 | 224 | //********************************************* 225 | // includes 226 | //********************************************* 227 | StateBase.prototype.resetIncludes = function() { 228 | // Speed up $detour.contains() as it's used a lot 229 | this.includes = (this.parent) 230 | ? common.extend({}, this.parent.includes) 231 | : {}; 232 | this.includes[this.name] = true; 233 | }; 234 | 235 | //********************************************* 236 | // views 237 | //********************************************* 238 | Object.defineProperty(StateBase.prototype, 'views', { 239 | get: function() { return this._views; }, 240 | set: function(val) { 241 | this._views= val; 242 | this.needsInit = true; 243 | } 244 | }); 245 | StateBase.prototype.resetViews = function() { 246 | var state = this; 247 | // If there is no explicit multi-view configuration, make one up so we don't have 248 | // to handle both cases in the view directive later. Note that having an explicit 249 | // 'views' property will mean the default unnamed view properties are ignored. This 250 | // is also a good time to resolve view names to absolute names, so everything is a 251 | // straight lookup at link time. 252 | var views = {}; 253 | var myViews = this.views; 254 | common.forEach(common.isDefined(myViews) ? myViews : { '': state }, function (view, name) { 255 | if (name.indexOf('@') < 0) { 256 | name = name + '@' + state.parent.name; 257 | } 258 | views[name] = view; 259 | }); 260 | this.preparedViews = views; 261 | }; 262 | 263 | //this is somewhat particular to client-side 264 | //for now, separate -- but revisit this when 265 | //implementing incremental matching 266 | // //********************************************* 267 | // // handleUrl 268 | // //********************************************* 269 | // StateBase.prototype._buildRule = function(what, handler) { 270 | // var rule, redirect; 271 | // if (isString(what)) { 272 | // what = new UrlMatcher(what); 273 | // } 274 | 275 | // if (what instanceof UrlMatcher) { 276 | // if (isString(handler)) { 277 | // redirect = new UrlMatcher(handler); 278 | // handler = [matchSvc, function ($match) { return redirect.format($match); }]; 279 | // } 280 | // else if (!isFunction(handler) && !isArray(handler)) { 281 | // throw new Error('invalid \'handler\' in when()'); 282 | // } 283 | 284 | // rule = function ($injector, $location) { 285 | // return handleIfMatch($injector, handler, what.exec($location.path(), $location.search())); 286 | // }; 287 | // rule.prefix = isString(what.prefix) ? what.prefix : ''; 288 | // } 289 | // else if (what instanceof RegExp) { 290 | // if (isString(handler)) { 291 | // redirect = handler; 292 | // handler = [matchSvc, function ($match) { return interpolate(redirect, $match); }]; 293 | // } 294 | // else if (!isFunction(handler) && !isArray(handler)) { 295 | // throw new Error('invalid \'handler\' in when()'); 296 | // } 297 | 298 | // if (what.global || what.sticky) { 299 | // throw new Error('when() RegExp must not be global or sticky'); 300 | // } 301 | 302 | // rule = function ($injector, $location) { 303 | // return handleIfMatch($injector, handler, what.exec($location.path())); 304 | // }; 305 | // rule.prefix = regExpPrefix(what); 306 | // } 307 | // else { 308 | // throw new Error('invalid \'what\' in when()'); 309 | // } 310 | // return rule; 311 | // }; 312 | 313 | StateBase.prototype.resetHandlers = function() { 314 | //this implementation is client-specific 315 | throw new Error('not implemented: "resetHandlers"'); 316 | // if (this[abstractVar] || !this.preparedUrl) { 317 | // this.handleUrl = null; 318 | // return; 319 | // } 320 | 321 | // var what = this.preparedUrl; 322 | // var that = this; 323 | // var handler = [matchSvc, detourSvc, function ($match, $detour) { 324 | // $detour.transitionTo(that, $match, false); 325 | // }]; 326 | 327 | // this.handleUrl = this._buildRule(what, handler); 328 | 329 | // this.preparedAliases = []; 330 | // if (this.aliases) { 331 | // common.forEach(this.aliases, function(value, key) { 332 | // value = value.charAt(0) === '^' 333 | // ? value.substring(1) 334 | // : (that.parent.navigable || that.root).preparedUrl.concat(value).source; 335 | // that.preparedAliases.push(that._buildRule(key, value)); 336 | // }); 337 | // } 338 | 339 | }; 340 | 341 | // StateBase.prototype.tryHandle = function($injector, $location) { 342 | // //this implementation is client-specific 343 | // throw new Error('not implemented: "resetHandlers"'); 344 | // var handled = false; 345 | 346 | // if (this.handleUrl) { 347 | // handled = this.handleUrl($injector, $location); 348 | // for (var i = 0; i < this.preparedAliases.length && !handled; i++) { 349 | // handled = this.preparedAliases[i]($injector, $location); 350 | // } 351 | // } 352 | 353 | // if (!handled) { 354 | // for (var child in this.children) { 355 | // handled = this.children[child].tryHandle($injector, $location); 356 | // if (handled) { 357 | // break; 358 | // } 359 | // } 360 | // } 361 | 362 | // return handled; 363 | // }; 364 | 365 | 366 | StateBase.prototype.newInstance = function() { 367 | return new StateBase(); 368 | }; 369 | 370 | //********************************************* 371 | // getChild 372 | //********************************************* 373 | StateBase.prototype.getChild = function(localName) { 374 | return (this.children) 375 | ? this.children[localName] 376 | : null; 377 | }; 378 | 379 | 380 | //********************************************* 381 | // setChild 382 | //********************************************* 383 | //this redefines the child in place (i.e. doesn't wipe out its children) 384 | StateBase.prototype.setChild = function(stateDef, deep) { 385 | var state = this.newInstance(); 386 | common.extend(state, stateDef); 387 | 388 | return this.setChildState(state, deep); 389 | }; 390 | 391 | //********************************************* 392 | // removeChild 393 | //********************************************* 394 | //undefines the child (and any descendants of the child) 395 | StateBase.prototype.removeChild = function(localName) { 396 | if (this.children[localName]) { 397 | delete this.children[localName]; 398 | } 399 | //this._needsInit = true; 400 | return this; 401 | }; 402 | 403 | 404 | //********************************************* 405 | // setChildState 406 | //********************************************* 407 | //this redefines the child in place (i.e. doesn't wipe out its children) 408 | StateBase.prototype.setChildState = function(state, deep) { 409 | if (!deep) { 410 | var existingChild = this.getChild(state.localName); 411 | var existingChildren = (existingChild) 412 | ? existingChild.children 413 | : null; 414 | 415 | if (existingChildren) { 416 | state._children = existingChildren; 417 | } 418 | 419 | } 420 | 421 | this.children[state.localName] = state; 422 | state.parent = this; 423 | this.needsInit = true; 424 | return state; 425 | }; 426 | 427 | 428 | //********************************************* 429 | // updateChild 430 | //********************************************* 431 | //this updates properties of the child in place (i.e. doesn't wipe out its children) 432 | //nor does it start with a fresh state, so properties not overwritten are maintained 433 | //however, if no existing state, a new one is created 434 | StateBase.prototype.updateChild = function(stateDef) { 435 | var state = this.getChild(stateDef.localName); 436 | if (!state) { 437 | // deep doesn't really matter since this will be a new state, but 438 | // for form it's set to true 439 | return this.setChild(stateDef, true); 440 | } 441 | else { 442 | common.extend(state, stateDef); 443 | 444 | return this.setChildState(state, false); 445 | } 446 | }; 447 | 448 | //********************************************* 449 | // prepareFlatDefinition 450 | //********************************************* 451 | StateBase.prototype.prepFlatGetParent = function(stateDef) { 452 | var parent, localName; 453 | if (stateDef.parent) { 454 | parent = this.getState(stateDef.parent); 455 | localName = stateDef.fullName; 456 | } 457 | else 458 | { 459 | var fullName = stateDef.fullName 460 | ? stateDef.fullName 461 | : stateDef.name; 462 | 463 | var parts = /^(.*?)\.?([^\.]*)$/.exec(fullName); 464 | 465 | var parentName = parts[1]; 466 | localName = parts[2]; 467 | 468 | parent = parentName 469 | ? this.getState(parentName) 470 | : this.root; 471 | } 472 | 473 | stateDef.localName = localName; 474 | 475 | delete stateDef.name; 476 | delete stateDef.fullName; 477 | delete stateDef.parent; 478 | 479 | return parent; 480 | }; 481 | 482 | 483 | //********************************************* 484 | // setState 485 | //********************************************* 486 | //specify name/fullName in the definition to indicate 487 | //parent (which must already exist) -- for compatibility 488 | //with ui-router or other non-oo definition style 489 | StateBase.prototype.setState = function(stateDef, deep) { 490 | var parent = this.prepFlatGetParent(stateDef); 491 | 492 | return parent.setChild(stateDef, deep); 493 | }; 494 | 495 | //********************************************* 496 | // updateState 497 | //********************************************* 498 | //specify name/fullName in the definition to indicate 499 | //parent (which must already exist) -- for compatibility 500 | //with ui-router or other non-oo definition style 501 | StateBase.prototype.updateState = function(stateDef) { 502 | var parent = this.prepFlatGetParent(stateDef); 503 | 504 | return parent.updateChild(stateDef); 505 | }; 506 | 507 | //********************************************* 508 | // findState 509 | //********************************************* 510 | StateBase.prototype.findState = function(partialName) { 511 | var parts = /^([^\.]+)(\.(.*))?$/.exec(partialName); 512 | var firstPart = parts[1]; 513 | if (this.localName === firstPart) 514 | { 515 | //first part matches this node 516 | //grab the rest of the name 517 | var rest = parts[3]; 518 | if (rest) { 519 | return this.findStateChildren(rest); 520 | } 521 | else { 522 | //there is no 'rest' -- we've found the state 523 | return this; 524 | } 525 | } 526 | else { 527 | //this is not a path for this partialName 528 | return null; 529 | } 530 | }; 531 | 532 | //********************************************* 533 | // findStateChildren 534 | //********************************************* 535 | StateBase.prototype.findStateChildren = function(partialName) { 536 | if (this.children) { 537 | for (var child in this.children) { 538 | var found = this.children[child].findState(partialName); 539 | if (found) { 540 | return found; 541 | } 542 | } 543 | } 544 | //nothing was found 545 | return null; 546 | }; 547 | 548 | //********************************************* 549 | // getState 550 | //********************************************* 551 | StateBase.prototype.getState = function(state) { 552 | if (!common.isString(state)) { 553 | return this.root.findStateChildren(state.fullName); 554 | } 555 | else { 556 | return this.root.findStateChildren(state); 557 | } 558 | }; 559 | 560 | //********************************************* 561 | // JSON support 562 | //********************************************* 563 | StateBase.prototype.getIntJson = function(object, longPropertyName, shortPropertyName) { 564 | return object[shortPropertyName] 565 | ? parseInt(object[shortPropertyName], 10) 566 | : object[longPropertyName] 567 | ? parseInt(object[longPropertyName], 10) 568 | : null; 569 | }; 570 | 571 | StateBase.prototype.getObjJson = function(object, longPropertyName, shortPropertyName) { 572 | return object[shortPropertyName] 573 | ? object[shortPropertyName] 574 | : object[longPropertyName] 575 | ? object[longPropertyName] 576 | : null; 577 | }; 578 | 579 | StateBase.prototype.expandDefinition = function(definition) { 580 | this.expandJson(definition, 'url', 'u'); 581 | this.expandJson(definition, 'dependencies', 'd'); 582 | this.expandJson(definition, 'resolveByService', 'r'); 583 | this.expandJson(definition, 'templateService', 'i'); 584 | this.expandJson(definition, 'aliases', 's'); 585 | this.expandJson(definition, 'controller', 'c'); 586 | this.expandJson(definition, 'templateUrl', 't'); 587 | this.expandJson(definition, 'template', 'l'); 588 | this.expandJson(definition, 'data', 'a'); 589 | this.expandJson(definition, 'abstract', 'b'); 590 | this.expandJson(definition, 'views', 'v'); 591 | }; 592 | 593 | StateBase.prototype.expandView = function(view) { 594 | this.expandJson(view, 'url', 'u'); 595 | this.expandJson(view, 'resolveByService', 'r'); 596 | this.expandJson(view, 'templateService', 'i'); 597 | this.expandJson(view, 'controller', 'c'); 598 | this.expandJson(view, 'templateUrl', 't'); 599 | this.expandJson(view, 'template', 'l'); 600 | this.expandJson(view, 'data', 'a'); 601 | }; 602 | 603 | StateBase.prototype.expandJson = function(object, longPropertyName, shortPropertyName) { 604 | if (object[shortPropertyName]) { 605 | object[longPropertyName] = object[shortPropertyName]; 606 | delete object[shortPropertyName]; 607 | } 608 | }; 609 | 610 | StateBase.prototype.mergeChild = function(name, childJson) { 611 | //the name of the child we're working with 612 | var del = this.getObjJson(childJson, 'delete', 'x'); 613 | if (del) { 614 | this.removeChild(name); 615 | } 616 | else 617 | { 618 | var definition = this.getObjJson(childJson, 'definition', 'd'); 619 | if (definition) { 620 | //a definition is specified -- update child 621 | definition.localName = name; 622 | this.expandDefinition(definition); 623 | if (definition.views) { 624 | for (var viewName in definition.views) { 625 | var view = definition.views[viewName]; 626 | this.expandView(view); 627 | } 628 | } 629 | 630 | this.updateChild(definition); 631 | } 632 | 633 | var children = this.getObjJson(childJson, 'children', 'c'); 634 | if (children) { 635 | var thisChild = this.getChild(name); 636 | for (var grandchildName in children) { 637 | var grandChild = children[grandchildName]; 638 | thisChild.mergeChild(grandchildName, grandChild); 639 | } 640 | } 641 | } 642 | 643 | return true; 644 | }; 645 | 646 | Object.defineProperty(StateBase.prototype, 'knownStates', { 647 | get: function() { 648 | var summary = {}; 649 | 650 | if (Object.keys(this.children).length > 0) { 651 | var children = {}; 652 | for (var childName in this.children) { 653 | var child = this.children[childName]; 654 | children[child.localName] = child.knownStates; 655 | } 656 | summary = children; 657 | } 658 | 659 | return summary; 660 | } 661 | }); 662 | 663 | return StateBase; 664 | 665 | }); 666 | -------------------------------------------------------------------------------- /src/UrlMatcher.js: -------------------------------------------------------------------------------- 1 | define(['common'], function(common) { 2 | /** 3 | * Matches URLs against patterns and extracts named parameters from the path or the search 4 | * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list 5 | * of search parameters. Multiple search parameter names are separated by '&'. Search parameters 6 | * do not influence whether or not a URL is matched, but their values are passed through into 7 | * the matched parameters returned by {@link UrlMatcher#exec exec}. 8 | * 9 | * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace 10 | * syntax, which optionally allows a regular expression for the parameter to be specified: 11 | * 12 | * * ':' name - colon placeholder 13 | * * '*' name - catch-all placeholder 14 | * * '{' name '}' - curly placeholder 15 | * * '{' name ':' regexp '}' - curly placeholder with regexp. Should the regexp itself contain 16 | * curly braces, they must be in matched pairs or escaped with a backslash. 17 | * 18 | * Parameter names may contain only word characters (latin letters, digits, and underscore) and 19 | * must be unique within the pattern (across both path and search parameters). For colon 20 | * placeholders or curly placeholders without an explicit regexp, a path parameter matches any 21 | * number of characters other than '/'. For catch-all placeholders the path parameter matches 22 | * any number of characters. 23 | * 24 | * ### Examples 25 | * 26 | * * '/hello/' - Matches only if the path is exactly '/hello/'. There is no special treatment for 27 | * trailing slashes, and patterns have to match the entire path, not just a prefix. 28 | * * '/user/:id' - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or 29 | * '/user/bob/details'. The second path segment will be captured as the parameter 'id'. 30 | * * '/user/{id}' - Same as the previous example, but using curly brace syntax. 31 | * * '/user/{id:[^/]*}' - Same as the previous example. 32 | * * '/user/{id:[0-9a-fA-F]{1,8}}' - Similar to the previous example, but only matches if the id 33 | * parameter consists of 1 to 8 hex digits. 34 | * * '/files/{path:.*}' - Matches any URL starting with '/files/' and captures the rest of the 35 | * path into the parameter 'path'. 36 | * * '/files/*path' - ditto. 37 | * 38 | * @constructor 39 | * @param {string} pattern the pattern to compile into a matcher. 40 | * 41 | * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any 42 | * URL matching this matcher (i.e. any string for which {@link UrlMatcher#exec exec()} returns 43 | * non-null) will start with this prefix. 44 | */ 45 | function UrlMatcher(pattern) { 46 | 47 | // Find all placeholders and create a compiled pattern, using either classic or curly syntax: 48 | // '*' name 49 | // ':' name 50 | // '{' name '}' 51 | // '{' name ':' regexp '}' 52 | // The regular expression is somewhat complicated due to the need to allow curly braces 53 | // inside the regular expression. The placeholder regexp breaks down as follows: 54 | // ([:*])(\w+) classic placeholder ($1 / $2) 55 | // \{(\w+)(?:\:( ... ))?\} curly brace placeholder ($3) with optional regexp ... ($4) 56 | // (?: ... | ... | ... )+ the regexp consists of any number of atoms, an atom being either 57 | // [^{}\\]+ - anything other than curly braces or backslash 58 | // \\. - a backslash escape 59 | // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms 60 | var placeholder = /([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, 61 | names = {}, compiled = '^', last = 0, m, 62 | segments = this.segments = [], 63 | params = this.params = []; 64 | 65 | function addParameter(id) { 66 | if (!/^\w+$/.test(id)) { 67 | throw new Error('Invalid parameter name \'' + id + '\' in pattern \'' + pattern + '\''); 68 | } 69 | if (names[id]) { 70 | throw new Error('Duplicate parameter name \'' + id + '\' in pattern \'' + pattern + '\''); 71 | } 72 | names[id] = true; 73 | params.push(id); 74 | } 75 | 76 | function quoteRegExp(string) { 77 | return string.replace(/[\\\[\]\^$*+?.()|{}]/g, '\\$&'); 78 | } 79 | 80 | this.source = pattern; 81 | 82 | // Split into static segments separated by path parameter placeholders. 83 | // The number of segments is always 1 more than the number of parameters. 84 | var id, regexp, segment; 85 | while ((m = placeholder.exec(pattern))) { 86 | id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null 87 | regexp = m[4] || (m[1] === '*' ? '.*' : '[^/]*'); 88 | segment = pattern.substring(last, m.index); 89 | if (segment.indexOf('?') >= 0) { 90 | break; // we're into the search part 91 | } 92 | compiled += quoteRegExp(segment) + '(' + regexp + ')'; 93 | addParameter(id); 94 | segments.push(segment); 95 | last = placeholder.lastIndex; 96 | } 97 | segment = pattern.substring(last); 98 | 99 | // Find any search parameter names and remove them from the last segment 100 | var i = segment.indexOf('?'); 101 | if (i >= 0) { 102 | var search = this.sourceSearch = segment.substring(i); 103 | segment = segment.substring(0, i); 104 | this.sourcePath = pattern.substring(0, last+i); 105 | 106 | // Allow parameters to be separated by '?' as well as '&' to make concat() easier 107 | common.forEach(search.substring(1).split(/[&?]/), addParameter); 108 | } else { 109 | this.sourcePath = pattern; 110 | this.sourceSearch = ''; 111 | } 112 | 113 | compiled += quoteRegExp(segment) + '$'; 114 | segments.push(segment); 115 | this.regexp = new RegExp(compiled); 116 | this.prefix = segments[0]; 117 | } 118 | 119 | /** 120 | * Returns a new matcher for a pattern constructed by appending the path part and adding the 121 | * search parameters of the specified pattern to this pattern. The current pattern is not 122 | * modified. This can be understood as creating a pattern for URLs that are relative to (or 123 | * suffixes of) the current pattern. 124 | * 125 | * ### Example 126 | * The following two matchers are equivalent: 127 | * ``` 128 | * new UrlMatcher('/user/{id}?q').concat('/details?date'); 129 | * new UrlMatcher('/user/{id}/details?q&date'); 130 | * ``` 131 | * 132 | * @param {string} pattern The pattern to append. 133 | * @return {UrlMatcher} A matcher for the concatenated pattern. 134 | */ 135 | UrlMatcher.prototype.concat = function (pattern) { 136 | // Because order of search parameters is irrelevant, we can add our own search 137 | // parameters to the end of the new pattern. Parse the new pattern by itself 138 | // and then join the bits together, but it's much easier to do this on a string level. 139 | return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch); 140 | }; 141 | 142 | UrlMatcher.prototype.toString = function () { 143 | return this.source; 144 | }; 145 | 146 | /** 147 | * Tests the specified path against this matcher, and returns an object containing the captured 148 | * parameter values, or null if the path does not match. The returned object contains the values 149 | * of any search parameters that are mentioned in the pattern, but their value may be null if 150 | * they are not present in `searchParams`. This means that search parameters are always treated 151 | * as optional. 152 | * 153 | * ### Example 154 | * ``` 155 | * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', { x:'1', q:'hello' }); 156 | * // returns { id:'bob', q:'hello', r:null } 157 | * ``` 158 | * 159 | * @param {string} path The URL path to match, e.g. `$location.path()`. 160 | * @param {Object} searchParams URL search parameters, e.g. `$location.search()`. 161 | * @return {Object} The captured parameter values. 162 | */ 163 | UrlMatcher.prototype.exec = function (path, searchParams) { 164 | var m = this.regexp.exec(path); 165 | if (!m) { 166 | return null; 167 | } 168 | 169 | var params = this.params, nTotal = params.length, 170 | nPath = this.segments.length-1, 171 | values = {}, i; 172 | 173 | for (i=0; i} An array of parameter names. Must be treated as read-only. If the 186 | * pattern has no parameters, an empty array is returned. 187 | */ 188 | UrlMatcher.prototype.parameters = function () { 189 | return this.params; 190 | }; 191 | 192 | /** 193 | * Creates a URL that matches this pattern by substituting the specified values 194 | * for the path and search parameters. Null values for path parameters are 195 | * treated as empty strings. 196 | * 197 | * ### Example 198 | * ``` 199 | * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' }); 200 | * // returns '/user/bob?q=yes' 201 | * ``` 202 | * 203 | * @param {Object} values the values to substitute for the parameters in this pattern. 204 | * @return {string} the formatted URL (path and optionally search part). 205 | */ 206 | UrlMatcher.prototype.format = function (values) { 207 | var segments = this.segments, params = this.params; 208 | if (!values) { 209 | return segments.join(''); 210 | } 211 | 212 | var nPath = segments.length-1, nTotal = params.length, 213 | result = segments[0], i, search, value; 214 | 215 | for (i=0; i nodeKey) { 94 | return searchNode(node.rightChild, key); 95 | } else { // key is equal to node key 96 | return node.value; 97 | } 98 | }, 99 | 100 | /* 101 | * Private Method: insertNode 102 | * 103 | * Insert into a binary tree. 104 | * 105 | * Parameters: 106 | * node - the node to search on. 107 | * key - the key to insert (as an integer). 108 | * value - the value to associate with the key (any type of 109 | * object). 110 | * 111 | * Returns: 112 | * true. 113 | * 114 | */ 115 | insertNode = function (node, key, value, parent) { 116 | if (node.key === null) { 117 | node.leftChild = new Node(); 118 | node.key = key; 119 | node.value = value; 120 | node.rightChild = new Node(); 121 | node.parent = parent; 122 | return true; 123 | } 124 | 125 | var nodeKey = parseInt(node.key, 10); 126 | 127 | if (key < nodeKey) { 128 | insertNode(node.leftChild, key, value, node); 129 | } else if (key > nodeKey) { 130 | insertNode(node.rightChild, key, value, node); 131 | } else { // key is equal to node key, update the value 132 | node.value = value; 133 | return true; 134 | } 135 | }, 136 | 137 | /* 138 | * Private Method: traverseNode 139 | * 140 | * Call a function on each node of a binary tree. 141 | * 142 | * Parameters: 143 | * node - the node to traverse. 144 | * callback - the function to call on each node, this function 145 | * takes a key and a value as parameters. 146 | * 147 | * Returns: 148 | * true. 149 | * 150 | */ 151 | traverseNode = function (node, callback) { 152 | if (node.key !== null) { 153 | traverseNode(node.leftChild, callback); 154 | callback(node.key, node.value); 155 | traverseNode(node.rightChild, callback); 156 | } 157 | 158 | return true; 159 | }, 160 | 161 | /* 162 | * Private Method: minNode 163 | * 164 | * Find the key of the node with the lowest key number. 165 | * 166 | * Parameters: 167 | * node - the node to traverse. 168 | * 169 | * Returns: the key of the node with the lowest key number. 170 | * 171 | */ 172 | minNode = function (node) { 173 | while (node.leftChild.key !== null) { 174 | node = node.leftChild; 175 | } 176 | 177 | return node.key; 178 | }, 179 | 180 | /* 181 | * Private Method: maxNode 182 | * 183 | * Find the key of the node with the highest key number. 184 | * 185 | * Parameters: 186 | * node - the node to traverse. 187 | * 188 | * Returns: the key of the node with the highest key number. 189 | * 190 | */ 191 | maxNode = function (node) { 192 | while (node.rightChild.key !== null) { 193 | node = node.rightChild; 194 | } 195 | 196 | return node.key; 197 | }, 198 | 199 | /* 200 | * Private Method: successorNode 201 | * 202 | * Find the key that successes the given node. 203 | * 204 | * Parameters: 205 | * node - the node to find the successor for 206 | * 207 | * Returns: the key of the node that successes the given node. 208 | * 209 | */ 210 | successorNode = function (node) { 211 | var parent; 212 | 213 | if (node.rightChild.key !== null) { 214 | return minNode(node.rightChild); 215 | } 216 | 217 | parent = node.parent; 218 | while (parent.key !== null && node == parent.rightChild) { 219 | node = parent; 220 | parent = parent.parent; 221 | } 222 | 223 | return parent.key; 224 | }, 225 | 226 | /* 227 | * Private Method: predecessorNode 228 | * 229 | * Find the key that preceeds the given node. 230 | * 231 | * Parameters: 232 | * node - the node to find the predecessor for 233 | * 234 | * Returns: the key of the node that preceeds the given node. 235 | * 236 | */ 237 | predecessorNode = function (node) { 238 | var parent; 239 | 240 | if (node.leftChild.key !== null) { 241 | return maxNode(node.leftChild); 242 | } 243 | 244 | parent = node.parent; 245 | while (parent.key !== null && node == parent.leftChild) { 246 | node = parent; 247 | parent = parent.parent; 248 | } 249 | 250 | return parent.key; 251 | }; 252 | 253 | return { 254 | /* 255 | * Method: search 256 | * 257 | * Search through a binary tree. 258 | * 259 | * Parameters: 260 | * key - the key to search for. 261 | * 262 | * Returns: 263 | * the value of the found node, 264 | * or null if no node was found, 265 | * or undefined if no key was specified. 266 | * 267 | */ 268 | search: function (key) { 269 | var keyInt = parseInt(key, 10); 270 | 271 | if (isNaN(keyInt)) { 272 | return undefined; // key must be a number 273 | } else { 274 | return searchNode(root, keyInt); 275 | } 276 | }, 277 | 278 | /* 279 | * Method: insert 280 | * 281 | * Insert into a binary tree. 282 | * 283 | * Parameters: 284 | * key - the key to search for. 285 | * value - the value to associate with the key (any type of 286 | * object). 287 | * 288 | * Returns: 289 | * true, 290 | * or undefined if no key was specified. 291 | * 292 | */ 293 | insert: function (key, value) { 294 | var keyInt = parseInt(key, 10); 295 | 296 | if (isNaN(keyInt)) { 297 | return undefined; // key must be a number 298 | } else { 299 | return insertNode(root, keyInt, value, null); 300 | } 301 | }, 302 | 303 | /* 304 | * Method: traverse 305 | * 306 | * Call a function on each node of a binary tree. 307 | * 308 | * Parameters: 309 | * callback - the function to call on each node, this function 310 | * takes a key and a value as parameters. If no 311 | * callback is specified, print is called. 312 | * 313 | * Returns: 314 | * true. 315 | * 316 | */ 317 | traverse: function (callback) { 318 | if (typeof callback === "undefined") { 319 | callback = function (key, value) { 320 | print(key + ": " + value); 321 | }; 322 | } 323 | 324 | return traverseNode(root, callback); 325 | }, 326 | 327 | /* 328 | * Method: min 329 | * 330 | * Find the key of the node with the lowest key number. 331 | * 332 | * Parameters: none 333 | * 334 | * Returns: the key of the node with the lowest key number. 335 | * 336 | */ 337 | min: function () { 338 | return minNode(root); 339 | }, 340 | 341 | /* 342 | * Method: max 343 | * 344 | * Find the key of the node with the highest key number. 345 | * 346 | * Parameters: none 347 | * 348 | * Returns: the key of the node with the highest key number. 349 | * 350 | */ 351 | max: function () { 352 | return maxNode(root); 353 | }, 354 | 355 | /* 356 | * Method: successor 357 | * 358 | * Find the key that successes the root node. 359 | * 360 | * Parameters: none 361 | * 362 | * Returns: the key of the node that successes the root node. 363 | * 364 | */ 365 | successor: function () { 366 | return successorNode(root); 367 | }, 368 | 369 | /* 370 | * Method: predecessor 371 | * 372 | * Find the key that preceeds the root node. 373 | * 374 | * Parameters: none 375 | * 376 | * Returns: the key of the node that preceeds the root node. 377 | * 378 | */ 379 | predecessor: function () { 380 | return predecessorNode(root); 381 | } 382 | }; 383 | }; 384 | 385 | return BST; 386 | }); 387 | -------------------------------------------------------------------------------- /src/common.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | function Common() { 3 | this.isDefined = function(value){return typeof value !== 'undefined';}; 4 | function isFunction(value){return typeof value === 'function';} 5 | this.isFunction = isFunction; 6 | 7 | this.isString = function(value){return typeof value === 'string';}; 8 | this.isObject = function(value){return value != null && typeof value === 'object';}; 9 | this.isArray = function(value){return toString.apply(value) === '[object Array]';}; 10 | function isArrayLike(obj) { 11 | if (!obj || (typeof obj.length !== 'number')) { 12 | return false; 13 | } 14 | 15 | // We have on object which has length property. Should we treat it as array? 16 | if (typeof obj.hasOwnProperty !== 'function' && 17 | typeof obj.constructor !== 'function') { 18 | // This is here for IE8: it is a bogus object treat it as array; 19 | return true; 20 | } else { 21 | return typeof obj === 'JQLite' || // JQLite 22 | (typeof jQuery !== 'undefined' && typeof obj === 'jQuery') || // jQuery 23 | toString.call(obj) !== '[object Object]' || // some browser native object 24 | typeof obj.callee === 'function'; // arguments (on IE8 looks like regular obj) 25 | } 26 | } 27 | this.isArrayLike = isArrayLike; 28 | 29 | function forEach(obj, iterator, context) { 30 | var key; 31 | if (obj) { 32 | if (isFunction(obj)){ 33 | for (key in obj) { 34 | if (key !== 'prototype' && key !== 'length' && key !== 'name' && obj.hasOwnProperty(key)) { 35 | iterator.call(context, obj[key], key); 36 | } 37 | } 38 | } else if (obj.forEach && obj.forEach !== forEach) { 39 | obj.forEach(iterator, context); 40 | } else if (isArrayLike(obj)) { 41 | for (key = 0; key < obj.length; key++) { 42 | iterator.call(context, obj[key], key); 43 | } 44 | } else { 45 | for (key in obj) { 46 | if (obj.hasOwnProperty(key)) { 47 | iterator.call(context, obj[key], key); 48 | } 49 | } 50 | } 51 | } 52 | return obj; 53 | } 54 | this.forEach = forEach; 55 | 56 | function setHashKey(obj, h) { 57 | if (h) { 58 | obj.$$hashKey = h; 59 | } 60 | else { 61 | delete obj.$$hashKey; 62 | } 63 | } 64 | this.setHashKey = setHashKey; 65 | 66 | function extend(dst) { 67 | var h = dst.$$hashKey; 68 | forEach(arguments, function(obj){ 69 | if (obj !== dst) { 70 | forEach(obj, function(value, key){ 71 | dst[key] = value; 72 | }); 73 | } 74 | }); 75 | 76 | setHashKey(dst,h); 77 | return dst; 78 | } 79 | this.extend = extend; 80 | 81 | this.inherit = function(parent, extra) { 82 | return extend(new (extend(function() {}, { prototype: parent }))(), extra); 83 | }; 84 | 85 | function deepMerge(target, src) { 86 | var array = Array.isArray(src); 87 | var dst = array && [] || {}; 88 | 89 | if (array) { 90 | target = target || []; 91 | dst = dst.concat(target); 92 | forEach(src, function(e, i) { 93 | if (typeof e === 'object') { 94 | dst[i] = deepMerge(target[i], e); 95 | } else { 96 | if (target.indexOf(e) === -1) { 97 | dst.push(e); 98 | } 99 | } 100 | }); 101 | } else { 102 | if (target && typeof target === 'object') { 103 | forEach(Object.keys(target), function (key) { 104 | dst[key] = target[key]; 105 | }); 106 | } 107 | forEach(Object.keys(src), function (key) { 108 | if (typeof src[key] !== 'object' || !src[key]) { 109 | dst[key] = src[key]; 110 | } 111 | else { 112 | if (!target[key]) { 113 | dst[key] = src[key]; 114 | } else { 115 | dst[key] = deepMerge(target[key], src[key]); 116 | } 117 | } 118 | }); 119 | } 120 | 121 | return dst; 122 | } 123 | this.deepMerge = deepMerge; 124 | 125 | this.merge = function(dst) { 126 | forEach(arguments, function(obj) { 127 | if (obj !== dst) { 128 | forEach(obj, function(value, key) { 129 | if (!dst.hasOwnProperty(key)) { 130 | dst[key] = value; 131 | } 132 | }); 133 | } 134 | }); 135 | return dst; 136 | }; 137 | 138 | } 139 | 140 | return new Common(); 141 | }); 142 | -------------------------------------------------------------------------------- /src/detourModule.js: -------------------------------------------------------------------------------- 1 | define(['couchPotato'], function() { 2 | 'use strict'; 3 | return angular.module('agt.detour', ['scs.couch-potato']); 4 | }); 5 | -------------------------------------------------------------------------------- /src/getRoute.txt: -------------------------------------------------------------------------------- 1 | getRoute: 2 | 3 | updates: 4 | if there is a non-null, non-undefined serial number then updates will be included 5 | 6 | updates include: 7 | all non-lazy routes that have a serial higher than the one in the summary 8 | deletes of any routes in the summary which no longer exist 9 | all lazy routes that appear in the summary and that have a serial higher than the one in the summary 10 | 11 | the route itself: 12 | return the route 13 | 14 | for ancestors, return the definitions *if* 15 | a) they are lazy and not in the summary or 16 | b) they have a serial higher than the one in the summary 17 | -------------------------------------------------------------------------------- /src/templateFactory.js: -------------------------------------------------------------------------------- 1 | define(['./detourModule'], function() { 2 | /** 3 | * Service. Manages loading of templates. 4 | * @constructor 5 | * @name $templateFactory 6 | * @requires $http 7 | * @requires $templateCache 8 | * @requires $injector 9 | */ 10 | function $TemplateFactory( $http, $templateCache, $injector) { 11 | 12 | /** 13 | * Creates a template from a configuration object. 14 | * @function 15 | * @name $templateFactory#fromConfig 16 | * @methodOf $templateFactory 17 | * @param {Object} config Configuration object for which to load a template. The following 18 | * properties are search in the specified order, and the first one that is defined is 19 | * used to create the template: 20 | * @param {string|Function} config.template html string template or function to load via 21 | * {@link $templateFactory#fromString fromString}. 22 | * @param {string|Function} config.templateUrl url to load or a function returning the url 23 | * to load via {@link $templateFactory#fromUrl fromUrl}. 24 | * @param {Function} config.templateProvider function to invoke via 25 | * {@link $templateFactory#fromProvider fromProvider}. 26 | * @param {Object} params Parameters to pass to the template function. 27 | * @param {Object} [locals] Locals to pass to `invoke` if the template is loaded via a 28 | * `templateProvider`. Defaults to `{ params: params }`. 29 | * @return {string|Promise.} The template html as a string, or a promise for that string, 30 | * or `null` if no template is configured. 31 | */ 32 | this.fromConfig = function (config, params, locals) { 33 | return ( 34 | angular.isDefined(config.template) ? this.fromString(config.template, params) : 35 | angular.isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) : 36 | angular.isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) : 37 | angular.isDefined(config.templateService) ? this.fromService(config.templateService, params, locals) : 38 | null 39 | ); 40 | }; 41 | 42 | /** 43 | * Creates a template from a string or a function returning a string. 44 | * @function 45 | * @name $templateFactory#fromString 46 | * @methodOf $templateFactory 47 | * @param {string|Function} template html template as a string or function that returns an html 48 | * template as a string. 49 | * @param {Object} params Parameters to pass to the template function. 50 | * @return {string|Promise.} The template html as a string, or a promise for that string. 51 | */ 52 | this.fromString = function (template, params) { 53 | return angular.isFunction(template) ? template(params) : template; 54 | }; 55 | 56 | /** 57 | * Loads a template from the a URL via `$http` and `$templateCache`. 58 | * @function 59 | * @name $templateFactory#fromUrl 60 | * @methodOf $templateFactory 61 | * @param {string|Function} url url of the template to load, or a function that returns a url. 62 | * @param {Object} params Parameters to pass to the url function. 63 | * @return {string|Promise.} The template html as a string, or a promise for that string. 64 | */ 65 | this.fromUrl = function (url, params) { 66 | if (angular.isFunction(url)) { 67 | url = url(params); 68 | } 69 | if (url == null) { 70 | return null; 71 | } 72 | else { 73 | return $http 74 | .get(url, { cache: $templateCache }) 75 | .then(function(response) { return response.data; }); 76 | } 77 | }; 78 | 79 | /** 80 | * Creates a template by invoking an injectable provider function. 81 | * @function 82 | * @name $templateFactory#fromProvider 83 | * @methodOf $templateFactory 84 | * @param {Function} provider Function to invoke via `$injector.invoke` 85 | * @param {Object} params Parameters for the template. 86 | * @param {Object} [locals] Locals to pass to `invoke`. Defaults to `{ params: params }`. 87 | * @return {string|Promise.} The template html as a string, or a promise for that string. 88 | */ 89 | this.fromProvider = function (provider, params, locals) { 90 | return $injector.invoke(provider, null, locals || { params: params }); 91 | }; 92 | 93 | /** 94 | * Creates a template by invoking a service. 95 | * @function 96 | * @name $templateFactory#fromService 97 | * @methodOf $templateFactory 98 | * @param {Function} serviceName Service to invoke via `$injector.invoke` 99 | * @param {Object} params Parameters for the template. 100 | * @param {Object} [locals] Locals to pass to `invoke`. Defaults to `{ params: params }`. 101 | * @return {string|Promise.} The template html as a string, or a promise for that string. 102 | */ 103 | this.fromService = function (serviceName, params, locals) { 104 | return $injector.invoke([serviceName, function(service) { return service.getTemplate(params, locals); }]); 105 | }; 106 | 107 | } 108 | $TemplateFactory.$inject = ['$http', '$templateCache', '$injector']; 109 | 110 | angular.module('agt.detour').service('$templateFactory', $TemplateFactory); 111 | 112 | }); 113 | -------------------------------------------------------------------------------- /src/viewDirective.js: -------------------------------------------------------------------------------- 1 | define(['./common', './detourModule', './detourProvider'], function(common) { 2 | function $ViewDirective( $detour, $compile, $controller, $injector, $anchorScroll) { 3 | // Unfortunately there is no neat way to ask $injector if a service exists 4 | var $animator; try { $animator = $injector.get('$animator'); } catch (e) { /* do nothing */ } 5 | 6 | var directive = { 7 | restrict: 'ECA', 8 | terminal: true, 9 | link: function(scope, element, attr) { 10 | var viewScope, viewLocals, 11 | initialContent = element.contents(), 12 | name = attr[directive.name] || attr.name || '', 13 | onloadExp = attr.onload || '', 14 | animate = common.isDefined($animator) && $animator(scope, attr); 15 | 16 | function updateView(doAnimate) { 17 | var locals = $detour.$current && $detour.$current.locals[name]; 18 | if (locals === viewLocals) { 19 | return; // nothing to do 20 | } 21 | 22 | // Remove existing content 23 | if (animate && doAnimate) { 24 | animate.leave(element.contents(), element); 25 | } else { 26 | element.html(''); 27 | } 28 | 29 | // Destroy previous view scope 30 | if (viewScope) { 31 | viewScope.$destroy(); 32 | viewScope = null; 33 | } 34 | 35 | if (locals) { 36 | viewLocals = locals; 37 | view.state = locals.$$state; 38 | 39 | var contents; 40 | if (animate && doAnimate) { 41 | contents = angular.element('
').html(locals.$template).contents(); 42 | animate.enter(contents, element); 43 | } else { 44 | element.html(locals.$template); 45 | contents = element.contents(); 46 | } 47 | 48 | var link = $compile(contents); 49 | viewScope = scope.$new(); 50 | if (locals.$$controller) { 51 | locals.$scope = viewScope; 52 | var controller = $controller(locals.$$controller, locals); 53 | element.children().data('$ngControllerController', controller); 54 | } 55 | link(viewScope); 56 | viewScope.$emit('$viewContentLoaded'); 57 | viewScope.$eval(onloadExp); 58 | 59 | // TODO: This seems strange, shouldn't $anchorScroll listen for $viewContentLoaded if necessary? 60 | // $anchorScroll might listen on event... 61 | $anchorScroll(); 62 | } else { 63 | viewLocals = null; 64 | view.state = null; 65 | 66 | // Restore initial view 67 | if (doAnimate) { 68 | animate.enter(initialContent, element); 69 | } else { 70 | element.html(initialContent); 71 | } 72 | } 73 | } 74 | 75 | // Find the details of the parent view directive (if any) and use it 76 | // to derive our own qualified view name, then hang our own details 77 | // off the DOM so child directives can find it. 78 | var parent = element.parent().inheritedData('$uiView'); 79 | if (name.indexOf('@') < 0) { 80 | name = name + '@' + (parent ? parent.state.name : ''); 81 | } 82 | var view = { name: name, state: null }; 83 | element.data('$uiView', view); 84 | 85 | scope.$on('$stateChangeSuccess', function() { updateView(true); }); 86 | updateView(false); 87 | } 88 | }; 89 | return directive; 90 | } 91 | $ViewDirective.$inject = ['$detour', '$compile', '$controller', '$injector', '$anchorScroll']; 92 | 93 | angular.module('agt.detour').directive('uiView', $ViewDirective); 94 | 95 | }); 96 | --------------------------------------------------------------------------------