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