Skip to content

Commit a428b2f

Browse files
nateabelechristopherthielen
authored andcommitted
chore(stateDirectives): merge in ui-state from 3831af1
1 parent eb2a919 commit a428b2f

File tree

2 files changed

+202
-103
lines changed

2 files changed

+202
-103
lines changed

Diff for: src/ng1/stateDirectives.ts

+136-76
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** @module state */ /** for typedoc */
22
/// <reference path='../../typings/angularjs/angular.d.ts' />
3-
import {copy, defaults, forEach, toJson} from "../common/common";
3+
import {extend, copy, defaults, forEach, toJson} from "../common/common";
44
import {isString, isObject} from "../common/predicates";
55
import {defaultTransOpts} from "../transition/module";
66

@@ -20,6 +20,43 @@ function stateContext(el) {
2020
}
2121
}
2222

23+
function getTypeInfo(el) {
24+
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
25+
var isSvg = Object.prototype.toString.call(el.prop('href')) === '[object SVGAnimatedString]';
26+
var isForm = el[0].nodeName === "FORM";
27+
28+
return {
29+
attr: isForm ? "action" : (isSvg ? 'xlink:href' : 'href'),
30+
isAnchor: el.prop("tagName").toUpperCase() === "A",
31+
clickable: !isForm
32+
};
33+
}
34+
35+
function clickHook(el, $state, $timeout, type, current) {
36+
return function(e) {
37+
var button = e.which || e.button, target = current();
38+
39+
if (!(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || el.attr('target'))) {
40+
// HACK: This is to allow ng-clicks to be processed before the transition is initiated:
41+
var transition = $timeout(function() {
42+
$state.go(target.state, target.params, target.options);
43+
});
44+
e.preventDefault();
45+
46+
// if the state has no URL, ignore one preventDefault from the <a> directive.
47+
var ignorePreventDefaultCount = type.isAnchor && !target.href ? 1: 0;
48+
49+
e.preventDefault = function() {
50+
if (ignorePreventDefaultCount-- <= 0) $timeout.cancel(transition);
51+
};
52+
}
53+
};
54+
}
55+
56+
function defaultOpts(el, $state) {
57+
return { relative: stateContext(el) || $state.$current, inherit: true };
58+
}
59+
2360
/**
2461
* @ngdoc directive
2562
* @name ui.router.state.directive:ui-sref
@@ -30,40 +67,40 @@ function stateContext(el) {
3067
* @restrict A
3168
*
3269
* @description
33-
* A directive that binds a link (`<a>` tag) to a state. If the state has an associated
34-
* URL, the directive will automatically generate & update the `href` attribute via
35-
* the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
36-
* the link will trigger a state transition with optional parameters.
70+
* A directive that binds a link (`<a>` tag) to a state. If the state has an associated
71+
* URL, the directive will automatically generate & update the `href` attribute via
72+
* the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
73+
* the link will trigger a state transition with optional parameters.
3774
*
38-
* Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
75+
* Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
3976
* handled natively by the browser.
4077
*
41-
* You can also use relative state paths within ui-sref, just like the relative
78+
* You can also use relative state paths within ui-sref, just like the relative
4279
* paths passed to `$state.go()`. You just need to be aware that the path is relative
43-
* to the state that the link lives in, in other words the state that loaded the
80+
* to the state that the link lives in, in other words the state that loaded the
4481
* template containing the link.
4582
*
4683
* You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
4784
* using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
4885
* and `reload`.
4986
*
5087
* @example
51-
* Here's an example of how you'd use ui-sref and how it would compile. If you have the
88+
* Here's an example of how you'd use ui-sref and how it would compile. If you have the
5289
* following template:
5390
* <pre>
5491
* <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
55-
*
92+
*
5693
* <ul>
5794
* <li ng-repeat="contact in contacts">
5895
* <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
5996
* </li>
6097
* </ul>
6198
* </pre>
62-
*
99+
*
63100
* Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
64101
* <pre>
65102
* <a href="#/home" ui-sref="home">Home</a> | <a href="#/about" ui-sref="about">About</a> | <a href="#/contacts?page=2" ui-sref="{page: 2}">Next page</a>
66-
*
103+
*
67104
* <ul>
68105
* <li ng-repeat="contact in contacts">
69106
* <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
@@ -88,62 +125,79 @@ function $StateRefDirective($state, $timeout) {
88125
restrict: 'A',
89126
require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
90127
link: function(scope, element, attrs, uiSrefActive) {
91-
let ref = parseStateRef(attrs.uiSref, $state.current.name);
92-
let params = null, base = stateContext(element) || $state.$current;
93-
let newHref = null, isAnchor = element.prop("tagName") === "A";
94-
let isForm = element[0].nodeName === "FORM";
95-
let attr = isForm ? "action" : "href", nav = true;
96-
97-
let srefOpts = scope.$eval(attrs.uiSrefOpts);
98-
let defaultSrefOpts = { relative: base, inherit: true };
99-
let options = defaults(srefOpts, defaultSrefOpts, defaultTransOpts);
128+
var ref = parseStateRef(attrs.uiSref, $state.current.name);
129+
var def = { state: ref.state, href: null, params: null, options: null };
130+
var type = getTypeInfo(element);
131+
var active = uiSrefActive[1] || uiSrefActive[0];
100132

101-
let update = function(newVal?: any) {
102-
if (newVal) params = copy(newVal);
103-
if (!nav) return;
133+
def.options = extend(defaultOpts(element, $state), attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {});
104134

105-
newHref = $state.href(ref.state, params, options);
135+
var update = function(val?) {
136+
if (val) def.params = angular.copy(val);
137+
def.href = $state.href(ref.state, def.params, def.options);
106138

107-
let activeDirective = uiSrefActive[1] || uiSrefActive[0];
108-
if (activeDirective) {
109-
activeDirective.$$addStateInfo(ref.state, params);
110-
}
111-
if (newHref === null) {
112-
nav = false;
113-
return false;
114-
}
115-
attrs.$set(attr, newHref);
139+
if (active) active.$$addStateInfo(ref.state, def.params);
140+
if (def.href !== null) attrs.$set(type.attr, def.href);
116141
};
117142

118143
if (ref.paramExpr) {
119-
scope.$watch(ref.paramExpr, newVal => { if (newVal !== params) update(newVal); }, true);
120-
params = copy(scope.$eval(ref.paramExpr));
144+
scope.$watch(ref.paramExpr, function(val) { if (val !== def.params) update(val); }, true);
145+
def.params = angular.copy(scope.$eval(ref.paramExpr));
121146
}
122147
update();
123148

124-
if (isForm) return;
125-
126-
element.bind("click", function(e) {
127-
let button = e.which || e.button;
128-
if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) {
129-
// HACK: This is to allow ng-clicks to be processed before the transition is initiated:
130-
let transition = $timeout(function() {
131-
$state.go(ref.state, params, options);
132-
});
133-
e.preventDefault();
134-
135-
// if the state has no URL, ignore one preventDefault from the <a> directive.
136-
let ignorePreventDefaultCount = isAnchor && !newHref ? 1 : 0;
137-
e.preventDefault = function() {
138-
if (ignorePreventDefaultCount-- <= 0)
139-
$timeout.cancel(transition);
140-
};
141-
}
142-
});
149+
if (!type.clickable) return;
150+
element.bind("click", clickHook(element, $state, $timeout, type, function() { return def; }));
143151
}
144152
};
145153
}
146154

155+
/**
156+
* @ngdoc directive
157+
* @name ui.router.state.directive:ui-state
158+
*
159+
* @requires ui.router.state.uiSref
160+
*
161+
* @restrict A
162+
*
163+
* @description
164+
* Much like ui-sref, but will accept named $scope properties to evaluate for a state definition,
165+
* params and override options.
166+
*
167+
* @param {string} ui-state 'stateName' can be any valid absolute or relative state
168+
* @param {Object} ui-state-params params to pass to {@link ui.router.state.$state#href $state.href()}
169+
* @param {Object} ui-state-opts options to pass to {@link ui.router.state.$state#go $state.go()}
170+
*/
171+
$StateRefDynamicDirective.$inject = ['$state', '$timeout'];
172+
function $StateRefDynamicDirective($state, $timeout) {
173+
return {
174+
restrict: 'A',
175+
require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
176+
link: function(scope, element, attrs, uiSrefActive) {
177+
var type = getTypeInfo(element);
178+
var active = uiSrefActive[1] || uiSrefActive[0];
179+
var group = [attrs.uiState, attrs.uiStateParams || null, attrs.uiStateOpts || null];
180+
var watch = '[' + group.map(function(val) { return val || 'null'; }).join(', ') + ']';
181+
var def = { state: null, params: null, options: null, href: null };
182+
183+
function runStateRefLink (group) {
184+
def.state = group[0]; def.params = group[1]; def.options = group[2];
185+
def.href = $state.href(def.state, def.params, def.options);
186+
187+
if (active) active.$$addStateInfo(def.state, def.params);
188+
if (def.href) attrs.$set(type.attr, def.href);
189+
}
190+
191+
scope.$watch(watch, runStateRefLink, true);
192+
runStateRefLink(scope.$eval(watch));
193+
194+
if (!type.clickable) return;
195+
element.bind("click", clickHook(element, $state, $timeout, type, function() { return def; }));
196+
}
197+
};
198+
}
199+
200+
147201
/**
148202
* @ngdoc directive
149203
* @name ui.router.state.directive:ui-sref-active
@@ -236,23 +290,29 @@ function $StateRefDirective($state, $timeout) {
236290
* when the exact target state used in the `ui-sref` is active; no child states.
237291
*
238292
*/
239-
$StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
240-
function $StateRefActiveDirective($state, $stateParams, $interpolate) {
293+
$StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate', '$transitions'];
294+
function $StateRefActiveDirective($state, $stateParams, $interpolate, $transitions) {
241295
return {
242296
restrict: "A",
243-
controller: ['$scope', '$element', '$attrs', '$timeout', '$transitions', function ($scope, $element, $attrs, $timeout, $transitions) {
244-
let states = [], activeClasses = {}, activeEqClass;
297+
controller: ['$scope', '$element', '$attrs', '$timeout', function ($scope, $element, $attrs, $timeout) {
298+
var states = [], activeClasses = {}, activeEqClass, uiSrefActive;
245299

246300
// There probably isn't much point in $observing this
247301
// uiSrefActive and uiSrefActiveEq share the same directive object with some
248302
// slight difference in logic routing
249303
activeEqClass = $interpolate($attrs.uiSrefActiveEq || '', false)($scope);
250304

251-
let uiSrefActive = $scope.$eval($attrs.uiSrefActive) || $interpolate($attrs.uiSrefActive || '', false)($scope);
305+
try {
306+
uiSrefActive = $scope.$eval($attrs.uiSrefActive);
307+
} catch (e) {
308+
// Do nothing. uiSrefActive is not a valid expression.
309+
// Fall back to using $interpolate below
310+
}
311+
uiSrefActive = uiSrefActive || $interpolate($attrs.uiSrefActive || '', false)($scope);
252312
if (isObject(uiSrefActive)) {
253313
forEach(uiSrefActive, function(stateOrName, activeClass) {
254314
if (isString(stateOrName)) {
255-
let ref = parseStateRef(stateOrName, $state.current.name);
315+
var ref = parseStateRef(stateOrName, $state.current.name);
256316
addState(ref.state, $scope.$eval(ref.paramExpr), activeClass);
257317
}
258318
});
@@ -270,10 +330,13 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
270330
};
271331

272332
$scope.$on('$stateChangeSuccess', update);
333+
let updateAfterTransition = ['$transition$', function($transition$) { $transition$.promise.then(update); }];
334+
let deregisterFn = $transitions.onStart({}, updateAfterTransition);
335+
$scope.$on('$destroy', deregisterFn);
273336

274337
function addState(stateName, stateParams, activeClass) {
275-
let state = $state.get(stateName, stateContext($element));
276-
let stateHash = createStateHash(stateName, stateParams);
338+
var state = $state.get(stateName, stateContext($element));
339+
var stateHash = createStateHash(stateName, stateParams);
277340

278341
states.push({
279342
state: state || { name: stateName },
@@ -284,11 +347,11 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
284347
activeClasses[stateHash] = activeClass;
285348
}
286349

287-
updateAfterTransition.$inject = ['$transition$'];
288-
function updateAfterTransition ($transition$) { $transition$.promise.then(update); }
289-
let deregisterFn = $transitions.onStart({}, updateAfterTransition);
290-
$scope.$on('$destroy', deregisterFn);
291-
350+
/**
351+
* @param {string} state
352+
* @param {Object|string} [params]
353+
* @return {string}
354+
*/
292355
function createStateHash(state, params) {
293356
if (!isString(state)) {
294357
throw new Error('state should be a string');
@@ -305,7 +368,7 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
305368

306369
// Update route state
307370
function update() {
308-
for (let i = 0; i < states.length; i++) {
371+
for (var i = 0; i < states.length; i++) {
309372
if (anyMatch(states[i].state, states[i].params)) {
310373
addClass($element, activeClasses[states[i].hash]);
311374
} else {
@@ -320,13 +383,9 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
320383
}
321384
}
322385

323-
324386
function addClass(el, className) { $timeout(function () { el.addClass(className); }); }
325-
326387
function removeClass(el, className) { el.removeClass(className); }
327-
328388
function anyMatch(state, params) { return $state.includes(state.name, params); }
329-
330389
function exactMatch(state, params) { return $state.is(state.name, params); }
331390

332391
update();
@@ -335,6 +394,7 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
335394
}
336395

337396
angular.module('ui.router.state')
338-
.directive('uiSref', $StateRefDirective)
339-
.directive('uiSrefActive', $StateRefActiveDirective)
340-
.directive('uiSrefActiveEq', $StateRefActiveDirective);
397+
.directive('uiSref', $StateRefDirective)
398+
.directive('uiSrefActive', $StateRefActiveDirective)
399+
.directive('uiSrefActiveEq', $StateRefActiveDirective)
400+
.directive('uiState', $StateRefDynamicDirective);

0 commit comments

Comments
 (0)