diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1242b88 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +# 1.3.0 (2014-05-15) + +## Features + +- New function `$routeSegment.getSegmentUrl(segmentName, routeParams)` which can return URL for the given segment ([2b255](https://github.com/artch/angular-route-segment/commit/2b255db63b7273be9f0c75b19c464620835db9b9)). +- Some handy filters like `routeSegmentUrl`,`routeSegmentEqualsTo`, etc ([2b255](https://github.com/artch/angular-route-segment/commit/2b255db63b7273be9f0c75b19c464620835db9b9)). +- New segment config option `default:true` which can be set if this child segment should be loaded by default when no child segment is specified in the route ([2eee0](https://github.com/artch/angular-route-segment/commit/2eee0a1dc7d6a6ff031d8451c06d4da5ae7e50fc)). +- `template` and `templateUrl` can be set as injectable functions ([8d1ac](https://github.com/artch/angular-route-segment/commit/8d1ac0d1ea1aee9243f90e32e4e4387a717049ac)). + +See updated [docs](https://github.com/artch/angular-route-segment/blob/master/README.md) and [demo example](http://angular-route-segment.com/src/example/) for usage. + +## Bug fixes + +- Fixed a bug when reloading a segment does not recover after the resolving error has gone ([ed11d](https://github.com/artch/angular-route-segment/commit/ed11d58e495ea7c611a59373fd6b909de1be33e3)). + + +# 1.2.4 (2014-05-08) + +- Fixed a bug with null exception on `contains` function ([1b014](https://github.com/artch/angular-route-segment/commit/1b014d3b5ea7740815c7e0b98467bdff556e6a5b) thanks to [paivaric](https://github.com/paivaric)). + +# 1.2.3 (2014-04-07) + +- Fixed a bug with double updates of view segments ([eb0d8](https://github.com/artch/angular-route-segment/commit/eb0d8a0c4aa01c2d8ab600aacef69e4a5479afd6)). +- `options.autoLoadTemplates` is true by default ([afab3](https://github.com/artch/angular-route-segment/commit/afab3ae7b827b7141ebcf0b8130311dc5aac0d7d)). \ No newline at end of file diff --git a/bower.json b/bower.json index 9201e65..87e472e 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-route-segment", - "version": "1.2.4", + "version": "1.3.0", "main": "build/angular-route-segment.js", "ignore": [ "**/.*", diff --git a/build/angular-route-segment.js b/build/angular-route-segment.js index 2f7cae4..785932d 100644 --- a/build/angular-route-segment.js +++ b/build/angular-route-segment.js @@ -1,5 +1,5 @@ /** - * angular-route-segment 1.2.3 + * angular-route-segment 1.3.0 * https://angular-route-segment.com * @author Artem Chivchalov * @license MIT License http://opensource.org/licenses/MIT @@ -8,7 +8,8 @@ (function(angular) { -angular.module( 'route-segment', [] ).provider( '$routeSegment', +var mod = angular.module( 'route-segment', [] ); +mod.provider( '$routeSegment', ['$routeProvider', function($routeProvider) { var $routeSegmentProvider = this; @@ -32,7 +33,8 @@ angular.module( 'route-segment', [] ).provider( '$routeSegment', }; var segments = this.segments = {}, - rootPointer = pointer(segments, null); + rootPointer = pointer(segments, null), + segmentRoutes = {}; function camelCase(name) { return name.replace(/([\:\-\_]+(.))/g, function(_, separator, letter, offset) { @@ -127,6 +129,7 @@ angular.module( 'route-segment', [] ).provider( '$routeSegment', */ $routeSegmentProvider.when = function(route, name) { $routeProvider.when(route, {segment: name}); + segmentRoutes[name] = route; return this; }; @@ -186,6 +189,31 @@ angular.module( 'route-segment', [] ).provider( '$routeSegment', if(this.chain[i] && this.chain[i].name == val) return true; return false; + }, + + /** + * A method for reverse routing which can return the route URL for the specified segment name + * @param {string} segmentName The name of a segment as defined in `when()` + * @param {Object} routeParams Route params hash to be put into route URL template + */ + getSegmentUrl: function(segmentName, routeParams) { + var url, i, m; + if(!segmentRoutes[segmentName]) + throw new Error('Can not get URL for segment with name `'+segmentName+'`'); + + routeParams = angular.extend({}, $routeParams, routeParams || {}); + + url = segmentRoutes[segmentName]; + for(i in routeParams) { + var regexp = new RegExp('\:'+i+'[\*\?]?','g'); + url = url.replace(regexp, routeParams[i]); + } + url = url.replace(/\/\:.*?\?/g, ''); + + if(m = url.match(/\/\:([^\/]*)/)) + throw new Error('Route param `'+m[1]+'` is not specified for route `'+segmentRoutes[segmentName]+'`'); + + return url; } }; @@ -199,7 +227,7 @@ angular.module( 'route-segment', [] ).provider( '$routeSegment', var segmentName = route.segment; var segmentNameChain = segmentName.split("."); - var updates = []; + var updates = [], lastUpdateIndex = -1; for(var i=0; i < segmentNameChain.length; i++) { @@ -211,8 +239,10 @@ angular.module( 'route-segment', [] ).provider( '$routeSegment', updates.length == 0 && !isDependenciesChanged(newSegment)) // if we went back to the same state as we were before resolving new segment resolvingSemaphoreChain[i] = newSegment.name; - else + else { updates.push({index: i, newSegment: newSegment}); + lastUpdateIndex = i; + } } } @@ -239,17 +269,12 @@ angular.module( 'route-segment', [] ).provider( '$routeSegment', updateSegment(j, null); } } - - } }) })(i); } - } - - curSegmentPromise.then(function() { // Removing redundant segment in case if new segment chain is shorter than old one @@ -257,13 +282,41 @@ angular.module( 'route-segment', [] ).provider( '$routeSegment', var oldLength = $routeSegment.chain.length; var shortenBy = $routeSegment.chain.length - segmentNameChain.length; $routeSegment.chain.splice(-shortenBy, shortenBy); - for(var i=segmentNameChain.length; i < oldLength; i++) + for(var i=segmentNameChain.length; i < oldLength; i++) { updateSegment(i, null); + lastUpdateIndex = $routeSegment.chain.length-1; + } + } + }).then(function() { + + var defaultChildUpdatePromise = $q.when(); + + if(lastUpdateIndex == $routeSegment.chain.length-1) { + + var curSegment = getSegmentInChain(lastUpdateIndex, $routeSegment.name.split(".")); + + while(curSegment) { + var children = curSegment.children, index = lastUpdateIndex+1; + curSegment = null; + for (var i in children) { + (function(i, children, index) { + if (children[i].params.default) { + defaultChildUpdatePromise = defaultChildUpdatePromise.then(function () { + return updateSegment(index, {name: i, params: children[i].params}) + .then(function (result) { + if (result.success) broadcast(result.success); + }); + }); + curSegment = children[i]; + lastUpdateIndex = index; + } + })(i, children, index); + } + } } - }) - - + return defaultChildUpdatePromise; + }); } }); @@ -312,15 +365,25 @@ angular.module( 'route-segment', [] ).provider( '$routeSegment', locals[key] = angular.isString(value) ? $injector.get(value) : $injector.invoke(value); }); - if(params.template) + if(params.template) { + locals.$template = params.template; + if(angular.isFunction(locals.$template)) + locals.$template = $injector.invoke(locals.$template); + } - if(options.autoLoadTemplates && params.templateUrl) - locals.$template = - $http.get(params.templateUrl, {cache: $templateCache}) - .then(function(response) { + if(options.autoLoadTemplates && params.templateUrl) { + + locals.$template = params.templateUrl; + if(angular.isFunction(locals.$template)) + locals.$template = $injector.invoke(locals.$template); + + locals.$template = + $http.get(locals.$template, {cache: $templateCache}) + .then(function (response) { return response.data; }); + } return $q.all(locals).then( @@ -334,7 +397,8 @@ angular.module( 'route-segment', [] ).provider( '$routeSegment', params: params, locals: resolvedLocals, reload: function() { - updateSegment(index, this).then(function(result) { + var originalSegment = getSegmentInChain(index, $routeSegment.name.split(".")); + updateSegment(index, originalSegment).then(function(result) { if(result.success != undefined) broadcast(index); }) @@ -417,13 +481,65 @@ angular.module( 'route-segment', [] ).provider( '$routeSegment', return { name: nextName, - params: curSegment.params + params: curSegment.params, + children: curSegment.children }; } return $routeSegment; }]; -}]) +}]); + +/** + * Usage: + * + * + */ +mod.filter('routeSegmentUrl', ['$routeSegment', function($routeSegment) { + return function(segmentName, params) { + return $routeSegment.getSegmentUrl(segmentName, params); + } +}]); + +/** + * Usage: + *
  • + */ +mod.filter('routeSegmentEqualsTo', ['$routeSegment', function($routeSegment) { + return function(value) { + return $routeSegment.name == value; + } +}]); + +/** + * Usage: + *
  • + */ +mod.filter('routeSegmentStartsWith', ['$routeSegment', function($routeSegment) { + return function(value) { + return $routeSegment.startsWith(value); + } +}]); + +/** + * Usage: + *
  • + */ +mod.filter('routeSegmentContains', ['$routeSegment', function($routeSegment) { + return function(value) { + return $routeSegment.contains(value); + } +}]); + +/** + * Usage: + *
  • + */ +mod.filter('routeSegmentParam', ['$routeSegment', function($routeSegment) { + return function(value) { + return $routeSegment.$routeParams[value]; + } +}]); })(angular);;'use strict'; diff --git a/build/angular-route-segment.min.js b/build/angular-route-segment.min.js index 38f1c50..0c7dc8b 100644 --- a/build/angular-route-segment.min.js +++ b/build/angular-route-segment.min.js @@ -1,7 +1,7 @@ /** - * angular-route-segment 1.2.3 + * angular-route-segment 1.3.0 * https://angular-route-segment.com * @author Artem Chivchalov * @license MIT License http://opensource.org/licenses/MIT */ -"use strict";!function(a){a.module("route-segment",[]).provider("$routeSegment",["$routeProvider",function(b){function c(a){return a.replace(/([\:\-\_]+(.))/g,function(a,b,c,d){return d?c.toUpperCase():c})}function d(a,b){if(!a)throw new Error("Invalid pointer segment");var e;return{segment:function(b,d){return a[c(b)]={params:d},e=b,this},within:function(b){var g;if(b=b||e,g=a[c(b)])void 0==g.children&&(g.children={});else{if(f.strictMode)throw new Error("Cannot get into unknown `"+b+"` segment");g=a[c(b)]={params:{},children:{}}}return d(g.children,this)},up:function(){return b},root:function(){return h}}}var e=this,f=e.options={autoLoadTemplates:!0,strictMode:!1},g=this.segments={},h=d(g,null);e.when=function(a,c){return b.when(a,{segment:c}),this},a.extend(e,h),this.$get=["$rootScope","$q","$http","$templateCache","$route","$routeParams","$injector",function(b,d,e,h,i,j,k){function l(b){var c=!1;return b.params.dependencies&&a.forEach(b.params.dependencies,function(b){a.equals(q.$routeParams[b],j[b])||(c=!0)}),c}function m(a,b){return q.chain[a]&&q.chain[a].clearWatcher&&q.chain[a].clearWatcher(),b?(r[a]=b.name,b.params.untilResolved?n(a,b.name,b.params.untilResolved).then(function(c){return void 0!=c.success&&o(a),n(a,b.name,b.params)}):n(a,b.name,b.params)):(r[a]=null,o(a),void 0)}function n(c,g,i){var j=a.extend({},i.resolve);return a.forEach(j,function(b,c){j[c]=a.isString(b)?k.get(b):k.invoke(b)}),i.template&&(j.$template=i.template),f.autoLoadTemplates&&i.templateUrl&&(j.$template=e.get(i.templateUrl,{cache:h}).then(function(a){return a.data})),d.all(j).then(function(e){if(r[c]!=g)return d.reject();if(q.chain[c]={name:g,params:i,locals:e,reload:function(){m(c,this).then(function(a){void 0!=a.success&&o(c)})}},i.watcher){var f=function(){if(!a.isFunction(i.watcher))throw new Error("Watcher is not a function in segment `"+g+"`");return k.invoke(i.watcher,{},{segment:q.chain[c]})},h=f();q.chain[c].clearWatcher=b.$watch(f,function(a){a!=h&&(h=a,q.chain[c].reload())})}return{success:c}},function(b){if(i.resolveFailed){var e={error:function(){return d.when(b)}};return n(c,g,a.extend({resolve:e},i.resolveFailed))}throw new Error("Resolving failed with a reason `"+b+"`, but no `resolveFailed` "+"provided for segment `"+g+"`")})}function o(c){q.$routeParams=a.copy(j),q.name="";for(var d=0;d=b.length)return null;for(var d,e=g,f=0;a>=f;f++)d=b[f],void 0!=e[c(d)]&&(e=e[c(d)]),a>f&&(e=e.children);return{name:d,params:e.params}}var q={name:"",$routeParams:a.copy(j),chain:[],startsWith:function(a){var b=new RegExp("^"+a);return b.test(q.name)},contains:function(a){for(var b=0;b0||l(i))&&(q.chain[h]&&q.chain[h].name==i.name&&0==g.length&&!l(i)?r[h]=i.name:g.push({index:h,newSegment:i}))}var j=d.when();if(g.length>0)for(var h=0;hf.length){var a=q.chain.length,b=q.chain.length-f.length;q.chain.splice(-b,b);for(var c=f.length;a>c;c++)m(c,null)}})}}),q}]}])}(angular),function(a){a.module("view-segment",["route-segment"]).directive("appViewSegment",["$route","$compile","$controller","$routeParams","$routeSegment","$q","$injector","$timeout",function(b,c,d,e,f,g,h,i){return{restrict:"ECA",priority:500,compile:function(b,e){var g=b.html(),j=!0,k=a.element(document.createComment(" view-segment "));return b.prepend(k),function(l){function m(){p&&(r.leave(p),p=null),o&&(o.$destroy(),o=null)}function n(e){if(q=e,j&&(j=!1,b.replaceWith(k)),!e)return m(),p=b.clone(),p.html(g),r.enter(p,null,k),c(p,!1,499)(l),void 0;var f=a.extend({},e.locals),h=f&&f.$template;m(),p=b.clone(),p.html(h?h:g),r.enter(p,null,k);var i,n=c(p,!1,499);o=l.$new(),e.params.controller&&(f.$scope=o,i=d(e.params.controller,f),e.params.controllerAs&&(o[e.params.controllerAs]=i),p.data("$ngControllerController",i),p.children().data("$ngControllerController",i)),n(o),o.$emit("$viewContentLoaded"),o.$eval(t)}var o,p,q,r,s,t=e.onload||"",u=parseInt(e.appViewSegment);try{var v=h.get("$animator");r=v(l,e)}catch(w){}try{r=h.get("$animate")}catch(w){}f.chain[u]&&(s=i(function(){n(f.chain[u])},0)),l.$on("routeSegmentChange",function(a,b){s&&i.cancel(s),b.index==u&&q!=b.segment&&n(b.segment)})}}}}])}(angular); \ No newline at end of file +"use strict";!function(a){var b=a.module("route-segment",[]);b.provider("$routeSegment",["$routeProvider",function(b){function c(a){return a.replace(/([\:\-\_]+(.))/g,function(a,b,c,d){return d?c.toUpperCase():c})}function d(a,b){if(!a)throw new Error("Invalid pointer segment");var e;return{segment:function(b,d){return a[c(b)]={params:d},e=b,this},within:function(b){var g;if(b=b||e,g=a[c(b)])void 0==g.children&&(g.children={});else{if(f.strictMode)throw new Error("Cannot get into unknown `"+b+"` segment");g=a[c(b)]={params:{},children:{}}}return d(g.children,this)},up:function(){return b},root:function(){return h}}}var e=this,f=e.options={autoLoadTemplates:!0,strictMode:!1},g=this.segments={},h=d(g,null),i={};e.when=function(a,c){return b.when(a,{segment:c}),i[c]=a,this},a.extend(e,h),this.$get=["$rootScope","$q","$http","$templateCache","$route","$routeParams","$injector",function(b,d,e,h,j,k,l){function m(b){var c=!1;return b.params.dependencies&&a.forEach(b.params.dependencies,function(b){a.equals(r.$routeParams[b],k[b])||(c=!0)}),c}function n(a,b){return r.chain[a]&&r.chain[a].clearWatcher&&r.chain[a].clearWatcher(),b?(s[a]=b.name,b.params.untilResolved?o(a,b.name,b.params.untilResolved).then(function(c){return void 0!=c.success&&p(a),o(a,b.name,b.params)}):o(a,b.name,b.params)):(s[a]=null,void p(a))}function o(c,g,i){var j=a.extend({},i.resolve);return a.forEach(j,function(b,c){j[c]=a.isString(b)?l.get(b):l.invoke(b)}),i.template&&(j.$template=i.template,a.isFunction(j.$template)&&(j.$template=l.invoke(j.$template))),f.autoLoadTemplates&&i.templateUrl&&(j.$template=i.templateUrl,a.isFunction(j.$template)&&(j.$template=l.invoke(j.$template)),j.$template=e.get(j.$template,{cache:h}).then(function(a){return a.data})),d.all(j).then(function(e){if(s[c]!=g)return d.reject();if(r.chain[c]={name:g,params:i,locals:e,reload:function(){var a=q(c,r.name.split("."));n(c,a).then(function(a){void 0!=a.success&&p(c)})}},i.watcher){var f=function(){if(!a.isFunction(i.watcher))throw new Error("Watcher is not a function in segment `"+g+"`");return l.invoke(i.watcher,{},{segment:r.chain[c]})},h=f();r.chain[c].clearWatcher=b.$watch(f,function(a){a!=h&&(h=a,r.chain[c].reload())})}return{success:c}},function(b){if(i.resolveFailed){var e={error:function(){return d.when(b)}};return o(c,g,a.extend({resolve:e},i.resolveFailed))}throw new Error("Resolving failed with a reason `"+b+"`, but no `resolveFailed` provided for segment `"+g+"`")})}function p(c){r.$routeParams=a.copy(k),r.name="";for(var d=0;d=b.length)return null;for(var d,e=g,f=0;a>=f;f++)d=b[f],void 0!=e[c(d)]&&(e=e[c(d)]),a>f&&(e=e.children);return{name:d,params:e.params,children:e.children}}var r={name:"",$routeParams:a.copy(k),chain:[],startsWith:function(a){var b=new RegExp("^"+a);return b.test(r.name)},contains:function(a){for(var b=0;b0||m(j))&&(r.chain[i]&&r.chain[i].name==j.name&&0==g.length&&!m(j)?s[i]=j.name:(g.push({index:i,newSegment:j}),h=i))}var k=d.when();if(g.length>0)for(var i=0;if.length){var a=r.chain.length,b=r.chain.length-f.length;r.chain.splice(-b,b);for(var c=f.length;a>c;c++)n(c,null),h=r.chain.length-1}}).then(function(){var a=d.when();if(h==r.chain.length-1)for(var b=q(h,r.name.split("."));b;){var c=b.children,e=h+1;b=null;for(var f in c)!function(c,d,e){d[c].params.default&&(a=a.then(function(){return n(e,{name:c,params:d[c].params}).then(function(a){a.success&&p(a.success)})}),b=d[c],h=e)}(f,c,e)}return a})}}),r}]}]),b.filter("routeSegmentUrl",["$routeSegment",function(a){return function(b,c){return a.getSegmentUrl(b,c)}}]),b.filter("routeSegmentEqualsTo",["$routeSegment",function(a){return function(b){return a.name==b}}]),b.filter("routeSegmentStartsWith",["$routeSegment",function(a){return function(b){return a.startsWith(b)}}]),b.filter("routeSegmentContains",["$routeSegment",function(a){return function(b){return a.contains(b)}}]),b.filter("routeSegmentParam",["$routeSegment",function(a){return function(b){return a.$routeParams[b]}}])}(angular),function(a){a.module("view-segment",["route-segment"]).directive("appViewSegment",["$route","$compile","$controller","$routeParams","$routeSegment","$q","$injector","$timeout",function(b,c,d,e,f,g,h,i){return{restrict:"ECA",priority:500,compile:function(b,e){var g=b.html(),j=!0,k=a.element(document.createComment(" view-segment "));return b.prepend(k),function(l){function m(){p&&(r.leave(p),p=null),o&&(o.$destroy(),o=null)}function n(e){if(q=e,j&&(j=!1,b.replaceWith(k)),!e)return m(),p=b.clone(),p.html(g),r.enter(p,null,k),void c(p,!1,499)(l);var f=a.extend({},e.locals),h=f&&f.$template;m(),p=b.clone(),p.html(h?h:g),r.enter(p,null,k);var i,n=c(p,!1,499);o=l.$new(),e.params.controller&&(f.$scope=o,i=d(e.params.controller,f),e.params.controllerAs&&(o[e.params.controllerAs]=i),p.data("$ngControllerController",i),p.children().data("$ngControllerController",i)),n(o),o.$emit("$viewContentLoaded"),o.$eval(t)}var o,p,q,r,s,t=e.onload||"",u=parseInt(e.appViewSegment);try{var v=h.get("$animator");r=v(l,e)}catch(w){}try{r=h.get("$animate")}catch(w){}f.chain[u]&&(s=i(function(){n(f.chain[u])},0)),l.$on("routeSegmentChange",function(a,b){s&&i.cancel(s),b.index==u&&q!=b.segment&&n(b.segment)})}}}}])}(angular); \ No newline at end of file diff --git a/package.json b/package.json index c670b20..8a4cc0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-route-segment", - "version": "1.2.4", + "version": "1.3.0", "dependencies": { "grunt": "", "grunt-contrib-uglify": "",