/*!
* Angular Material Design
* https://github.com/angular/material
* @license MIT
* v0.10.0
*/
(function( window, angular, undefined ){
"use strict";
/**
* @ngdoc module
* @name material.components.menu
*/
angular.module('material.components.menu', [
'material.core',
'material.components.backdrop'
])
.directive('mdMenu', MenuDirective)
.controller('mdMenuCtrl', MenuController);
/**
* @ngdoc directive
* @name mdMenu
* @module material.components.menu
* @restrict E
* @description
*
* Menus are elements that open when clicked. They are useful for displaying
* additional options within the context of an action.
*
* Every `md-menu` must specify exactly two child elements. The first element is what is
* left in the DOM and is used to open the menu. This element is called the trigger element.
* The trigger element's scope has access to `$mdOpenMenu()`
* which it may call to open the menu.
*
* The second element is the `md-menu-content` element which represents the
* contents of the menu when it is open. Typically this will contain `md-menu-item`s,
* but you can do custom content as well.
*
*
*
*
*
*
*
*
* Do Something
*
*
*
* ## Sizing Menus
*
* The width of the menu when it is open may be specified by specifying a `width`
* attribute on the `md-menu-content` element.
* See the [Material Design Spec](http://www.google.com/design/spec/components/menus.html#menus-specs)
* for more information.
*
*
* ## Aligning Menus
*
* When a menu opens, it is important that the content aligns with the trigger element.
* Failure to align menus can result in jarring experiences for users as content
* suddenly shifts. To help with this, `md-menu` provides serveral APIs to help
* with alignment.
*
* ### Target Mode
*
* By default, `md-menu` will attempt to align the `md-menu-content` by aligning
* designated child elements in both the trigger and the menu content.
*
* To specify the alignment element in the `trigger` you can use the `md-menu-origin`
* attribute on a child element. If no `md-menu-origin` is specified, the `md-menu`
* will be used as the origin element.
*
* Similarly, the `md-menu-content` may specify a `md-menu-align-target` for a
* `md-menu-item` to specify the node that it should try and align with.
*
* In this example code, we specify an icon to be our origin element, and an
* icon in our menu content to be our alignment target. This ensures that both
* icons are aligned when the menu opens.
*
*
*
*
*
*
*
*
*
*
* Do Something
*
*
*
*
*
*
* Sometimes we want to specify alignment on the right side of an element, for example
* if we have a menu on the right side a toolbar, we want to right align our menu content.
*
* We can specify the origin by using the `md-position-mode` attribute on both
* the `x` and `y` axis. Right now only the `x-axis` has more than one option.
* You may specify the default mode of `target target` or
* `target-right target` to specify a right-oriented alignment target. See the
* position section of the demos for more examples.
*
* ### Menu Offsets
*
* It is sometimes unavoidable to need to have a deeper level of control for
* the positioning of a menu to ensure perfect alignment. `md-menu` provides
* the `md-offset` attribute to allow pixel level specificty of adjusting the
* exact positioning.
*
* This offset is provided in the format of `x y` or `n` where `n` will be used
* in both the `x` and `y` axis.
*
* For example, to move a menu by `2px` from the top, we can use:
*
*
*
*
*
*
* @usage
*
*
*
*
*
*
* Do Something
*
*
*
*
* @param {string} md-position-mode The position mode in the form of
`x`, `y`. Default value is `target`,`target`. Right now the `x` axis
also suppports `target-right`.
* @param {string} md-offset An offset to apply to the dropdown after positioning
`x`, `y`. Default value is `0`,`0`.
*
*/
function MenuDirective($mdMenu) {
return {
restrict: 'E',
require: 'mdMenu',
controller: 'mdMenuCtrl', // empty function to be built by link
scope: true,
compile: compile
};
function compile(templateElement) {
templateElement.addClass('md-menu');
var triggerElement = templateElement.children()[0];
if (!triggerElement.hasAttribute('ng-click')) {
triggerElement = triggerElement.querySelector('[ng-click]');
}
triggerElement && triggerElement.setAttribute('aria-haspopup', 'true');
if (templateElement.children().length != 2) {
throw Error('Invalid HTML for md-menu. Expected two children elements.');
}
return link;
}
function link(scope, element, attrs, mdMenuCtrl) {
// Move everything into a md-menu-container and pass it to the controller
var menuContainer = angular.element(
'
'
);
var menuContents = element.children()[1];
menuContainer.append(menuContents);
mdMenuCtrl.init(menuContainer);
scope.$on('$destroy', function() {
if (mdMenuCtrl.isOpen) {
menuContainer.remove();
mdMenuCtrl.close();
}
});
}
}
MenuDirective.$inject = ["$mdMenu"];
function MenuController($mdMenu, $attrs, $element, $scope) {
var menuContainer;
var ctrl = this;
var triggerElement;
// Called by our linking fn to provide access to the menu-content
// element removed during link
this.init = function(setMenuContainer) {
menuContainer = setMenuContainer;
triggerElement = $element[0].querySelector('[ng-click]');
};
// Uses the $mdMenu interim element service to open the menu contents
this.open = function openMenu() {
ctrl.isOpen = true;
triggerElement.setAttribute('aria-expanded', 'true');
$mdMenu.show({
mdMenuCtrl: ctrl,
element: menuContainer,
target: $element[0]
});
};
// Expose a open function to the child scope for html to use
$scope.$mdOpenMenu = this.open;
// Use the $mdMenu interim element service to close the menu contents
this.close = function closeMenu(skipFocus) {
ctrl.isOpen = false;
triggerElement.setAttribute('aria-expanded', 'false');
$mdMenu.hide();
if (!skipFocus) {
$element.children()[0].focus();
}
};
// Build a nice object out of our string attribute which specifies the
// target mode for left and top positioning
this.positionMode = function() {
var attachment = ($attrs.mdPositionMode || 'target').split(' ');
// If attachment is a single item, duplicate it for our second value.
// ie. 'target' -> 'target target'
if (attachment.length == 1) {
attachment.push(attachment[0]);
}
return {
left: attachment[0],
top: attachment[1]
};
};
// Build a nice object out of our string attribute which specifies
// the offset of top and left in pixels.
this.offsets = function() {
var offsets = ($attrs.mdOffset || '0 0').split(' ').map(parseFloat);
if (offsets.length == 2) {
return {
left: offsets[0],
top: offsets[1]
};
} else if (offsets.length == 1) {
return {
top: offsets[0],
left: offsets[0]
};
} else {
throw Error('Invalid offsets specified. Please follow format or ');
}
};
}
MenuController.$inject = ["$mdMenu", "$attrs", "$element", "$scope"];
angular.module('material.components.menu')
.provider('$mdMenu', MenuProvider);
/*
* Interim element provider for the menu.
* Handles behavior for a menu while it is open, including:
* - handling animating the menu opening/closing
* - handling key/mouse events on the menu element
* - handling enabling/disabling scroll while the menu is open
* - handling redrawing during resizes and orientation changes
*
*/
function MenuProvider($$interimElementProvider) {
var MENU_EDGE_MARGIN = 8;
menuDefaultOptions.$inject = ["$$rAF", "$window", "$mdUtil", "$mdTheming", "$timeout", "$mdConstant", "$document"];
return $$interimElementProvider('$mdMenu')
.setDefaults({
methods: ['target'],
options: menuDefaultOptions
});
/* ngInject */
function menuDefaultOptions($$rAF, $window, $mdUtil, $mdTheming, $timeout, $mdConstant, $document) {
return {
parent: 'body',
onShow: onShow,
onRemove: onRemove,
hasBackdrop: true,
disableParentScroll: true,
skipCompile: true,
themable: true
};
/**
* Boilerplate interimElement onShow function
* Handles inserting the menu into the DOM, positioning it, and wiring up
* various interaction events
*/
function onShow(scope, element, opts) {
// Sanitize and set defaults on opts
buildOpts(opts);
// Wire up theming on our menu element
$mdTheming.inherit(opts.menuContentEl, opts.target);
// Register various listeners to move menu on resize/orientation change
handleResizing();
// Disable scrolling
if (opts.disableParentScroll) {
opts.restoreScroll = $mdUtil.disableScrollAround(opts.element);
}
// Only activate click listeners after a short time to stop accidental double taps/clicks
// from clicking the wrong item
$timeout(activateInteraction, 75, false);
if (opts.backdrop) {
$mdTheming.inherit(opts.backdrop, opts.parent);
opts.parent.append(opts.backdrop);
}
showMenu();
// Return the promise for when our menu is done animating in
return $mdUtil.transitionEndPromise(element, {timeout: 350});
/** Check for valid opts and set some sane defaults */
function buildOpts() {
if (!opts.target) {
throw Error(
'$mdMenu.show() expected a target to animate from in options.target'
);
}
angular.extend(opts, {
alreadyOpen: false,
isRemoved: false,
target: angular.element(opts.target), //make sure it's not a naked dom node
parent: angular.element(opts.parent),
menuContentEl: angular.element(element[0].querySelector('md-menu-content')),
backdrop: opts.hasBackdrop && angular.element('