Skip to content

Commit c3d543a

Browse files
release 0.2.13
1 parent ca6bc65 commit c3d543a

File tree

2 files changed

+110
-75
lines changed

2 files changed

+110
-75
lines changed

release/angular-ui-router.js

+108-73
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* State-based routing for AngularJS
3-
* @version v0.2.12
3+
* @version v0.2.13
44
* @link http://angular-ui.github.com/
55
* @license MIT License, http://www.opensource.org/licenses/MIT
66
*/
@@ -187,7 +187,7 @@ function omit(obj) {
187187
var copy = {};
188188
var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
189189
for (var key in obj) {
190-
if (keys.indexOf(key) == -1) copy[key] = obj[key];
190+
if (indexOf(keys, key) == -1) copy[key] = obj[key];
191191
}
192192
return copy;
193193
}
@@ -202,10 +202,12 @@ function pluck(collection, key) {
202202
}
203203

204204
function filter(collection, callback) {
205-
var result = isArray(collection) ? [] : {};
205+
var array = isArray(collection);
206+
var result = array ? [] : {};
206207
forEach(collection, function(val, i) {
207-
if (callback(val, i))
208-
result[i] = val;
208+
if (callback(val, i)) {
209+
result[array ? result.length : i] = val;
210+
}
209211
});
210212
return result;
211213
}
@@ -754,9 +756,11 @@ function UrlMatcher(pattern, config, parentMatcher) {
754756
compiled = '^', last = 0, m,
755757
segments = this.segments = [],
756758
parentParams = parentMatcher ? parentMatcher.params : {},
757-
params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet();
759+
params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),
760+
paramNames = [];
758761

