| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 |
- /*!
- * AngularJS Material Design
- * https://github.com/angular/material
- * @license MIT
- * v1.1.8-master-aba7b2b
- */
- goog.provide('ngmaterial.components.navBar');
- goog.require('ngmaterial.core');
- /**
- * @ngdoc module
- * @name material.components.navBar
- */
- MdNavBarController['$inject'] = ["$element", "$scope", "$timeout", "$mdConstant"];
- MdNavItem['$inject'] = ["$mdAria", "$$rAF", "$mdUtil", "$window"];
- MdNavItemController['$inject'] = ["$element"];
- MdNavBar['$inject'] = ["$mdAria", "$mdTheming"];
- angular.module('material.components.navBar', ['material.core'])
- .controller('MdNavBarController', MdNavBarController)
- .directive('mdNavBar', MdNavBar)
- .controller('MdNavItemController', MdNavItemController)
- .directive('mdNavItem', MdNavItem);
- /*****************************************************************************
- * PUBLIC DOCUMENTATION *
- *****************************************************************************/
- /**
- * @ngdoc directive
- * @name mdNavBar
- * @module material.components.navBar
- *
- * @restrict E
- *
- * @description
- * The `<md-nav-bar>` directive renders a list of material tabs that can be used
- * for top-level page navigation. Unlike `<md-tabs>`, it has no concept of a tab
- * body and no bar pagination.
- *
- * Because it deals with page navigation, certain routing concepts are built-in.
- * Route changes via `ng-href`, `ui-sref`, or `ng-click` events are supported.
- * Alternatively, the user could simply watch the value of `md-selected-nav-item`
- * (`currentNavItem` in the below example) for changes.
- *
- * Accessibility functionality is implemented as a site navigator with a
- * listbox, according to the
- * <a href="https://www.w3.org/TR/2016/WD-wai-aria-practices-1.1-20160317/#Site_Navigator_Tabbed_Style">
- * WAI-ARIA Authoring Practices 1.1 Working Draft from March 2016</a>.
- * We've kept the `role="navigation"` on the `<nav>`, for backwards compatibility, even though
- * it is not required in the
- * <a href="https://www.w3.org/TR/wai-aria-practices/#aria_lh_navigation">
- * latest Working Group Note from December 2017</a>.
- *
- * @param {string=} md-selected-nav-item The name of the current tab; this must
- * match the `name` attribute of `<md-nav-item>`.
- * @param {boolean=} md-no-ink-bar If set to true, the ink bar will be hidden.
- * @param {string=} nav-bar-aria-label An `aria-label` applied to the `md-nav-bar`'s listbox
- * for accessibility.
- *
- * @usage
- * <hljs lang="html">
- * <md-nav-bar md-selected-nav-item="currentNavItem">
- * <md-nav-item md-nav-click="goto('page1')" name="page1">
- * Page One
- * </md-nav-item>
- * <md-nav-item md-nav-href="#page2" name="page3">Page Two</md-nav-item>
- * <md-nav-item md-nav-sref="page3" name="page2">Page Three</md-nav-item>
- * <md-nav-item
- * md-nav-sref="app.page4"
- * sref-opts="{reload: true, notify: true}"
- * name="page4">
- * Page Four
- * </md-nav-item>
- * </md-nav-bar>
- *</hljs>
- * <hljs lang="js">
- * (function() {
- * 'use strict';
- *
- * $rootScope.$on('$routeChangeSuccess', function(event, current) {
- * $scope.currentLink = getCurrentLinkFromRoute(current);
- * });
- * });
- * </hljs>
- */
- /*****************************************************************************
- * mdNavItem
- *****************************************************************************/
- /**
- * @ngdoc directive
- * @name mdNavItem
- * @module material.components.navBar
- *
- * @restrict E
- *
- * @description
- * `<md-nav-item>` describes a page navigation link within the `<md-nav-bar>` component.
- * It renders an `<md-button>` as the actual link.
- *
- * Exactly one of the `md-nav-click`, `md-nav-href`, or `md-nav-sref` attributes are required
- * to be specified.
- *
- * @param {string=} aria-label Adds alternative text for accessibility.
- * @param {expression=} md-nav-click Expression which will be evaluated when the
- * link is clicked to change the page. Renders as an `ng-click`.
- * @param {string=} md-nav-href url to transition to when this link is clicked.
- * Renders as an `ng-href`.
- * @param {string=} md-nav-sref UI-Router state to transition to when this link is
- * clicked. Renders as a `ui-sref`.
- * @param {string=} name The name of this link. Used by the nav bar to know
- * which link is currently selected.
- * @param {!object=} sref-opts UI-Router options that are passed to the
- * `$state.go()` function. See the [UI-Router documentation for details]
- * (https://ui-router.github.io/docs/latest/interfaces/transition.transitionoptions.html).
- *
- * @usage
- * See `<md-nav-bar>` for usage.
- */
- /*****************************************************************************
- * IMPLEMENTATION *
- *****************************************************************************/
- function MdNavBar($mdAria, $mdTheming) {
- return {
- restrict: 'E',
- transclude: true,
- controller: MdNavBarController,
- controllerAs: 'ctrl',
- bindToController: true,
- scope: {
- 'mdSelectedNavItem': '=?',
- 'mdNoInkBar': '=?',
- 'navBarAriaLabel': '@?',
- },
- template:
- '<div class="md-nav-bar">' +
- '<nav role="navigation">' +
- '<ul class="_md-nav-bar-list" ng-transclude role="listbox" ' +
- 'tabindex="0" ' +
- 'ng-focus="ctrl.onFocus()" ' +
- 'ng-keydown="ctrl.onKeydown($event)" ' +
- 'aria-label="{{ctrl.navBarAriaLabel}}">' +
- '</ul>' +
- '</nav>' +
- '<md-nav-ink-bar ng-hide="ctrl.mdNoInkBar"></md-nav-ink-bar>' +
- '</div>',
- link: function(scope, element, attrs, ctrl) {
- $mdTheming(element);
- if (!ctrl.navBarAriaLabel) {
- $mdAria.expectAsync(element, 'aria-label', angular.noop);
- }
- },
- };
- }
- /**
- * Controller for the nav-bar component.
- *
- * Accessibility functionality is implemented as a site navigator with a
- * listbox, according to
- * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style
- * @param {!angular.JQLite} $element
- * @param {!angular.Scope} $scope
- * @param {!angular.Timeout} $timeout
- * @param {!Object} $mdConstant
- * @constructor
- * @final
- * ngInject
- */
- function MdNavBarController($element, $scope, $timeout, $mdConstant) {
- // Injected variables
- /** @private @const {!angular.Timeout} */
- this._$timeout = $timeout;
- /** @private @const {!angular.Scope} */
- this._$scope = $scope;
- /** @private @const {!Object} */
- this._$mdConstant = $mdConstant;
- // Data-bound variables.
- /** @type {string} */
- this.mdSelectedNavItem;
- /** @type {string} */
- this.navBarAriaLabel;
- // State variables.
- /** @type {?angular.JQLite} */
- this._navBarEl = $element[0];
- /** @type {?angular.JQLite} */
- this._inkbar;
- var self = this;
- // need to wait for transcluded content to be available
- var deregisterTabWatch = this._$scope.$watch(function() {
- return self._navBarEl.querySelectorAll('._md-nav-button').length;
- },
- function(newLength) {
- if (newLength > 0) {
- self._initTabs();
- deregisterTabWatch();
- }
- });
- }
- /**
- * Initializes the tab components once they exist.
- * @private
- */
- MdNavBarController.prototype._initTabs = function() {
- this._inkbar = angular.element(this._navBarEl.querySelector('md-nav-ink-bar'));
- var self = this;
- this._$timeout(function() {
- self._updateTabs(self.mdSelectedNavItem, undefined);
- });
- this._$scope.$watch('ctrl.mdSelectedNavItem', function(newValue, oldValue) {
- // Wait a digest before update tabs for products doing
- // anything dynamic in the template.
- self._$timeout(function() {
- self._updateTabs(newValue, oldValue);
- });
- });
- };
- /**
- * Set the current tab to be selected.
- * @param {string|undefined} newValue New current tab name.
- * @param {string|undefined} oldValue Previous tab name.
- * @private
- */
- MdNavBarController.prototype._updateTabs = function(newValue, oldValue) {
- var self = this;
- var tabs = this._getTabs();
- // this._getTabs can return null if nav-bar has not yet been initialized
- if(!tabs)
- return;
- var oldIndex = -1;
- var newIndex = -1;
- var newTab = this._getTabByName(newValue);
- var oldTab = this._getTabByName(oldValue);
- if (oldTab) {
- oldTab.setSelected(false);
- oldIndex = tabs.indexOf(oldTab);
- }
- if (newTab) {
- newTab.setSelected(true);
- newIndex = tabs.indexOf(newTab);
- }
- this._$timeout(function() {
- self._updateInkBarStyles(newTab, newIndex, oldIndex);
- });
- };
- /**
- * Repositions the ink bar to the selected tab.
- * @private
- */
- MdNavBarController.prototype._updateInkBarStyles = function(tab, newIndex, oldIndex) {
- this._inkbar.toggleClass('_md-left', newIndex < oldIndex)
- .toggleClass('_md-right', newIndex > oldIndex);
- this._inkbar.css({display: newIndex < 0 ? 'none' : ''});
- if (tab) {
- var tabEl = tab.getButtonEl();
- var left = tabEl.offsetLeft;
- this._inkbar.css({left: left + 'px', width: tabEl.offsetWidth + 'px'});
- }
- };
- /**
- * Returns an array of the current tabs.
- * @return {!Array<!NavItemController>}
- * @private
- */
- MdNavBarController.prototype._getTabs = function() {
- var controllers = Array.prototype.slice.call(
- this._navBarEl.querySelectorAll('.md-nav-item'))
- .map(function(el) {
- return angular.element(el).controller('mdNavItem');
- });
- return controllers.indexOf(undefined) ? controllers : null;
- };
- /**
- * Returns the tab with the specified name.
- * @param {string} name The name of the tab, found in its name attribute.
- * @return {!NavItemController|undefined}
- * @private
- */
- MdNavBarController.prototype._getTabByName = function(name) {
- return this._findTab(function(tab) {
- return tab.getName() == name;
- });
- };
- /**
- * Returns the selected tab.
- * @return {!NavItemController|undefined}
- * @private
- */
- MdNavBarController.prototype._getSelectedTab = function() {
- return this._findTab(function(tab) {
- return tab.isSelected();
- });
- };
- /**
- * Returns the focused tab.
- * @return {!NavItemController|undefined}
- */
- MdNavBarController.prototype.getFocusedTab = function() {
- return this._findTab(function(tab) {
- return tab.hasFocus();
- });
- };
- /**
- * Find a tab that matches the specified function.
- * @private
- */
- MdNavBarController.prototype._findTab = function(fn) {
- var tabs = this._getTabs();
- for (var i = 0; i < tabs.length; i++) {
- if (fn(tabs[i])) {
- return tabs[i];
- }
- }
- return null;
- };
- /**
- * Direct focus to the selected tab when focus enters the nav bar.
- */
- MdNavBarController.prototype.onFocus = function() {
- var tab = this._getSelectedTab();
- if (tab) {
- tab.setFocused(true);
- }
- };
- /**
- * Move focus from oldTab to newTab.
- * @param {!NavItemController} oldTab
- * @param {!NavItemController} newTab
- * @private
- */
- MdNavBarController.prototype._moveFocus = function(oldTab, newTab) {
- oldTab.setFocused(false);
- newTab.setFocused(true);
- };
- /**
- * Responds to keypress events.
- * @param {!Event} e
- */
- MdNavBarController.prototype.onKeydown = function(e) {
- var keyCodes = this._$mdConstant.KEY_CODE;
- var tabs = this._getTabs();
- var focusedTab = this.getFocusedTab();
- if (!focusedTab) return;
- var focusedTabIndex = tabs.indexOf(focusedTab);
- // use arrow keys to navigate between tabs
- switch (e.keyCode) {
- case keyCodes.UP_ARROW:
- case keyCodes.LEFT_ARROW:
- if (focusedTabIndex > 0) {
- this._moveFocus(focusedTab, tabs[focusedTabIndex - 1]);
- }
- break;
- case keyCodes.DOWN_ARROW:
- case keyCodes.RIGHT_ARROW:
- if (focusedTabIndex < tabs.length - 1) {
- this._moveFocus(focusedTab, tabs[focusedTabIndex + 1]);
- }
- break;
- case keyCodes.SPACE:
- case keyCodes.ENTER:
- // timeout to avoid a "digest already in progress" console error
- this._$timeout(function() {
- focusedTab.getButtonEl().click();
- });
- break;
- }
- };
- /**
- * ngInject
- */
- function MdNavItem($mdAria, $$rAF, $mdUtil, $window) {
- return {
- restrict: 'E',
- require: ['mdNavItem', '^mdNavBar'],
- controller: MdNavItemController,
- bindToController: true,
- controllerAs: 'ctrl',
- replace: true,
- transclude: true,
- template: function(tElement, tAttrs) {
- var hasNavClick = tAttrs.mdNavClick;
- var hasNavHref = tAttrs.mdNavHref;
- var hasNavSref = tAttrs.mdNavSref;
- var hasSrefOpts = tAttrs.srefOpts;
- var navigationAttribute;
- var navigationOptions;
- var buttonTemplate;
- // Cannot specify more than one nav attribute
- if ((hasNavClick ? 1:0) + (hasNavHref ? 1:0) + (hasNavSref ? 1:0) > 1) {
- throw Error(
- 'Must not specify more than one of the md-nav-click, md-nav-href, ' +
- 'or md-nav-sref attributes per nav-item directive.'
- );
- }
- if (hasNavClick) {
- navigationAttribute = 'ng-click="ctrl.mdNavClick()"';
- } else if (hasNavHref) {
- navigationAttribute = 'ng-href="{{ctrl.mdNavHref}}"';
- } else if (hasNavSref) {
- navigationAttribute = 'ui-sref="{{ctrl.mdNavSref}}"';
- }
- navigationOptions = hasSrefOpts ? 'ui-sref-opts="{{ctrl.srefOpts}}" ' : '';
- if (navigationAttribute) {
- buttonTemplate = '' +
- '<md-button class="_md-nav-button md-accent" ' +
- 'ng-class="ctrl.getNgClassMap()" ' +
- 'ng-blur="ctrl.setFocused(false)" ' +
- 'ng-disabled="ctrl.disabled" ' +
- 'tabindex="-1" ' +
- navigationOptions +
- navigationAttribute + '>' +
- '<span ng-transclude class="_md-nav-button-text"></span>' +
- '</md-button>';
- }
- return '' +
- '<li class="md-nav-item" ' +
- 'role="option" ' +
- 'aria-selected="{{ctrl.isSelected()}}">' +
- (buttonTemplate || '') +
- '</li>';
- },
- scope: {
- 'mdNavClick': '&?',
- 'mdNavHref': '@?',
- 'mdNavSref': '@?',
- 'srefOpts': '=?',
- 'name': '@',
- },
- link: function(scope, element, attrs, controllers) {
- var disconnect;
- // When accessing the element's contents synchronously, they
- // may not be defined yet because of transclusion. There is a higher
- // chance that it will be accessible if we wait one frame.
- $$rAF(function() {
- var mdNavItem = controllers[0];
- var mdNavBar = controllers[1];
- var navButton = angular.element(element[0].querySelector('._md-nav-button'));
- if (!mdNavItem.name) {
- mdNavItem.name = angular.element(element[0]
- .querySelector('._md-nav-button-text')).text().trim();
- }
- navButton.on('click', function() {
- mdNavBar.mdSelectedNavItem = mdNavItem.name;
- scope.$apply();
- });
- // Get the disabled attribute value first, then setup observing of value changes
- mdNavItem.disabled = $mdUtil.parseAttributeBoolean(attrs['disabled'], false);
- if ('MutationObserver' in $window) {
- var config = {attributes: true, attributeFilter: ['disabled']};
- var targetNode = element[0];
- var mutationCallback = function(mutationList) {
- $mdUtil.nextTick(function() {
- mdNavItem.disabled = $mdUtil.parseAttributeBoolean(attrs[mutationList[0].attributeName], false);
- });
- };
- var observer = new MutationObserver(mutationCallback);
- observer.observe(targetNode, config);
- disconnect = observer.disconnect.bind(observer);
- } else {
- attrs.$observe('disabled', function (value) {
- mdNavItem.disabled = $mdUtil.parseAttributeBoolean(value, false);
- });
- }
- $mdAria.expectWithText(element, 'aria-label');
- });
- scope.$on('destroy', function() {
- disconnect();
- })
- }
- };
- }
- /**
- * Controller for the nav-item component.
- * @param {!angular.JQLite} $element
- * @constructor
- * @final
- * ngInject
- */
- function MdNavItemController($element) {
- /** @private @const {!angular.JQLite} */
- this._$element = $element;
- // Data-bound variables
- /** @const {?Function} */
- this.mdNavClick;
- /** @const {?string} */
- this.mdNavHref;
- /** @const {?string} */
- this.mdNavSref;
- /** @const {?Object} */
- this.srefOpts;
- /** @const {?string} */
- this.name;
- // State variables
- /** @private {boolean} */
- this._selected = false;
- /** @private {boolean} */
- this._focused = false;
- }
- /**
- * Returns a map of class names and values for use by ng-class.
- * @return {!Object<string,boolean>}
- */
- MdNavItemController.prototype.getNgClassMap = function() {
- return {
- 'md-active': this._selected,
- 'md-primary': this._selected,
- 'md-unselected': !this._selected,
- 'md-focused': this._focused,
- };
- };
- /**
- * Get the name attribute of the tab.
- * @return {string}
- */
- MdNavItemController.prototype.getName = function() {
- return this.name;
- };
- /**
- * Get the button element associated with the tab.
- * @return {!Element}
- */
- MdNavItemController.prototype.getButtonEl = function() {
- return this._$element[0].querySelector('._md-nav-button');
- };
- /**
- * Set the selected state of the tab.
- * @param {boolean} isSelected
- */
- MdNavItemController.prototype.setSelected = function(isSelected) {
- this._selected = isSelected;
- };
- /**
- * @return {boolean}
- */
- MdNavItemController.prototype.isSelected = function() {
- return this._selected;
- };
- /**
- * Set the focused state of the tab.
- * @param {boolean} isFocused
- */
- MdNavItemController.prototype.setFocused = function(isFocused) {
- this._focused = isFocused;
- if (isFocused) {
- this.getButtonEl().focus();
- }
- };
- /**
- * @return {boolean}
- */
- MdNavItemController.prototype.hasFocus = function() {
- return this._focused;
- };
- ngmaterial.components.navBar = angular.module("material.components.navBar");
|