commonroutesApp/www/lib/angular-material/modules/js/sidenav/sidenav.js
www/lib/angular-material/modules/js/sidenav/sidenav.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.sidenav
13 *
14 * @description
15 * A Sidenav QP component.
16 */
17SidenavService.$inject = ["$mdComponentRegistry", "$mdUtil", "$q", "$log"];
18SidenavDirective.$inject = ["$mdMedia", "$mdUtil", "$mdConstant", "$mdTheming", "$animate", "$compile", "$parse", "$log", "$q", "$document"];
19SidenavController.$inject = ["$scope", "$element", "$attrs", "$mdComponentRegistry", "$q"];
20angular
21 .module('material.components.sidenav', [
22 'material.core',
23 'material.components.backdrop'
24 ])
25 .factory('$mdSidenav', SidenavService )
26 .directive('mdSidenav', SidenavDirective)
27 .directive('mdSidenavFocus', SidenavFocusDirective)
28 .controller('$mdSidenavController', SidenavController);
29
30
31/**
32 * @ngdoc service
33 * @name $mdSidenav
34 * @module material.components.sidenav
35 *
36 * @description
37 * `$mdSidenav` makes it easy to interact with multiple sidenavs
38 * in an app. When looking up a sidenav instance, you can either look
39 * it up synchronously or wait for it to be initializied asynchronously.
40 * This is done by passing the second argument to `$mdSidenav`.
41 *
42 * @usage
43 * <hljs lang="js">
44 * // Async lookup for sidenav instance; will resolve when the instance is available
45 * $mdSidenav(componentId, true).then(function(instance) {
46 * $log.debug( componentId + "is now ready" );
47 * });
48 * // Sync lookup for sidenav instance; this will resolve immediately.
49 * $mdSidenav(componentId).then(function(instance) {
50 * $log.debug( componentId + "is now ready" );
51 * });
52 * // Async toggle the given sidenav;
53 * // when instance is known ready and lazy lookup is not needed.
54 * $mdSidenav(componentId)
55 * .toggle()
56 * .then(function(){
57 * $log.debug('toggled');
58 * });
59 * // Async open the given sidenav
60 * $mdSidenav(componentId)
61 * .open()
62 * .then(function(){
63 * $log.debug('opened');
64 * });
65 * // Async close the given sidenav
66 * $mdSidenav(componentId)
67 * .close()
68 * .then(function(){
69 * $log.debug('closed');
70 * });
71 * // Sync check to see if the specified sidenav is set to be open
72 * $mdSidenav(componentId).isOpen();
73 * // Sync check to whether given sidenav is locked open
74 * // If this is true, the sidenav will be open regardless of close()
75 * $mdSidenav(componentId).isLockedOpen();
76 * // On close callback to handle close, backdrop click or escape key pressed
77 * // Callback happens BEFORE the close action occurs.
78 * $mdSidenav(componentId).onClose(function () {
79 * $log.debug('closing');
80 * });
81 * </hljs>
82 */
83function SidenavService($mdComponentRegistry, $mdUtil, $q, $log) {
84 var errorMsg = "SideNav '{0}' is not available! Did you use md-component-id='{0}'?";
85 var service = {
86 find : findInstance, // sync - returns proxy API
87 waitFor : waitForInstance // async - returns promise
88 };
89
90 /**
91 * Service API that supports three (3) usages:
92 * $mdSidenav().find("left") // sync (must already exist) or returns undefined
93 * $mdSidenav("left").toggle(); // sync (must already exist) or returns reject promise;
94 * $mdSidenav("left",true).then( function(left){ // async returns instance when available
95 * left.toggle();
96 * });
97 */
98 return function(handle, enableWait) {
99 if ( angular.isUndefined(handle) ) return service;
100
101 var shouldWait = enableWait === true;
102 var instance = service.find(handle, shouldWait);
103 return !instance && shouldWait ? service.waitFor(handle) :
104 !instance && angular.isUndefined(enableWait) ? addLegacyAPI(service, handle) : instance;
105 };
106
107 /**
108 * For failed instance/handle lookups, older-clients expect an response object with noops
109 * that include `rejected promise APIs`
110 */
111 function addLegacyAPI(service, handle) {
112 var falseFn = function() { return false; };
113 var rejectFn = function() {
114 return $q.when($mdUtil.supplant(errorMsg, [handle || ""]));
115 };
116
117 return angular.extend({
118 isLockedOpen : falseFn,
119 isOpen : falseFn,
120 toggle : rejectFn,
121 open : rejectFn,
122 close : rejectFn,
123 onClose : angular.noop,
124 then : function(callback) {
125 return waitForInstance(handle)
126 .then(callback || angular.noop);
127 }
128 }, service);
129 }
130 /**
131 * Synchronously lookup the controller instance for the specified sidNav instance which has been
132 * registered with the markup `md-component-id`
133 */
134 function findInstance(handle, shouldWait) {
135 var instance = $mdComponentRegistry.get(handle);
136
137 if (!instance && !shouldWait) {
138
139 // Report missing instance
140 $log.error( $mdUtil.supplant(errorMsg, [handle || ""]) );
141
142 // The component has not registered itself... most like NOT yet created
143 // return null to indicate that the Sidenav is not in the DOM
144 return undefined;
145 }
146 return instance;
147 }
148
149 /**
150 * Asynchronously wait for the component instantiation,
151 * Deferred lookup of component instance using $component registry
152 */
153 function waitForInstance(handle) {
154 return $mdComponentRegistry.when(handle).catch($log.error);
155 }
156}
157/**
158 * @ngdoc directive
159 * @name mdSidenavFocus
160 * @module material.components.sidenav
161 *
162 * @restrict A
163 *
164 * @description
165 * `mdSidenavFocus` provides a way to specify the focused element when a sidenav opens.
166 * This is completely optional, as the sidenav itself is focused by default.
167 *
168 * @usage
169 * <hljs lang="html">
170 * <md-sidenav>
171 * <form>
172 * <md-input-container>
173 * <label for="testInput">Label</label>
174 * <input id="testInput" type="text" md-sidenav-focus>
175 * </md-input-container>
176 * </form>
177 * </md-sidenav>
178 * </hljs>
179 **/
180function SidenavFocusDirective() {
181 return {
182 restrict: 'A',
183 require: '^mdSidenav',
184 link: function(scope, element, attr, sidenavCtrl) {
185 // @see $mdUtil.findFocusTarget(...)
186 }
187 };
188}
189/**
190 * @ngdoc directive
191 * @name mdSidenav
192 * @module material.components.sidenav
193 * @restrict E
194 *
195 * @description
196 *
197 * A Sidenav component that can be opened and closed programatically.
198 *
199 * By default, upon opening it will slide out on top of the main content area.
200 *
201 * For keyboard and screen reader accessibility, focus is sent to the sidenav wrapper by default.
202 * It can be overridden with the `md-autofocus` directive on the child element you want focused.
203 *
204 * @usage
205 * <hljs lang="html">
206 * <div layout="row" ng-controller="MyController">
207 * <md-sidenav md-component-id="left" class="md-sidenav-left">
208 * Left Nav!
209 * </md-sidenav>
210 *
211 * <md-content>
212 * Center Content
213 * <md-button ng-click="openLeftMenu()">
214 * Open Left Menu
215 * </md-button>
216 * </md-content>
217 *
218 * <md-sidenav md-component-id="right"
219 * md-is-locked-open="$mdMedia('min-width: 333px')"
220 * class="md-sidenav-right">
221 * <form>
222 * <md-input-container>
223 * <label for="testInput">Test input</label>
224 * <input id="testInput" type="text"
225 * ng-model="data" md-autofocus>
226 * </md-input-container>
227 * </form>
228 * </md-sidenav>
229 * </div>
230 * </hljs>
231 *
232 * <hljs lang="js">
233 * var app = angular.module('myApp', ['ngMaterial']);
234 * app.controller('MyController', function($scope, $mdSidenav) {
235 * $scope.openLeftMenu = function() {
236 * $mdSidenav('left').toggle();
237 * };
238 * });
239 * </hljs>
240 *
241 * @param {expression=} md-is-open A model bound to whether the sidenav is opened.
242 * @param {boolean=} md-disable-backdrop When present in the markup, the sidenav will not show a backdrop.
243 * @param {string=} md-component-id componentId to use with $mdSidenav service.
244 * @param {expression=} md-is-locked-open When this expression evaluates to true,
245 * the sidenav 'locks open': it falls into the content's flow instead
246 * of appearing over it. This overrides the `md-is-open` attribute.
247 * @param {string=} md-disable-scroll-target Selector, pointing to an element, whose scrolling will
248 * be disabled when the sidenav is opened. By default this is the sidenav's direct parent.
249 *
250* The $mdMedia() service is exposed to the is-locked-open attribute, which
251 * can be given a media query or one of the `sm`, `gt-sm`, `md`, `gt-md`, `lg` or `gt-lg` presets.
252 * Examples:
253 *
254 * - `<md-sidenav md-is-locked-open="shouldLockOpen"></md-sidenav>`
255 * - `<md-sidenav md-is-locked-open="$mdMedia('min-width: 1000px')"></md-sidenav>`
256 * - `<md-sidenav md-is-locked-open="$mdMedia('sm')"></md-sidenav>` (locks open on small screens)
257 */
258function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, $compile, $parse, $log, $q, $document) {
259 return {
260 restrict: 'E',
261 scope: {
262 isOpen: '=?mdIsOpen'
263 },
264 controller: '$mdSidenavController',
265 compile: function(element) {
266 element.addClass('md-closed');
267 element.attr('tabIndex', '-1');
268 return postLink;
269 }
270 };
271
272 /**
273 * Directive Post Link function...
274 */
275 function postLink(scope, element, attr, sidenavCtrl) {
276 var lastParentOverFlow;
277 var backdrop;
278 var disableScrollTarget = null;
279 var triggeringElement = null;
280 var previousContainerStyles;
281 var promise = $q.when(true);
282 var isLockedOpenParsed = $parse(attr.mdIsLockedOpen);
283 var isLocked = function() {
284 return isLockedOpenParsed(scope.$parent, {
285 $media: function(arg) {
286 $log.warn("$media is deprecated for is-locked-open. Use $mdMedia instead.");
287 return $mdMedia(arg);
288 },
289 $mdMedia: $mdMedia
290 });
291 };
292
293 if (attr.mdDisableScrollTarget) {
294 disableScrollTarget = $document[0].querySelector(attr.mdDisableScrollTarget);
295
296 if (disableScrollTarget) {
297 disableScrollTarget = angular.element(disableScrollTarget);
298 } else {
299 $log.warn($mdUtil.supplant('mdSidenav: couldn\'t find element matching ' +
300 'selector "{selector}". Falling back to parent.', { selector: attr.mdDisableScrollTarget }));
301 }
302 }
303
304 if (!disableScrollTarget) {
305 disableScrollTarget = element.parent();
306 }
307
308 // Only create the backdrop if the backdrop isn't disabled.
309 if (!attr.hasOwnProperty('mdDisableBackdrop')) {
310 backdrop = $mdUtil.createBackdrop(scope, "md-sidenav-backdrop md-opaque ng-enter");
311 }
312
313 element.addClass('_md'); // private md component indicator for styling
314 $mdTheming(element);
315
316 // The backdrop should inherit the sidenavs theme,
317 // because the backdrop will take its parent theme by default.
318 if ( backdrop ) $mdTheming.inherit(backdrop, element);
319
320 element.on('$destroy', function() {
321 backdrop && backdrop.remove();
322 sidenavCtrl.destroy();
323 });
324
325 scope.$on('$destroy', function(){
326 backdrop && backdrop.remove();
327 });
328
329 scope.$watch(isLocked, updateIsLocked);
330 scope.$watch('isOpen', updateIsOpen);
331
332
333 // Publish special accessor for the Controller instance
334 sidenavCtrl.$toggleOpen = toggleOpen;
335
336 /**
337 * Toggle the DOM classes to indicate `locked`
338 * @param isLocked
339 */
340 function updateIsLocked(isLocked, oldValue) {
341 scope.isLockedOpen = isLocked;
342 if (isLocked === oldValue) {
343 element.toggleClass('md-locked-open', !!isLocked);
344 } else {
345 $animate[isLocked ? 'addClass' : 'removeClass'](element, 'md-locked-open');
346 }
347 if (backdrop) {
348 backdrop.toggleClass('md-locked-open', !!isLocked);
349 }
350 }
351
352 /**
353 * Toggle the SideNav view and attach/detach listeners
354 * @param isOpen
355 */
356 function updateIsOpen(isOpen) {
357 // Support deprecated md-sidenav-focus attribute as fallback
358 var focusEl = $mdUtil.findFocusTarget(element) || $mdUtil.findFocusTarget(element,'[md-sidenav-focus]') || element;
359 var parent = element.parent();
360
361 parent[isOpen ? 'on' : 'off']('keydown', onKeyDown);
362 if (backdrop) backdrop[isOpen ? 'on' : 'off']('click', close);
363
364 var restorePositioning = updateContainerPositions(parent, isOpen);
365
366 if ( isOpen ) {
367 // Capture upon opening..
368 triggeringElement = $document[0].activeElement;
369 }
370
371 disableParentScroll(isOpen);
372
373 return promise = $q.all([
374 isOpen && backdrop ? $animate.enter(backdrop, parent) : backdrop ?
375 $animate.leave(backdrop) : $q.when(true),
376 $animate[isOpen ? 'removeClass' : 'addClass'](element, 'md-closed')
377 ]).then(function() {
378 // Perform focus when animations are ALL done...
379 if (scope.isOpen) {
380 focusEl && focusEl.focus();
381 }
382
383 // Restores the positioning on the sidenav and backdrop.
384 restorePositioning && restorePositioning();
385 });
386 }
387
388 function updateContainerPositions(parent, willOpen) {
389 var drawerEl = element[0];
390 var scrollTop = parent[0].scrollTop;
391
392 if (willOpen && scrollTop) {
393 previousContainerStyles = {
394 top: drawerEl.style.top,
395 bottom: drawerEl.style.bottom,
396 height: drawerEl.style.height
397 };
398
399 // When the parent is scrolled down, then we want to be able to show the sidenav at the current scroll
400 // position. We're moving the sidenav down to the correct scroll position and apply the height of the
401 // parent, to increase the performance. Using 100% as height, will impact the performance heavily.
402 var positionStyle = {
403 top: scrollTop + 'px',
404 bottom: 'auto',
405 height: parent[0].clientHeight + 'px'
406 };
407
408 // Apply the new position styles to the sidenav and backdrop.
409 element.css(positionStyle);
410 backdrop.css(positionStyle);
411 }
412
413 // When the sidenav is closing and we have previous defined container styles,
414 // then we return a restore function, which resets the sidenav and backdrop.
415 if (!willOpen && previousContainerStyles) {
416 return function() {
417 drawerEl.style.top = previousContainerStyles.top;
418 drawerEl.style.bottom = previousContainerStyles.bottom;
419 drawerEl.style.height = previousContainerStyles.height;
420
421 backdrop[0].style.top = null;
422 backdrop[0].style.bottom = null;
423 backdrop[0].style.height = null;
424
425 previousContainerStyles = null;
426 };
427 }
428 }
429
430 /**
431 * Prevent parent scrolling (when the SideNav is open)
432 */
433 function disableParentScroll(disabled) {
434 if ( disabled && !lastParentOverFlow ) {
435 lastParentOverFlow = disableScrollTarget.css('overflow');
436 disableScrollTarget.css('overflow', 'hidden');
437 } else if (angular.isDefined(lastParentOverFlow)) {
438 disableScrollTarget.css('overflow', lastParentOverFlow);
439 lastParentOverFlow = undefined;
440 }
441 }
442
443 /**
444 * Toggle the sideNav view and publish a promise to be resolved when
445 * the view animation finishes.
446 *
447 * @param isOpen
448 * @returns {*}
449 */
450 function toggleOpen( isOpen ) {
451 if (scope.isOpen == isOpen ) {
452
453 return $q.when(true);
454
455 } else {
456 if (scope.isOpen && sidenavCtrl.onCloseCb) sidenavCtrl.onCloseCb();
457
458 return $q(function(resolve){
459 // Toggle value to force an async `updateIsOpen()` to run
460 scope.isOpen = isOpen;
461
462 $mdUtil.nextTick(function() {
463 // When the current `updateIsOpen()` animation finishes
464 promise.then(function(result) {
465
466 if ( !scope.isOpen ) {
467 // reset focus to originating element (if available) upon close
468 triggeringElement && triggeringElement.focus();
469 triggeringElement = null;
470 }
471
472 resolve(result);
473 });
474 });
475
476 });
477
478 }
479 }
480
481 /**
482 * Auto-close sideNav when the `escape` key is pressed.
483 * @param evt
484 */
485 function onKeyDown(ev) {
486 var isEscape = (ev.keyCode === $mdConstant.KEY_CODE.ESCAPE);
487 return isEscape ? close(ev) : $q.when(true);
488 }
489
490 /**
491 * With backdrop `clicks` or `escape` key-press, immediately
492 * apply the CSS close transition... Then notify the controller
493 * to close() and perform its own actions.
494 */
495 function close(ev) {
496 ev.preventDefault();
497
498 return sidenavCtrl.close();
499 }
500
501 }
502}
503
504/*
505 * @private
506 * @ngdoc controller
507 * @name SidenavController
508 * @module material.components.sidenav
509 *
510 */
511function SidenavController($scope, $element, $attrs, $mdComponentRegistry, $q) {
512
513 var self = this;
514
515 // Use Default internal method until overridden by directive postLink
516
517 // Synchronous getters
518 self.isOpen = function() { return !!$scope.isOpen; };
519 self.isLockedOpen = function() { return !!$scope.isLockedOpen; };
520
521 // Synchronous setters
522 self.onClose = function (callback) {
523 self.onCloseCb = callback;
524 return self;
525 };
526
527 // Async actions
528 self.open = function() { return self.$toggleOpen( true ); };
529 self.close = function() { return self.$toggleOpen( false ); };
530 self.toggle = function() { return self.$toggleOpen( !$scope.isOpen ); };
531 self.$toggleOpen = function(value) { return $q.when($scope.isOpen = value); };
532
533 self.destroy = $mdComponentRegistry.register(self, $attrs.mdComponentId);
534}
535
536})(window, window.angular);