commonroutesApp/www/lib/angular-material/modules/closure/navBar/navBar.js
www/lib/angular-material/modules/closure/navBar/navBar.jsRaw
1/*!
2 * Angular Material Design
3 * https://github.com/angular/material
4 * @license MIT
5 * v1.1.1
6 */
7goog.provide('ngmaterial.components.navBar');
8goog.require('ngmaterial.core');
9/**
10 * @ngdoc module
11 * @name material.components.navBar
12 */
13
14
15MdNavBarController.$inject = ["$element", "$scope", "$timeout", "$mdConstant"];
16MdNavItem.$inject = ["$$rAF"];
17MdNavItemController.$inject = ["$element"];
18MdNavBar.$inject = ["$mdAria", "$mdTheming"];
19angular.module('material.components.navBar', ['material.core'])
20 .controller('MdNavBarController', MdNavBarController)
21 .directive('mdNavBar', MdNavBar)
22 .controller('MdNavItemController', MdNavItemController)
23 .directive('mdNavItem', MdNavItem);
24
25
26/*****************************************************************************
27 * PUBLIC DOCUMENTATION *
28 *****************************************************************************/
29/**
30 * @ngdoc directive
31 * @name mdNavBar
32 * @module material.components.navBar
33 *
34 * @restrict E
35 *
36 * @description
37 * The `<md-nav-bar>` directive renders a list of material tabs that can be used
38 * for top-level page navigation. Unlike `<md-tabs>`, it has no concept of a tab
39 * body and no bar pagination.
40 *
41 * Because it deals with page navigation, certain routing concepts are built-in.
42 * Route changes via via ng-href, ui-sref, or ng-click events are supported.
43 * Alternatively, the user could simply watch currentNavItem for changes.
44 *
45 * Accessibility functionality is implemented as a site navigator with a
46 * listbox, according to
47 * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style
48 *
49 * @param {string=} mdSelectedNavItem The name of the current tab; this must
50 * match the name attribute of `<md-nav-item>`
51 * @param {string=} navBarAriaLabel An aria-label for the nav-bar
52 *
53 * @usage
54 * <hljs lang="html">
55 * <md-nav-bar md-selected-nav-item="currentNavItem">
56 * <md-nav-item md-nav-click="goto('page1')" name="page1">Page One</md-nav-item>
57 * <md-nav-item md-nav-sref="app.page2" name="page2">Page Two</md-nav-item>
58 * <md-nav-item md-nav-href="#page3" name="page3">Page Three</md-nav-item>
59 * </md-nav-bar>
60 *</hljs>
61 * <hljs lang="js">
62 * (function() {
63 * ‘use strict’;
64 *
65 * $rootScope.$on('$routeChangeSuccess', function(event, current) {
66 * $scope.currentLink = getCurrentLinkFromRoute(current);
67 * });
68 * });
69 * </hljs>
70 */
71
72/*****************************************************************************
73 * mdNavItem
74 *****************************************************************************/
75/**
76 * @ngdoc directive
77 * @name mdNavItem
78 * @module material.components.navBar
79 *
80 * @restrict E
81 *
82 * @description
83 * `<md-nav-item>` describes a page navigation link within the `<md-nav-bar>`
84 * component. It renders an md-button as the actual link.
85 *
86 * Exactly one of the mdNavClick, mdNavHref, mdNavSref attributes are required to be
87 * specified.
88 *
89 * @param {Function=} mdNavClick Function which will be called when the
90 * link is clicked to change the page. Renders as an `ng-click`.
91 * @param {string=} mdNavHref url to transition to when this link is clicked.
92 * Renders as an `ng-href`.
93 * @param {string=} mdNavSref Ui-router state to transition to when this link is
94 * clicked. Renders as a `ui-sref`.
95 * @param {string=} name The name of this link. Used by the nav bar to know
96 * which link is currently selected.
97 *
98 * @usage
99 * See `<md-nav-bar>` for usage.
100 */
101
102
103/*****************************************************************************
104 * IMPLEMENTATION *
105 *****************************************************************************/
106
107function MdNavBar($mdAria, $mdTheming) {
108 return {
109 restrict: 'E',
110 transclude: true,
111 controller: MdNavBarController,
112 controllerAs: 'ctrl',
113 bindToController: true,
114 scope: {
115 'mdSelectedNavItem': '=?',
116 'navBarAriaLabel': '@?',
117 },
118 template:
119 '<div class="md-nav-bar">' +
120 '<nav role="navigation">' +
121 '<ul class="_md-nav-bar-list" ng-transclude role="listbox"' +
122 'tabindex="0"' +
123 'ng-focus="ctrl.onFocus()"' +
124 'ng-blur="ctrl.onBlur()"' +
125 'ng-keydown="ctrl.onKeydown($event)"' +
126 'aria-label="{{ctrl.navBarAriaLabel}}">' +
127 '</ul>' +
128 '</nav>' +
129 '<md-nav-ink-bar></md-nav-ink-bar>' +
130 '</div>',
131 link: function(scope, element, attrs, ctrl) {
132 $mdTheming(element);
133 if (!ctrl.navBarAriaLabel) {
134 $mdAria.expectAsync(element, 'aria-label', angular.noop);
135 }
136 },
137 };
138}
139
140/**
141 * Controller for the nav-bar component.
142 *
143 * Accessibility functionality is implemented as a site navigator with a
144 * listbox, according to
145 * https://www.w3.org/TR/wai-aria-practices/#Site_Navigator_Tabbed_Style
146 * @param {!angular.JQLite} $element
147 * @param {!angular.Scope} $scope
148 * @param {!angular.Timeout} $timeout
149 * @param {!Object} $mdConstant
150 * @constructor
151 * @final
152 * ngInject
153 */
154function MdNavBarController($element, $scope, $timeout, $mdConstant) {
155 // Injected variables
156 /** @private @const {!angular.Timeout} */
157 this._$timeout = $timeout;
158
159 /** @private @const {!angular.Scope} */
160 this._$scope = $scope;
161
162 /** @private @const {!Object} */
163 this._$mdConstant = $mdConstant;
164
165 // Data-bound variables.
166 /** @type {string} */
167 this.mdSelectedNavItem;
168
169 /** @type {string} */
170 this.navBarAriaLabel;
171
172 // State variables.
173
174 /** @type {?angular.JQLite} */
175 this._navBarEl = $element[0];
176
177 /** @type {?angular.JQLite} */
178 this._inkbar;
179
180 var self = this;
181 // need to wait for transcluded content to be available
182 var deregisterTabWatch = this._$scope.$watch(function() {
183 return self._navBarEl.querySelectorAll('._md-nav-button').length;
184 },
185 function(newLength) {
186 if (newLength > 0) {
187 self._initTabs();
188 deregisterTabWatch();
189 }
190 });
191}
192
193
194
195/**
196 * Initializes the tab components once they exist.
197 * @private
198 */
199MdNavBarController.prototype._initTabs = function() {
200 this._inkbar = angular.element(this._navBarEl.getElementsByTagName('md-nav-ink-bar')[0]);
201
202 var self = this;
203 this._$timeout(function() {
204 self._updateTabs(self.mdSelectedNavItem, undefined);
205 });
206
207 this._$scope.$watch('ctrl.mdSelectedNavItem', function(newValue, oldValue) {
208 // Wait a digest before update tabs for products doing
209 // anything dynamic in the template.
210 self._$timeout(function() {
211 self._updateTabs(newValue, oldValue);
212 });
213 });
214};
215
216/**
217 * Set the current tab to be selected.
218 * @param {string|undefined} newValue New current tab name.
219 * @param {string|undefined} oldValue Previous tab name.
220 * @private
221 */
222MdNavBarController.prototype._updateTabs = function(newValue, oldValue) {
223 var self = this;
224 var tabs = this._getTabs();
225 var oldIndex = -1;
226 var newIndex = -1;
227 var newTab = this._getTabByName(newValue);
228 var oldTab = this._getTabByName(oldValue);
229
230 if (oldTab) {
231 oldTab.setSelected(false);
232 oldIndex = tabs.indexOf(oldTab);
233 }
234
235 if (newTab) {
236 newTab.setSelected(true);
237 newIndex = tabs.indexOf(newTab);
238 }
239
240 this._$timeout(function() {
241 self._updateInkBarStyles(newTab, newIndex, oldIndex);
242 });
243};
244
245/**
246 * Repositions the ink bar to the selected tab.
247 * @private
248 */
249MdNavBarController.prototype._updateInkBarStyles = function(tab, newIndex, oldIndex) {
250 this._inkbar.toggleClass('_md-left', newIndex < oldIndex)
251 .toggleClass('_md-right', newIndex > oldIndex);
252
253 this._inkbar.css({display: newIndex < 0 ? 'none' : ''});
254
255 if(tab){
256 var tabEl = tab.getButtonEl();
257 var left = tabEl.offsetLeft;
258
259 this._inkbar.css({left: left + 'px', width: tabEl.offsetWidth + 'px'});
260 }
261};
262
263/**
264 * Returns an array of the current tabs.
265 * @return {!Array<!NavItemController>}
266 * @private
267 */
268MdNavBarController.prototype._getTabs = function() {
269 var linkArray = Array.prototype.slice.call(
270 this._navBarEl.querySelectorAll('.md-nav-item'));
271 return linkArray.map(function(el) {
272 return angular.element(el).controller('mdNavItem')
273 });
274};
275
276/**
277 * Returns the tab with the specified name.
278 * @param {string} name The name of the tab, found in its name attribute.
279 * @return {!NavItemController|undefined}
280 * @private
281 */
282MdNavBarController.prototype._getTabByName = function(name) {
283 return this._findTab(function(tab) {
284 return tab.getName() == name;
285 });
286};
287
288/**
289 * Returns the selected tab.
290 * @return {!NavItemController|undefined}
291 * @private
292 */
293MdNavBarController.prototype._getSelectedTab = function() {
294 return this._findTab(function(tab) {
295 return tab.isSelected()
296 });
297};
298
299/**
300 * Returns the focused tab.
301 * @return {!NavItemController|undefined}
302 */
303MdNavBarController.prototype.getFocusedTab = function() {
304 return this._findTab(function(tab) {
305 return tab.hasFocus()
306 });
307};
308
309/**
310 * Find a tab that matches the specified function.
311 * @private
312 */
313MdNavBarController.prototype._findTab = function(fn) {
314 var tabs = this._getTabs();
315 for (var i = 0; i < tabs.length; i++) {
316 if (fn(tabs[i])) {
317 return tabs[i];
318 }
319 }
320
321 return null;
322};
323
324/**
325 * Direct focus to the selected tab when focus enters the nav bar.
326 */
327MdNavBarController.prototype.onFocus = function() {
328 var tab = this._getSelectedTab();
329 if (tab) {
330 tab.setFocused(true);
331 }
332};
333
334/**
335 * Clear tab focus when focus leaves the nav bar.
336 */
337MdNavBarController.prototype.onBlur = function() {
338 var tab = this.getFocusedTab();
339 if (tab) {
340 tab.setFocused(false);
341 }
342};
343
344/**
345 * Move focus from oldTab to newTab.
346 * @param {!NavItemController} oldTab
347 * @param {!NavItemController} newTab
348 * @private
349 */
350MdNavBarController.prototype._moveFocus = function(oldTab, newTab) {
351 oldTab.setFocused(false);
352 newTab.setFocused(true);
353};
354
355/**
356 * Responds to keypress events.
357 * @param {!Event} e
358 */
359MdNavBarController.prototype.onKeydown = function(e) {
360 var keyCodes = this._$mdConstant.KEY_CODE;
361 var tabs = this._getTabs();
362 var focusedTab = this.getFocusedTab();
363 if (!focusedTab) return;
364
365 var focusedTabIndex = tabs.indexOf(focusedTab);
366
367 // use arrow keys to navigate between tabs
368 switch (e.keyCode) {
369 case keyCodes.UP_ARROW:
370 case keyCodes.LEFT_ARROW:
371 if (focusedTabIndex > 0) {
372 this._moveFocus(focusedTab, tabs[focusedTabIndex - 1]);
373 }
374 break;
375 case keyCodes.DOWN_ARROW:
376 case keyCodes.RIGHT_ARROW:
377 if (focusedTabIndex < tabs.length - 1) {
378 this._moveFocus(focusedTab, tabs[focusedTabIndex + 1]);
379 }
380 break;
381 case keyCodes.SPACE:
382 case keyCodes.ENTER:
383 // timeout to avoid a "digest already in progress" console error
384 this._$timeout(function() {
385 focusedTab.getButtonEl().click();
386 });
387 break;
388 }
389};
390
391/**
392 * ngInject
393 */
394function MdNavItem($$rAF) {
395 return {
396 restrict: 'E',
397 require: ['mdNavItem', '^mdNavBar'],
398 controller: MdNavItemController,
399 bindToController: true,
400 controllerAs: 'ctrl',
401 replace: true,
402 transclude: true,
403 template:
404 '<li class="md-nav-item" role="option" aria-selected="{{ctrl.isSelected()}}">' +
405 '<md-button ng-if="ctrl.mdNavSref" class="_md-nav-button md-accent"' +
406 'ng-class="ctrl.getNgClassMap()"' +
407 'tabindex="-1"' +
408 'ui-sref="{{ctrl.mdNavSref}}">' +
409 '<span ng-transclude class="_md-nav-button-text"></span>' +
410 '</md-button>' +
411 '<md-button ng-if="ctrl.mdNavHref" class="_md-nav-button md-accent"' +
412 'ng-class="ctrl.getNgClassMap()"' +
413 'tabindex="-1"' +
414 'ng-href="{{ctrl.mdNavHref}}">' +
415 '<span ng-transclude class="_md-nav-button-text"></span>' +
416 '</md-button>' +
417 '<md-button ng-if="ctrl.mdNavClick" class="_md-nav-button md-accent"' +
418 'ng-class="ctrl.getNgClassMap()"' +
419 'tabindex="-1"' +
420 'ng-click="ctrl.mdNavClick()">' +
421 '<span ng-transclude class="_md-nav-button-text"></span>' +
422 '</md-button>' +
423 '</li>',
424 scope: {
425 'mdNavClick': '&?',
426 'mdNavHref': '@?',
427 'mdNavSref': '@?',
428 'name': '@',
429 },
430 link: function(scope, element, attrs, controllers) {
431 var mdNavItem = controllers[0];
432 var mdNavBar = controllers[1];
433
434 // When accessing the element's contents synchronously, they
435 // may not be defined yet because of transclusion. There is a higher chance
436 // that it will be accessible if we wait one frame.
437 $$rAF(function() {
438 if (!mdNavItem.name) {
439 mdNavItem.name = angular.element(element[0].querySelector('._md-nav-button-text'))
440 .text().trim();
441 }
442
443 var navButton = angular.element(element[0].querySelector('._md-nav-button'));
444 navButton.on('click', function() {
445 mdNavBar.mdSelectedNavItem = mdNavItem.name;
446 scope.$apply();
447 });
448 });
449 }
450 };
451}
452
453/**
454 * Controller for the nav-item component.
455 * @param {!angular.JQLite} $element
456 * @constructor
457 * @final
458 * ngInject
459 */
460function MdNavItemController($element) {
461
462 /** @private @const {!angular.JQLite} */
463 this._$element = $element;
464
465 // Data-bound variables
466 /** @const {?Function} */
467 this.mdNavClick;
468 /** @const {?string} */
469 this.mdNavHref;
470 /** @const {?string} */
471 this.name;
472
473 // State variables
474 /** @private {boolean} */
475 this._selected = false;
476
477 /** @private {boolean} */
478 this._focused = false;
479
480 var hasNavClick = !!($element.attr('md-nav-click'));
481 var hasNavHref = !!($element.attr('md-nav-href'));
482 var hasNavSref = !!($element.attr('md-nav-sref'));
483
484 // Cannot specify more than one nav attribute
485 if ((hasNavClick ? 1:0) + (hasNavHref ? 1:0) + (hasNavSref ? 1:0) > 1) {
486 throw Error(
487 'Must specify exactly one of md-nav-click, md-nav-href, ' +
488 'md-nav-sref for nav-item directive');
489 }
490}
491
492/**
493 * Returns a map of class names and values for use by ng-class.
494 * @return {!Object<string,boolean>}
495 */
496MdNavItemController.prototype.getNgClassMap = function() {
497 return {
498 'md-active': this._selected,
499 'md-primary': this._selected,
500 'md-unselected': !this._selected,
501 'md-focused': this._focused,
502 };
503};
504
505/**
506 * Get the name attribute of the tab.
507 * @return {string}
508 */
509MdNavItemController.prototype.getName = function() {
510 return this.name;
511};
512
513/**
514 * Get the button element associated with the tab.
515 * @return {!Element}
516 */
517MdNavItemController.prototype.getButtonEl = function() {
518 return this._$element[0].querySelector('._md-nav-button');
519};
520
521/**
522 * Set the selected state of the tab.
523 * @param {boolean} isSelected
524 */
525MdNavItemController.prototype.setSelected = function(isSelected) {
526 this._selected = isSelected;
527};
528
529/**
530 * @return {boolean}
531 */
532MdNavItemController.prototype.isSelected = function() {
533 return this._selected;
534};
535
536/**
537 * Set the focused state of the tab.
538 * @param {boolean} isFocused
539 */
540MdNavItemController.prototype.setFocused = function(isFocused) {
541 this._focused = isFocused;
542};
543
544/**
545 * @return {boolean}
546 */
547MdNavItemController.prototype.hasFocus = function() {
548 return this._focused;
549};
550
551ngmaterial.components.navBar = angular.module("material.components.navBar");