mcs-ng-material
Version:
MCS NG-Meterial is based on mcs-web.
1,653 lines (1,463 loc) • 103 kB
JavaScript
/*!
* AngularJS Material Design
* https://github.com/angular/material
* @license MIT
* v1.1.6
*/
goog.provide('ngmaterial.components.panel');
goog.require('ngmaterial.components.backdrop');
goog.require('ngmaterial.core');
/**
* @ngdoc module
* @name material.components.panel
*/
MdPanelService['$inject'] = ["presets", "$rootElement", "$rootScope", "$injector", "$window"];
angular
.module('material.components.panel', [
'material.core',
'material.components.backdrop'
])
.provider('$mdPanel', MdPanelProvider);
/*****************************************************************************
* PUBLIC DOCUMENTATION *
*****************************************************************************/
/**
* @ngdoc service
* @name $mdPanelProvider
* @module material.components.panel
*
* @description
* `$mdPanelProvider` allows users to create configuration presets that will be
* stored within a cached presets object. When the configuration is needed, the
* user can request the preset by passing it as the first parameter in the
* `$mdPanel.create` or `$mdPanel.open` methods.
*
* @usage
* <hljs lang="js">
* (function(angular, undefined) {
* 'use strict';
*
* angular
* .module('demoApp', ['ngMaterial'])
* .config(DemoConfig)
* .controller('DemoCtrl', DemoCtrl)
* .controller('DemoMenuCtrl', DemoMenuCtrl);
*
* function DemoConfig($mdPanelProvider) {
* $mdPanelProvider.definePreset('demoPreset', {
* attachTo: angular.element(document.body),
* controller: DemoMenuCtrl,
* controllerAs: 'ctrl',
* template: '' +
* '<div class="menu-panel" md-whiteframe="4">' +
* ' <div class="menu-content">' +
* ' <div class="menu-item" ng-repeat="item in ctrl.items">' +
* ' <button class="md-button">' +
* ' <span>{{item}}</span>' +
* ' </button>' +
* ' </div>' +
* ' <md-divider></md-divider>' +
* ' <div class="menu-item">' +
* ' <button class="md-button" ng-click="ctrl.closeMenu()">' +
* ' <span>Close Menu</span>' +
* ' </button>' +
* ' </div>' +
* ' </div>' +
* '</div>',
* panelClass: 'menu-panel-container',
* focusOnOpen: false,
* zIndex: 100,
* propagateContainerEvents: true,
* groupName: 'menus'
* });
* }
*
* function PanelProviderCtrl($mdPanel) {
* this.navigation = {
* name: 'navigation',
* items: [
* 'Home',
* 'About',
* 'Contact'
* ]
* };
* this.favorites = {
* name: 'favorites',
* items: [
* 'Add to Favorites'
* ]
* };
* this.more = {
* name: 'more',
* items: [
* 'Account',
* 'Sign Out'
* ]
* };
*
* $mdPanel.newPanelGroup('menus', {
* maxOpen: 2
* });
*
* this.showMenu = function($event, menu) {
* $mdPanel.open('demoPreset', {
* id: 'menu_' + menu.name,
* position: $mdPanel.newPanelPosition()
* .relativeTo($event.srcElement)
* .addPanelPosition(
* $mdPanel.xPosition.ALIGN_START,
* $mdPanel.yPosition.BELOW
* ),
* locals: {
* items: menu.items
* },
* openFrom: $event
* });
* };
* }
*
* function PanelMenuCtrl(mdPanelRef) {
* // The controller is provided with an import named 'mdPanelRef'
* this.closeMenu = function() {
* mdPanelRef && mdPanelRef.close();
* };
* }
* })(angular);
* </hljs>
*/
/**
* @ngdoc method
* @name $mdPanelProvider#definePreset
* @description
* Takes the passed in preset name and preset configuration object and adds it
* to the `_presets` object of the provider. This `_presets` object is then
* passed along to the `$mdPanel` service.
*
* @param {string} name Preset name.
* @param {!Object} preset Specific configuration object that can contain any
* and all of the parameters avaialble within the `$mdPanel.create` method.
* However, parameters that pertain to id, position, animation, and user
* interaction are not allowed and will be removed from the preset
* configuration.
*/
/*****************************************************************************
* MdPanel Service *
*****************************************************************************/
/**
* @ngdoc service
* @name $mdPanel
* @module material.components.panel
*
* @description
* `$mdPanel` is a robust, low-level service for creating floating panels on
* the screen. It can be used to implement tooltips, dialogs, pop-ups, etc.
*
* @usage
* <hljs lang="js">
* (function(angular, undefined) {
* 'use strict';
*
* angular
* .module('demoApp', ['ngMaterial'])
* .controller('DemoDialogController', DialogController);
*
* var panelRef;
*
* function showPanel($event) {
* var panelPosition = $mdPanel.newPanelPosition()
* .absolute()
* .top('50%')
* .left('50%');
*
* var panelAnimation = $mdPanel.newPanelAnimation()
* .targetEvent($event)
* .defaultAnimation('md-panel-animate-fly')
* .closeTo('.show-button');
*
* var config = {
* attachTo: angular.element(document.body),
* controller: DialogController,
* controllerAs: 'ctrl',
* position: panelPosition,
* animation: panelAnimation,
* targetEvent: $event,
* templateUrl: 'dialog-template.html',
* clickOutsideToClose: true,
* escapeToClose: true,
* focusOnOpen: true
* };
*
* $mdPanel.open(config)
* .then(function(result) {
* panelRef = result;
* });
* }
*
* function DialogController(MdPanelRef) {
* function closeDialog() {
* if (MdPanelRef) MdPanelRef.close();
* }
* }
* })(angular);
* </hljs>
*/
/**
* @ngdoc method
* @name $mdPanel#create
* @description
* Creates a panel with the specified options.
*
* @param config {!Object=} Specific configuration object that may contain the
* following properties:
*
* - `id` - `{string=}`: An ID to track the panel by. When an ID is provided,
* the created panel is added to a tracked panels object. Any subsequent
* requests made to create a panel with that ID are ignored. This is useful
* in having the panel service not open multiple panels from the same user
* interaction when there is no backdrop and events are propagated. Defaults
* to an arbitrary string that is not tracked.
* - `template` - `{string=}`: HTML template to show in the panel. This
* **must** be trusted HTML with respect to AngularJS’s
* [$sce service](https://docs.angularjs.org/api/ng/service/$sce).
* - `templateUrl` - `{string=}`: The URL that will be used as the content of
* the panel.
* - `contentElement` - `{(string|!angular.JQLite|!Element)=}`: Pre-compiled
* element to be used as the panel's content.
* - `controller` - `{(function|string)=}`: The controller to associate with
* the panel. The controller can inject a reference to the returned
* panelRef, which allows the panel to be closed, hidden, and shown. Any
* fields passed in through locals or resolve will be bound to the
* controller.
* - `controllerAs` - `{string=}`: An alias to assign the controller to on
* the scope.
* - `bindToController` - `{boolean=}`: Binds locals to the controller
* instead of passing them in. Defaults to true, as this is a best
* practice.
* - `locals` - `{Object=}`: An object containing key/value pairs. The keys
* will be used as names of values to inject into the controller. For
* example, `locals: {three: 3}` would inject `three` into the controller,
* with the value 3. 'mdPanelRef' is a reserved key, and will always
* be set to the created MdPanelRef instance.
* - `resolve` - `{Object=}`: Similar to locals, except it takes promises as
* values. The panel will not open until all of the promises resolve.
* - `attachTo` - `{(string|!angular.JQLite|!Element)=}`: The element to
* attach the panel to. Defaults to appending to the root element of the
* application.
* - `propagateContainerEvents` - `{boolean=}`: Whether pointer or touch
* events should be allowed to propagate 'go through' the container, aka the
* wrapper, of the panel. Defaults to false.
* - `panelClass` - `{string=}`: A css class to apply to the panel element.
* This class should define any borders, box-shadow, etc. for the panel.
* - `zIndex` - `{number=}`: The z-index to place the panel at.
* Defaults to 80.
* - `position` - `{MdPanelPosition=}`: An MdPanelPosition object that
* specifies the alignment of the panel. For more information, see
* `MdPanelPosition`.
* - `clickOutsideToClose` - `{boolean=}`: Whether the user can click
* outside the panel to close it. Defaults to false.
* - `escapeToClose` - `{boolean=}`: Whether the user can press escape to
* close the panel. Defaults to false.
* - `onCloseSuccess` - `{function(!panelRef, string)=}`: Function that is
* called after the close successfully finishes. The first parameter passed
* into this function is the current panelRef and the 2nd is an optional
* string explaining the close reason. The currently supported closeReasons
* can be found in the MdPanelRef.closeReasons enum. These are by default
* passed along by the panel.
* - `trapFocus` - `{boolean=}`: Whether focus should be trapped within the
* panel. If `trapFocus` is true, the user will not be able to interact
* with the rest of the page until the panel is dismissed. Defaults to
* false.
* - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on
* open. Only disable if focusing some other way, as focus management is
* required for panels to be accessible. Defaults to true.
* - `fullscreen` - `{boolean=}`: Whether the panel should be full screen.
* Applies the class `._md-panel-fullscreen` to the panel on open. Defaults
* to false.
* - `animation` - `{MdPanelAnimation=}`: An MdPanelAnimation object that
* specifies the animation of the panel. For more information, see
* `MdPanelAnimation`.
* - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop
* behind the panel. Defaults to false.
* - `disableParentScroll` - `{boolean=}`: Whether the user can scroll the
* page behind the panel. Defaults to false.
* - `onDomAdded` - `{function=}`: Callback function used to announce when
* the panel is added to the DOM.
* - `onOpenComplete` - `{function=}`: Callback function used to announce
* when the open() action is finished.
* - `onRemoving` - `{function=}`: Callback function used to announce the
* close/hide() action is starting.
* - `onDomRemoved` - `{function=}`: Callback function used to announce when
* the panel is removed from the DOM.
* - `origin` - `{(string|!angular.JQLite|!Element)=}`: The element to focus
* on when the panel closes. This is commonly the element which triggered
* the opening of the panel. If you do not use `origin`, you need to control
* the focus manually.
* - `groupName` - `{(string|!Array<string>)=}`: A group name or an array of
* group names. The group name is used for creating a group of panels. The
* group is used for configuring the number of open panels and identifying
* specific behaviors for groups. For instance, all tooltips could be
* identified using the same groupName.
*
* @returns {!MdPanelRef} panelRef
*/
/**
* @ngdoc method
* @name $mdPanel#open
* @description
* Calls the create method above, then opens the panel. This is a shortcut for
* creating and then calling open manually. If custom methods need to be
* called when the panel is added to the DOM or opened, do not use this method.
* Instead create the panel, chain promises on the domAdded and openComplete
* methods, and call open from the returned panelRef.
*
* @param {!Object=} config Specific configuration object that may contain
* the properties defined in `$mdPanel.create`.
* @returns {!angular.$q.Promise<!MdPanelRef>} panelRef A promise that resolves
* to an instance of the panel.
*/
/**
* @ngdoc method
* @name $mdPanel#newPanelPosition
* @description
* Returns a new instance of the MdPanelPosition object. Use this to create
* the position config object.
*
* @returns {!MdPanelPosition} panelPosition
*/
/**
* @ngdoc method
* @name $mdPanel#newPanelAnimation
* @description
* Returns a new instance of the MdPanelAnimation object. Use this to create
* the animation config object.
*
* @returns {!MdPanelAnimation} panelAnimation
*/
/**
* @ngdoc method
* @name $mdPanel#newPanelGroup
* @description
* Creates a panel group and adds it to a tracked list of panel groups.
*
* @param {string} groupName Name of the group to create.
* @param {!Object=} config Specific configuration object that may contain the
* following properties:
*
* - `maxOpen` - `{number=}`: The maximum number of panels that are allowed to
* be open within a defined panel group.
*
* @returns {!Object<string,
* {panels: !Array<!MdPanelRef>,
* openPanels: !Array<!MdPanelRef>,
* maxOpen: number}>} panelGroup
*/
/**
* @ngdoc method
* @name $mdPanel#setGroupMaxOpen
* @description
* Sets the maximum number of panels in a group that can be opened at a given
* time.
*
* @param {string} groupName The name of the group to configure.
* @param {number} maxOpen The maximum number of panels that can be
* opened. Infinity can be passed in to remove the maxOpen limit.
*/
/*****************************************************************************
* MdPanelRef *
*****************************************************************************/
/**
* @ngdoc type
* @name MdPanelRef
* @module material.components.panel
* @description
* A reference to a created panel. This reference contains a unique id for the
* panel, along with the following properties:
*
* - `id` - `{string}`: The unique id for the panel. This id is used to track
* when a panel was interacted with.
* - `config` - `{!Object=}`: The entire config object that was used in
* create.
* - `isAttached` - `{boolean}`: Whether the panel is attached to the DOM.
* Visibility to the user does not factor into isAttached.
* - `panelContainer` - `{angular.JQLite}`: The wrapper element containing the
* panel. This property is added in order to have access to the `addClass`,
* `removeClass`, `toggleClass`, etc methods.
* - `panelEl` - `{angular.JQLite}`: The panel element. This property is added
* in order to have access to the `addClass`, `removeClass`, `toggleClass`,
* etc methods.
*/
/**
* @ngdoc method
* @name MdPanelRef#open
* @description
* Attaches and shows the panel.
*
* @returns {!angular.$q.Promise} A promise that is resolved when the panel is
* opened.
*/
/**
* @ngdoc method
* @name MdPanelRef#close
* @description
* Hides and detaches the panel. Note that this will **not** destroy the panel.
* If you don't intend on using the panel again, call the {@link #destroy
* destroy} method afterwards.
*
* @returns {!angular.$q.Promise} A promise that is resolved when the panel is
* closed.
*/
/**
* @ngdoc method
* @name MdPanelRef#attach
* @description
* Create the panel elements and attach them to the DOM. The panel will be
* hidden by default.
*
* @returns {!angular.$q.Promise} A promise that is resolved when the panel is
* attached.
*/
/**
* @ngdoc method
* @name MdPanelRef#detach
* @description
* Removes the panel from the DOM. This will NOT hide the panel before removing
* it.
*
* @returns {!angular.$q.Promise} A promise that is resolved when the panel is
* detached.
*/
/**
* @ngdoc method
* @name MdPanelRef#show
* @description
* Shows the panel.
*
* @returns {!angular.$q.Promise} A promise that is resolved when the panel has
* shown and animations are completed.
*/
/**
* @ngdoc method
* @name MdPanelRef#hide
* @description
* Hides the panel.
*
* @returns {!angular.$q.Promise} A promise that is resolved when the panel has
* hidden and animations are completed.
*/
/**
* @ngdoc method
* @name MdPanelRef#destroy
* @description
* Destroys the panel. The panel cannot be opened again after this is called.
*/
/**
* @ngdoc method
* @name MdPanelRef#addClass
* @deprecated
* This method is in the process of being deprecated in favor of using the panel
* and container JQLite elements that are referenced in the MdPanelRef object.
* Full deprecation is scheduled for material 1.2.
* @description
* Adds a class to the panel. DO NOT use this hide/show the panel.
*
* @param {string} newClass class to be added.
* @param {boolean} toElement Whether or not to add the class to the panel
* element instead of the container.
*/
/**
* @ngdoc method
* @name MdPanelRef#removeClass
* @deprecated
* This method is in the process of being deprecated in favor of using the panel
* and container JQLite elements that are referenced in the MdPanelRef object.
* Full deprecation is scheduled for material 1.2.
* @description
* Removes a class from the panel. DO NOT use this to hide/show the panel.
*
* @param {string} oldClass Class to be removed.
* @param {boolean} fromElement Whether or not to remove the class from the
* panel element instead of the container.
*/
/**
* @ngdoc method
* @name MdPanelRef#toggleClass
* @deprecated
* This method is in the process of being deprecated in favor of using the panel
* and container JQLite elements that are referenced in the MdPanelRef object.
* Full deprecation is scheduled for material 1.2.
* @description
* Toggles a class on the panel. DO NOT use this to hide/show the panel.
*
* @param {string} toggleClass Class to be toggled.
* @param {boolean} onElement Whether or not to remove the class from the panel
* element instead of the container.
*/
/**
* @ngdoc method
* @name MdPanelRef#updatePosition
* @description
* Updates the position configuration of a panel. Use this to update the
* position of a panel that is open, without having to close and re-open the
* panel.
*
* @param {!MdPanelPosition} position
*/
/**
* @ngdoc method
* @name MdPanelRef#addToGroup
* @description
* Adds a panel to a group if the panel does not exist within the group already.
* A panel can only exist within a single group.
*
* @param {string} groupName The name of the group to add the panel to.
*/
/**
* @ngdoc method
* @name MdPanelRef#removeFromGroup
* @description
* Removes a panel from a group if the panel exists within that group. The group
* must be created ahead of time.
*
* @param {string} groupName The name of the group.
*/
/**
* @ngdoc method
* @name MdPanelRef#registerInterceptor
* @description
* Registers an interceptor with the panel. The callback should return a promise,
* which will allow the action to continue when it gets resolved, or will
* prevent an action if it is rejected. The interceptors are called sequentially
* and it reverse order. `type` must be one of the following
* values available on `$mdPanel.interceptorTypes`:
* * `CLOSE` - Gets called before the panel begins closing.
*
* @param {string} type Type of interceptor.
* @param {!angular.$q.Promise<any>} callback Callback to be registered.
* @returns {!MdPanelRef}
*/
/**
* @ngdoc method
* @name MdPanelRef#removeInterceptor
* @description
* Removes a registered interceptor.
*
* @param {string} type Type of interceptor to be removed.
* @param {function(): !angular.$q.Promise<any>} callback Interceptor to be removed.
* @returns {!MdPanelRef}
*/
/**
* @ngdoc method
* @name MdPanelRef#removeAllInterceptors
* @description
* Removes all interceptors. If a type is supplied, only the
* interceptors of that type will be cleared.
*
* @param {string=} type Type of interceptors to be removed.
* @returns {!MdPanelRef}
*/
/**
* @ngdoc method
* @name MdPanelRef#updateAnimation
* @description
* Updates the animation configuration for a panel. You can use this to change
* the panel's animation without having to re-create it.
*
* @param {!MdPanelAnimation} animation
*/
/*****************************************************************************
* MdPanelPosition *
*****************************************************************************/
/**
* @ngdoc type
* @name MdPanelPosition
* @module material.components.panel
* @description
*
* Object for configuring the position of the panel.
*
* @usage
*
* #### Centering the panel
*
* <hljs lang="js">
* new MdPanelPosition().absolute().center();
* </hljs>
*
* #### Overlapping the panel with an element
*
* <hljs lang="js">
* new MdPanelPosition()
* .relativeTo(someElement)
* .addPanelPosition(
* $mdPanel.xPosition.ALIGN_START,
* $mdPanel.yPosition.ALIGN_TOPS
* );
* </hljs>
*
* #### Aligning the panel with the bottom of an element
*
* <hljs lang="js">
* new MdPanelPosition()
* .relativeTo(someElement)
* .addPanelPosition($mdPanel.xPosition.CENTER, $mdPanel.yPosition.BELOW);
* </hljs>
*/
/**
* @ngdoc method
* @name MdPanelPosition#absolute
* @description
* Positions the panel absolutely relative to the parent element. If the parent
* is document.body, this is equivalent to positioning the panel absolutely
* within the viewport.
*
* @returns {!MdPanelPosition}
*/
/**
* @ngdoc method
* @name MdPanelPosition#relativeTo
* @description
* Positions the panel relative to a specific element.
*
* @param {string|!Element|!angular.JQLite} element Query selector, DOM element,
* or angular element to position the panel with respect to.
* @returns {!MdPanelPosition}
*/
/**
* @ngdoc method
* @name MdPanelPosition#top
* @description
* Sets the value of `top` for the panel. Clears any previously set vertical
* position.
*
* @param {string=} top Value of `top`. Defaults to '0'.
* @returns {!MdPanelPosition}
*/
/**
* @ngdoc method
* @name MdPanelPosition#bottom
* @description
* Sets the value of `bottom` for the panel. Clears any previously set vertical
* position.
*
* @param {string=} bottom Value of `bottom`. Defaults to '0'.
* @returns {!MdPanelPosition}
*/
/**
* @ngdoc method
* @name MdPanelPosition#start
* @description
* Sets the panel to the start of the page - `left` if `ltr` or `right` for
* `rtl`. Clears any previously set horizontal position.
*
* @param {string=} start Value of position. Defaults to '0'.
* @returns {!MdPanelPosition}
*/
/**
* @ngdoc method
* @name MdPanelPosition#end
* @description
* Sets the panel to the end of the page - `right` if `ltr` or `left` for `rtl`.
* Clears any previously set horizontal position.
*
* @param {string=} end Value of position. Defaults to '0'.
* @returns {!MdPanelPosition}
*/
/**
* @ngdoc method
* @name MdPanelPosition#left
* @description
* Sets the value of `left` for the panel. Clears any previously set
* horizontal position.
*
* @param {string=} left Value of `left`. Defaults to '0'.
* @returns {!MdPanelPosition}
*/
/**
* @ngdoc method
* @name MdPanelPosition#right
* @description
* Sets the value of `right` for the panel. Clears any previously set
* horizontal position.
*
* @param {string=} right Value of `right`. Defaults to '0'.
* @returns {!MdPanelPosition}
*/
/**
* @ngdoc method
* @name MdPanelPosition#centerHorizontally
* @description
* Centers the panel horizontally in the viewport. Clears any previously set
* horizontal position.
*
* @returns {!MdPanelPosition}
*/
/**
* @ngdoc method
* @name MdPanelPosition#centerVertically
* @description
* Centers the panel vertically in the viewport. Clears any previously set
* vertical position.
*
* @returns {!MdPanelPosition}
*/
/**
* @ngdoc method
* @name MdPanelPosition#center
* @description
* Centers the panel horizontally and vertically in the viewport. This is
* equivalent to calling both `centerHorizontally` and `centerVertically`.
* Clears any previously set horizontal and vertical positions.
*
* @returns {!MdPanelPosition}
*/
/**
* @ngdoc method
* @name MdPanelPosition#addPanelPosition
* @description
* Sets the x and y position for the panel relative to another element. Can be
* called multiple times to specify an ordered list of panel positions. The
* first position which allows the panel to be completely on-screen will be
* chosen; the last position will be chose whether it is on-screen or not.
*
* xPosition must be one of the following values available on
* $mdPanel.xPosition:
*
*
* CENTER | ALIGN_START | ALIGN_END | OFFSET_START | OFFSET_END
*
* <pre>
* *************
* * *
* * PANEL *
* * *
* *************
* A B C D E
*
* A: OFFSET_START (for LTR displays)
* B: ALIGN_START (for LTR displays)
* C: CENTER
* D: ALIGN_END (for LTR displays)
* E: OFFSET_END (for LTR displays)
* </pre>
*
* yPosition must be one of the following values available on
* $mdPanel.yPosition:
*
* CENTER | ALIGN_TOPS | ALIGN_BOTTOMS | ABOVE | BELOW
*
* <pre>
* F
* G *************
* * *
* H * PANEL *
* * *
* I *************
* J
*
* F: BELOW
* G: ALIGN_TOPS
* H: CENTER
* I: ALIGN_BOTTOMS
* J: ABOVE
* </pre>
*
* @param {string} xPosition
* @param {string} yPosition
* @returns {!MdPanelPosition}
*/
/**
* @ngdoc method
* @name MdPanelPosition#withOffsetX
* @description
* Sets the value of the offset in the x-direction.
*
* @param {string} offsetX
* @returns {!MdPanelPosition}
*/
/**
* @ngdoc method
* @name MdPanelPosition#withOffsetY
* @description
* Sets the value of the offset in the y-direction.
*
* @param {string} offsetY
* @returns {!MdPanelPosition}
*/
/*****************************************************************************
* MdPanelAnimation *
*****************************************************************************/
/**
* @ngdoc type
* @name MdPanelAnimation
* @module material.components.panel
* @description
* Animation configuration object. To use, create an MdPanelAnimation with the
* desired properties, then pass the object as part of $mdPanel creation.
*
* @usage
*
* <hljs lang="js">
* var panelAnimation = new MdPanelAnimation()
* .openFrom(myButtonEl)
* .duration(1337)
* .closeTo('.my-button')
* .withAnimation($mdPanel.animation.SCALE);
*
* $mdPanel.create({
* animation: panelAnimation
* });
* </hljs>
*/
/**
* @ngdoc method
* @name MdPanelAnimation#openFrom
* @description
* Specifies where to start the open animation. `openFrom` accepts a
* click event object, query selector, DOM element, or a Rect object that
* is used to determine the bounds. When passed a click event, the location
* of the click will be used as the position to start the animation.
*
* @param {string|!Element|!Event|{top: number, left: number}}
* @returns {!MdPanelAnimation}
*/
/**
* @ngdoc method
* @name MdPanelAnimation#closeTo
* @description
* Specifies where to animate the panel close. `closeTo` accepts a
* query selector, DOM element, or a Rect object that is used to determine
* the bounds.
*
* @param {string|!Element|{top: number, left: number}}
* @returns {!MdPanelAnimation}
*/
/**
* @ngdoc method
* @name MdPanelAnimation#withAnimation
* @description
* Specifies the animation class.
*
* There are several default animations that can be used:
* ($mdPanel.animation)
* SLIDE: The panel slides in and out from the specified
* elements. It will not fade in or out.
* SCALE: The panel scales in and out. Slide and fade are
* included in this animation.
* FADE: The panel fades in and out.
*
* Custom classes will by default fade in and out unless
* "transition: opacity 1ms" is added to the to custom class.
*
* @param {string|{open: string, close: string}} cssClass
* @returns {!MdPanelAnimation}
*/
/**
* @ngdoc method
* @name MdPanelAnimation#duration
* @description
* Specifies the duration of the animation in milliseconds. The `duration`
* method accepts either a number or an object with separate open and close
* durations.
*
* @param {number|{open: number, close: number}} duration
* @returns {!MdPanelAnimation}
*/
/*****************************************************************************
* PUBLIC DOCUMENTATION *
*****************************************************************************/
var MD_PANEL_Z_INDEX = 80;
var MD_PANEL_HIDDEN = '_md-panel-hidden';
var FOCUS_TRAP_TEMPLATE = angular.element(
'<div class="_md-panel-focus-trap" tabindex="0"></div>');
var _presets = {};
/**
* A provider that is used for creating presets for the panel API.
* @final @constructor ngInject
*/
function MdPanelProvider() {
return {
'definePreset': definePreset,
'getAllPresets': getAllPresets,
'clearPresets': clearPresets,
'$get': $getProvider()
};
}
/**
* Takes the passed in panel configuration object and adds it to the `_presets`
* object at the specified name.
* @param {string} name Name of the preset to set.
* @param {!Object} preset Specific configuration object that can contain any
* and all of the parameters avaialble within the `$mdPanel.create` method.
* However, parameters that pertain to id, position, animation, and user
* interaction are not allowed and will be removed from the preset
* configuration.
*/
function definePreset(name, preset) {
if (!name || !preset) {
throw new Error('mdPanelProvider: The panel preset definition is ' +
'malformed. The name and preset object are required.');
} else if (_presets.hasOwnProperty(name)) {
throw new Error('mdPanelProvider: The panel preset you have requested ' +
'has already been defined.');
}
// Delete any property on the preset that is not allowed.
delete preset.id;
delete preset.position;
delete preset.animation;
_presets[name] = preset;
}
/**
* Gets a clone of the `_presets`.
* @return {!Object}
*/
function getAllPresets() {
return angular.copy(_presets);
}
/**
* Clears all of the stored presets.
*/
function clearPresets() {
_presets = {};
}
/**
* Represents the `$get` method of the AngularJS provider. From here, a new
* reference to the MdPanelService is returned where the needed arguments are
* passed in including the MdPanelProvider `_presets`.
* @param {!Object} _presets
* @param {!angular.JQLite} $rootElement
* @param {!angular.Scope} $rootScope
* @param {!angular.$injector} $injector
* @param {!angular.$window} $window
*/
function $getProvider() {
return [
'$rootElement', '$rootScope', '$injector', '$window',
function($rootElement, $rootScope, $injector, $window) {
return new MdPanelService(_presets, $rootElement, $rootScope,
$injector, $window);
}
];
}
/*****************************************************************************
* MdPanel Service *
*****************************************************************************/
/**
* A service that is used for controlling/displaying panels on the screen.
* @param {!Object} presets
* @param {!angular.JQLite} $rootElement
* @param {!angular.Scope} $rootScope
* @param {!angular.$injector} $injector
* @param {!angular.$window} $window
* @final @constructor ngInject
*/
function MdPanelService(presets, $rootElement, $rootScope, $injector, $window) {
/**
* Default config options for the panel.
* Anything angular related needs to be done later. Therefore
* scope: $rootScope.$new(true),
* attachTo: $rootElement,
* are added later.
* @private {!Object}
*/
this._defaultConfigOptions = {
bindToController: true,
clickOutsideToClose: false,
disableParentScroll: false,
escapeToClose: false,
focusOnOpen: true,
fullscreen: false,
hasBackdrop: false,
propagateContainerEvents: false,
transformTemplate: angular.bind(this, this._wrapTemplate),
trapFocus: false,
zIndex: MD_PANEL_Z_INDEX
};
/** @private {!Object} */
this._config = {};
/** @private {!Object} */
this._presets = presets;
/** @private @const */
this._$rootElement = $rootElement;
/** @private @const */
this._$rootScope = $rootScope;
/** @private @const */
this._$injector = $injector;
/** @private @const */
this._$window = $window;
/** @private @const */
this._$mdUtil = this._$injector.get('$mdUtil');
/** @private {!Object<string, !MdPanelRef>} */
this._trackedPanels = {};
/**
* @private {!Object<string,
* {panels: !Array<!MdPanelRef>,
* openPanels: !Array<!MdPanelRef>,
* maxOpen: number}>}
*/
this._groups = Object.create(null);
/**
* Default animations that can be used within the panel.
* @type {enum}
*/
this.animation = MdPanelAnimation.animation;
/**
* Possible values of xPosition for positioning the panel relative to
* another element.
* @type {enum}
*/
this.xPosition = MdPanelPosition.xPosition;
/**
* Possible values of yPosition for positioning the panel relative to
* another element.
* @type {enum}
*/
this.yPosition = MdPanelPosition.yPosition;
/**
* Possible values for the interceptors that can be registered on a panel.
* @type {enum}
*/
this.interceptorTypes = MdPanelRef.interceptorTypes;
/**
* Possible values for closing of a panel.
* @type {enum}
*/
this.closeReasons = MdPanelRef.closeReasons;
/**
* Possible values of absolute position.
* @type {enum}
*/
this.absPosition = MdPanelPosition.absPosition;
}
/**
* Creates a panel with the specified options.
* @param {string=} preset Name of a preset configuration that can be used to
* extend the panel configuration.
* @param {!Object=} config Configuration object for the panel.
* @returns {!MdPanelRef}
*/
MdPanelService.prototype.create = function(preset, config) {
if (typeof preset === 'string') {
preset = this._getPresetByName(preset);
} else if (typeof preset === 'object' &&
(angular.isUndefined(config) || !config)) {
config = preset;
preset = {};
}
preset = preset || {};
config = config || {};
// If the passed-in config contains an ID and the ID is within _trackedPanels,
// return the tracked panel after updating its config with the passed-in
// config.
if (angular.isDefined(config.id) && this._trackedPanels[config.id]) {
var trackedPanel = this._trackedPanels[config.id];
angular.extend(trackedPanel.config, config);
return trackedPanel;
}
// Combine the passed-in config, the _defaultConfigOptions, and the preset
// configuration into the `_config`.
this._config = angular.extend({
// If no ID is set within the passed-in config, then create an arbitrary ID.
id: config.id || 'panel_' + this._$mdUtil.nextUid(),
scope: this._$rootScope.$new(true),
attachTo: this._$rootElement
}, this._defaultConfigOptions, config, preset);
// Create the panelRef and add it to the `_trackedPanels` object.
var panelRef = new MdPanelRef(this._config, this._$injector);
this._trackedPanels[config.id] = panelRef;
// Add the panel to each of its requested groups.
if (this._config.groupName) {
if (angular.isString(this._config.groupName)) {
this._config.groupName = [this._config.groupName];
}
angular.forEach(this._config.groupName, function(group) {
panelRef.addToGroup(group);
});
}
this._config.scope.$on('$destroy', angular.bind(panelRef, panelRef.detach));
return panelRef;
};
/**
* Creates and opens a panel with the specified options.
* @param {string=} preset Name of a preset configuration that can be used to
* extend the panel configuration.
* @param {!Object=} config Configuration object for the panel.
* @returns {!angular.$q.Promise<!MdPanelRef>} The panel created from create.
*/
MdPanelService.prototype.open = function(preset, config) {
var panelRef = this.create(preset, config);
return panelRef.open().then(function() {
return panelRef;
});
};
/**
* Gets a specific preset configuration object saved within `_presets`.
* @param {string} preset Name of the preset to search for.
* @returns {!Object} The preset configuration object.
*/
MdPanelService.prototype._getPresetByName = function(preset) {
if (!this._presets[preset]) {
throw new Error('mdPanel: The panel preset configuration that you ' +
'requested does not exist. Use the $mdPanelProvider to create a ' +
'preset before requesting one.');
}
return this._presets[preset];
};
/**
* Returns a new instance of the MdPanelPosition. Use this to create the
* positioning object.
* @returns {!MdPanelPosition}
*/
MdPanelService.prototype.newPanelPosition = function() {
return new MdPanelPosition(this._$injector);
};
/**
* Returns a new instance of the MdPanelAnimation. Use this to create the
* animation object.
* @returns {!MdPanelAnimation}
*/
MdPanelService.prototype.newPanelAnimation = function() {
return new MdPanelAnimation(this._$injector);
};
/**
* Creates a panel group and adds it to a tracked list of panel groups.
* @param groupName {string} Name of the group to create.
* @param config {!Object=} Specific configuration object that may contain the
* following properties:
*
* - `maxOpen` - `{number=}`: The maximum number of panels that are allowed
* open within a defined panel group.
*
* @returns {!Object<string,
* {panels: !Array<!MdPanelRef>,
* openPanels: !Array<!MdPanelRef>,
* maxOpen: number}>} panelGroup
*/
MdPanelService.prototype.newPanelGroup = function(groupName, config) {
if (!this._groups[groupName]) {
config = config || {};
var group = {
panels: [],
openPanels: [],
maxOpen: config.maxOpen > 0 ? config.maxOpen : Infinity
};
this._groups[groupName] = group;
}
return this._groups[groupName];
};
/**
* Sets the maximum number of panels in a group that can be opened at a given
* time.
* @param {string} groupName The name of the group to configure.
* @param {number} maxOpen The maximum number of panels that can be
* opened. Infinity can be passed in to remove the maxOpen limit.
*/
MdPanelService.prototype.setGroupMaxOpen = function(groupName, maxOpen) {
if (this._groups[groupName]) {
this._groups[groupName].maxOpen = maxOpen;
} else {
throw new Error('mdPanel: Group does not exist yet. Call newPanelGroup().');
}
};
/**
* Determines if the current number of open panels within a group exceeds the
* limit of allowed open panels.
* @param {string} groupName The name of the group to check.
* @returns {boolean} true if open count does exceed maxOpen and false if not.
* @private
*/
MdPanelService.prototype._openCountExceedsMaxOpen = function(groupName) {
if (this._groups[groupName]) {
var group = this._groups[groupName];
return group.maxOpen > 0 && group.openPanels.length > group.maxOpen;
}
return false;
};
/**
* Closes the first open panel within a specific group.
* @param {string} groupName The name of the group.
* @private
*/
MdPanelService.prototype._closeFirstOpenedPanel = function(groupName) {
this._groups[groupName].openPanels[0].close();
};
/**
* Wraps the users template in two elements, md-panel-outer-wrapper, which
* covers the entire attachTo element, and md-panel, which contains only the
* template. This allows the panel control over positioning, animations,
* and similar properties.
* @param {string} origTemplate The original template.
* @returns {string} The wrapped template.
* @private
*/
MdPanelService.prototype._wrapTemplate = function(origTemplate) {
var template = origTemplate || '';
// The panel should be initially rendered offscreen so we can calculate
// height and width for positioning.
return '' +
'<div class="md-panel-outer-wrapper">' +
' <div class="md-panel _md-panel-offscreen">' + template + '</div>' +
'</div>';
};
/**
* Wraps a content element in a md-panel-outer wrapper and
* positions it off-screen. Allows for proper control over positoning
* and animations.
* @param {!angular.JQLite} contentElement Element to be wrapped.
* @return {!angular.JQLite} Wrapper element.
* @private
*/
MdPanelService.prototype._wrapContentElement = function(contentElement) {
var wrapper = angular.element('<div class="md-panel-outer-wrapper">');
contentElement.addClass('md-panel _md-panel-offscreen');
wrapper.append(contentElement);
return wrapper;
};
/*****************************************************************************
* MdPanelRef *
*****************************************************************************/
/**
* A reference to a created panel. This reference contains a unique id for the
* panel, along with properties/functions used to control the panel.
* @param {!Object} config
* @param {!angular.$injector} $injector
* @final @constructor
*/
function MdPanelRef(config, $injector) {
// Injected variables.
/** @private @const {!angular.$q} */
this._$q = $injector.get('$q');
/** @private @const {!angular.$mdCompiler} */
this._$mdCompiler = $injector.get('$mdCompiler');
/** @private @const {!angular.$mdConstant} */
this._$mdConstant = $injector.get('$mdConstant');
/** @private @const {!angular.$mdUtil} */
this._$mdUtil = $injector.get('$mdUtil');
/** @private @const {!angular.$mdTheming} */
this._$mdTheming = $injector.get('$mdTheming');
/** @private @const {!angular.Scope} */
this._$rootScope = $injector.get('$rootScope');
/** @private @const {!angular.$animate} */
this._$animate = $injector.get('$animate');
/** @private @const {!MdPanelRef} */
this._$mdPanel = $injector.get('$mdPanel');
/** @private @const {!angular.$log} */
this._$log = $injector.get('$log');
/** @private @const {!angular.$window} */
this._$window = $injector.get('$window');
/** @private @const {!Function} */
this._$$rAF = $injector.get('$$rAF');
// Public variables.
/**
* Unique id for the panelRef.
* @type {string}
*/
this.id = config.id;
/** @type {!Object} */
this.config = config;
/** @type {!angular.JQLite|undefined} */
this.panelContainer;
/** @type {!angular.JQLite|undefined} */
this.panelEl;
/**
* Whether the panel is attached. This is synchronous. When attach is called,
* isAttached is set to true. When detach is called, isAttached is set to
* false.
* @type {boolean}
*/
this.isAttached = false;
// Private variables.
/** @private {Array<function()>} */
this._removeListeners = [];
/** @private {!angular.JQLite|undefined} */
this._topFocusTrap;
/** @private {!angular.JQLite|undefined} */
this._bottomFocusTrap;
/** @private {!$mdPanel|undefined} */
this._backdropRef;
/** @private {Function?} */
this._restoreScroll = null;
/**
* Keeps track of all the panel interceptors.
* @private {!Object}
*/
this._interceptors = Object.create(null);
/**
* Cleanup function, provided by `$mdCompiler` and assigned after the element
* has been compiled. When `contentElement` is used, the function is used to
* restore the element to it's proper place in the DOM.
* @private {!Function}
*/
this._compilerCleanup = null;
/**
* Cache for saving and restoring element inline styles, CSS classes etc.
* @type {{styles: string, classes: string}}
*/
this._restoreCache = {
styles: '',
classes: ''
};
}
MdPanelRef.interceptorTypes = {
CLOSE: 'onClose'
};
/**
* Opens an already created and configured panel. If the panel is already
* visible, does nothing.
* @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
* the panel is opened and animations finish.
*/
MdPanelRef.prototype.open = function() {
var self = this;
return this._$q(function(resolve, reject) {
var done = self._done(resolve, self);
var show = self._simpleBind(self.show, self);
var checkGroupMaxOpen = function() {
if (self.config.groupName) {
angular.forEach(self.config.groupName, function(group) {
if (self._$mdPanel._openCountExceedsMaxOpen(group)) {
self._$mdPanel._closeFirstOpenedPanel(group);
}
});
}
};
self.attach()
.then(show)
.then(checkGroupMaxOpen)
.then(done)
.catch(reject);
});
};
/**
* Closes the panel.
* @param {string} closeReason The event type that triggered the close.
* @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
* the panel is closed and animations finish.
*/
MdPanelRef.prototype.close = function(closeReason) {
var self = this;
return this._$q(function(resolve, reject) {
self._callInterceptors(MdPanelRef.interceptorTypes.CLOSE).then(function() {
var done = self._done(resolve, self);
var detach = self._simpleBind(self.detach, self);
var onCloseSuccess = self.config['onCloseSuccess'] || angular.noop;
onCloseSuccess = angular.bind(self, onCloseSuccess, self, closeReason);
self.hide()
.then(detach)
.then(done)
.then(onCloseSuccess)
.catch(reject);
}, reject);
});
};
/**
* Attaches the panel. The panel will be hidden afterwards.
* @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
* the panel is attached.
*/
MdPanelRef.prototype.attach = function() {
if (this.isAttached && this.panelEl) {
return this._$q.when(this);
}
var self = this;
return this._$q(function(resolve, reject) {
var done = self._done(resolve, self);
var onDomAdded = self.config['onDomAdded'] || angular.noop;
var addListeners = function(response) {
self.isAttached = true;
self._addEventListeners();
return response;
};
self._$q.all([
self._createBackdrop(),
self._createPanel()
.then(addListeners)
.catch(reject)
]).then(onDomAdded)
.then(done)
.catch(reject);
});
};
/**
* Only detaches the panel. Will NOT hide the panel first.
* @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
* the panel is detached.
*/
MdPanelRef.prototype.detach = function() {
if (!this.isAttached) {
return this._$q.when(this);
}
var self = this;
var onDomRemoved = self.config['onDomRemoved'] || angular.noop;
var detachFn = function() {
self._removeEventListeners();
// Remove the focus traps that we added earlier for keeping focus within
// the panel.
if (self._topFocusTrap && self._topFocusTrap.parentNode) {
self._topFocusTrap.parentNode.removeChild(self._topFocusTrap);
}
if (self._bottomFocusTrap && self._bottomFocusTrap.parentNode) {
self._bottomFocusTrap.parentNode.removeChild(self._bottomFocusTrap);
}
if (self._restoreCache.classes) {
self.panelEl[0].className = self._restoreCache.classes;
}
// Either restore the saved styles or clear the ones set by mdPanel.
self.panelEl[0].style.cssText = self._restoreCache.styles || '';
self._compilerCleanup();
self.panelContainer.remove();
self.isAttached = false;
return self._$q.when(self);
};
if (this._restoreScroll) {
this._restoreScroll();
this._restoreScroll = null;
}
return this._$q(function(resolve, reject) {
var done = self._done(resolve, self);
self._$q.all([
detachFn(),
self._backdropRef ? self._backdropRef.detach() : true
]).then(onDomRemoved)
.then(done)
.catch(reject);
});
};
/**
* Destroys the panel. The Panel cannot be opened again after this.
*/
MdPanelRef.prototype.destroy = function() {
var self = this;
if (this.config.groupName) {
angular.forEach(this.config.groupName, function(group) {
self.removeFromGroup(group);
});
}
this.config.scope.$destroy();
this.config.locals = null;
this._interceptors = null;
};
/**
* Shows the panel.
* @returns {!angular.$q.Promise<!MdPanelRef>} A promise that is resolved when
* the panel has shown and animations finish.
*/
MdPanelRef.prototype.show = function() {
if (!this.panelContainer) {
return this._$q(function(resolve, reject) {
reject('mdPanel: Panel does not exist yet. Call open() or attach().');
});
}
if (!this.panelContainer.hasClass(MD_PANEL_HIDDEN)) {
return this._$q.when(this);
}
var self = this;
var animatePromise = function() {
self.panelContainer.removeClass(MD_PANEL_HIDDEN);
return self._animateOpen();
};
return this._$q(function(resolve, reject) {
var done = self._done(resolve, self);
var onOpenComplete = self.config['onOpenComplete'] || angular.noop;
var addToGroupOpen = function() {
if (self.config.groupName) {
angular.forEach(self.config.groupName, function(group) {