@@ -3,6 +3,99 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
33
44 var root , states = { } , $state ;
55
6+ // Builds state properties from definition passed to registerState()
7+ var stateBuilder = {
8+ // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
9+ // state.children = [];
10+ // if (parent) parent.children.push(state);
11+ parent : function ( state ) {
12+ if ( isDefined ( state . parent ) && state . parent ) return findState ( state . parent ) ;
13+ // regex matches any valid composite state name
14+ // would match "contact.list" but not "contacts"
15+ var compositeName = / ^ ( .+ ) \. [ ^ . ] + $ / . exec ( state . name ) ;
16+ return compositeName ? findState ( compositeName [ 1 ] ) : root ;
17+ } ,
18+
19+ // Build a URLMatcher if necessary, either via a relative or absolute URL
20+ url : function ( state ) {
21+ var url = state . url ;
22+
23+ if ( isString ( url ) ) {
24+ if ( url . charAt ( 0 ) == '^' ) {
25+ return $urlMatcherFactory . compile ( url . substring ( 1 ) ) ;
26+ }
27+ return ( state . parent . navigable || root ) . url . concat ( url ) ;
28+ }
29+ var isMatcher = (
30+ isObject ( url ) && isFunction ( url . exec ) && isFunction ( url . format ) && isFunction ( url . concat )
31+ ) ;
32+
33+ if ( isMatcher || url == null ) {
34+ return url ;
35+ }
36+ throw new Error ( "Invalid url '" + url + "' in state '" + state + "'" ) ;
37+ } ,
38+
39+ // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
40+ navigable : function ( state ) {
41+ return state . url ? state : ( state . parent ? state . parent . navigable : null ) ;
42+ } ,
43+
44+ // Derive parameters for this state and ensure they're a super-set of parent's parameters
45+ params : function ( state ) {
46+ if ( ! state . params ) {
47+ return state . url ? state . url . parameters ( ) : state . parent . params ;
48+ }
49+ if ( ! isArray ( state . params ) ) throw new Error ( "Invalid params in state '" + state + "'" ) ;
50+ if ( state . url ) throw new Error ( "Both params and url specicified in state '" + state + "'" ) ;
51+ return state . params ;
52+ } ,
53+
54+ // If there is no explicit multi-view configuration, make one up so we don't have
55+ // to handle both cases in the view directive later. Note that having an explicit
56+ // 'views' property will mean the default unnamed view properties are ignored. This
57+ // is also a good time to resolve view names to absolute names, so everything is a
58+ // straight lookup at link time.
59+ views : function ( state ) {
60+ var views = { } ;
61+
62+ forEach ( isDefined ( state . views ) ? state . views : { '' : state } , function ( view , name ) {
63+ if ( name . indexOf ( '@' ) < 0 ) name += '@' + state . parent . name ;
64+ views [ name ] = view ;
65+ } ) ;
66+ return views ;
67+ } ,
68+
69+ ownParams : function ( state ) {
70+ if ( ! state . parent ) {
71+ return state . params ;
72+ }
73+ var paramNames = { } ; forEach ( state . params , function ( p ) { paramNames [ p ] = true ; } ) ;
74+
75+ forEach ( state . parent . params , function ( p ) {
76+ if ( ! paramNames [ p ] ) {
77+ throw new Error ( "Missing required parameter '" + p + "' in state '" + state . name + "'" ) ;
78+ }
79+ paramNames [ p ] = false ;
80+ } ) ;
81+ var ownParams = [ ] ;
82+
83+ forEach ( paramNames , function ( own , p ) {
84+ if ( own ) ownParams . push ( p ) ;
85+ } ) ;
86+ return ownParams ;
87+ } ,
88+
89+ data : function ( state ) {
90+ // inherit 'data' from parent and override by own values (if any)
91+ if ( state . parent && state . parent . data ) {
92+ state . data = angular . extend ( { } , state . parent . data , state . data ) ;
93+ state . self . data = state . data ;
94+ }
95+ return state . data ;
96+ }
97+ } ;
98+
699 function findState ( stateOrName ) {
7100 var state ;
8101 if ( isString ( stateOrName ) ) {
@@ -20,102 +113,28 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
20113 // Wrap a new object around the state so we can store our private details easily.
21114 state = inherit ( state , {
22115 self : state ,
116+ resolve : state . resolve || { } ,
23117 toString : function ( ) { return this . name ; }
24118 } ) ;
25119
26120 var name = state . name ;
27121 if ( ! isString ( name ) || name . indexOf ( '@' ) >= 0 ) throw new Error ( "State must have a valid name" ) ;
28122 if ( states [ name ] ) throw new Error ( "State '" + name + "'' is already defined" ) ;
29123
30- // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
31- var parent = root ;
32- if ( ! isDefined ( state . parent ) ) {
33- // regex matches any valid composite state name
34- // would match "contact.list" but not "contacts"
35- var compositeName = / ^ ( .+ ) \. [ ^ . ] + $ / . exec ( name ) ;
36- if ( compositeName != null ) {
37- parent = findState ( compositeName [ 1 ] ) ;
38- }
39- } else if ( state . parent != null ) {
40- parent = findState ( state . parent ) ;
124+ for ( var key in stateBuilder ) {
125+ state [ key ] = stateBuilder [ key ] ( state ) ;
41126 }
42- state . parent = parent ;
43- // inherit 'data' from parent and override by own values (if any)
44- if ( state . parent && state . parent . data ) {
45- state . data = angular . extend ( { } , state . parent . data , state . data ) ;
46- state . self . data = state . data ;
47- }
48- // state.children = [];
49- // if (parent) parent.children.push(state);
50-
51- // Build a URLMatcher if necessary, either via a relative or absolute URL
52- var url = state . url ;
53- if ( isString ( url ) ) {
54- if ( url . charAt ( 0 ) == '^' ) {
55- url = state . url = $urlMatcherFactory . compile ( url . substring ( 1 ) ) ;
56- } else {
57- url = state . url = ( parent . navigable || root ) . url . concat ( url ) ;
58- }
59- } else if ( isObject ( url ) &&
60- isFunction ( url . exec ) && isFunction ( url . format ) && isFunction ( url . concat ) ) {
61- /* use UrlMatcher (or compatible object) as is */
62- } else if ( url != null ) {
63- throw new Error ( "Invalid url '" + url + "' in state '" + state + "'" ) ;
64- }
65-
66- // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
67- state . navigable = url ? state : parent ? parent . navigable : null ;
68-
69- // Derive parameters for this state and ensure they're a super-set of parent's parameters
70- var params = state . params ;
71- if ( params ) {
72- if ( ! isArray ( params ) ) throw new Error ( "Invalid params in state '" + state + "'" ) ;
73- if ( url ) throw new Error ( "Both params and url specicified in state '" + state + "'" ) ;
74- } else {
75- params = state . params = url ? url . parameters ( ) : state . parent . params ;
76- }
77-
78- var paramNames = { } ; forEach ( params , function ( p ) { paramNames [ p ] = true ; } ) ;
79- if ( parent ) {
80- forEach ( parent . params , function ( p ) {
81- if ( ! paramNames [ p ] ) {
82- throw new Error ( "Missing required parameter '" + p + "' in state '" + name + "'" ) ;
83- }
84- paramNames [ p ] = false ;
85- } ) ;
86-
87- var ownParams = state . ownParams = [ ] ;
88- forEach ( paramNames , function ( own , p ) {
89- if ( own ) ownParams . push ( p ) ;
90- } ) ;
91- } else {
92- state . ownParams = params ;
93- }
94-
95- // If there is no explicit multi-view configuration, make one up so we don't have
96- // to handle both cases in the view directive later. Note that having an explicit
97- // 'views' property will mean the default unnamed view properties are ignored. This
98- // is also a good time to resolve view names to absolute names, so everything is a
99- // straight lookup at link time.
100- var views = { } ;
101- forEach ( isDefined ( state . views ) ? state . views : { '' : state } , function ( view , name ) {
102- if ( name . indexOf ( '@' ) < 0 ) name = name + '@' + state . parent . name ;
103- views [ name ] = view ;
104- } ) ;
105- state . views = views ;
106127
107128 // Keep a full path from the root down to this state as this is needed for state activation.
108- state . path = parent ? parent . path . concat ( state ) : [ ] ; // exclude root from path
129+ state . path = state . parent ? state . parent . path . concat ( state ) : [ ] ; // exclude root from path
109130
110131 // Speed up $state.contains() as it's used a lot
111- var includes = state . includes = parent ? extend ( { } , parent . includes ) : { } ;
132+ var includes = state . includes = state . parent ? extend ( { } , state . parent . includes ) : { } ;
112133 includes [ name ] = true ;
113134
114- if ( ! state . resolve ) state . resolve = { } ; // prevent null checks later
115-
116135 // Register the state in the global state list and with $urlRouter if necessary.
117- if ( ! state [ 'abstract' ] && url ) {
118- $urlRouterProvider . when ( url , [ '$match' , '$stateParams' , function ( $match , $stateParams ) {
136+ if ( ! state [ 'abstract' ] && state . url ) {
137+ $urlRouterProvider . when ( state . url , [ '$match' , '$stateParams' , function ( $match , $stateParams ) {
119138 if ( $state . $current . navigable != state || ! equalForKeys ( $match , $stateParams ) ) {
120139 $state . transitionTo ( state , $match , false ) ;
121140 }
@@ -194,8 +213,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
194213 toParams = normalize ( to . params , toParams || { } ) ;
195214
196215 // Broadcast start event and cancel the transition if requested
197- if ( $rootScope . $broadcast ( '$stateChangeStart' , to . self , toParams , from . self , fromParams )
198- . defaultPrevented ) return TransitionPrevented ;
216+ var evt = $rootScope . $broadcast ( '$stateChangeStart' , to . self , toParams , from . self , fromParams ) ;
217+ if ( evt . defaultPrevented ) return TransitionPrevented ;
199218
200219 // Resolve locals for the remaining states, but don't update any global state just
201220 // yet -- if anything fails to resolve the current state needs to remain untouched.
0 commit comments