arnaucube
arnaucube/commonroutesAppPublic Self-hosted repository
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");