| function parseStateRef(ref, current) { |
| var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed; |
| if (preparsed) ref = current + '(' + preparsed[1] + ')'; |
| parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/); |
| if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'"); |
| return { state: parsed[1], paramExpr: parsed[3] || null }; |
| } |
| |
| function stateContext(el) { |
| var stateData = el.parent().inheritedData('$uiView'); |
| |
| if (stateData && stateData.state && stateData.state.name) { |
| return stateData.state; |
| } |
| } |
| |
| /** |
| * @ngdoc directive |
| * @name ui.router.state.directive:ui-sref |
| * |
| * @requires ui.router.state.$state |
| * @requires $timeout |
| * |
| * @restrict A |
| * |
| * @description |
| * A directive that binds a link (`<a>` tag) to a state. If the state has an associated |
| * URL, the directive will automatically generate & update the `href` attribute via |
| * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking |
| * the link will trigger a state transition with optional parameters. |
| * |
| * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be |
| * handled natively by the browser. |
| * |
| * You can also use relative state paths within ui-sref, just like the relative |
| * paths passed to `$state.go()`. You just need to be aware that the path is relative |
| * to the state that the link lives in, in other words the state that loaded the |
| * template containing the link. |
| * |
| * You can specify options to pass to {@link ui.router.state.$state#go $state.go()} |
| * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`, |
| * and `reload`. |
| * |
| * @example |
| * Here's an example of how you'd use ui-sref and how it would compile. If you have the |
| * following template: |
| * <pre> |
| * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a> |
| * |
| * <ul> |
| * <li ng-repeat="contact in contacts"> |
| * <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a> |
| * </li> |
| * </ul> |
| * </pre> |
| * |
| * Then the compiled html would be (assuming Html5Mode is off and current state is contacts): |
| * <pre> |
| * <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> |
| * |
| * <ul> |
| * <li ng-repeat="contact in contacts"> |
| * <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a> |
| * </li> |
| * <li ng-repeat="contact in contacts"> |
| * <a href="#/contacts/2" ui-sref="contacts.detail({ id: contact.id })">Alice</a> |
| * </li> |
| * <li ng-repeat="contact in contacts"> |
| * <a href="#/contacts/3" ui-sref="contacts.detail({ id: contact.id })">Bob</a> |
| * </li> |
| * </ul> |
| * |
| * <a ui-sref="home" ui-sref-opts="{reload: true}">Home</a> |
| * </pre> |
| * |
| * @param {string} ui-sref 'stateName' can be any valid absolute or relative state |
| * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()} |
| */ |
| $StateRefDirective.$inject = ['$state', '$timeout']; |
| function $StateRefDirective($state, $timeout) { |
| var allowedOptions = ['location', 'inherit', 'reload', 'absolute']; |
| |
| return { |
| restrict: 'A', |
| require: ['?^uiSrefActive', '?^uiSrefActiveEq'], |
| link: function(scope, element, attrs, uiSrefActive) { |
| var ref = parseStateRef(attrs.uiSref, $state.current.name); |
| var params = null, url = null, base = stateContext(element) || $state.$current; |
| // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. |
| var hrefKind = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? |
| 'xlink:href' : 'href'; |
| var newHref = null, isAnchor = element.prop("tagName").toUpperCase() === "A"; |
| var isForm = element[0].nodeName === "FORM"; |
| var attr = isForm ? "action" : hrefKind, nav = true; |
| |
| var options = { relative: base, inherit: true }; |
| var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {}; |
| |
| angular.forEach(allowedOptions, function(option) { |
| if (option in optionsOverride) { |
| options[option] = optionsOverride[option]; |
| } |
| }); |
| |
| var update = function(newVal) { |
| if (newVal) params = angular.copy(newVal); |
| if (!nav) return; |
| |
| newHref = $state.href(ref.state, params, options); |
| |
| var activeDirective = uiSrefActive[1] || uiSrefActive[0]; |
| if (activeDirective) { |
| activeDirective.$$addStateInfo(ref.state, params); |
| } |
| if (newHref === null) { |
| nav = false; |
| return false; |
| } |
| attrs.$set(attr, newHref); |
| }; |
| |
| if (ref.paramExpr) { |
| scope.$watch(ref.paramExpr, function(newVal, oldVal) { |
| if (newVal !== params) update(newVal); |
| }, true); |
| params = angular.copy(scope.$eval(ref.paramExpr)); |
| } |
| update(); |
| |
| if (isForm) return; |
| |
| element.bind("click", function(e) { |
| var button = e.which || e.button; |
| if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) { |
| // HACK: This is to allow ng-clicks to be processed before the transition is initiated: |
| var transition = $timeout(function() { |
| $state.go(ref.state, params, options); |
| }); |
| e.preventDefault(); |
| |
| // if the state has no URL, ignore one preventDefault from the <a> directive. |
| var ignorePreventDefaultCount = isAnchor && !newHref ? 1: 0; |
| e.preventDefault = function() { |
| if (ignorePreventDefaultCount-- <= 0) |
| $timeout.cancel(transition); |
| }; |
| } |
| }); |
| } |
| }; |
| } |
| |
| /** |
| * @ngdoc directive |
| * @name ui.router.state.directive:ui-sref-active |
| * |
| * @requires ui.router.state.$state |
| * @requires ui.router.state.$stateParams |
| * @requires $interpolate |
| * |
| * @restrict A |
| * |
| * @description |
| * A directive working alongside ui-sref to add classes to an element when the |
| * related ui-sref directive's state is active, and removing them when it is inactive. |
| * The primary use-case is to simplify the special appearance of navigation menus |
| * relying on `ui-sref`, by having the "active" state's menu button appear different, |
| * distinguishing it from the inactive menu items. |
| * |
| * ui-sref-active can live on the same element as ui-sref or on a parent element. The first |
| * ui-sref-active found at the same level or above the ui-sref will be used. |
| * |
| * Will activate when the ui-sref's target state or any child state is active. If you |
| * need to activate only when the ui-sref target state is active and *not* any of |
| * it's children, then you will use |
| * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq} |
| * |
| * @example |
| * Given the following template: |
| * <pre> |
| * <ul> |
| * <li ui-sref-active="active" class="item"> |
| * <a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a> |
| * </li> |
| * </ul> |
| * </pre> |
| * |
| * |
| * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins", |
| * the resulting HTML will appear as (note the 'active' class): |
| * <pre> |
| * <ul> |
| * <li ui-sref-active="active" class="item active"> |
| * <a ui-sref="app.user({user: 'bilbobaggins'})" href="/users/bilbobaggins">@bilbobaggins</a> |
| * </li> |
| * </ul> |
| * </pre> |
| * |
| * The class name is interpolated **once** during the directives link time (any further changes to the |
| * interpolated value are ignored). |
| * |
| * Multiple classes may be specified in a space-separated format: |
| * <pre> |
| * <ul> |
| * <li ui-sref-active='class1 class2 class3'> |
| * <a ui-sref="app.user">link</a> |
| * </li> |
| * </ul> |
| * </pre> |
| */ |
| |
| /** |
| * @ngdoc directive |
| * @name ui.router.state.directive:ui-sref-active-eq |
| * |
| * @requires ui.router.state.$state |
| * @requires ui.router.state.$stateParams |
| * @requires $interpolate |
| * |
| * @restrict A |
| * |
| * @description |
| * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate |
| * when the exact target state used in the `ui-sref` is active; no child states. |
| * |
| */ |
| $StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate']; |
| function $StateRefActiveDirective($state, $stateParams, $interpolate) { |
| return { |
| restrict: "A", |
| controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) { |
| var states = [], activeClass; |
| |
| // There probably isn't much point in $observing this |
| // uiSrefActive and uiSrefActiveEq share the same directive object with some |
| // slight difference in logic routing |
| activeClass = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive || '', false)($scope); |
| |
| // Allow uiSref to communicate with uiSrefActive[Equals] |
| this.$$addStateInfo = function (newState, newParams) { |
| var state = $state.get(newState, stateContext($element)); |
| |
| states.push({ |
| state: state || { name: newState }, |
| params: newParams |
| }); |
| |
| update(); |
| }; |
| |
| $scope.$on('$stateChangeSuccess', update); |
| |
| // Update route state |
| function update() { |
| if (anyMatch()) { |
| $element.addClass(activeClass); |
| } else { |
| $element.removeClass(activeClass); |
| } |
| } |
| |
| function anyMatch() { |
| for (var i = 0; i < states.length; i++) { |
| if (isMatch(states[i].state, states[i].params)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| function isMatch(state, params) { |
| if (typeof $attrs.uiSrefActiveEq !== 'undefined') { |
| return $state.is(state.name, params); |
| } else { |
| return $state.includes(state.name, params); |
| } |
| } |
| }] |
| }; |
| } |
| |
| angular.module('ui.router.state') |
| .directive('uiSref', $StateRefDirective) |
| .directive('uiSrefActive', $StateRefActiveDirective) |
| .directive('uiSrefActiveEq', $StateRefActiveDirective); |