UNPKG

mcs-ng-material

Version:

MCS NG-Meterial is based on mcs-web.

1,655 lines (1,464 loc) 103 kB
/*! * AngularJS Material Design * https://github.com/angular/material * @license MIT * v1.1.6 */ (function( window, angular, undefined ){ "use strict"; /** * @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) { self._$mdPanel._groups[group].openPanels.push(self); });