Skip to content

Commit 88b8fd1

Browse files
committed
Refactoring 'registerState()' into 'stateBuilder'.
- Adding tests - Adding comments
1 parent 8941fbf commit 88b8fd1

File tree

3 files changed

+117
-83
lines changed

3 files changed

+117
-83
lines changed

src/state.js

Lines changed: 102 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -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.

src/viewDirective.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ function $ViewDirective( $state, $compile, $controller, $injector, $an
1717
onloadExp = attr.onload || '',
1818
animate = isDefined($animator) && $animator(scope, attr);
1919

20+
// Returns a set of DOM manipulation functions based on whether animation
21+
// should be performed
2022
var renderer = function(doAnimate) {
2123
return ({
2224
"true": {

test/stateSpec.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,4 +344,17 @@ describe('state', function () {
344344
expect($state.params).toEqual({ person: "larry" });
345345
}));
346346
});
347+
348+
describe('default properties', function () {
349+
it('should always have a name', inject(function ($state, $q) {
350+
$state.transitionTo(A);
351+
$q.flush();
352+
expect($state.$current.name).toBe('A');
353+
expect($state.$current.toString()).toBe('A');
354+
}));
355+
356+
it('should always have a resolve object', inject(function ($state) {
357+
expect($state.$current.resolve).toEqual({});
358+
}));
359+
});
347360
});

0 commit comments

Comments
 (0)