/*! * Angular Material Design * https://github.com/angular/material * @license MIT * v1.1.1 */ goog.provide('ngmaterial.components.navBar'); goog.require('ngmaterial.core'); /** * @ngdoc module * @name material.components.navBar */ MdNavBarController.$inject = ["$element", "$scope", "$timeout", "$mdConstant"]; MdNavItem.$inject = ["$$rAF"]; 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 `` directive renders a list of material tabs that can be used * for top-level page navigation. Unlike ``, 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 via ng-href, ui-sref, or ng-click events are supported. * Alternatively, the user could simply watch currentNavItem for changes. * * 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 {string=} mdSelectedNavItem The name of the current tab; this must * match the name attribute of `` * @param {string=} navBarAriaLabel An aria-label for the nav-bar * * @usage * * * Page One * Page Two * Page Three * * * * (function() { * ‘use strict’; * * $rootScope.$on('$routeChangeSuccess', function(event, current) { * $scope.currentLink = getCurrentLinkFromRoute(current); * }); * }); * */ /***************************************************************************** * mdNavItem *****************************************************************************/ /** * @ngdoc directive * @name mdNavItem * @module material.components.navBar * * @restrict E * * @description * `` describes a page navigation link within the `` * component. It renders an md-button as the actual link. * * Exactly one of the mdNavClick, mdNavHref, mdNavSref attributes are required to be * specified. * * @param {Function=} mdNavClick Function which will be called when the * link is clicked to change the page. Renders as an `ng-click`. * @param {string=} mdNavHref url to transition to when this link is clicked. * Renders as an `ng-href`. * @param {string=} mdNavSref 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. * * @usage * See `` for usage. */ /***************************************************************************** * IMPLEMENTATION * *****************************************************************************/ function MdNavBar($mdAria, $mdTheming) { return { restrict: 'E', transclude: true, controller: MdNavBarController, controllerAs: 'ctrl', bindToController: true, scope: { 'mdSelectedNavItem': '=?', 'navBarAriaLabel': '@?', }, template: '
' + '' + '' + '
', 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.getElementsByTagName('md-nav-ink-bar')[0]); 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(); 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} * @private */ MdNavBarController.prototype._getTabs = function() { var linkArray = Array.prototype.slice.call( this._navBarEl.querySelectorAll('.md-nav-item')); return linkArray.map(function(el) { return angular.element(el).controller('mdNavItem') }); }; /** * 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); } }; /** * Clear tab focus when focus leaves the nav bar. */ MdNavBarController.prototype.onBlur = function() { var tab = this.getFocusedTab(); if (tab) { tab.setFocused(false); } }; /** * 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($$rAF) { return { restrict: 'E', require: ['mdNavItem', '^mdNavBar'], controller: MdNavItemController, bindToController: true, controllerAs: 'ctrl', replace: true, transclude: true, template: '
  • ' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
  • ', scope: { 'mdNavClick': '&?', 'mdNavHref': '@?', 'mdNavSref': '@?', 'name': '@', }, link: function(scope, element, attrs, controllers) { var mdNavItem = controllers[0]; var mdNavBar = controllers[1]; // 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() { if (!mdNavItem.name) { mdNavItem.name = angular.element(element[0].querySelector('._md-nav-button-text')) .text().trim(); } var navButton = angular.element(element[0].querySelector('._md-nav-button')); navButton.on('click', function() { mdNavBar.mdSelectedNavItem = mdNavItem.name; scope.$apply(); }); }); } }; } /** * 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.name; // State variables /** @private {boolean} */ this._selected = false; /** @private {boolean} */ this._focused = false; var hasNavClick = !!($element.attr('md-nav-click')); var hasNavHref = !!($element.attr('md-nav-href')); var hasNavSref = !!($element.attr('md-nav-sref')); // Cannot specify more than one nav attribute if ((hasNavClick ? 1:0) + (hasNavHref ? 1:0) + (hasNavSref ? 1:0) > 1) { throw Error( 'Must specify exactly one of md-nav-click, md-nav-href, ' + 'md-nav-sref for nav-item directive'); } } /** * Returns a map of class names and values for use by ng-class. * @return {!Object} */ 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; }; /** * @return {boolean} */ MdNavItemController.prototype.hasFocus = function() { return this._focused; }; ngmaterial.components.navBar = angular.module("material.components.navBar");