Skip to content

Commit 4d062e3

Browse files
committed
$destroy event is triggered on leaving view before animation/transition ends
angular-ui#1643
1 parent 3e06565 commit 4d062e3

File tree

3 files changed

+120
-65
lines changed

3 files changed

+120
-65
lines changed

release/angular-ui-router.js

+92-49
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* State-based routing for AngularJS
3-
* @version v0.2.13
3+
* @version v0.2.13-dev-2015-03-22
44
* @link http://angular-ui.github.com/
55
* @license MIT License, http://www.opensource.org/licenses/MIT
66
*/
@@ -68,7 +68,7 @@ function objectKeys(object) {
6868
}
6969
var result = [];
7070

71-
angular.forEach(object, function(val, key) {
71+
forEach(object, function(val, key) {
7272
result.push(key);
7373
});
7474
return result;
@@ -768,13 +768,13 @@ function UrlMatcher(pattern, config, parentMatcher) {
768768
return params[id];
769769
}
770770

771-
function quoteRegExp(string, pattern, squash) {
771+
function quoteRegExp(string, pattern, squash, optional) {
772772
var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
773773
if (!pattern) return result;
774774
switch(squash) {
775-
case false: surroundPattern = ['(', ')']; break;
775+
case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break;
776776
case true: surroundPattern = ['?(', ')?']; break;
777-
default: surroundPattern = ['(' + squash + "|", ')?']; break;
777+
default: surroundPattern = ['(' + squash + "|", ')?']; break;
778778
}
779779
return result + surroundPattern[0] + pattern + surroundPattern[1];
780780
}
@@ -801,7 +801,7 @@ function UrlMatcher(pattern, config, parentMatcher) {
801801
if (p.segment.indexOf('?') >= 0) break; // we're into the search part
802802

803803
param = addParameter(p.id, p.type, p.cfg, "path");
804-
compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash);
804+
compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional);
805805
segments.push(p.segment);
806806
last = placeholder.lastIndex;
807807
}
@@ -912,7 +912,7 @@ UrlMatcher.prototype.exec = function (path, searchParams) {
912912

913913
function decodePathArray(string) {
914914
function reverseString(str) { return str.split("").reverse().join(""); }
915-
function unquoteDashes(str) { return str.replace(/\\-/, "-"); }
915+
function unquoteDashes(str) { return str.replace(/\\-/g, "-"); }
916916

917917
var split = reverseString(string).split(/-(?!\\)/);
918918
var allReversed = map(split, reverseString);
@@ -1150,6 +1150,11 @@ Type.prototype.pattern = /.*/;
11501150

11511151
Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
11521152

1153+
/** Given an encoded string, or a decoded object, returns a decoded object */
1154+
Type.prototype.$normalize = function(val) {
1155+
return this.is(val) ? val : this.decode(val);
1156+
};
1157+
11531158
/*
11541159
* Wraps an existing custom Type as an array of Type, depending on 'mode'.
11551160
* e.g.:
@@ -1163,7 +1168,6 @@ Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
11631168
Type.prototype.$asArray = function(mode, isSearch) {
11641169
if (!mode) return this;
11651170
if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");
1166-
return new ArrayType(this, mode);
11671171

11681172
function ArrayType(type, mode) {
11691173
function bindTo(type, callbackName) {
@@ -1212,8 +1216,12 @@ Type.prototype.$asArray = function(mode, isSearch) {
12121216
this.is = arrayHandler(bindTo(type, 'is'), true);
12131217
this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
12141218
this.pattern = type.pattern;
1219+
this.$normalize = arrayHandler(bindTo(type, '$normalize'));
1220+
this.name = type.name;
12151221
this.$arrayMode = mode;
12161222
}
1223+
1224+
return new ArrayType(this, mode);
12171225
};
12181226

12191227

@@ -1241,7 +1249,7 @@ function $UrlMatcherFactory() {
12411249
string: {
12421250
encode: valToString,
12431251
decode: valFromString,
1244-
is: regexpMatches,
1252+
is: function(val) { return typeof val === "string"; },
12451253
pattern: /[^/]*/
12461254
},
12471255
int: {
@@ -1615,7 +1623,10 @@ function $UrlMatcherFactory() {
16151623
*/
16161624
function $$getDefaultValue() {
16171625
if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
1618-
return injector.invoke(config.$$fn);
1626+
var defaultValue = injector.invoke(config.$$fn);
1627+
if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))
1628+
throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")");
1629+
return defaultValue;
16191630
}
16201631

16211632
/**
@@ -1629,7 +1640,7 @@ function $UrlMatcherFactory() {
16291640
return replacement.length ? replacement[0] : value;
16301641
}
16311642
value = $replace(value);
1632-
return isDefined(value) ? self.type.decode(value) : $$getDefaultValue();
1643+
return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);
16331644
}
16341645

16351646
function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }
@@ -1685,15 +1696,20 @@ function $UrlMatcherFactory() {
16851696
return equal;
16861697
},
16871698
$$validates: function $$validate(paramValues) {
1688-
var result = true, isOptional, val, param, self = this;
1689-
1690-
forEach(this.$$keys(), function(key) {
1691-
param = self[key];
1692-
val = paramValues[key];
1693-
isOptional = !val && param.isOptional;
1694-
result = result && (isOptional || !!param.type.is(val));
1695-
});
1696-
return result;
1699+
var keys = this.$$keys(), i, param, rawVal, normalized, encoded;
1700+
for (i = 0; i < keys.length; i++) {
1701+
param = this[keys[i]];
1702+
rawVal = paramValues[keys[i]];
1703+
if ((rawVal === undefined || rawVal === null) && param.isOptional)
1704+
break; // There was no parameter value, but the param is optional
1705+
normalized = param.type.$normalize(rawVal);
1706+
if (!param.type.is(normalized))
1707+
return false; // The value was not of the correct Type, and could not be decoded to the correct Type
1708+
encoded = param.type.encode(normalized);
1709+
if (angular.isString(encoded) && !param.type.pattern.exec(encoded))
1710+
return false; // The value was of the correct type, but when encoded, did not match the Type's regexp
1711+
}
1712+
return true;
16971713
},
16981714
$$parent: undefined
16991715
};
@@ -2336,6 +2352,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
23362352
var globSegments = glob.split('.'),
23372353
segments = $state.$current.name.split('.');
23382354

2355+
//match single stars
2356+
for (var i = 0, l = globSegments.length; i < l; i++) {
2357+
if (globSegments[i] === '*') {
2358+
segments[i] = '*';
2359+
}
2360+
}
2361+
23392362
//match greedy starts
23402363
if (globSegments[0] === '**') {
23412364
segments = segments.slice(indexOf(segments, globSegments[1]));
@@ -2351,13 +2374,6 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
23512374
return false;
23522375
}
23532376

2354-
//match single stars
2355-
for (var i = 0, l = globSegments.length; i < l; i++) {
2356-
if (globSegments[i] === '*') {
2357-
segments[i] = '*';
2358-
}
2359-
}
2360-
23612377
return segments.join('') === globSegments.join('');
23622378
}
23632379

@@ -2566,6 +2582,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
25662582
* published to scope under the controllerAs name.
25672583
* <pre>controllerAs: "myCtrl"</pre>
25682584
*
2585+
* @param {string|object=} stateConfig.parent
2586+
* <a id='parent'></a>
2587+
* Optionally specifies the parent state of this state.
2588+
*
2589+
* <pre>parent: 'parentState'</pre>
2590+
* <pre>parent: parentState // JS variable</pre>
2591+
*
25692592
* @param {object=} stateConfig.resolve
25702593
* <a id='resolve'></a>
25712594
*
@@ -2597,15 +2620,19 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
25972620
* transitioned to, the `$stateParams` service will be populated with any
25982621
* parameters that were passed.
25992622
*
2623+
* (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for
2624+
* more details on acceptable patterns )
2625+
*
26002626
* examples:
26012627
* <pre>url: "/home"
26022628
* url: "/users/:userid"
26032629
* url: "/books/{bookid:[a-zA-Z_-]}"
26042630
* url: "/books/{categoryid:int}"
26052631
* url: "/books/{publishername:string}/{categoryid:int}"
26062632
* url: "/messages?before&after"
2607-
* url: "/messages?{before:date}&{after:date}"</pre>
2633+
* url: "/messages?{before:date}&{after:date}"
26082634
* url: "/messages/:mailboxid?{before:date}&{after:date}"
2635+
* </pre>
26092636
*
26102637
* @param {object=} stateConfig.views
26112638
* <a id='views'></a>
@@ -2909,8 +2936,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
29092936
* @methodOf ui.router.state.$state
29102937
*
29112938
* @description
2912-
* A method that force reloads the current state. All resolves are re-resolved, events are not re-fired,
2913-
* and controllers reinstantiated (bug with controllers reinstantiating right now, fixing soon).
2939+
* A method that force reloads the current state. All resolves are re-resolved,
2940+
* controllers reinstantiated, and events re-fired.
29142941
*
29152942
* @example
29162943
* <pre>
@@ -3609,7 +3636,7 @@ function $ViewScrollProvider() {
36093636
}
36103637

36113638
return function ($element) {
3612-
$timeout(function () {
3639+
return $timeout(function () {
36133640
$element[0].scrollIntoView();
36143641
}, 0, false);
36153642
};
@@ -3807,24 +3834,36 @@ function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate)
38073834
updateView(true);
38083835

38093836
function cleanupLastView() {
3810-
if (previousEl) {
3811-
previousEl.remove();
3812-
previousEl = null;
3813-
}
3837+
if (previousEl) {
3838+
previousEl.remove();
3839+
previousEl = null;
3840+
}
38143841

3815-
if (currentScope) {
3816-
currentScope.$destroy();
3817-
currentScope = null;
3818-
}
3842+
if (previousScope) {
3843+
previousScope.$destroy();
3844+
previousScope = null;
3845+
}
38193846

3820-
if (currentEl) {
3821-
renderer.leave(currentEl, function() {
3822-
previousEl = null;
3823-
});
3847+
var previousScope = currentScope;
3848+
currentScope = null;
38243849

3825-
previousEl = currentEl;
3826-
currentEl = null;
3827-
}
3850+
if (currentEl) {
3851+
renderer.leave(currentEl, function () {
3852+
previousEl = null;
3853+
3854+
if (previousScope) {
3855+
previousScope.$destroy();
3856+
previousScope = null;
3857+
}
3858+
});
3859+
3860+
previousEl = currentEl;
3861+
currentEl = null;
3862+
}
3863+
else if (previousScope) {
3864+
previousScope.$destroy();
3865+
previousScope = null;
3866+
}
38283867
}
38293868

38303869
function updateView(firstTime) {
@@ -3894,6 +3933,7 @@ function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate
38943933

38953934
if (locals.$$controller) {
38963935
locals.$scope = scope;
3936+
locals.$element = $element;
38973937
var controller = $controller(locals.$$controller, locals);
38983938
if (locals.$$controllerAs) {
38993939
scope[locals.$$controllerAs] = controller;
@@ -4001,17 +4041,20 @@ function stateContext(el) {
40014041
*/
40024042
$StateRefDirective.$inject = ['$state', '$timeout'];
40034043
function $StateRefDirective($state, $timeout) {
4004-
var allowedOptions = ['location', 'inherit', 'reload'];
4044+
var allowedOptions = ['location', 'inherit', 'reload', 'absolute'];
40054045

40064046
return {
40074047
restrict: 'A',
40084048
require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
40094049
link: function(scope, element, attrs, uiSrefActive) {
40104050
var ref = parseStateRef(attrs.uiSref, $state.current.name);
40114051
var params = null, url = null, base = stateContext(element) || $state.$current;
4012-
var newHref = null, isAnchor = element.prop("tagName") === "A";
4052+
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
4053+
var hrefKind = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
4054+
'xlink:href' : 'href';
4055+
var newHref = null, isAnchor = element.prop("tagName").toUpperCase() === "A";
40134056
var isForm = element[0].nodeName === "FORM";
4014-
var attr = isForm ? "action" : "href", nav = true;
4057+
var attr = isForm ? "action" : hrefKind, nav = true;
40154058

40164059
var options = { relative: base, inherit: true };
40174060
var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {};

0 commit comments

Comments
 (0)