From 322c85bf8bdf39ad785ea919820ebb6591925a96 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 10 Sep 2013 14:42:31 -0500 Subject: [PATCH 01/21] add flag in .transitionTo() to allow ability to disable broadcast of $stateChangeSuccess --- src/state.js | 8 ++++++-- test/stateSpec.js | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/state.js b/src/state.js index 8020876a6..9e5085f78 100644 --- a/src/state.js +++ b/src/state.js @@ -215,7 +215,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ $state.transitionTo = function transitionTo(to, toParams, options) { if (!isDefined(options)) options = (options === true || options === false) ? { location: options } : {}; toParams = toParams || {}; - options = extend({ location: true, inherit: false, relative: null }, options); + options = extend({ location: true, inherit: false, relative: null, broadcastStateChangeSuccess : true }, options); var toState = findState(to, options.relative); @@ -274,6 +274,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ var transition = $state.transition = resolved.then(function () { var l, entering, exiting; + if ($state.transition !== transition) return TransitionSuperseded; // Exit 'from' states not kept @@ -307,7 +308,10 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ $location.url(toNav.url.format(toNav.locals.globals.$stateParams)); } - $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams); + if(options.broadcastStateChangeSuccess){ + $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams); + } + return $state.current; }, function (error) { diff --git a/test/stateSpec.js b/test/stateSpec.js index 02b180a96..cc76e6cc8 100644 --- a/test/stateSpec.js +++ b/test/stateSpec.js @@ -157,6 +157,32 @@ describe('state', function () { expect($state.current).toBe(D); })); + + it('does not trigger $stateChangeSuccess when flag prevents, but still transitions when different state', inject(function ($state, $q, $rootScope) { + initStateTo(E, { i: 'iii' }); + var called; + $rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) { + called = true; + }); + $state.transitionTo(D, { x: '1', y: '2' }, {broadcastStateChangeSuccess : false}); + $q.flush(); + expect(called).toBeFalsy(); + expect($state.current).toBe(D); + })); + + it('does not trigger $stateChangeSuccess when flag prevents, yet still updates params', inject(function ($state, $q, $rootScope) { + initStateTo(E, { x: 'iii' }); + var called; + $rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) { + called = true; + }); + $state.transitionTo(E, { i: '1', y: '2' }, {broadcastStateChangeSuccess : false}); + $q.flush(); + expect(called).toBeFalsy(); + expect($state.params.i).toBe('1'); + expect($state.current).toBe(E); + })); + it('is a no-op when passing the current state and identical parameters', inject(function ($state, $q) { initStateTo(A); var trans = $state.transitionTo(A, {}); // no-op From 8518db23edb0cf1b7b818457ce176fde3ffc6a13 Mon Sep 17 00:00:00 2001 From: "Todd H. Gardner" Date: Thu, 19 Sep 2013 09:53:10 -0500 Subject: [PATCH 02/21] can register a type --- sample/states.js | 15 +++++++++++++++ src/state.js | 7 +++++++ src/urlMatcherFactory.js | 12 ++++++++++++ test/urlMatcherFactorySpec.js | 9 +++++++++ 4 files changed, 43 insertions(+) diff --git a/sample/states.js b/sample/states.js index 45e57dd83..bc78f6ee3 100644 --- a/sample/states.js +++ b/sample/states.js @@ -27,6 +27,21 @@ angular.module('uiRouterSample') // Use $stateProvider to configure your states. $stateProvider + ///////////////////// + // Parameter Types // + ///////////////////// + .registerType("date", { + equals: function (typeObj, otherObj) { + return typeObj.toISOString() === otherObj.toISOString(); + }, + decode: function (typeObj) { + return typeObj.toISOString(); + }, + encode: function (value) { + return new Date(value); + } + }) + ////////// // Home // ////////// diff --git a/src/state.js b/src/state.js index beb9f8f05..a88f7afbd 100644 --- a/src/state.js +++ b/src/state.js @@ -107,6 +107,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ $delegates: {} }; + // Create a proxy through to the Type registration on the UrlMatcherFactory + this.isTypeRegistered = $urlMatcherFactory.isTypeRegistered; + this.registerType = function (name, handler) { + $urlMatcherFactory.registerType(name, handler); + return this; + } + function isRelative(stateName) { return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0; } diff --git a/src/urlMatcherFactory.js b/src/urlMatcherFactory.js index ec78c522a..6b4f04a12 100644 --- a/src/urlMatcherFactory.js +++ b/src/urlMatcherFactory.js @@ -247,6 +247,18 @@ function $UrlMatcherFactory() { return isObject(o) && isFunction(o.exec) && isFunction(o.format) && isFunction(o.concat); }; + var typeRegistrar = {}; + this.registerType = function (name, handler) { + if (!isString(name) || !isObject(handler) || !isFunction(handler.equals) || !isFunction(handler.decode) || !isFunction(handler.encode)) { + throw new Error("Invalid type '" + name + "'"); + } + typeRegistrar[name] = handler; + }; + + this.isTypeRegistered = function (name) { + return isDefined(typeRegistrar[name]); + }; + this.$get = function () { return this; }; diff --git a/test/urlMatcherFactorySpec.js b/test/urlMatcherFactorySpec.js index b16ea4dbb..259fdc2ee 100644 --- a/test/urlMatcherFactorySpec.js +++ b/test/urlMatcherFactorySpec.js @@ -132,4 +132,13 @@ describe("urlMatcherFactory", function () { it("recognizes matchers", function () { expect($umf.isMatcher(new UrlMatcher('/'))).toBe(true); }); + + it("registers types", function () { + $umf.registerType("test", { + equals: function (typeObj, otherObj) {}, + decode: function (typeObj) {}, + encode: function (value) {} + }); + expect($umf.isTypeRegistered("test")).toBe(true); + }); }); From 5220d5c0661fd1a9d873c44f70b2d67989852bb0 Mon Sep 17 00:00:00 2001 From: "Todd H. Gardner" Date: Thu, 19 Sep 2013 11:41:32 -0500 Subject: [PATCH 03/21] UrlMatcher looks for types in the Url and generates a typeMap between params and known types. Exec will run any found types through the decode function --- src/urlMatcherFactory.js | 50 ++++++++++++++++++++++++++--------- test/urlMatcherFactorySpec.js | 23 ++++++++++++++-- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/urlMatcherFactory.js b/src/urlMatcherFactory.js index 6b4f04a12..9fcdb87f5 100644 --- a/src/urlMatcherFactory.js +++ b/src/urlMatcherFactory.js @@ -59,7 +59,8 @@ function UrlMatcher(pattern) { var placeholder = /([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, names = {}, compiled = '^', last = 0, m, segments = this.segments = [], - params = this.params = []; + params = this.params = [], + typeMap = this.typeMap = {}; function addParameter(id) { if (!/^\w+(-+\w+)*$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'"); @@ -80,6 +81,10 @@ function UrlMatcher(pattern) { while ((m = placeholder.exec(pattern))) { id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null regexp = m[4] || (m[1] == '*' ? '.*' : '[^/]*'); + if (isDefined(this.types[regexp])) { + this.typeMap[id] = regexp; + regexp = '[^/]*'; + } segment = pattern.substring(last, m.index); if (segment.indexOf('?') >= 0) break; // we're into the search part compiled += quoteRegExp(segment) + '(' + regexp + ')'; @@ -154,7 +159,10 @@ UrlMatcher.prototype.toString = function () { * @return {Object} The captured parameter values. */ UrlMatcher.prototype.exec = function (path, searchParams) { - var m = this.regexp.exec(path); + var m = this.regexp.exec(path), + types = this.types, + typeMap = this.typeMap; + if (!m) return null; var params = this.params, nTotal = params.length, @@ -166,6 +174,12 @@ UrlMatcher.prototype.exec = function (path, searchParams) { for (i=0; i Date: Fri, 20 Sep 2013 14:23:43 -0500 Subject: [PATCH 04/21] format decodes typed values. removed normalization that was forcing back to strings --- sample/states.js | 4 +-- src/state.js | 9 ++++--- src/urlMatcherFactory.js | 46 ++++++++++++++++++++++++++++++----- test/stateDirectivesSpec.js | 12 ++++----- test/stateSpec.js | 10 ++++---- test/urlMatcherFactorySpec.js | 28 +++++++++++++++------ 6 files changed, 78 insertions(+), 31 deletions(-) diff --git a/sample/states.js b/sample/states.js index bc78f6ee3..94d6e7261 100644 --- a/sample/states.js +++ b/sample/states.js @@ -30,7 +30,7 @@ angular.module('uiRouterSample') ///////////////////// // Parameter Types // ///////////////////// - .registerType("date", { + .type("date", { equals: function (typeObj, otherObj) { return typeObj.toISOString() === otherObj.toISOString(); }, @@ -154,7 +154,7 @@ angular.module('uiRouterSample') // So its url will end up being '/contacts/{contactId:[0-9]{1,8}}'. When the // url becomes something like '/contacts/42' then this state becomes active // and the $stateParams object becomes { contactId: 42 }. - url: '/{contactId:[0-9]{1,4}}', + url: '/{contactId:integer}', // If there is more than a single ui-view in the parent template, or you would // like to target a ui-view from even higher up the state tree, you can use the diff --git a/src/state.js b/src/state.js index a88f7afbd..1c416ea0c 100644 --- a/src/state.js +++ b/src/state.js @@ -109,10 +109,10 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ // Create a proxy through to the Type registration on the UrlMatcherFactory this.isTypeRegistered = $urlMatcherFactory.isTypeRegistered; - this.registerType = function (name, handler) { - $urlMatcherFactory.registerType(name, handler); + this.type = function (name, handler) { + $urlMatcherFactory.type(name, handler); return this; - } + }; function isRelative(stateName) { return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0; @@ -463,7 +463,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ forEach(keys, function (name) { var value = values[name]; - normalized[name] = (value != null) ? String(value) : null; + //normalized[name] = (value != null) ? String(value) : null; + normalized[name] = (value != null) ? value : null; }); return normalized; } diff --git a/src/urlMatcherFactory.js b/src/urlMatcherFactory.js index 9fcdb87f5..c98f5921b 100644 --- a/src/urlMatcherFactory.js +++ b/src/urlMatcherFactory.js @@ -174,13 +174,17 @@ UrlMatcher.prototype.exec = function (path, searchParams) { for (i=0; i Date: Fri, 20 Sep 2013 20:22:37 -0400 Subject: [PATCH 05/21] Correctly detect left-click on IE8, fixes #452. --- src/stateDirectives.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/stateDirectives.js b/src/stateDirectives.js index da0c64668..00a57a580 100644 --- a/src/stateDirectives.js +++ b/src/stateDirectives.js @@ -44,7 +44,9 @@ function $StateRefDirective($state) { if (isForm) return; element.bind("click", function(e) { - if ((e.which == 1) && !e.ctrlKey && !e.metaKey && !e.shiftKey) { + var button = e.which || e.button; + + if ((button == 1) && !e.ctrlKey && !e.metaKey && !e.shiftKey) { $state.go(ref.state, params, { relative: base }); scope.$apply(); e.preventDefault(); From 4cdadcf46875698aee6c3684cc32f2a0ce553c45 Mon Sep 17 00:00:00 2001 From: Nate Abele Date: Fri, 20 Sep 2013 22:41:57 -0400 Subject: [PATCH 06/21] fix($resolve): resolve factories from string names $resolve now correctly executes factory functions when provided the name of the factory as a string. Closes #449 --- src/resolve.js | 2 +- test/resolveSpec.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/resolve.js b/src/resolve.js index 4f1a4cfcf..45cf6cf88 100644 --- a/src/resolve.js +++ b/src/resolve.js @@ -42,7 +42,7 @@ function $Resolve( $q, $injector) { visited[key] = VISIT_IN_PROGRESS; if (isString(value)) { - plan.push(key, [ function() { return $injector.get(key); }], NO_DEPENDENCIES); + plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES); } else { var params = $injector.annotate(value); forEach(params, function (param) { diff --git a/test/resolveSpec.js b/test/resolveSpec.js index d5c7d745d..fda9e0ecc 100644 --- a/test/resolveSpec.js +++ b/test/resolveSpec.js @@ -3,6 +3,12 @@ describe("resolve", function () { var $r, tick; beforeEach(module('ui.router.util')); + beforeEach(module(function($provide) { + $provide.factory('Foo', function() { + return "Working"; + }); + })); + beforeEach(inject(function($resolve, $q) { $r = $resolve; tick = $q.flush; @@ -292,6 +298,15 @@ describe("resolve", function () { r({ what: 'hi' }); expect(trace).toEqual([ 'a: 1', 'a: hi' ]); }); + + it("resolves values from string factory names", function () { + var result, r = $r.study({ foo: "Foo" })().then(function(values) { + result = values['foo']; + }); + tick(); + + expect(result).toBe("Working"); + }); }); }); From d3c6a5dea073b104b758c5d119660eea05e4628d Mon Sep 17 00:00:00 2001 From: "Todd H. Gardner" Date: Mon, 23 Sep 2013 11:50:19 -0500 Subject: [PATCH 07/21] added is and normalization to type --- src/state.js | 1 - src/urlMatcherFactory.js | 46 +++++++++++++++++++++++++++++--- test/urlMatcherFactorySpec.js | 49 +++++++++++++++++++++++++++++++---- 3 files changed, 87 insertions(+), 9 deletions(-) diff --git a/src/state.js b/src/state.js index 1c416ea0c..a1061bfa8 100644 --- a/src/state.js +++ b/src/state.js @@ -108,7 +108,6 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ }; // Create a proxy through to the Type registration on the UrlMatcherFactory - this.isTypeRegistered = $urlMatcherFactory.isTypeRegistered; this.type = function (name, handler) { $urlMatcherFactory.type(name, handler); return this; diff --git a/src/urlMatcherFactory.js b/src/urlMatcherFactory.js index c98f5921b..9eb16b7d7 100644 --- a/src/urlMatcherFactory.js +++ b/src/urlMatcherFactory.js @@ -249,8 +249,14 @@ UrlMatcher.prototype.format = function (values) { UrlMatcher.prototype.types = { 'boolean': { + is: function (typeObj) { + return (typeObj === true || typeObj === false); + }, equals: function (typeObj, otherObj) { - return typeObj === otherObj; + if (this.is(typeObj) && this.is(otherObj)) { + return typeObj === otherObj; + } + return false; }, encode: function (typeObj) { return typeObj.toString(); @@ -262,8 +268,15 @@ UrlMatcher.prototype.types = { } }, 'integer': { + is: function (typeObj) { + var regexp = /^\d+$/; + return !!regexp.exec(this.encode(typeObj)); + }, equals: function (typeObj, otherObj) { - return typeObj === otherObj; + if (this.is(typeObj) && this.is(otherObj)) { + return typeObj === otherObj; + } + return false; }, encode: function (typeObj) { return typeObj.toString(); @@ -278,9 +291,36 @@ UrlMatcher.prototype.types = { TODO */ UrlMatcher.prototype.type = function (name, handler) { - if (!isString(name) || !isObject(handler) || !isFunction(handler.equals) || !isFunction(handler.decode) || !isFunction(handler.encode)) { + // return the handle if only the name was provided + if (!handler && UrlMatcher.prototype.types[name]) { + return UrlMatcher.prototype.types[name]; + } + + if (!isString(name) || !isObject(handler) || !isFunction(handler.decode) || !isFunction(handler.encode)) { throw new Error("Invalid type '" + name + "'"); } + + // normalize the handler + if (isString(handler.is)) { + handler.regexp = new RegExp(handler.is); + handler.is = function (typeObj) { + return !!handler.regexp.exec(handler.encode(typeObj)); + }; + } + if (!isFunction(handler.is)) { + handler.is = function (typeObj) { + return (JSON.stringify(handler.decode(handler.encode(typeObj))) === JSON.stringify(typeObj)); + }; + } + if (!isFunction(handler.equals)) { + handler.equals = function (typeObj, otherObj) { + if (handler.is(typeObj) && handler.is(otherObj)) { + return handler.encode(typeObj) === handler.encode(otherObj); + } + return false; + }; + } + UrlMatcher.prototype.types[name] = handler; }; diff --git a/test/urlMatcherFactorySpec.js b/test/urlMatcherFactorySpec.js index 7cb780ebe..3ffac2a62 100644 --- a/test/urlMatcherFactorySpec.js +++ b/test/urlMatcherFactorySpec.js @@ -164,12 +164,51 @@ describe("urlMatcherFactory", function () { expect($umf.isMatcher(new UrlMatcher('/'))).toBe(true); }); - it("registers types", function () { + it("defines builtin boolean type", function () { + var booleanHandler = $umf.type("boolean"); + expect(booleanHandler.equals(true, true)).toBe(true); + expect(booleanHandler.equals(true, "blue")).toBe(false); + expect(booleanHandler.is(false)).toBe(true); + expect(booleanHandler.is(456)).toBe(false); + expect(booleanHandler.encode(false)).toBe("false"); + expect(booleanHandler.decode("False")).toBe(false); + expect(booleanHandler.decode("purple")).toBe(undefined); + }); + + it("defines builtin integer type", function () { + var integerHandler = $umf.type("integer"); + expect(integerHandler.equals(5, 5)).toBe(true); + expect(integerHandler.equals(5, "blue")).toBe(false); + expect(integerHandler.is(67)).toBe(true); + expect(integerHandler.is(45.2)).toBe(false); + expect(integerHandler.is({})).toBe(false); + expect(integerHandler.encode(342)).toBe("342"); + expect(integerHandler.decode("5563")).toBe(5563); + }); + + it("registers minimal custom types", function () { $umf.type("test", { - equals: function (typeObj, otherObj) {}, - decode: function (typeObj) {}, - encode: function (value) {} + encode: function (typeObj) { return typeObj.value; }, + decode: function (value) { return { value: value }; } }); - expect($umf.compile('/').types["test"]).toBeDefined(); + var typeHandler = $umf.type("test"); + expect(typeHandler.equals({ value: "one" }, { value: "one" })).toBe(true); + expect(typeHandler.equals({ value: "one" }, { value: "two" })).toBe(false); + expect(typeHandler.is({ value: "one" })).toBe(true); + expect(typeHandler.is(456)).toBe(false); + }); + + it("registers complete custom types", function () { + $umf.type("test", { + encode: function (typeObj) { return typeObj.value; }, + decode: function (value) { return { value: value }; }, + is: function (typeObj) { return (isObject(typeObj) && !!typeObj.value); }, + equals: function (typeObj, otherObj) { return (typeObj.value === otherObj.value && typeObj.value !== undefined); } + }); + var typeHandler = $umf.type("test"); + expect(typeHandler.equals({ value: "one" }, { value: "one" })).toBe(true); + expect(typeHandler.equals({ value: "one" }, { value: "two" })).toBe(false); + expect(typeHandler.is({ value: "one" })).toBe(true); + expect(typeHandler.is(456)).toBe(false); }); }); From 0cd78aeee09d5995cc1cb9f00a26530520fa32e5 Mon Sep 17 00:00:00 2001 From: "Todd H. Gardner" Date: Mon, 23 Sep 2013 12:04:16 -0500 Subject: [PATCH 08/21] matcher.exec checks typed parameters are of correct type --- src/urlMatcherFactory.js | 12 +++++++++--- test/urlMatcherFactorySpec.js | 26 ++++++++++---------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/urlMatcherFactory.js b/src/urlMatcherFactory.js index 9eb16b7d7..56c8a9ca6 100644 --- a/src/urlMatcherFactory.js +++ b/src/urlMatcherFactory.js @@ -174,17 +174,23 @@ UrlMatcher.prototype.exec = function (path, searchParams) { for (i=0; i Date: Mon, 23 Sep 2013 13:34:57 -0500 Subject: [PATCH 09/21] split pattern and is --- src/urlMatcherFactory.js | 28 ++++++++++------------------ test/urlMatcherFactorySpec.js | 21 ++++++++++++++++++--- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/urlMatcherFactory.js b/src/urlMatcherFactory.js index 56c8a9ca6..5336eeb90 100644 --- a/src/urlMatcherFactory.js +++ b/src/urlMatcherFactory.js @@ -83,7 +83,7 @@ function UrlMatcher(pattern) { regexp = m[4] || (m[1] == '*' ? '.*' : '[^/]*'); if (isDefined(this.types[regexp])) { this.typeMap[id] = regexp; - regexp = '[^/]*'; + regexp = this.types[regexp].pattern; // use the regexp defined for this type instead } segment = pattern.substring(last, m.index); if (segment.indexOf('?') >= 0) break; // we're into the search part @@ -174,23 +174,17 @@ UrlMatcher.prototype.exec = function (path, searchParams) { for (i=0; i Date: Mon, 23 Sep 2013 13:42:53 -0500 Subject: [PATCH 10/21] format checks the type before encoding --- src/urlMatcherFactory.js | 5 ++++- test/urlMatcherFactorySpec.js | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/urlMatcherFactory.js b/src/urlMatcherFactory.js index 5336eeb90..228f88cd7 100644 --- a/src/urlMatcherFactory.js +++ b/src/urlMatcherFactory.js @@ -223,7 +223,10 @@ UrlMatcher.prototype.format = function (values) { var encodedValues = {}; forEach(values, function (value, key) { if (isDefined(typeMap[key])) { - encodedValues[key] = types[typeMap[key]].encode(value); + var typeHandler = types[typeMap[key]]; + if (typeHandler.is(value)) { + encodedValues[key] = typeHandler.encode(value); + } } else { encodedValues[key] = value; diff --git a/test/urlMatcherFactorySpec.js b/test/urlMatcherFactorySpec.js index 9d2216c92..30f5c88ac 100644 --- a/test/urlMatcherFactorySpec.js +++ b/test/urlMatcherFactorySpec.js @@ -124,6 +124,8 @@ describe("UrlMatcher", function () { it(".format() encode typed URL parameters", function () { expect(new UrlMatcher('/users/{id:integer}').format({ id: 55 })).toEqual('/users/55'); + expect(new UrlMatcher('/users/{id:boolean}').format({ id: false })).toEqual('/users/false'); + expect(new UrlMatcher('/users/{id:boolean}').format({ id: "something" })).toEqual('/users/'); }); it(".concat() concatenates matchers", function () { From 308cc1d263afbfccfa3fb132fc7ede23e23986e3 Mon Sep 17 00:00:00 2001 From: "Todd H. Gardner" Date: Mon, 23 Sep 2013 15:30:15 -0500 Subject: [PATCH 11/21] evaluates type equality when transitioning --- src/state.js | 20 ++++++++++++++------ test/stateSpec.js | 16 +++++++++++++++- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/state.js b/src/state.js index a1061bfa8..8ff07932c 100644 --- a/src/state.js +++ b/src/state.js @@ -170,7 +170,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ // Register the state in the global state list and with $urlRouter if necessary. if (!state['abstract'] && state.url) { $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) { - if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) { + if ($state.$current.navigable != state || !equalForKeys(state, $match, $stateParams)) { $state.transitionTo(state, $match, false); } }]); @@ -294,7 +294,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ // Starting from the root of the path, keep all levels that haven't changed var keep, state, locals = root.locals, toLocals = []; for (keep = 0, state = toPath[keep]; - state && state === fromPath[keep] && equalForKeys(toParams, fromParams, state.ownParams); + state && state === fromPath[keep] && equalForKeys(state, toParams, fromParams, state.ownParams); keep++, state = toPath[keep]) { locals = toLocals[keep] = state.locals; } @@ -468,16 +468,24 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $ return normalized; } - function equalForKeys(a, b, keys) { + function equalForKeys(state, aParams, bParams, keys) { + var url = state.url || {}, + types = url.types || {}, + typeMap = url.typeMap || {}; + // If keys not provided, assume keys from object 'a' if (!keys) { keys = []; - for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility + for (var n in aParams) keys.push(n); // Used instead of Object.keys() for IE8 compatibility } for (var i=0; i Date: Tue, 24 Sep 2013 13:01:56 -0500 Subject: [PATCH 12/21] added sample for custom type. documented the type function --- sample/states.js | 27 ++++++++++++++++++++------- src/urlMatcherFactory.js | 23 ++++++++++++++++++++++- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/sample/states.js b/sample/states.js index 94d6e7261..1d758e4f9 100644 --- a/sample/states.js +++ b/sample/states.js @@ -30,15 +30,27 @@ angular.module('uiRouterSample') ///////////////////// // Parameter Types // ///////////////////// - .type("date", { + // contact id is formatted like ##-####, as dependent on some backend system. + .type("myCustomId", { + pattern: "[0-9]{2}[\-][0-9]{4}", + is: function (typeObj) { + return (angular.isObject(typeObj) && typeObj.firstPart && typeObj.secondPart); + }, equals: function (typeObj, otherObj) { - return typeObj.toISOString() === otherObj.toISOString(); + if (this.is(typeObj) && this.is(otherObj)) { + return (typeObj.firstPart === otherObj.firstPart && typeObj.secondPart === otherObj.secondPart); + } + return false; }, - decode: function (typeObj) { - return typeObj.toISOString(); + decode: function (value) { + var tokens = value.split("-"); + return { + firstPart: tokens[0], + secondPart: tokens[1] + }; }, - encode: function (value) { - return new Date(value); + encode: function (typeObj) { + return typeObj.firstPart + "-" + typeObj.secondPart; } }) @@ -154,7 +166,7 @@ angular.module('uiRouterSample') // So its url will end up being '/contacts/{contactId:[0-9]{1,8}}'. When the // url becomes something like '/contacts/42' then this state becomes active // and the $stateParams object becomes { contactId: 42 }. - url: '/{contactId:integer}', + url: '/{contactId:myCustomId}', // If there is more than a single ui-view in the parent template, or you would // like to target a ui-view from even higher up the state tree, you can use the @@ -172,6 +184,7 @@ angular.module('uiRouterSample') templateUrl: 'contacts.detail.html', controller: ['$scope', '$stateParams', 'utils', function ( $scope, $stateParams, utils) { + console.log($stateParams.contactId); $scope.contact = utils.findById($scope.contacts, $stateParams.contactId); }] }, diff --git a/src/urlMatcherFactory.js b/src/urlMatcherFactory.js index 228f88cd7..1c75ecfce 100644 --- a/src/urlMatcherFactory.js +++ b/src/urlMatcherFactory.js @@ -292,7 +292,28 @@ UrlMatcher.prototype.types = { }; /** -TODO + * Registers a custom type for parameters or gets a handler for a registered type. + * A handler object must include a `decode` function that decodes a string value + * from the URL into the type, and a `encode` function that encodes the type into + * a string value for the URL. + * + * ### Example + * ``` + * // Register myType + * .type('myType', { + * pattern: "[0-9]+", // (Optional) Regex pattern used to match the URL to this type. + * is : function (typeObj) {}, // (Optional) Determines if a param is of this type when saving to the URL. + * equals: function (typeObj, otherObj) {}, // (Optional) Determines if two objects of this type are equal. + * encode: function (typeObj) {}, // (Required) Encode this type to the URL. + * decode: function (value) {} // (Required) Decode the URL segment to this type. + * }); + * // Get myType + * .type('myType'); + * ``` + * + * @param {string} name the name of the type to register or get. + * @param {Object} handler the handler object with functions of working with this type. + * @return {Object} the handler object. */ UrlMatcher.prototype.type = function (name, handler) { // return the handle if only the name was provided From e22517d220d8039eb105be5d582f57d81aa710a7 Mon Sep 17 00:00:00 2001 From: "Todd H. Gardner" Date: Tue, 24 Sep 2013 13:14:44 -0500 Subject: [PATCH 13/21] fixed equality in integer type --- src/urlMatcherFactory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/urlMatcherFactory.js b/src/urlMatcherFactory.js index 1c75ecfce..c592e8df9 100644 --- a/src/urlMatcherFactory.js +++ b/src/urlMatcherFactory.js @@ -274,7 +274,7 @@ UrlMatcher.prototype.types = { 'integer': { pattern: "[0-9]+", is: function (typeObj) { - return typeof typeObj === 'number' && typeObj % 1 == 0; + return typeof typeObj === 'number' && typeObj % 1 === 0; }, equals: function (typeObj, otherObj) { if (this.is(typeObj) && this.is(otherObj)) { From bcd71cb4ac0d52dcd841e8ebb5859469fd0ab951 Mon Sep 17 00:00:00 2001 From: "Todd H. Gardner" Date: Tue, 24 Sep 2013 13:33:21 -0500 Subject: [PATCH 14/21] fixing jsdoc for windows --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 7af87827b..00f6b4b68 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -106,7 +106,7 @@ module.exports = function (grunt) { grunt.registerTask('jsdoc', 'Generate documentation', function () { promising(this, - system('node_modules/jsdoc/jsdoc -c config/jsdoc.js -d \'' + grunt.config('builddir') + '\'/doc src') + system('./node_modules/jsdoc/jsdoc -c ./config/jsdoc.js -d ./\'' + grunt.config('builddir') + '\'/doc src') ); }); From c9da9f235bade3b75f10db86e431dd483ac17570 Mon Sep 17 00:00:00 2001 From: "Todd H. Gardner" Date: Tue, 24 Sep 2013 13:36:25 -0500 Subject: [PATCH 15/21] fixing jsdoc for windows --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 00f6b4b68..33e74ed3a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -106,7 +106,7 @@ module.exports = function (grunt) { grunt.registerTask('jsdoc', 'Generate documentation', function () { promising(this, - system('./node_modules/jsdoc/jsdoc -c ./config/jsdoc.js -d ./\'' + grunt.config('builddir') + '\'/doc src') + system('\"./node_modules/jsdoc/jsdoc\" -c ./config/jsdoc.js -d \"./' + grunt.config('builddir') + '/doc\" src') ); }); From 841239c1c7d15793e280d2e19e093d34ae28f280 Mon Sep 17 00:00:00 2001 From: "Todd H. Gardner" Date: Tue, 24 Sep 2013 13:38:01 -0500 Subject: [PATCH 16/21] fixing release for windows --- Gruntfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 33e74ed3a..3cbd82b80 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -150,10 +150,10 @@ module.exports = function (grunt) { var version = grunt.config('pkg.version'), releasedir = grunt.config('builddir'); promising(this, - system('git add \'' + releasedir + '\'').then(function () { + system('git add \"' + releasedir + '\"').then(function () { return system('git commit -m \'release ' + version + '\''); }).then(function () { - return system('git tag \'' + version + '\''); + return system('git tag \"' + version + '\"'); }) ); }); From cf3e5b69c765a79828136e87a4d99ea2fdf51662 Mon Sep 17 00:00:00 2001 From: "Todd H. Gardner" Date: Tue, 24 Sep 2013 13:43:40 -0500 Subject: [PATCH 17/21] for 0.2.1. release --- bower.json | 2 +- component.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index 73b6ff0ee..f19776ced 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-ui-router", - "version": "0.2.0", + "version": "0.2.1", "main": "./release/angular-ui-router.min.js", "dependencies": { "angular": ">= 1.0.6" diff --git a/component.json b/component.json index 4f23115ba..151f9576e 100644 --- a/component.json +++ b/component.json @@ -1,6 +1,6 @@ { "name": "angular-ui-router", - "version": "0.2.0", + "version": "0.2.1", "description": "State-based routing for AngularJS", "keywords": [ "angular", diff --git a/package.json b/package.json index afb8ebf78..3149ad136 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "angular-ui-router", "description": "State-based routing for AngularJS", - "version": "0.2.0", + "version": "0.2.1", "homepage": "http://angular-ui.github.com/", "author": { "name": "Karsten Sperling", From 7e96189e5bafd25bcf4b90a7a2a11355babadaa0 Mon Sep 17 00:00:00 2001 From: "Todd H. Gardner" Date: Tue, 24 Sep 2013 13:45:32 -0500 Subject: [PATCH 18/21] release 0.2.1 --- release/angular-ui-router.js | 317 +++++++++++++++++++++++++--- release/angular-ui-router.min.js | 4 +- release/doc/$resolve.html | 34 +-- release/doc/$templateFactory.html | 37 +++- release/doc/$urlMatcherFactory.html | 23 +- release/doc/UrlMatcher.html | 236 ++++++++++++++++++--- release/doc/global.html | 13 +- release/doc/index.html | 5 +- release/doc/scripts/linenumber.js | 17 ++ 9 files changed, 599 insertions(+), 87 deletions(-) create mode 100644 release/doc/scripts/linenumber.js diff --git a/release/angular-ui-router.js b/release/angular-ui-router.js index feb17c7ce..db2cf24b9 100644 --- a/release/angular-ui-router.js +++ b/release/angular-ui-router.js @@ -1,9 +1,15 @@ /** * State-based routing for AngularJS - * @version v0.2.0 + * @version v0.2.1 * @link http://angular-ui.github.com/ * @license MIT License, http://www.opensource.org/licenses/MIT */ + +/* commonjs package manager support (eg componentjs) */ +if (module && exports && module.exports === exports){ + module.exports = 'ui.router'; +} + (function (window, angular, undefined) { /*jshint globalstrict:true*/ /*global angular:false*/ @@ -127,7 +133,7 @@ function $Resolve( $q, $injector) { visited[key] = VISIT_IN_PROGRESS; if (isString(value)) { - plan.push(key, [ function() { return $injector.get(key); }], NO_DEPENDENCIES); + plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES); } else { var params = $injector.annotate(value); forEach(params, function (param) { @@ -214,7 +220,7 @@ function $Resolve( $q, $injector) { } // Wait for any parameter that we have a promise for (either from parent or from this // resolve; in that case study() will have made sure it's ordered before us in the plan). - params.forEach(function (dep) { + forEach(params, function (dep) { if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) { waitParams++; promises[dep].then(function (result) { @@ -448,7 +454,8 @@ function UrlMatcher(pattern) { var placeholder = /([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, names = {}, compiled = '^', last = 0, m, segments = this.segments = [], - params = this.params = []; + params = this.params = [], + typeMap = this.typeMap = {}; function addParameter(id) { if (!/^\w+(-+\w+)*$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'"); @@ -469,6 +476,10 @@ function UrlMatcher(pattern) { while ((m = placeholder.exec(pattern))) { id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null regexp = m[4] || (m[1] == '*' ? '.*' : '[^/]*'); + if (isDefined(this.types[regexp])) { + this.typeMap[id] = regexp; + regexp = this.types[regexp].pattern; // use the regexp defined for this type instead + } segment = pattern.substring(last, m.index); if (segment.indexOf('?') >= 0) break; // we're into the search part compiled += quoteRegExp(segment) + '(' + regexp + ')'; @@ -543,7 +554,10 @@ UrlMatcher.prototype.toString = function () { * @return {Object} The captured parameter values. */ UrlMatcher.prototype.exec = function (path, searchParams) { - var m = this.regexp.exec(path); + var m = this.regexp.exec(path), + types = this.types, + typeMap = this.typeMap; + if (!m) return null; var params = this.params, nTotal = params.length, @@ -555,7 +569,17 @@ UrlMatcher.prototype.exec = function (path, searchParams) { for (i=0; i=0||(s.push(a[c]),u[a[c]]=r[a[c]])}return P({},u,t)}function u(r,t){var n=1,o=2,i={},u=[],s=i,l=P(r.when(i),{$$promises:i,$$values:i});this.study=function(i){function c(r,e){if(v[e]!==o){if(p.push(e),v[e]===n)throw p.splice(0,p.indexOf(e)),Error("Cyclic dependency: "+p.join(" -> "));if(v[e]=n,b(r))h.push(e,[function(){return t.get(e)}],u);else{var a=t.annotate(r);y(a,function(r){r!==e&&i.hasOwnProperty(r)&&c(i[r],r)}),h.push(e,r,a)}p.pop(),v[e]=o}}function f(r){return E(r)&&r.then&&r.$$promises}if(!E(i))throw Error("'invocables' must be an object");var h=[],p=[],v={};return y(i,c),i=p=v=null,function(n,o,i){function u(){--w||(b||a(d,o.$$values),$.$$values=d,$.$$promises=!0,v.resolve(d))}function c(r){$.$$failure=r,v.reject(r)}function p(e,a,o){function s(r){f.reject(r),c(r)}function l(){if(!g($.$$failure))try{f.resolve(t.invoke(a,i,d)),f.promise.then(function(r){d[e]=r,u()},s)}catch(r){s(r)}}var f=r.defer(),h=0;o.forEach(function(r){m.hasOwnProperty(r)&&!n.hasOwnProperty(r)&&(h++,m[r].then(function(t){d[r]=t,--h||l()},s))}),h||l(),m[e]=f.promise}if(f(n)&&i===e&&(i=o,o=n,n=null),n){if(!E(n))throw Error("'locals' must be an object")}else n=s;if(o){if(!f(o))throw Error("'parent' must be a promise returned by $resolve.resolve()")}else o=l;var v=r.defer(),$=v.promise,m=$.$$promises={},d=P({},n),w=1+h.length/3,b=!1;if(g(o.$$failure))return c(o.$$failure),$;o.$$values?(b=a(d,o.$$values),u()):(P(m,o.$$promises),o.then(u,c));for(var x=0,y=h.length;y>x;x+=3)n.hasOwnProperty(h[x])?u():p(h[x],h[x+1],h[x+2]);return $}},this.resolve=function(r,t,e,n){return this.study(r)(t,e,n)}}function s(r,t,e){this.fromConfig=function(r,t,e){return g(r.template)?this.fromString(r.template,t):g(r.templateUrl)?this.fromUrl(r.templateUrl,t):g(r.templateProvider)?this.fromProvider(r.templateProvider,t,e):null},this.fromString=function(r,t){return w(r)?r(t):r},this.fromUrl=function(e,n){return w(e)&&(e=e(n)),null==e?null:r.get(e,{cache:t}).then(function(r){return r.data})},this.fromProvider=function(r,t,n){return e.invoke(r,null,n||{params:t})}}function l(r){function t(t){if(!/^\w+(-+\w+)*$/.test(t))throw Error("Invalid parameter name '"+t+"' in pattern '"+r+"'");if(o[t])throw Error("Duplicate parameter name '"+t+"' in pattern '"+r+"'");o[t]=!0,l.push(t)}function e(r){return r.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&")}var n,a=/([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,o={},i="^",u=0,s=this.segments=[],l=this.params=[];this.source=r;for(var c,f,h;(n=a.exec(r))&&(c=n[2]||n[3],f=n[4]||("*"==n[1]?".*":"[^/]*"),h=r.substring(u,n.index),!(h.indexOf("?")>=0));)i+=e(h)+"("+f+")",t(c),s.push(h),u=a.lastIndex;h=r.substring(u);var p=h.indexOf("?");if(p>=0){var v=this.sourceSearch=h.substring(p);h=h.substring(0,p),this.sourcePath=r.substring(0,u+p),y(v.substring(1).split(/[&?]/),t)}else this.sourcePath=r,this.sourceSearch="";i+=e(h)+"$",s.push(h),this.regexp=RegExp(i),this.prefix=s[0]}function c(){this.compile=function(r){return new l(r)},this.isMatcher=function(r){return E(r)&&w(r.exec)&&w(r.format)&&w(r.concat)},this.$get=function(){return this}}function f(r){function t(r){var t=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(r.source);return null!=t?t[1].replace(/\\(.)/g,"$1"):""}function e(r,t){return r.replace(/\$(\$|\d{1,2})/,function(r,e){return t["$"===e?0:Number(e)]})}function n(r,t,e){if(!e)return!1;var n=r.invoke(t,t,{$match:e});return g(n)?n:!0}var a=[],o=null;this.rule=function(r){if(!w(r))throw Error("'rule' must be a function");return a.push(r),this},this.otherwise=function(r){if(b(r)){var t=r;r=function(){return t}}else if(!w(r))throw Error("'rule' must be a function");return o=r,this},this.when=function(a,o){var i,u=b(o);if(b(a)&&(a=r.compile(a)),!u&&!w(o)&&!x(o))throw Error("invalid 'handler' in when()");var s={matcher:function(t,e){return u&&(i=r.compile(e),e=["$match",function(r){return i.format(r)}]),P(function(r,a){return n(r,e,t.exec(a.path(),a.search()))},{prefix:b(t.prefix)?t.prefix:""})},regex:function(r,a){if(r.global||r.sticky)throw Error("when() RegExp must not be global or sticky");return u&&(i=a,a=["$match",function(r){return e(i,r)}]),P(function(t,e){return n(t,a,r.exec(e.path()))},{prefix:t(r)})}},l={matcher:r.isMatcher(a),regex:a instanceof RegExp};for(var c in l)if(l[c])return this.rule(s[c](a,o));throw Error("invalid 'what' in when()")},this.$get=["$location","$rootScope","$injector",function(r,t,e){function n(){function t(t){var n=t(e,r);return n?(b(n)&&r.replace().url(n),!0):!1}var n,i=a.length;for(n=0;i>n;n++)if(t(a[n]))return;o&&t(o)}return t.$on("$locationChangeSuccess",n),{}}]}function h(r,a,o){function u(r,t){var n=b(r),a=n?r:r.name,o=0===a.indexOf(".")||0===a.indexOf("^");if(o){if(!t)throw Error("No reference point given for path '"+a+"'");for(var i=a.split("."),u=0,s=i.length,l=t;s>u;u++)if(""!==i[u]||0!==u){if("^"!==i[u])break;if(!l.parent)throw Error("Path '"+a+"' not valid for state '"+t.name+"'");l=l.parent}else l=t;i=i.slice(u).join("."),a=l.name+(l.name&&i?".":"")+i}var c=m[a];return!c||!n&&(n||c!==r&&c.self!==r)?e:c}function s(t){t=n(t,{self:t,resolve:t.resolve||{},toString:function(){return this.name}});var e=t.name;if(!b(e)||e.indexOf("@")>=0)throw Error("State must have a valid name");if(m[e])throw Error("State '"+e+"'' is already defined");for(var a in d)t[a]=d[a](t);return m[e]=t,!t["abstract"]&&t.url&&r.when(t.url,["$match","$stateParams",function(r,e){$.$current.navigable==t&&h(r,e)||$.transitionTo(t,r,!1)}]),t}function l(r,t){return E(r)?t=r:t.name=r,s(t),this}function c(r,t,a,s,l,c,m){function d(r,e,n,o,i){var u=n?e:p(r.params,e),s={$stateParams:u};i.resolve=l.resolve(r.resolve,s,i.resolve,r);var c=[i.resolve.then(function(r){i.globals=r})];return o&&c.push(o),y(r.views,function(t,e){var n=t.resolve&&t.resolve!==r.resolve?t.resolve:{};n.$template=[function(){return a.load(e,{view:t,locals:s,params:u,notify:!1})||""}],c.push(l.resolve(n,s,i.resolve,r).then(function(n){n.$$controller=t.controller,n.$$state=r,i[e]=n}))}),t.all(c).then(function(){return i})}var w=t.reject(Error("transition superseded")),b=t.reject(Error("transition prevented"));return v.locals={resolve:null,globals:{$stateParams:{}}},$={params:{},current:v.self,$current:v,transition:null},$.go=function(r,t,e){return this.transitionTo(r,t,P({inherit:!0,relative:$.$current},e))},$.transitionTo=function(e,a,o){g(o)||(o=o===!0||o===!1?{location:o}:{}),a=a||{},o=P({location:!0,inherit:!1,relative:null},o);var l=u(e,o.relative);if(!g(l))throw Error("No such state "+l);if(l["abstract"])throw Error("Cannot transition to abstract state '"+e+"'");o.inherit&&(a=i(c,a||{},$.$current,l)),e=l;var p,E,x=e.path,y=$.$current,C=$.params,S=y.path,O=v.locals,k=[];for(p=0,E=x[p];E&&E===S[p]&&h(a,C,E.ownParams);p++,E=x[p])O=k[p]=E.locals;if(e===y&&O===y.locals)return $.transition=null,t.when($.current);a=f(e.params,a||{});var R=r.$broadcast("$stateChangeStart",e.self,a,y.self,C);if(R.defaultPrevented)return b;for(var I=t.when(O),M=p;x.length>M;M++,E=x[M])O=k[M]=n(O),I=d(E,a,E===e,I,O);var U=$.transition=I.then(function(){var t,n,i;if($.transition!==U)return w;for(t=S.length-1;t>=p;t--)i=S[t],i.self.onExit&&s.invoke(i.self.onExit,i.self,i.locals.globals),i.locals=null;for(t=p;x.length>t;t++)n=x[t],n.locals=k[t],n.self.onEnter&&s.invoke(n.self.onEnter,n.self,n.locals.globals);$.$current=e,$.current=e.self,$.params=a,j($.params,c),$.transition=null;var u=e.navigable;return o.location&&u&&m.url(u.url.format(u.locals.globals.$stateParams)),r.$broadcast("$stateChangeSuccess",e.self,a,y.self,C),$.current},function(n){return $.transition!==U?w:($.transition=null,r.$broadcast("$stateChangeError",e.self,a,y.self,C,n),t.reject(n))});return U},$.is=function(r){var t=u(r);return g(t)?$.$current===t:e},$.includes=function(r){var t=u(r);return g(t)?g($.$current.includes[t.name]):e},$.href=function(r,t,e){e=P({lossy:!0,inherit:!1,relative:$.$current},e||{});var n=u(r,e.relative);if(!g(n))return null;t=i(c,t||{},$.$current,n);var a=n&&e.lossy?n.navigable:n,s=a&&a.url?a.url.format(f(n.params,t||{})):null;return!o.html5Mode()&&s?"#"+s:s},$.get=function(r){var t=u(r);return t&&t.self?t.self:null},$}function f(r,t){var e={};return y(r,function(r){var n=t[r];e[r]=null!=n?n+"":null}),e}function h(r,t,e){if(!e){e=[];for(var n in r)e.push(n)}for(var a=0;e.length>a;a++){var o=e[a];if(r[o]!=t[o])return!1}return!0}function p(r,t){var e={};return y(r,function(r){e[r]=t[r]}),e}var v,$,m={},d={parent:function(r){if(g(r.parent)&&r.parent)return u(r.parent);var t=/^(.+)\.[^.]+$/.exec(r.name);return t?u(t[1]):v},data:function(r){return r.parent&&r.parent.data&&(r.data=r.self.data=t.extend({},r.parent.data,r.data)),r.data},url:function(r){var t=r.url;if(b(t))return"^"==t.charAt(0)?a.compile(t.substring(1)):(r.parent.navigable||v).url.concat(t);if(a.isMatcher(t)||null==t)return t;throw Error("Invalid url '"+t+"' in state '"+r+"'")},navigable:function(r){return r.url?r:r.parent?r.parent.navigable:null},params:function(r){if(!r.params)return r.url?r.url.parameters():r.parent.params;if(!x(r.params))throw Error("Invalid params in state '"+r+"'");if(r.url)throw Error("Both params and url specicified in state '"+r+"'");return r.params},views:function(r){var t={};return y(g(r.views)?r.views:{"":r},function(e,n){0>n.indexOf("@")&&(n+="@"+r.parent.name),t[n]=e}),t},ownParams:function(r){if(!r.parent)return r.params;var t={};y(r.params,function(r){t[r]=!0}),y(r.parent.params,function(e){if(!t[e])throw Error("Missing required parameter '"+e+"' in state '"+r.name+"'");t[e]=!1});var e=[];return y(t,function(r,t){r&&e.push(t)}),e},path:function(r){return r.parent?r.parent.path.concat(r):[]},includes:function(r){var t=r.parent?P({},r.parent.includes):{};return t[r.name]=!0,t}};v=s({name:"",url:"^",views:null,"abstract":!0}),v.navigable=null,this.state=l,this.$get=c,c.$inject=["$rootScope","$q","$view","$injector","$resolve","$stateParams","$location","$urlRouter"]}function p(){function r(r,t){return{load:function(e,n){var a,o={template:null,controller:null,view:null,locals:null,notify:!0,async:!0,params:{}};return n=P(o,n),n.view&&(a=t.fromConfig(n.view,n.params,n.locals)),a&&n.notify&&r.$broadcast("$viewContentLoading",n),a}}}this.$get=r,r.$inject=["$rootScope","$templateFactory"]}function v(r,e,n,a,o){var i;try{i=a.get("$animator")}catch(u){}var s=!1,l={restrict:"ECA",terminal:!0,transclude:!0,compile:function(a,u,c){return function(a,u,f){function h(t){var i=r.$current&&r.$current.locals[$];if(i!==v){var s=w(d&&t);if(s.remove(u),p&&(p.$destroy(),p=null),!i)return v=null,E.state=null,s.restore(c(a),u);v=i,E.state=i.$$state;var l=e(s.populate(i.$template,u));if(p=a.$new(),i.$$controller){i.$scope=p;var f=n(i.$$controller,i);u.children().data("$ngControllerController",f)}l(p),p.$emit("$viewContentLoaded"),m&&p.$eval(m),o()}}var p,v,$=f[l.name]||f.name||"",m=f.onload||"",d=g(i)&&i(a,f),w=function(r){return{"true":{remove:function(r){d.leave(r.contents(),r)},restore:function(r,t){d.enter(r,t)},populate:function(r,e){var n=t.element("
").html(r).contents();return d.enter(n,e),n}},"false":{remove:function(r){r.html("")},restore:function(r,t){t.append(r)},populate:function(r,t){return t.html(r),t.contents()}}}[""+r]};u.append(c(a));var b=u.parent().inheritedData("$uiView");0>$.indexOf("@")&&($=$+"@"+(b?b.state.name:""));var E={name:$,state:null};u.data("$uiView",E);var x=function(){if(!s){s=!0;try{h(!0)}catch(r){throw s=!1,r}s=!1}};a.$on("$stateChangeSuccess",x),a.$on("$viewContentLoading",x),h(!1)}}};return l}function $(r){var t=r.match(/^([^(]+?)\s*(\((.*)\))?$/);if(!t||4!==t.length)throw Error("Invalid state ref '"+r+"'");return{state:t[1],paramExpr:t[3]||null}}function m(r){return{restrict:"A",link:function(t,n,a){var o=$(a.uiSref),i=null,u=r.$current,s="FORM"===n[0].nodeName,l=s?"action":"href",c=!0,f=n.parent().inheritedData("$uiView");f&&f.state&&f.state.name&&(u=f.state);var h=function(t){if(t&&(i=t),c){var a=r.href(o.state,i,{relative:u});return a?(n[0][l]=a,e):(c=!1,!1)}};o.paramExpr&&(t.$watch(o.paramExpr,function(r,t){r!==t&&h(r)},!0),i=t.$eval(o.paramExpr)),h(),s||n.bind("click",function(e){1!=e.which||e.ctrlKey||e.metaKey||e.shiftKey||(r.go(o.state,i,{relative:u}),t.$apply(),e.preventDefault())})}}}function d(r,t){function a(r){this.locals=r.locals.globals,this.params=this.locals.$stateParams}function o(){this.locals=null,this.params=null}function i(e,i){if(null!=i.redirectTo){var u,l=i.redirectTo;if(b(l))u=l;else{if(!w(l))throw Error("Invalid 'redirectTo' in when()");u=function(r,t){return l(r,t.path(),t.search())}}t.when(e,u)}else r.state(n(i,{parent:null,name:"route:"+encodeURIComponent(e),url:e,onEnter:a,onExit:o}));return s.push(i),this}function u(r,t,n){function a(r){return""!==r.name?r:e}var o={routes:s,params:n,current:e};return t.$on("$stateChangeStart",function(r,e,n,o){t.$broadcast("$routeChangeStart",a(e),a(o))}),t.$on("$stateChangeSuccess",function(r,e,n,i){o.current=a(e),t.$broadcast("$routeChangeSuccess",a(e),a(i)),j(n,o.params)}),t.$on("$stateChangeError",function(r,e,n,o,i,u){t.$broadcast("$routeChangeError",a(e),a(o),u)}),o}var s=[];a.$inject=["$$state"],this.when=i,this.$get=u,u.$inject=["$state","$rootScope","$routeParams"]}var g=t.isDefined,w=t.isFunction,b=t.isString,E=t.isObject,x=t.isArray,y=t.forEach,P=t.extend,j=t.copy;t.module("ui.router.util",["ng"]),t.module("ui.router.router",["ui.router.util"]),t.module("ui.router.state",["ui.router.router","ui.router.util"]),t.module("ui.router",["ui.router.state"]),t.module("ui.router.compat",["ui.router"]),u.$inject=["$q","$injector"],t.module("ui.router.util").service("$resolve",u),s.$inject=["$http","$templateCache","$injector"],t.module("ui.router.util").service("$templateFactory",s),l.prototype.concat=function(r){return new l(this.sourcePath+r+this.sourceSearch)},l.prototype.toString=function(){return this.source},l.prototype.exec=function(r,t){var e=this.regexp.exec(r);if(!e)return null;var n,a=this.params,o=a.length,i=this.segments.length-1,u={};if(i!==e.length-1)throw Error("Unbalanced capture group in route '"+this.source+"'");for(n=0;i>n;n++)u[a[n]]=e[n+1];for(;o>n;n++)u[a[n]]=t[a[n]];return u},l.prototype.parameters=function(){return this.params},l.prototype.format=function(r){var t=this.segments,e=this.params;if(!r)return t.join("");var n,a,o,i=t.length-1,u=e.length,s=t[0];for(n=0;i>n;n++)o=r[e[n]],null!=o&&(s+=encodeURIComponent(o)),s+=t[n+1];for(;u>n;n++)o=r[e[n]],null!=o&&(s+=(a?"&":"?")+e[n]+"="+encodeURIComponent(o),a=!0);return s},t.module("ui.router.util").provider("$urlMatcherFactory",c),f.$inject=["$urlMatcherFactoryProvider"],t.module("ui.router.router").provider("$urlRouter",f),h.$inject=["$urlRouterProvider","$urlMatcherFactoryProvider","$locationProvider"],t.module("ui.router.state").value("$stateParams",{}).provider("$state",h),p.$inject=[],t.module("ui.router.state").provider("$view",p),v.$inject=["$state","$compile","$controller","$injector","$anchorScroll"],t.module("ui.router.state").directive("uiView",v),m.$inject=["$state"],t.module("ui.router.state").directive("uiSref",m),d.$inject=["$stateProvider","$urlRouterProvider"],t.module("ui.router.compat").provider("$route",d).directive("ngView",v)})(window,window.angular); \ No newline at end of file +module&&exports&&module.exports===exports&&(module.exports="ui.router"),function(a,b,c){"use strict";function d(a,b){return y(new(y(function(){},{prototype:a})),b)}function e(a){return x(arguments,function(b){b!==a&&x(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)})}),a}function f(a,b){var c=[];for(var d in a.path)if(""!==a.path[d]){if(!b.path[d])break;c.push(a.path[d])}return c}function g(a,b,c,d){var e,g=f(c,d),h={},i=[];for(var j in g)if(g[j].params&&g[j].params.length){e=g[j].params;for(var k in e)i.indexOf(e[k])>=0||(i.push(e[k]),h[e[k]]=a[e[k]])}return y({},h,b)}function h(a,b){var d=1,f=2,g={},h=[],i=g,j=y(a.when(g),{$$promises:g,$$values:g});this.study=function(g){function k(a,c){if(o[c]!==f){if(n.push(c),o[c]===d)throw n.splice(0,n.indexOf(c)),new Error("Cyclic dependency: "+n.join(" -> "));if(o[c]=d,u(a))m.push(c,[function(){return b.get(a)}],h);else{var e=b.annotate(a);x(e,function(a){a!==c&&g.hasOwnProperty(a)&&k(g[a],a)}),m.push(c,a,e)}n.pop(),o[c]=f}}function l(a){return v(a)&&a.then&&a.$$promises}if(!v(g))throw new Error("'invocables' must be an object");var m=[],n=[],o={};return x(g,k),g=n=o=null,function(d,f,g){function h(){--t||(u||e(r,f.$$values),p.$$values=r,p.$$promises=!0,o.resolve(r))}function k(a){p.$$failure=a,o.reject(a)}function n(c,e,f){function i(a){l.reject(a),k(a)}function j(){if(!s(p.$$failure))try{l.resolve(b.invoke(e,g,r)),l.promise.then(function(a){r[c]=a,h()},i)}catch(a){i(a)}}var l=a.defer(),m=0;x(f,function(a){q.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,q[a].then(function(b){r[a]=b,--m||j()},i))}),m||j(),q[c]=l.promise}if(l(d)&&g===c&&(g=f,f=d,d=null),d){if(!v(d))throw new Error("'locals' must be an object")}else d=i;if(f){if(!l(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=j;var o=a.defer(),p=o.promise,q=p.$$promises={},r=y({},d),t=1+m.length/3,u=!1;if(s(f.$$failure))return k(f.$$failure),p;f.$$values?(u=e(r,f.$$values),h()):(y(q,f.$$promises),f.then(h,k));for(var w=0,z=m.length;z>w;w+=3)d.hasOwnProperty(m[w])?h():n(m[w],m[w+1],m[w+2]);return p}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function i(a,b,c){this.fromConfig=function(a,b,c){return s(a.template)?this.fromString(a.template,b):s(a.templateUrl)?this.fromUrl(a.templateUrl,b):s(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return t(a)?a(b):a},this.fromUrl=function(c,d){return t(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function j(a){function b(b){if(!/^\w+(-+\w+)*$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(f[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");f[b]=!0,j.push(b)}function c(a){return a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&")}var d,e=/([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,f={},g="^",h=0,i=this.segments=[],j=this.params=[];this.typeMap={},this.source=a;for(var k,l,m;(d=e.exec(a))&&(k=d[2]||d[3],l=d[4]||("*"==d[1]?".*":"[^/]*"),s(this.types[l])&&(this.typeMap[k]=l,l=this.types[l].pattern),m=a.substring(h,d.index),!(m.indexOf("?")>=0));)g+=c(m)+"("+l+")",b(k),i.push(m),h=e.lastIndex;m=a.substring(h);var n=m.indexOf("?");if(n>=0){var o=this.sourceSearch=m.substring(n);m=m.substring(0,n),this.sourcePath=a.substring(0,h+n),x(o.substring(1).split(/[&?]/),b)}else this.sourcePath=a,this.sourceSearch="";g+=c(m)+"$",i.push(m),this.regexp=new RegExp(g),this.prefix=i[0]}function k(){this.compile=function(a){return new j(a)},this.isMatcher=function(a){return v(a)&&t(a.exec)&&t(a.format)&&t(a.concat)},this.type=function(a,b){return j.prototype.type(a,b)},this.$get=function(){return this}}function l(a){function b(a){var b=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(a.source);return null!=b?b[1].replace(/\\(.)/g,"$1"):""}function c(a,b){return a.replace(/\$(\$|\d{1,2})/,function(a,c){return b["$"===c?0:Number(c)]})}function d(a,b,c){if(!c)return!1;var d=a.invoke(b,b,{$match:c});return s(d)?d:!0}var e=[],f=null;this.rule=function(a){if(!t(a))throw new Error("'rule' must be a function");return e.push(a),this},this.otherwise=function(a){if(u(a)){var b=a;a=function(){return b}}else if(!t(a))throw new Error("'rule' must be a function");return f=a,this},this.when=function(e,f){var g,h=u(f);if(u(e)&&(e=a.compile(e)),!h&&!t(f)&&!w(f))throw new Error("invalid 'handler' in when()");var i={matcher:function(b,c){return h&&(g=a.compile(c),c=["$match",function(a){return g.format(a)}]),y(function(a,e){return d(a,c,b.exec(e.path(),e.search()))},{prefix:u(b.prefix)?b.prefix:""})},regex:function(a,e){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(g=e,e=["$match",function(a){return c(g,a)}]),y(function(b,c){return d(b,e,a.exec(c.path()))},{prefix:b(a)})}},j={matcher:a.isMatcher(e),regex:e instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](e,f));throw new Error("invalid 'what' in when()")},this.$get=["$location","$rootScope","$injector",function(a,b,c){function d(){function b(b){var d=b(c,a);return d?(u(d)&&a.replace().url(d),!0):!1}var d,g=e.length;for(d=0;g>d;d++)if(b(e[d]))return;f&&b(f)}return b.$on("$locationChangeSuccess",d),{}}]}function m(a,e,f){function h(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function i(a,b){var d=u(a),e=d?a:a.name,f=h(e);if(f){if(!b)throw new Error("No reference point given for path '"+e+"'");for(var g=e.split("."),i=0,j=g.length,k=b;j>i;i++)if(""!==g[i]||0!==i){if("^"!==g[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;g=g.slice(i).join("."),e=k.name+(k.name&&g?".":"")+g}var l=A[e];return!l||!d&&(d||l!==a&&l.self!==a)?c:l}function j(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!u(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(A[c])throw new Error("State '"+c+"'' is already defined");for(var e in B)t(B[e])&&(b[e]=B[e](b,B.$delegates[e]));return A[c]=b,!b["abstract"]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){r.$current.navigable==b&&o(b,a,c)||r.transitionTo(b,a,!1)}]),b}function k(a,b){return u(a)&&!s(b)?B[a]:t(b)&&u(a)?(B[a]&&!B.$delegates[a]&&(B.$delegates[a]=B[a]),B[a]=b,this):this}function l(a,b){return v(a)?b=a:b.name=a,j(b),this}function m(a,e,h,j,k,l,m){function t(a,b,c,d,f){var g=c?b:p(a.params,b),i={$stateParams:g};f.resolve=k.resolve(a.resolve,i,f.resolve,a);var j=[f.resolve.then(function(a){f.globals=a})];return d&&j.push(d),x(a.views,function(b,c){var d=b.resolve&&b.resolve!==a.resolve?b.resolve:{};d.$template=[function(){return h.load(c,{view:b,locals:i,params:g,notify:!1})||""}],j.push(k.resolve(d,i,f.resolve,a).then(function(d){d.$$controller=b.controller,d.$$state=a,f[c]=d}))}),e.all(j).then(function(){return f})}var u=e.reject(new Error("transition superseded")),v=e.reject(new Error("transition prevented")),w=e.reject(new Error("transition aborted")),B=e.reject(new Error("transition failed"));return q.locals={resolve:null,globals:{$stateParams:{}}},r={params:{},current:q.self,$current:q,transition:null},r.go=function(a,b,c){return this.transitionTo(a,b,y({inherit:!0,relative:r.$current},c))},r.transitionTo=function(b,c,f){s(f)||(f=f===!0||f===!1?{location:f}:{}),c=c||{},f=y({location:!0,inherit:!1,relative:null,broadcastStateChangeSuccess:!0,$retry:!1},f);var h,k=r.$current,p=r.params,x=k.path,A=i(b,f.relative);if(!s(A)){var C={to:b,toParams:c,options:f};if(h=a.$broadcast("$stateNotFound",C,k.self,p),h.defaultPrevented)return w;if(h.retry){if(f.$retry)return B;var D=r.transition=e.when(h.retry);return D.then(function(){return D!==r.transition?u:(C.options.$retry=!0,r.transitionTo(C.to,C.toParams,C.options))},function(){return w}),D}if(b=C.to,c=C.toParams,f=C.options,A=i(b,f.relative),!s(A)){if(f.relative)throw new Error("Could not resolve '"+b+"' from state '"+f.relative+"'");throw new Error("No such state '"+b+"'")}}if(A["abstract"])throw new Error("Cannot transition to abstract state '"+b+"'");f.inherit&&(c=g(l,c||{},r.$current,A)),b=A;var E,F,G=b.path,H=q.locals,I=[];for(E=0,F=G[E];F&&F===x[E]&&o(F,c,p,F.ownParams);E++,F=G[E])H=I[E]=F.locals;if(b===k&&H===k.locals)return r.transition=null,e.when(r.current);if(c=n(b.params,c||{}),h=a.$broadcast("$stateChangeStart",b.self,c,k.self,p),h.defaultPrevented)return v;for(var J=e.when(H),K=E;K=E;d--)g=x[d],g.self.onExit&&j.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=E;d").html(a).contents();return r.enter(d,c),d}},"false":{remove:function(a){a.html("")},restore:function(a,b){b.append(a)},populate:function(a,b){return b.html(a),b.contents()}}}[a.toString()]};h.append(k(e));var u=h.parent().inheritedData("$uiView");p.indexOf("@")<0&&(p=p+"@"+(u?u.state.name:""));var v={name:p,state:null};h.data("$uiView",v);var w=function(){if(!i){i=!0;try{m(!0)}catch(a){throw i=!1,a}i=!1}};e.$on("$stateChangeSuccess",w),e.$on("$viewContentLoading",w),m(!1)}}};return j}function p(a){var b=a.match(/^([^(]+?)\s*(\((.*)\))?$/);if(!b||4!==b.length)throw new Error("Invalid state ref '"+a+"'");return{state:b[1],paramExpr:b[3]||null}}function q(a){return{restrict:"A",link:function(b,c,d){var e=p(d.uiSref),f=null,g=a.$current,h="FORM"===c[0].nodeName,i=h?"action":"href",j=!0,k=c.parent().inheritedData("$uiView");k&&k.state&&k.state.name&&(g=k.state);var l=function(b){if(b&&(f=b),j){var d=a.href(e.state,f,{relative:g});return d?(c[0][i]=d,void 0):(j=!1,!1)}};e.paramExpr&&(b.$watch(e.paramExpr,function(a){a!==f&&l(a)},!0),f=b.$eval(e.paramExpr)),l(),h||c.bind("click",function(c){var d=c.which||c.button;1!=d||c.ctrlKey||c.metaKey||c.shiftKey||(a.go(e.state,f,{relative:g}),b.$apply(),c.preventDefault())})}}}function r(a,b){function e(a){this.locals=a.locals.globals,this.params=this.locals.$stateParams}function f(){this.locals=null,this.params=null}function g(c,g){if(null!=g.redirectTo){var h,j=g.redirectTo;if(u(j))h=j;else{if(!t(j))throw new Error("Invalid 'redirectTo' in when()");h=function(a,b){return j(a,b.path(),b.search())}}b.when(c,h)}else a.state(d(g,{parent:null,name:"route:"+encodeURIComponent(c),url:c,onEnter:e,onExit:f}));return i.push(g),this}function h(a,b,d){function e(a){return""!==a.name?a:c}var f={routes:i,params:d,current:c};return b.$on("$stateChangeStart",function(a,c,d,f){b.$broadcast("$routeChangeStart",e(c),e(f))}),b.$on("$stateChangeSuccess",function(a,c,d,g){f.current=e(c),b.$broadcast("$routeChangeSuccess",e(c),e(g)),z(d,f.params)}),b.$on("$stateChangeError",function(a,c,d,f,g,h){b.$broadcast("$routeChangeError",e(c),e(f),h)}),f}var i=[];e.$inject=["$$state"],this.when=g,this.$get=h,h.$inject=["$state","$rootScope","$routeParams"]}var s=b.isDefined,t=b.isFunction,u=b.isString,v=b.isObject,w=b.isArray,x=b.forEach,y=b.extend,z=b.copy;b.module("ui.router.util",["ng"]),b.module("ui.router.router",["ui.router.util"]),b.module("ui.router.state",["ui.router.router","ui.router.util"]),b.module("ui.router",["ui.router.state"]),b.module("ui.router.compat",["ui.router"]),h.$inject=["$q","$injector"],b.module("ui.router.util").service("$resolve",h),i.$inject=["$http","$templateCache","$injector"],b.module("ui.router.util").service("$templateFactory",i),j.prototype.concat=function(a){return new j(this.sourcePath+a+this.sourceSearch)},j.prototype.toString=function(){return this.source},j.prototype.exec=function(a,b){var c=this.regexp.exec(a),d=this.types,e=this.typeMap;if(!c)return null;var f,g=this.params,h=g.length,i=this.segments.length-1,j={};if(i!==c.length-1)throw new Error("Unbalanced capture group in route '"+this.source+"'");for(f=0;i>f;f++)j[g[f]]=c[f+1];for(;h>f;f++)j[g[f]]=b[g[f]];var k={};return x(j,function(a,b){k[b]=s(e[b])?d[e[b]].decode(a):a}),k},j.prototype.parameters=function(){return this.params},j.prototype.format=function(a){var b=this.segments,c=this.params,d=this.types,e=this.typeMap;if(!a)return b.join("");var f,g,h,i=b.length-1,j=c.length,k=b[0],l={};for(x(a,function(a,b){if(s(e[b])){var c=d[e[b]];c.is(a)&&(l[b]=c.encode(a))}else l[b]=a}),f=0;i>f;f++)h=l[c[f]],null!=h&&(k+=encodeURIComponent(h)),k+=b[f+1];for(;j>f;f++)h=l[c[f]],null!=h&&(k+=(g?"&":"?")+c[f]+"="+encodeURIComponent(h),g=!0);return k},j.prototype.types={"boolean":{pattern:"true|false",is:function(a){return a===!0||a===!1},equals:function(a,b){return this.is(a)&&this.is(b)?a===b:!1},encode:function(a){return a.toString().toLowerCase()},decode:function(a){return a&&"true"===a.toLowerCase()?!0:a&&"false"===a.toLowerCase()?!1:c}},integer:{pattern:"[0-9]+",is:function(a){return"number"==typeof a&&0===a%1},equals:function(a,b){return this.is(a)&&this.is(b)?a===b:!1},encode:function(a){return a.toString()},decode:function(a){return parseInt(a,10)}}},j.prototype.type=function(a,b){if(!b&&j.prototype.types[a])return j.prototype.types[a];if(!(u(a)&&v(b)&&t(b.decode)&&t(b.encode)))throw new Error("Invalid type '"+a+"'");u(b.pattern)||(b.pattern=".*"),t(b.is)||(b.is=function(a){return JSON.stringify(b.decode(b.encode(a)))===JSON.stringify(a)}),t(b.equals)||(b.equals=function(a,c){return b.is(a)&&b.is(c)?b.encode(a)===b.encode(c):!1}),j.prototype.types[a]=b},b.module("ui.router.util").provider("$urlMatcherFactory",k),l.$inject=["$urlMatcherFactoryProvider"],b.module("ui.router.router").provider("$urlRouter",l),m.$inject=["$urlRouterProvider","$urlMatcherFactoryProvider","$locationProvider"],b.module("ui.router.state").value("$stateParams",{}).provider("$state",m),n.$inject=[],b.module("ui.router.state").provider("$view",n),o.$inject=["$state","$compile","$controller","$injector","$anchorScroll"],b.module("ui.router.state").directive("uiView",o),q.$inject=["$state"],b.module("ui.router.state").directive("uiSref",q),r.$inject=["$stateProvider","$urlRouterProvider"],b.module("ui.router.compat").provider("$route",r).directive("ngView",o)}(window,window.angular); \ No newline at end of file diff --git a/release/doc/$resolve.html b/release/doc/$resolve.html index 224845b81..b224c56ac 100644 --- a/release/doc/$resolve.html +++ b/release/doc/$resolve.html @@ -118,40 +118,32 @@

resolve$q promise. If a promise is returned it will be resolved and the resulting value will be used instead. Dependencies of invocables are resolved (in this order of precedence)

-
  • from the specified locals
  • from another invocable that is part of this $resolve call
  • from an invocable that is inherited from a parent call to $resolve (or recursively from any ancestor $resolve of that parent).
-

The return value of $resolve is a promise for an object that contains (in this order of precedence)

-
  • any locals (if specified)
  • the resolved return values of all injectables
  • any values inherited from a parent call to $resolve (if specified)
-

The promise will resolve after the parent promise (if any) and all promises returned by injectables have been resolved. If any invocable (or $injector.invoke) throws an exception, or if a promise returned by an invocable is rejected, the $resolve promise is immediately rejected with the same error. A rejection of a parent promise (if specified) will likewise be propagated immediately. Once the $resolve promise has been rejected, no further invocables will be called.

-

Cyclic dependencies between invocables are not permitted and will caues $resolve to throw an error. As a special case, an injectable can depend on a parameter with the same name as the injectable, which will be fulfilled from the parent injectable of the same name. This allows inherited values to be decorated. Note that in this case any other injectable in the same $resolve with the same dependency would see the decorated value, not the inherited value.

-

Note that missing dependencies -- unlike cyclic dependencies -- will cause an (asynchronous) rejection of the $resolve promise rather than a (synchronous) exception.

-

Invocables are invoked eagerly as soon as all dependencies are available. This is true even for dependencies inherited from a parent call to $resolve.

-

As a special case, an invocable can be a string, in which case it is taken to be a service name to be passed to $injector.get(). This is supported primarily for backwards-compatibility with the resolve property of $routeProvider routes.

@@ -196,10 +188,7 @@
Parameters:
-Object.<string, Function -| - -string> +Object.<string, (Function|string)> @@ -210,6 +199,8 @@
Parameters:
+ + @@ -241,6 +232,8 @@
Parameters:
+ + @@ -272,6 +265,8 @@
Parameters:
+ + @@ -303,6 +298,8 @@
Parameters:
+ + @@ -352,6 +349,10 @@
Parameters:
+ + + + @@ -493,6 +494,10 @@
Parameters:
+ + + + @@ -541,9 +546,10 @@

Index

Modules

  • + - + \ No newline at end of file diff --git a/release/doc/$templateFactory.html b/release/doc/$templateFactory.html index f45308105..ab0b572a6 100644 --- a/release/doc/$templateFactory.html +++ b/release/doc/$templateFactory.html @@ -93,6 +93,10 @@

    new $ + + + + @@ -193,6 +197,8 @@

    Parameters:
    + + @@ -327,6 +333,8 @@
    Properties
    + + @@ -358,6 +366,8 @@
    Properties
    + + @@ -408,6 +418,10 @@
    Properties
    + + + + @@ -574,6 +588,10 @@
    Parameters:
    + + + + @@ -738,6 +756,10 @@
    Parameters:
    + + + + @@ -835,6 +857,8 @@
    Parameters:
    + + @@ -864,6 +888,8 @@
    Parameters:
    + + @@ -895,6 +921,8 @@
    Parameters:
    + + @@ -944,6 +972,10 @@
    Parameters:
    + + + + @@ -999,9 +1031,10 @@

    Index

    Modules

    • + - + \ No newline at end of file diff --git a/release/doc/$urlMatcherFactory.html b/release/doc/$urlMatcherFactory.html index f4fcf4cf5..5fd556a22 100644 --- a/release/doc/$urlMatcherFactory.html +++ b/release/doc/$urlMatcherFactory.html @@ -81,7 +81,7 @@

      new
      Source:
      • - urlMatcherFactory.js, line 219 + urlMatcherFactory.js, line 349
      @@ -94,6 +94,10 @@

      new + + + + @@ -212,7 +216,7 @@

      Parameters:
      Source:
      • - urlMatcherFactory.js, line 226 + urlMatcherFactory.js, line 356
      @@ -225,6 +229,10 @@
      Parameters:
      + + + + @@ -347,7 +355,7 @@
      Parameters:
      Source:
      • - urlMatcherFactory.js, line 238 + urlMatcherFactory.js, line 368
      @@ -360,6 +368,10 @@
      Parameters:
      + + + + @@ -408,9 +420,10 @@

      Index

      Modules

      • + - + \ No newline at end of file diff --git a/release/doc/UrlMatcher.html b/release/doc/UrlMatcher.html index 3c2faf125..caadec077 100644 --- a/release/doc/UrlMatcher.html +++ b/release/doc/UrlMatcher.html @@ -52,10 +52,8 @@

        new UrlMatc of search parameters. Multiple search parameter names are separated by '&'. Search parameters do not influence whether or not a URL is matched, but their values are passed through into the matched parameters returned by exec.

        -

        Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace syntax, which optionally allows a regular expression for the parameter to be specified:

        -
        • ':' name - colon placeholder
        • '*' name - catch-all placeholder
        • @@ -63,15 +61,12 @@

          new UrlMatc
        • '{' name ':' regexp '}' - curly placeholder with regexp. Should the regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
        -

        Parameter names may contain only word characters (latin letters, digits, and underscore) and must be unique within the pattern (across both path and search parameters). For colon placeholders or curly placeholders without an explicit regexp, a path parameter matches any number of characters other than '/'. For catch-all placeholders the path parameter matches any number of characters.

        -

        Examples

        -
        • '/hello/' - Matches only if the path is exactly '/hello/'. There is no special treatment for trailing slashes, and patterns have to match the entire path, not just a prefix.
        • @@ -231,6 +226,10 @@
          Properties:
          + + + + @@ -272,14 +271,10 @@

          concat -

          Example

          -

          The following two matchers are equivalent:

          - -
          new UrlMatcher('/user/{id}?q').concat('/details?date');
          -new UrlMatcher('/user/{id}/details?q&date');
          -
          +
          new UrlMatcher('/user/{id}?q').concat('/details?date');
          +new UrlMatcher('/user/{id}/details?q&date');
          @@ -360,7 +355,7 @@
          Parameters:
          Source:
          • - urlMatcherFactory.js, line 128 + urlMatcherFactory.js, line 133
          @@ -373,6 +368,10 @@
          Parameters:
          + + + + @@ -419,12 +418,9 @@

          exec
          new UrlMatcher('/user/{id}?q&r').exec('/user/bob', { x:'1', q:'hello' });
          -// returns { id:'bob', q:'hello', r:null }
          -
          +
          new UrlMatcher('/user/{id}?q&r').exec('/user/bob', { x:'1', q:'hello' });
          +// returns { id:'bob', q:'hello', r:null }
          @@ -528,7 +524,7 @@
          Parameters:
          Source:
          • - urlMatcherFactory.js, line 156 + urlMatcherFactory.js, line 161
          @@ -541,6 +537,10 @@
          Parameters:
          + + + + @@ -585,12 +585,9 @@

          formatCreates a URL that matches this pattern by substituting the specified values for the path and search parameters. Null values for path parameters are treated as empty strings.

          -

          Example

          - -
          new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
          -// returns '/user/bob?q=yes'
          -
          +
          new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
          +// returns '/user/bob?q=yes'
          @@ -671,7 +668,7 @@
          Parameters:
          Source:
          • - urlMatcherFactory.js, line 195 + urlMatcherFactory.js, line 213
          @@ -684,6 +681,10 @@
          Parameters:
          + + + + @@ -758,7 +759,7 @@

          parameters<
          Source:
          • - urlMatcherFactory.js, line 177 + urlMatcherFactory.js, line 195
          @@ -771,6 +772,10 @@

          parameters< + + + + @@ -800,6 +805,182 @@

          Returns:
          + + + + +
          +

          type(name, handler) → {Object}

          + + +
          +
          + + +
          +

          Registers a custom type for parameters or gets a handler for a registered type. +A handler object must include a decode function that decodes a string value +from the URL into the type, and a encode function that encodes the type into +a string value for the URL.

          +

          Example

          +
          // Register myType
          +.type('myType', {
          +   pattern: "[0-9]+",                       // (Optional) Regex pattern used to match the URL to this type.
          +   is : function (typeObj) {},              // (Optional) Determines if a param is of this type when saving to the URL.
          +   equals: function (typeObj, otherObj) {}, // (Optional) Determines if two objects of this type are equal.
          +   encode: function (typeObj) {},           // (Required) Encode this type to the URL.
          +   decode: function (value) {}              // (Required) Decode the URL segment to this type.
          + });
          +// Get myType
          +.type('myType');
          +
          + + + + + + + +
          Parameters:
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          NameTypeDescription
          name + + +string + + + +

          the name of the type to register or get.

          handler + + +Object + + + +

          the handler object with functions of working with this type.

          + + + +
          + + + + + + + + + + + + + + + + + + + +
          Source:
          +
          • + urlMatcherFactory.js, line 318 +
          + + + + + + + +
          + + + + + + + + + + + +
          Returns:
          + + +
          +

          the handler object.

          +
          + + + +
          +
          + Type +
          +
          + +Object + + +
          +
          + + + +
          @@ -824,9 +1005,10 @@

          Index

          Modules

          • + - + \ No newline at end of file diff --git a/release/doc/global.html b/release/doc/global.html index 2dd9004bd..e0c1d4398 100644 --- a/release/doc/global.html +++ b/release/doc/global.html @@ -216,6 +216,10 @@
            Parameters:
            + + + + @@ -421,6 +425,10 @@
            Parameters:
            + + + + @@ -451,9 +459,10 @@

            Index

            Modules

            • + - + \ No newline at end of file diff --git a/release/doc/index.html b/release/doc/index.html index 351a274a5..7f06bf759 100644 --- a/release/doc/index.html +++ b/release/doc/index.html @@ -54,9 +54,10 @@

              Index

              Modules

              • + - + \ No newline at end of file diff --git a/release/doc/scripts/linenumber.js b/release/doc/scripts/linenumber.js new file mode 100644 index 000000000..a0c570d5d --- /dev/null +++ b/release/doc/scripts/linenumber.js @@ -0,0 +1,17 @@ +(function() { + var counter = 0; + var numbered; + var source = document.getElementsByClassName('prettyprint source'); + + if (source && source[0]) { + source = source[0].getElementsByTagName('code')[0]; + + numbered = source.innerHTML.split('\n'); + numbered = numbered.map(function(item) { + counter++; + return '' + item; + }); + + source.innerHTML = numbered.join('\n'); + } +})(); From 92249d22aff8b532062e288bcab53fcb0543cb5c Mon Sep 17 00:00:00 2001 From: "Todd H. Gardner" Date: Tue, 24 Sep 2013 16:52:30 -0500 Subject: [PATCH 19/21] added simple typing to UrlMatch Contructor search params --- src/urlMatcherFactory.js | 17 ++++++++++++++++- test/urlMatcherFactorySpec.js | 10 ++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/urlMatcherFactory.js b/src/urlMatcherFactory.js index c592e8df9..4bc836232 100644 --- a/src/urlMatcherFactory.js +++ b/src/urlMatcherFactory.js @@ -102,7 +102,22 @@ function UrlMatcher(pattern) { this.sourcePath = pattern.substring(0, last+i); // Allow parameters to be separated by '?' as well as '&' to make concat() easier - forEach(search.substring(1).split(/[&?]/), addParameter); + var searchParams = search.substring(1).split(/[&?]/), j; + for(j=0;j Date: Wed, 25 Sep 2013 10:02:14 -0500 Subject: [PATCH 20/21] added anonymous type registration for search param regex --- src/urlMatcherFactory.js | 26 ++++++++++++++++++-------- test/urlMatcherFactorySpec.js | 16 +++++++++++++++- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/urlMatcherFactory.js b/src/urlMatcherFactory.js index 4bc836232..bb47a066a 100644 --- a/src/urlMatcherFactory.js +++ b/src/urlMatcherFactory.js @@ -60,7 +60,7 @@ function UrlMatcher(pattern) { names = {}, compiled = '^', last = 0, m, segments = this.segments = [], params = this.params = [], - typeMap = this.typeMap = {}; + type = this.type, types = this.types, typeMap = this.typeMap = {}; function addParameter(id) { if (!/^\w+(-+\w+)*$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'"); @@ -69,6 +69,15 @@ function UrlMatcher(pattern) { params.push(id); } + function registerAnonymousType(id, regexp) { + type('$'+id, { + pattern: regexp, + encode: function (typeObj) { return typeObj; }, + decode: function (value) { return value; } + }); + typeMap[id] = '$'+id; + } + function quoteRegExp(string) { return string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&"); } @@ -81,9 +90,9 @@ function UrlMatcher(pattern) { while ((m = placeholder.exec(pattern))) { id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null regexp = m[4] || (m[1] == '*' ? '.*' : '[^/]*'); - if (isDefined(this.types[regexp])) { - this.typeMap[id] = regexp; - regexp = this.types[regexp].pattern; // use the regexp defined for this type instead + if (isDefined(types[regexp])) { + typeMap[id] = regexp; + regexp = types[regexp].pattern; // use the regexp defined for this type instead } segment = pattern.substring(last, m.index); if (segment.indexOf('?') >= 0) break; // we're into the search part @@ -108,10 +117,11 @@ function UrlMatcher(pattern) { if ((m = placeholder.exec(searchParams[j]))) { id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null regexp = m[4] || (m[1] == '*' ? '.*' : '[^/]*'); - if (isDefined(this.types[regexp])) { - this.typeMap[id] = regexp; - // todo what if its really a regexp? - } + if (isDefined(types[regexp])) { + typeMap[id] = regexp; + } else { + registerAnonymousType(id, regexp); + } } else { id = searchParams[j]; diff --git a/test/urlMatcherFactorySpec.js b/test/urlMatcherFactorySpec.js index 0a9c22d12..2f52acf86 100644 --- a/test/urlMatcherFactorySpec.js +++ b/test/urlMatcherFactorySpec.js @@ -34,7 +34,21 @@ describe("UrlMatcher", function () { expect(params).toContain('from'); expect(params).toContain('to'); expect(typeMap.from).toBe('integer'); - expect(typeMap.to).toBe('integer'); + expect(typeMap.to).toBe('integer'); + }); + + it("parses search parameters with regex", function () { + var matcher = new UrlMatcher('/users?{from:[0-9]+}&{to:[0-9]+}'); + var params = matcher.parameters(); + var fromType = matcher.type('$from'); + var toType = matcher.type('$to'); + var typeMap = matcher.typeMap; + expect(params).toContain('from'); + expect(params).toContain('to'); + expect(fromType.pattern).toBe('[0-9]+'); + expect(toType.pattern).toBe('[0-9]+'); + expect(typeMap.from).toBe('$from'); + expect(typeMap.to).toBe('$to'); }); it("handles proper snake case parameter names", function(){ From 8271d0f5a2d9cf1d48b110691d15d109b3ee970c Mon Sep 17 00:00:00 2001 From: "Todd H. Gardner" Date: Wed, 25 Sep 2013 10:28:00 -0500 Subject: [PATCH 21/21] added pattern checking before decoding types in the exec() --- src/urlMatcherFactory.js | 24 +++++++++++++----------- test/urlMatcherFactorySpec.js | 11 +++++++++++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/urlMatcherFactory.js b/src/urlMatcherFactory.js index bb47a066a..efcf8009c 100644 --- a/src/urlMatcherFactory.js +++ b/src/urlMatcherFactory.js @@ -194,22 +194,24 @@ UrlMatcher.prototype.exec = function (path, searchParams) { nPath = this.segments.length-1, values = {}, i; - if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'"); - - for (i=0; i