Skip to content

Commit d539202

Browse files
committed
Multiple improvements
Support for specifying state.params explicitly Pass stateParams to $stateChange* events Support preventDefault on $stateChangeStart (#14) Also more tests (#41)
1 parent 5c108d1 commit d539202

File tree

4 files changed

+217
-68
lines changed

4 files changed

+217
-68
lines changed

LICENSE

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
The MIT License
22

33
Copyright (c) 2013 Foxtrot Media Ltd, http://foxtrotmedia.co.nz/
4+
Copyright (c) 2013 Karsten Sperling
45

56
Permission is hereby granted, free of charge, to any person obtaining a copy
67
of this software and associated documentation files (the "Software"), to deal

src/common.js

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var isDefined = angular.isDefined,
66
isFunction = angular.isFunction,
77
isString = angular.isString,
88
isObject = angular.isObject,
9+
isArray = angular.isArray,
910
forEach = angular.forEach,
1011
extend = angular.extend,
1112
copy = angular.copy;

src/state.js

+46-22
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
2424
});
2525

2626
var name = state.name;
27-
if (!isString(name)) throw new Error("State must have a name");
27+
if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name");
2828
if (states[name]) throw new Error("State '" + name + "'' is already defined");
2929

3030
// Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
@@ -59,12 +59,21 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
5959
// Keep track of the closest ancestor state that has a URL (i.e. is navigable)
6060
state.navigable = state.url ? state : parent ? parent.navigable : null;
6161

62-
// Figure out the parameters for this state and ensure they're a super-set of parent's parameters
63-
var params = state.params = url ? url.parameters() : state.parent.params;
62+
// Derive parameters for this state and ensure they're a super-set of parent's parameters
63+
var params = state.params;
64+
if (params) {
65+
if (!isArray(params)) throw new Error("Invalid params in state '" + state + "'");
66+
if (url) throw new Error("Both params and url specicified in state '" + state + "'");
67+
} else {
68+
params = state.params = url ? url.parameters() : state.parent.params;
69+
}
70+
6471
var paramNames = {}; forEach(params, function (p) { paramNames[p] = true; });
6572
if (parent) {
6673
forEach(parent.params, function (p) {
67-
if (!paramNames[p]) throw new Error("State '" + name + "' does not define parameter '" + p + "'");
74+
if (!paramNames[p]) {
75+
throw new Error("Missing required parameter '" + p + "' in state '" + name + "'");
76+
}
6877
paramNames[p] = false;
6978
});
7079

@@ -148,6 +157,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
148157
}
149158
};
150159

160+
var TransitionSuperseded = $q.reject(new Error('transition superseded'));
161+
var TransitionPrevented = $q.reject(new Error('transition prevented'));
162+
151163
function transitionTo(to, toParams, updateLocation) {
152164
if (!isDefined(updateLocation)) updateLocation = true;
153165

@@ -171,8 +183,17 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
171183
return $q.when($state.current);
172184
}
173185

174-
// TODO: should we be passing from and to $stateParams as well?
175-
$rootScope.$broadcast('$stateChangeStart', to.self, from.self);
186+
// Normalize parameters before we pass them to event handlers etc.
187+
var normalizedToParams = {};
188+
forEach(to.params, function (name) {
189+
var value = toParams[name];
190+
normalizedToParams[name] = (value != null) ? String(value) : null;
191+
});
192+
toParams = normalizedToParams;
193+
194+
// Broadcast start event and cancel the transition if requested
195+
if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams)
196+
.defaultPrevented) return TransitionPrevented;
176197

177198
// Resolve locals for the remaining states, but don't update any global state just
178199
// yet -- if anything fails to resolve the current state needs to remain untouched.
@@ -184,7 +205,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
184205
var resolved = $q.when(locals);
185206
for (var l=keep; l<toPath.length; l++, state=toPath[l]) {
186207
locals = toLocals[l] = inherit(locals);
187-
resolved = resolveState(state, toParams, resolved, locals);
208+
resolved = resolveState(state, toParams, state===to, resolved, locals);
188209
}
189210

190211
// Once everything is resolved, we are ready to perform the actual transition
@@ -194,7 +215,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
194215
var transition = $state.transition = resolved.then(function () {
195216
var l, entering, exiting;
196217

197-
if ($state.transition !== transition) return $q.reject(new Error('transition superseded'));
218+
if ($state.transition !== transition) return TransitionSuperseded;
198219

199220
// Exit 'from' states not kept
200221
for (l=fromPath.length-1; l>=keep; l--) {
@@ -218,7 +239,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
218239
// Update globals in $state
219240
$state.$current = to;
220241
$state.current = to.self;
221-
$state.params = locals.globals.$stateParams; // these are normalized, unlike toParams
242+
$state.params = toParams
222243
copy($state.params, $stateParams);
223244
$state.transition = null;
224245

@@ -228,36 +249,39 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
228249
$location.url(toNav.url.format(toNav.locals.globals.$stateParams));
229250
}
230251

231-
$rootScope.$broadcast('$stateChangeSuccess', to.self, from.self);
252+
$rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
232253

233254
return $state.current;
234255
}, function (error) {
235-
if ($state.transition !== transition) return; // superseded by a new transition
256+
if ($state.transition !== transition) return TransitionSuperseded;
236257

237258
$state.transition = null;
259+
$rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
238260

239-
$rootScope.$broadcast('$stateChangeError', to.self, from.self, error);
240261
return $q.reject(error);
241262
});
242263

243264
return transition;
244265
}
245266

246-
function resolveState(state, params, inherited, dst) {
267+
function resolveState(state, params, paramsAreFiltered, inherited, dst) {
247268
// We need to track all the promises generated during the resolution process.
248269
// The first of these is for the fully resolved parent locals.
249270
var promises = [inherited];
250271

251-
// Make a restricted $stateParams with only the parameters that apply to this state, and
252-
// force them all to strings while we're at it. In addition to being available to the
253-
// controller and onEnter/onExit callbacks, we also need $stateParams to be available
254-
// for any $injector calls we make during the dependency resolution process.
255-
var $stateParams = {};
272+
// Make a restricted $stateParams with only the parameters that apply to this state if
273+
// necessary. In addition to being available to the controller and onEnter/onExit callbacks,
274+
// we also need $stateParams to be available for any $injector calls we make during the
275+
// dependency resolution process.
276+
var $stateParams;
277+
if (paramsAreFiltered) $stateParams = params;
278+
else {
279+
$stateParams = {};
280+
forEach(state.params, function (name) {
281+
$stateParams[name] = params[name];
282+
});
283+
}
256284
var locals = { $stateParams: $stateParams };
257-
forEach(state.params, function (name) {
258-
var value = params[name];
259-
$stateParams[name] = (value != null) ? String(value) : null;
260-
});
261285

262286
// Resolves the values from an individual 'resolve' dependency spec
263287
function resolve(deps, dst) {

0 commit comments

Comments
 (0)