source: trip-planner-front/node_modules/angular-material/modules/closure/sidenav/sidenav.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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