1 | /*!
|
---|
2 | * AngularJS Material Design
|
---|
3 | * https://github.com/angular/material
|
---|
4 | * @license MIT
|
---|
5 | * v1.2.3
|
---|
6 | */
|
---|
7 | goog.provide('ngmaterial.components.sidenav');
|
---|
8 | goog.require('ngmaterial.components.backdrop');
|
---|
9 | goog.require('ngmaterial.core');
|
---|
10 | /**
|
---|
11 | * @ngdoc module
|
---|
12 | * @name material.components.sidenav
|
---|
13 | *
|
---|
14 | * @description
|
---|
15 | * A Sidenav component.
|
---|
16 | */
|
---|
17 | SidenavService['$inject'] = ["$mdComponentRegistry", "$mdUtil", "$q", "$log"];
|
---|
18 | SidenavDirective['$inject'] = ["$mdMedia", "$mdUtil", "$mdConstant", "$mdTheming", "$mdInteraction", "$animate", "$compile", "$parse", "$log", "$q", "$document", "$window", "$$rAF"];
|
---|
19 | SidenavController['$inject'] = ["$scope", "$attrs", "$mdComponentRegistry", "$q", "$interpolate"];
|
---|
20 | angular
|
---|
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 | */
|
---|
84 | function 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 | */
|
---|
242 | function 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 | */
|
---|
508 | function 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 |
|
---|
549 | ngmaterial.components.sidenav = angular.module("material.components.sidenav"); |
---|