From c8ca36a2fa0b87231d089f4511151f083349d70a Mon Sep 17 00:00:00 2001 From: Marty Field Date: Fri, 6 Dec 2013 00:41:49 -0800 Subject: [PATCH 1/2] feat(stateDirectives): specify options in ui-sref for $state calls --- src/stateDirectives.js | 57 +++++++++++++++++++------------------ test/stateDirectivesSpec.js | 45 +++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 27 deletions(-) diff --git a/src/stateDirectives.js b/src/stateDirectives.js index b617a0a65..9262a5974 100644 --- a/src/stateDirectives.js +++ b/src/stateDirectives.js @@ -1,7 +1,10 @@ function parseStateRef(ref) { - var parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/); - if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'"); - return { state: parsed[1], paramExpr: parsed[3] || null }; + var parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\(.*\))?\s*$/); + if (!parsed || parsed.length !== 3) throw new Error("Invalid state ref '" + ref + "'"); + + var state = parsed[1], + paramExpr = parsed[2]; + return '["' + state + '"].concat' + (paramExpr || '()'); } function stateContext(el) { @@ -18,43 +21,43 @@ function $StateRefDirective($state) { restrict: 'A', require: '?^uiSrefActive', link: function(scope, element, attrs, uiSrefActive) { - var ref = parseStateRef(attrs.uiSref); - var params = null, url = null, base = stateContext(element) || $state.$current; - var isForm = element[0].nodeName === "FORM"; - var attr = isForm ? "action" : "href", nav = true; - - var update = function(newVal) { - if (newVal) params = newVal; - if (!nav) return; + var ref = parseStateRef(attrs.uiSref), + params = [], + base = stateContext(element) || $state.$current, + isForm = element[0].nodeName === "FORM", + attr = isForm ? "action" : "href"; + + var defaults = { + relative: base + }; - var newHref = $state.href(ref.state, params, { relative: base }); + var update = function(newParams) { + params = [].concat(newParams); + params[1] = params[1] || {}; + params[2] = angular.extend({}, defaults, params[2] || {}); + var newHref = $state.href.apply($state, params); if (!newHref) { - nav = false; - return false; + return; } - element[0][attr] = newHref; + + attrs.$set(attr, newHref); + if (uiSrefActive) { - uiSrefActive.$$setStateInfo(ref.state, params); + uiSrefActive.$$setStateInfo.apply(uiSrefActive, params); } }; - if (ref.paramExpr) { - scope.$watch(ref.paramExpr, function(newVal, oldVal) { - if (newVal !== params) update(newVal); - }, true); - params = scope.$eval(ref.paramExpr); - } - update(); + scope.$watch(ref, update, true); if (isForm) return; element.bind("click", function(e) { var button = e.which || e.button; - if ((button === 0 || button == 1) && !e.ctrlKey && !e.metaKey && !e.shiftKey) { + if ((button === 0 || button === 1) && !e.ctrlKey && !e.metaKey && !e.shiftKey) { scope.$evalAsync(function() { - $state.go(ref.state, params, { relative: base }); + $state.go.apply($state, params); }); e.preventDefault(); } @@ -74,8 +77,8 @@ function $StateActiveDirective($state, $stateParams, $interpolate) { activeClass = $interpolate($attrs.uiSrefActive || '', false)($scope); // Allow uiSref to communicate with uiSrefActive - this.$$setStateInfo = function(newState, newParams) { - state = $state.get(newState, stateContext($element)); + this.$$setStateInfo = function(newState, newParams, options) { + state = $state.get(newState, options.relative || stateContext($element)); params = newParams; update(); }; diff --git a/test/stateDirectivesSpec.js b/test/stateDirectivesSpec.js index 5f71c2a79..b18dacf8d 100644 --- a/test/stateDirectivesSpec.js +++ b/test/stateDirectivesSpec.js @@ -148,6 +148,51 @@ describe('uiStateRef', function() { expect($state.current.name).toEqual(''); expect($stateParams).toEqual({ id: "5" }); })); + + it('should call $state.href with the provided options', inject(function($compile, $rootScope, $state) { + el = angular.element('Details'); + $rootScope.$index = 3; + $rootScope.$apply(); + + $compile(el)($rootScope); + $rootScope.$digest(); + expect(el.attr('href')).toBe('http://server/#/contacts/3'); + })); + + it('should call $state.href with the provided options when parameters change', inject(function($compile, $rootScope, $state) { + el = angular.element('Details'); + $rootScope.$index = 3; + $rootScope.$apply(); + + $compile(el)($rootScope); + $rootScope.$digest(); + expect(el.attr('href')).toBe('#/contacts/3'); + + $rootScope.myOptions = {absolute: true} + $rootScope.$digest(); + expect(el.attr('href')).toBe('http://server/#/contacts/3'); + })); + + it('should call $state.go with the provided options', inject(function($compile, $rootScope, $state) { + el = angular.element('Details'); + $compile(el)($rootScope); + $rootScope.shouldNotify = true; + $rootScope.$digest(); + + $rootScope.$on('$stateChangeStart', function(event) { + expect($rootScope.shouldNotify).toBe(true); + event.preventDefault(); + }); + + triggerClick(el); + $rootScope.$digest(); + + $rootScope.shouldNotify = false; + $rootScope.$digest(); + + triggerClick(el); + $rootScope.$digest(); + })); }); describe('forms', function() { From d80442f7177f20b45d932d762223f29b6de31189 Mon Sep 17 00:00:00 2001 From: Marty Field Date: Fri, 6 Dec 2013 09:36:22 -0800 Subject: [PATCH 2/2] Perhaps it's not all sunshine and rainbows. --- test/stateDirectivesSpec.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/test/stateDirectivesSpec.js b/test/stateDirectivesSpec.js index b18dacf8d..64d446644 100644 --- a/test/stateDirectivesSpec.js +++ b/test/stateDirectivesSpec.js @@ -149,7 +149,7 @@ describe('uiStateRef', function() { expect($stateParams).toEqual({ id: "5" }); })); - it('should call $state.href with the provided options', inject(function($compile, $rootScope, $state) { + it('should call $state.href with the provided options', inject(function($compile, $rootScope) { el = angular.element('Details'); $rootScope.$index = 3; $rootScope.$apply(); @@ -159,7 +159,7 @@ describe('uiStateRef', function() { expect(el.attr('href')).toBe('http://server/#/contacts/3'); })); - it('should call $state.href with the provided options when parameters change', inject(function($compile, $rootScope, $state) { + it('should call $state.href with the provided options when parameters change', inject(function($compile, $rootScope) { el = angular.element('Details'); $rootScope.$index = 3; $rootScope.$apply(); @@ -173,7 +173,7 @@ describe('uiStateRef', function() { expect(el.attr('href')).toBe('http://server/#/contacts/3'); })); - it('should call $state.go with the provided options', inject(function($compile, $rootScope, $state) { + it('should call $state.go with the provided options', inject(function($compile, $rootScope) { el = angular.element('Details'); $compile(el)($rootScope); $rootScope.shouldNotify = true; @@ -193,6 +193,27 @@ describe('uiStateRef', function() { triggerClick(el); $rootScope.$digest(); })); + + it('can be used for evil', inject(function($compile, $rootScope) { + $rootScope.doEvil = function() { + throw Error('doing some evil'); + } + + el = angular.element('Details'); + $compile(el)($rootScope); + expect($rootScope.$digest).toThrow(); + })); + + it('actually fixes #395, if you are a terrible human being', inject(function($compile, $rootScope) { + el = angular.element('Details'); + $compile(el)($rootScope); + + $rootScope.stateName = 'contacts.item.detail'; + $rootScope.stateParams = {id: 3}; + + $rootScope.$digest(); + expect(el.attr('href')).toBe('#/contacts/3'); + })); }); describe('forms', function() {