759762
function addParameter(id, type, config, location) {
763+
paramNames.push(id);
760764
if (parentParams[id]) return parentParams[id];
761765
if (!/^\w+(-+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
762766
if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
@@ -830,6 +834,7 @@ function UrlMatcher(pattern, config, parentMatcher) {
830834

831835
this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
832836
this.prefix = segments[0];
837+
this.$$paramNames = paramNames;
833838
}
834839

835840
/**
@@ -945,7 +950,7 @@ UrlMatcher.prototype.exec = function (path, searchParams) {
945950
* pattern has no parameters, an empty array is returned.
946951
*/
947952
UrlMatcher.prototype.parameters = function (param) {
948-
if (!isDefined(param)) return this.params.$$keys();
953+
if (!isDefined(param)) return this.$$paramNames;
949954
return this.params[param] || null;
950955
};
951956

@@ -1161,28 +1166,51 @@ Type.prototype.$asArray = function(mode, isSearch) {
11611166
return new ArrayType(this, mode);
11621167

11631168
function ArrayType(type, mode) {
1164-
function bindTo(thisObj, callback) {
1169+
function bindTo(type, callbackName) {
11651170
return function() {
1166-
return callback.apply(thisObj, arguments);
1171+
return type[callbackName].apply(type, arguments);
11671172
};
11681173
}
11691174

1170-
function arrayHandler(callback, reducefn) {
1171-
// Wraps type functions to operate on each value of an array
1175+
// Wrap non-array value as array
1176+
function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
1177+
// Unwrap array value for "auto" mode. Return undefined for empty array.
1178+
function arrayUnwrap(val) {
1179+
switch(val.length) {
1180+
case 0: return undefined;
1181+
case 1: return mode === "auto" ? val[0] : val;
1182+
default: return val;
1183+
}
1184+
}
1185+
function falsey(val) { return !val; }
1186+
1187+
// Wraps type (.is/.encode/.decode) functions to operate on each value of an array
1188+
function arrayHandler(callback, allTruthyMode) {
11721189
return function handleArray(val) {
1173-
if (!isArray(val)) val = [ val ];
1190+
val = arrayWrap(val);
11741191
var result = map(val, callback);
1175-
if (reducefn)
1176-
return result.reduce(reducefn, true);
1177-
return (result && result.length == 1 && mode === "auto") ? result[0] : result;
1192+
if (allTruthyMode === true)
1193+
return filter(result, falsey).length === 0;
1194+
return arrayUnwrap(result);
1195+
};
1196+
}
1197+
1198+
// Wraps type (.equals) functions to operate on each value of an array
1199+
function arrayEqualsHandler(callback) {
1200+
return function handleArray(val1, val2) {
1201+
var left = arrayWrap(val1), right = arrayWrap(val2);
1202+
if (left.length !== right.length) return false;
1203+
for (var i = 0; i < left.length; i++) {
1204+
if (!callback(left[i], right[i])) return false;
1205+
}
1206+
return true;
11781207
};
11791208
}
11801209

1181-
function alltruthy(val, memo) { return val && memo; }
1182-
this.encode = arrayHandler(bindTo(this, type.encode));
1183-
this.decode = arrayHandler(bindTo(this, type.decode));
1184-
this.equals = arrayHandler(bindTo(this, type.equals), alltruthy);
1185-
this.is = arrayHandler(bindTo(this, type.is), alltruthy);
1210+
this.encode = arrayHandler(bindTo(type, 'encode'));
1211+
this.decode = arrayHandler(bindTo(type, 'decode'));
1212+
this.is = arrayHandler(bindTo(type, 'is'), true);
1213+
this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
11861214
this.pattern = type.pattern;
11871215
this.$arrayMode = mode;
11881216
}
@@ -1203,9 +1231,8 @@ function $UrlMatcherFactory() {
12031231

12041232
var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;
12051233

1206-
function valToString(val) { return val != null ? val.toString().replace("/", "%2F") : val; }
1207-
function valFromString(val) { return val != null ? val.toString().replace("%2F", "/") : val; }
1208-
function angularEquals(left, right) { return angular.equals(left, right); }
1234+
function valToString(val) { return val != null ? val.toString().replace(/\//g, "%2F") : val; }
1235+
function valFromString(val) { return val != null ? val.toString().replace(/%2F/g, "/") : val; }
12091236
// TODO: in 1.0, make string .is() return false if value is undefined by default.
12101237
// function regexpMatches(val) { /*jshint validthis:true */ return isDefined(val) && this.pattern.test(val); }
12111238
function regexpMatches(val) { /*jshint validthis:true */ return this.pattern.test(val); }
@@ -1230,16 +1257,37 @@ function $UrlMatcherFactory() {
12301257
pattern: /0|1/
12311258
},
12321259
date: {
1233-
encode: function (val) { return [
1234-
val.getFullYear(),
1260+
encode: function (val) {
1261+
if (!this.is(val))
1262+
return undefined;
1263+
return [ val.getFullYear(),
12351264
('0' + (val.getMonth() + 1)).slice(-2),
12361265
('0' + val.getDate()).slice(-2)
12371266
].join("-");
12381267
},
1239-
decode: function (val) { return new Date(val); },
1268+
decode: function (val) {
1269+
if (this.is(val)) return val;
1270+
var match = this.capture.exec(val);
1271+
return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
1272+
},
12401273
is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); },
1241-
equals: function (a, b) { return a.toISOString() === b.toISOString(); },
1242-
pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/
1274+
equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },
1275+
pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
1276+
capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
1277+
},
1278+
json: {
1279+
encode: angular.toJson,
1280+
decode: angular.fromJson,
1281+
is: angular.isObject,
1282+
equals: angular.equals,
1283+
pattern: /[^/]*/
1284+
},
1285+
any: { // does not encode/decode
1286+
encode: angular.identity,
1287+
decode: angular.identity,
1288+
is: angular.identity,
1289+
equals: angular.equals,
1290+
pattern: /.*/
12431291
}
12441292
};
12451293

@@ -1506,33 +1554,29 @@ function $UrlMatcherFactory() {
15061554

15071555
this.Param = function Param(id, type, config, location) {
15081556
var self = this;
1509-
var defaultValueConfig = getDefaultValueConfig(config);
1510-
config = config || {};
1511-
type = getType(config, type);
1557+
config = unwrapShorthand(config);
1558+
type = getType(config, type, location);
15121559
var arrayMode = getArrayMode();
15131560
type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
1514-
if (type.name === "string" && !arrayMode && location === "path" && defaultValueConfig.value === undefined)
1515-
defaultValueConfig.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
1516-
var isOptional = defaultValueConfig.value !== undefined;
1561+
if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined)
1562+
config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
1563+
var isOptional = config.value !== undefined;
15171564
var squash = getSquashPolicy(config, isOptional);
15181565
var replace = getReplace(config, arrayMode, isOptional, squash);
15191566

1520-
function getDefaultValueConfig(config) {
1567+
function unwrapShorthand(config) {
15211568
var keys = isObject(config) ? objectKeys(config) : [];
15221569
var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 &&
15231570
indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1;
1524-
var configValue = isShorthand ? config : config.value;
1525-
var result = {
1526-
fn: isInjectable(configValue) ? configValue : function () { return result.value; },
1527-
value: configValue
1528-
};
1529-
return result;
1571+
if (isShorthand) config = { value: config };
1572+
config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };
1573+
return config;
15301574
}
15311575

1532-
function getType(config, urlType) {
1576+
function getType(config, urlType, location) {
15331577
if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations.");
15341578
if (urlType) return urlType;
1535-
if (!config.type) return $types.string;
1579+
if (!config.type) return (location === "config" ? $types.any : $types.string);
15361580
return config.type instanceof Type ? config.type : new Type(config.type);
15371581
}
15381582

@@ -1571,7 +1615,7 @@ function $UrlMatcherFactory() {
15711615
*/
15721616
function $$getDefaultValue() {
15731617
if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
1574-
return injector.invoke(defaultValueConfig.fn);
1618+
return injector.invoke(config.$$fn);
15751619
}
15761620

15771621
/**
@@ -1593,13 +1637,14 @@ function $UrlMatcherFactory() {
15931637
extend(this, {
15941638
id: id,
15951639
type: type,
1640+
location: location,
15961641
array: arrayMode,
1597-
config: config,
15981642
squash: squash,
15991643
replace: replace,
16001644
isOptional: isOptional,
1601-
dynamic: undefined,
16021645
value: $value,
1646+
dynamic: undefined,
1647+
config: config,
16031648
toString: toString
16041649
});
16051650
};
@@ -1646,7 +1691,7 @@ function $UrlMatcherFactory() {
16461691
param = self[key];
16471692
val = paramValues[key];
16481693
isOptional = !val && param.isOptional;
1649-
result = result && (isOptional || param.type.is(val));
1694+
result = result && (isOptional || !!param.type.is(val));
16501695
});
16511696
return result;
16521697
},
@@ -1927,7 +1972,7 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
19271972
$get.$inject = ['$location', '$rootScope', '$injector', '$browser'];
19281973
function $get( $location, $rootScope, $injector, $browser) {
19291974

1930-
var baseHref = $browser.baseHref(), location = $location.url();
1975+
var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;
19311976

19321977
function appendBasePath(url, isHtml5, absolute) {
19331978
if (baseHref === '/') return url;
@@ -1939,6 +1984,9 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
19391984
// TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
19401985
function update(evt) {
19411986
if (evt && evt.defaultPrevented) return;
1987+
var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
1988+
lastPushedUrl = undefined;
1989+
if (ignoreUpdate) return true;
19421990

19431991
function check(rule) {
19441992
var handled = rule($injector, $location);
@@ -2011,6 +2059,7 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
20112059

20122060
push: function(urlMatcher, params, options) {
20132061
$location.url(urlMatcher.format(params || {}));
2062+
lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
20142063
if (options && options.replace) $location.replace();
20152064
},
20162065

@@ -2140,7 +2189,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
21402189
ownParams: function(state) {
21412190
var params = state.url && state.url.params || new $$UMFP.ParamSet();
21422191
forEach(state.params || {}, function(config, id) {
2143-
if (!params[id]) params[id] = new $$UMFP.Param(id, null, config);
2192+
if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config");
21442193
});
21452194
return params;
21462195
},
@@ -2266,7 +2315,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
22662315
if (!state[abstractKey] && state.url) {
22672316
$urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
22682317
if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
2269-
$state.transitionTo(state, $match, { location: false });
2318+
$state.transitionTo(state, $match, { inherit: true, location: false });
22702319
}
22712320
}]);
22722321
}
@@ -3147,7 +3196,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
31473196

31483197
if (options.location && to.navigable) {
31493198
$urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, {
3150-
replace: options.location === 'replace'
3199+
$$avoidResync: true, replace: options.location === 'replace'
31513200
});
31523201
}
31533202

@@ -3243,15 +3292,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
32433292
options = extend({ relative: $state.$current }, options || {});
32443293
var state = findState(stateOrName, options.relative);
32453294

3246-
if (!isDefined(state)) {
3247-
return undefined;
3248-
}
3249-
3250-
if ($state.$current !== state) {
3251-
return false;
3252-
}
3253-
3254-
return isDefined(params) && params !== null ? angular.equals($stateParams, params) : true;
3295+
if (!isDefined(state)) { return undefined; }
3296+
if ($state.$current !== state) { return false; }
3297+
return params ? equalForKeys(state.params.$$values(params), $stateParams) : true;
32553298
};
32563299

32573300
/**
@@ -3315,13 +3358,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
33153358
}
33163359

33173360
var state = findState(stateOrName, options.relative);
3318-
if (!isDefined(state)) {
3319-
return undefined;
3320-
}
3321-
if (!isDefined($state.$current.includes[state.name])) {
3322-
return false;
3323-
}
3324-
return equalForKeys(params, $stateParams);
3361+
if (!isDefined(state)) { return undefined; }
3362+
if (!isDefined($state.$current.includes[state.name])) { return false; }
3363+
return params ? equalForKeys(state.params.$$values(params), $stateParams, objectKeys(params)) : true;
33253364
};
33263365

33273366

@@ -4137,15 +4176,11 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
41374176

41384177
function isMatch() {
41394178
if (typeof $attrs.uiSrefActiveEq !== 'undefined') {
4140-
return $state.$current.self === state && matchesParams();
4179+
return state && $state.is(state.name, params);
41414180
} else {
4142-
return state && $state.includes(state.name) && matchesParams();
4181+
return state && $state.includes(state.name, params);
41434182
}
41444183
}
4145-
4146-
function matchesParams() {
4147-
return !params || equalForKeys(params, $stateParams);
4148-
}
41494184
}]
41504185
};
41514186
}

0 commit comments

Comments
 (0)