Create a diretive for content rating. I was used ng-mouseenter and ng-mouseleave.
There are two types content rating user can click and one is readonly.
when I passed readonly attribute ng-mouseleave is working. but When I didn't pass it is not working
basically I am using ng-mouseenter and ng-mouseleave for providing hovering effect on it and remove hovering effect.
This is my jsfiddle link
https://jsfiddle.net/amitme/3Ldad4v6/1/
var app = angular.module("glyph", []);
app.controller("contentRatingController", ['$scope', function($scope) {
$scope.rating1 = 5;
$scope.rating2 = 2.5;
$scope.isReadonly = true;
}]);
app.directive('contentRating', function contentRating() {
var defaults = JSON.parse('{}');
return {
restrict: 'E',
transclude: false,
require: '',
scope: {},
bindToController: {
ratingValue: "=ngModel",
max: "#",
onRatingSelect: "&?",
readonly: "=?",
name: "#"
},
controller: function() {
var g = this;
g.gRatingTemp = g.ratingValue;
if (g.max == undefined) {
g.max = 5;
}
g.updateStars = function(ratingValue) {
g.stars = [];
for (var i = 0; i < g.max; i++) {
g.stars.push({
filled: (i < ratingValue),
halffilled: (ratingValue)
});
}
console.log(g.stars);
};
g.updateStars(g.ratingValue);
g.toggle = function(index) {
if (g.readonly == undefined || g.readonly == false) {
g.ratingValue = index + 1;
console.log("----------toggle--------" + g.ratingValue);
if (g.gRatingTemp != g.ratingValue) {
g.gRatingTemp = g.ratingValue;
g.updateStars(g.ratingValue);
}
}
}
g.mouseEnter = function(index) {
console.log("----------mouseEnter--------" + index);
if (g.readonly == undefined || g.readonly == false) {
g.updateStars(index + 1);
}
};
g.mouseLeave = function(index) {
alert("----------mouseLeave--------" + index);
if (g.readonly == undefined || g.readonly == false) {
g.updateStars(index + 1);
}
};
},
compile: function() {
return {
pre: function(scope, el, attrs) {
for (var key in defaults) {
if (!attrs[key]) {
attrs[key] = defaults[key];
}
}
},
post: function(scope, el, attrs) {
}
};
},
template: '<ul class="rate" ng-class="{readonly: Ctrl.readonly}"> <li ng-repeat="star in Ctrl.stars" class="star" ng-mouseover="$event.stopPropagation(); Ctrl.mouseEnter($index)" ng-mouseleave="$event.stopPropagation(); Ctrl.mouseLeave($index)"> <input type="radio" id="{{Ctrl.name}}{{$index}}" name="{{Ctrl.name}}" value="{{$index+1}}" ng-model="Ctrl.ratingValue" ng-click="Ctrl.toggle($index)"/><label for="{{Ctrl.name}}{{$index}}" title="{{$index+1}}" ng-class="{filled:( star.filled && star.halffilled >= $index+1)}"></label> <input type="radio" id="{{Ctrl.name}}{{$index+1/2}}" name="{{Ctrl.name}}" value="{{$index+1/2}}" ng-model="Ctrl.ratingValue" ng-click="Ctrl.toggle($index-1/2)"/><label class="half" for="{{Ctrl.name}}{{$index+1/2}}" title="{{$index+1/2}}" ng-class="{filled: star.filled}" ></label> </li></ul><!-- <ul class="star-rating" ng-class="{readonly: Ctrl.readonly}"> <li ng-repeat="star in Ctrl.stars" class="star {{yellowStar}}" ng-class="{filled: star.filled}" ng-click="Ctrl.toggle($index)" ng-mouseenter="Ctrl.mouseEnter($index)" ng-mouseleave="Ctrl.mouseLeave($index)"> <span> <i class="fa fa-star"></i> </span> </li></ul> -->',
controllerAs: 'Ctrl',
};
});
Related
Uncaught ReferenceError: Unable to process binding "visible: function (){return NeedPaging }"
Message: NeedPaging is not defined
at visible (eval at parseBindingsString (knockout-3.4.2.js:68), :3:60)
at update (knockout-3.4.2.js:104)
at function.a.B.i (knockout-3.4.2.js:73)
at Function.Uc (knockout-3.4.2.js:52)
at Function.Vc (knockout-3.4.2.js:51)
at Function.U (knockout-3.4.2.js:51)
at Object.a.m.a.B (knockout-3.4.2.js:49)
at knockout-3.4.2.js:73
at Object.r (knockout-3.4.2.js:11)
at m (knockout-3.4.2.js:72)
I'm stuck any help would be great thank you in advance.
I'm only getting this error in production however in my local it is working just fine I'm not sure what I'm missing. It looks like the comment is being removed which is giving me the error.
$(function () {
"use strict";
var PagingVm = function (options) {
var self = this;
self.PageSize = ko.observable(options.pageSize);
self.CurrentPage = ko.observable(1);
self.TotalCount = ko.observable(options.totalCount);
self.PageCount = ko.pureComputed(function () {
return Math.ceil(self.TotalCount() / self.PageSize());
});
self.SetCurrentPage = function (page) {
if (page < self.FirstPage)
page = self.FirstPage;
if (page > self.LastPage())
page = self.LastPage();
self.CurrentPage(page);
};
self.FirstPage = 1;
self.LastPage = ko.pureComputed(function () {
return self.PageCount();
});
self.NextPage = ko.pureComputed(function () {
var next = self.CurrentPage() + 1;
if (next > self.LastPage())
return null;
return next;
});
self.PreviousPage = ko.pureComputed(function () {
var previous = self.CurrentPage() - 1;
if (previous < self.FirstPage)
return null;
return previous;
});
self.NeedPaging = ko.pureComputed(function () {
return self.PageCount() > 1;
});
self.NextPageActive = ko.pureComputed(function () {
return self.NextPage() != null;
});
self.PreviousPageActive = ko.pureComputed(function () {
return self.PreviousPage() != null;
});
self.LastPageActive = ko.pureComputed(function () {
return (self.LastPage() !== self.CurrentPage());
});
self.FirstPageActive = ko.pureComputed(function () {
return (self.FirstPage !== self.CurrentPage());
});
// this should be odd number always
var maxPageCount = 7;
self.generateAllPages = function () {
var pages = [];
for (var i = self.FirstPage; i <= self.LastPage(); i++)
pages.push(i);
return pages;
};
self.generateMaxPage = function () {
var current = self.CurrentPage();
var pageCount = self.PageCount();
var first = self.FirstPage;
var upperLimit = current + parseInt((maxPageCount - 1) / 2);
var downLimit = current - parseInt((maxPageCount - 1) / 2);
while (upperLimit > pageCount) {
upperLimit--;
if (downLimit > first)
downLimit--;
}
while (downLimit < first) {
downLimit++;
if (upperLimit < pageCount)
upperLimit++;
}
var pages = [];
for (var i = downLimit; i <= upperLimit; i++) {
pages.push(i);
}
return pages;
};
self.GetPages = ko.pureComputed(function () {
self.CurrentPage();
self.TotalCount();
if (self.PageCount() <= maxPageCount) {
return ko.observableArray(self.generateAllPages());
} else {
return ko.observableArray(self.generateMaxPage());
}
});
self.Update = function (e) {
self.TotalCount(e.TotalCount);
self.PageSize(e.PageSize);
self.SetCurrentPage(e.CurrentPage);
};
self.GoToPage = function (page) {
if (page >= self.FirstPage && page <= self.LastPage())
self.SetCurrentPage(page);
}
self.GoToFirst = function () {
self.SetCurrentPage(self.FirstPage);
};
self.GoToPrevious = function () {
var previous = self.PreviousPage();
if (previous != null)
self.SetCurrentPage(previous);
};
self.GoToNext = function () {
var next = self.NextPage();
if (next != null)
self.SetCurrentPage(next);
};
self.GoToLast = function () {
self.SetCurrentPage(self.LastPage());
};
}
var MainViewModel = function () {
var self = this;
self.PageSize = ko.observable(10);
self.AllData = ko.observableArray();
self.PagaData = ko.observableArray();
self.ActivePaging = ko.observable(false);
self.Paging = ko.observable(new PagingVm({
pageSize: self.PageSize(),
totalCount: self.AllData.length
}));
self.DeSelect = function (mainModel, event) {
if (event.target.value === self.SelectedSearchOption()) {
self.SelectedSearchOption(null);
event.target.checked = false;
}
return true;
};
self.pageSizeSubscription = self.PageSize.subscribe(function (newPageSize) {
self.Paging().Update({
PageSize: newPageSize,
TotalCount: self.AllData().length,
CurrentPage: self.Paging().CurrentPage()
});
self.RenderAgain();
});
self.currentPageSubscription = self.Paging().CurrentPage.subscribe(function () {
self.RenderAgain();
});
self.RenderAgain = function () {
var result = [];
var startIndex = (self.Paging().CurrentPage() - 1) * self.Paging().PageSize();
var endIndex = self.Paging().CurrentPage() * self.Paging().PageSize();
for (var i = startIndex; i < endIndex; i++) {
if (i < self.AllData().length)
result.push(self.AllData()[i]);
}
self.PagaData(result);
}
self.dispose = function () {
self.currentPageSubscription.dispose();
self.pageSizeSubscription.dispose();
}
}
var vm = new MainViewModel();
ko.applyBindings(vm);
vm.RenderAgain();
});
<div data-bind="visible: ActivePaging" class="row" style="display: none;">
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 form-group">
<label>page size</label>
<input class="form-control" type="number" min="1" data-bind="textInput:PageSize" />
</div>
<div class="col-sm-6">
<div class="form-group marg-top-reports">
<!-- ko with:Paging()-->
<ul data-bind="visible: NeedPaging" class="pull-left pagination">
<li data-bind="css: { disabled: !FirstPageActive() }">
<a data-bind="click: GoToFirst">First</a>
</li>
<li data-bind="css: { disabled: !PreviousPageActive() }">
<a data-bind="click: GoToPrevious">Previous</a>
</li>
<!-- ko foreach: GetPages() -->
<li data-bind="css: { active: $parent.CurrentPage() === $data }">
<a data-bind="click: $parent.GoToPage, text: $data"></a>
</li>
<!-- /ko -->
<li data-bind="css: { disabled: !NextPageActive() }">
<a data-bind="click: GoToNext">Next</a>
</li>
<li data-bind="css: { disabled: !LastPageActive() }">
<a data-bind="click: GoToLast">Last</a>
</li>
</ul>
<!-- /ko -->
</div>
</div>
</div>
</div>
Hi I am developing web application in angularjs. I have requirement to validate textbox. It should accept only numbers with max 10 digits. I have directive but current directive should not restrict number of digits typed.
myapp.directive('validNumber', function () {
return {
require: '?ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
if (!ngModelCtrl) {
return;
}
ngModelCtrl.$parsers.push(function (val) {
if (angular.isUndefined(val)) {
var val = '';
}
var clean = val.replace(/[^0-9]+/g, '');
if (val !== clean) {
ngModelCtrl.$setViewValue(clean);
ngModelCtrl.$render();
}
return clean;
});
element.bind('keypress', function (event) {
if (event.keyCode === 32) {
event.preventDefault();
}
});
}
};
});
<div class="inputblock" ng-class="{ 'has-error' : ((form1.$submitted && form1.idnumber.$invalid )|| (form1.idnumber.$invalid && form1.idnumber.$dirty))}">
<label class="inputblock-label" ng-show="idnumber">{{ 'ID Number' | translate }}</label>
<span class="ang-error" style="color:#fff" ng-show="form1.idnumber.$dirty && form1.idnumber.$invalid ">
<span ng-show="form1.idnumber.$invalid && form1.idnumber.$dirty">*{{'Max allowed digits 10' | translate}}</span>
</span>
<input class="with-icon" type="text" name="idnumber" placeholder="{{ 'ID Number' | translate }}" ng-model="idnumber" required ng-pattern="/^[0-9]{1,7}$/" > <!--valid-number-->
</div>
May i know what should be changed in the above directive so that it can accept maximum only 10 digits! Any help would be appreciated. Thank you.
Use the following code and try. my original purpose of this code was to limit the number to integer. But I have modified it a little so you can use this
(function() {
'use strict';
angular.module('app').directive('intValidate', intValidate);
function intValidate($locale) {
var decimalSep = $locale.NUMBER_FORMATS.DECIMAL_SEP;
var toNumberRegex = new RegExp('[^0-9\\' + decimalSep + ']', 'g');
function toNumber(currencyStr) {
return parseFloat(currencyStr.toString().replace(toNumberRegex, ''), 10);
}
return {
restrict : 'A',
require : 'ngModel',
link : function validate(scope, elem, attrs, modelCtrl) {
modelCtrl.$parsers.push(function(newViewValue) {
var modelValue = toNumber(newViewValue);
var valid = modelValue <= 9999999999;
modelCtrl.$setValidity('limitcheck', valid);
return valid ? newViewValue : undefined;
});
}
};
}
})();
and use,
<input type="text" id="value" name="value" int-validate>
and if you want an error message
<p class="help-block" ng-if="cacc.form.value.$error.limitcheck">Max 10 digits allowed</p>
I have developed a directive, but it is always applying functionality on the last element in the page, here is my code:
HTML:
<div ng-controller="MyCtrl">
<input type="text" email-commy comma-separated-data="model1" class="form-control"/>
<input type="text" email-commy comma-separated-data="model2" class="form-control"/>
</div>
JS:
var app=angular.module('myApp', []);
function MyCtrl($scope) {
$scope.model1 = "abc#gmail.com";
$scope.model2 = "abc2#gmail.com";
}
app.directive("emailCommy", function(){
return {
scope: {
commaSeparatedData: '='
},
restrict: "A",
multiple: true,
link: function (scope, elem, attrs, ngModel) {
function handler(dataString){
function buildEmailList() {
var lis = data.map(function (em, index) {
return "<li>" + em + "<span class='remove-element' data-index='" + index + "'>×</span>" + "</li>";
}).join("");
var input = $("<input/>", {"class": "form-control shadow-element", "placeholder": "Enter..."});
lis += "<li class='input-field'>" + input.wrap("<p/>").parent().html() + "</li>";
return "<ul class='list-inline'>" + lis + "</ul>";
}
function renderer() {
$wrapper.empty().append(buildEmailList());
elem.addClass("hide").before($wrapper);
listener();
}
function listener() {
angular.element(".shadow-element").off("keypress").on("keypress", function (evt) {
var $this = $(this),
charCode = evt.which || evt.keyCode;
//add input in case of (comma, enter, and semi-colon) pressed
if ($this.val() && (charCode === 44 || charCode === 13 || charCode === 59)) {
data.push($this.val().toLowerCase());
renderer();
updateModel();
$this.val("");
}
});
angular.element(".remove-element").off("click").on("click", function () {
var $this = $(this),
index = $this.data("index");
data.splice(index, 1);
updateModel();
renderer();
});
}
function updateModel(){
var updatedVal = data.join(",");
$(elem).val(updatedVal);
}
if(dataString) {
var data = dataString.split(",").map(function (em) {
return em.toLowerCase().trim();
});
var $wrapper = $("<div/>", {"class": "email-container"});
renderer();
}
}
handler(scope.commaSeparatedData);
//scope.$watch(attrs.commaSeparatedData, handler);
}
};
});
Here is fiddle:
http://jsfiddle.net/RmDuw/928/
Directive only working on last element. If i add data in first directive it adds to below one.
Please help, and suggest me how to fix.
Thanks
Just change your code in order to select the input of current directive like this:
function listener() {
$wrapper.find('input').on(...
and for remove element:
$wrapper.find('span')..
See the fiddle
I have a custom AngularJS directive for a textdown control. In it is an ng-repeat that is printing a list of div's for the emulated dropdown, and each item has an ng-click function. That function will not fire when the div is clicked. Can you please help me figure out why?
Plunkr: https://plnkr.co/edit/vOwtjqltq2WfCM9A0dFJ?p=preview
I don't remember where I first heard that concept, but it's very similar to StackOverflow's question Tags input, except you can only select 1 item. If you haven't seen that example, it is a text input that has a dropdown list when you start typing into it with related items that has fields that partially match what you've typed so far. Then the user can click on an item in the dropdown and it fills in the text input.
Here is the main page's HTML:
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>
document.write('<base href="' + document.location + '" />');
</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.4.x" src="https://code.angularjs.org/1.4.7/angular.js" data-semver="1.4.7"></script>
<script src="app.js"></script>
<script src="textdown.js"></script>
</head>
<body ng-controller="MainCtrl">
<p>Hello and welcome to the Textdown example!</p>
<label>City:
<textdown input-placeholder-text="Select a City..." is-editable="true" items="cities" ng-model="selectedCity" title="Name" width="150px"></textdown>
</label>
</body>
</html>
Here is the directive's HTML:
var HYG_TEXTBOX_DROPDOWN_TEMPLATE = '\
<div class="hyg-textdown-container activate-textdown" \
ng-class="{ \'hyg-focused\': isFocused() }"> \
<input type="search" class="activate-textdown" placeholder="{{ inputPlaceholderText }}" style="width: 100%;" \
ng-class="{ \'invalid\': !isValid() }" \
ng-change="onInputChanged()" \
ng-focus="onInputFocus($event)" \
ng-model="input" \
ng-blur="onInputBlur()" \
ng-show="isEditable"> \
</input> \
<div class="hyg-textdown-list activate-textdown" ng-show="selectActive" ng-style="{ top: ytop, left: xleft }" style="z-index:5; width: {{ width }}"> \
<div class="hyg-textdown-listed activate-textdown" \
ng-repeat="item in items | property: title: (ngModel != null ? \'\' : input) | orderBy: title | limitTo:5" \
ng-class="{ \'hyg-textdown-listed-active\': isSelected(item) }" \
ng-click="selectItem(item, $event);"> \
<span class="activate-textdown">{{ item[title] }}</span> \
</div> \
</div> \
</div>';
Here is the module, directive, controller, and associated filter code:
angular.module("hyg.Textdown", [])
.directive("textdown", ["$compile", "$document", "$filter", "$log", "$timeout", function ($compile, $document, $filter, $log, $timeout) {
return {
restrict: "E",
replace: false,
controller: "hygTextdownCtrl",
template: function (element, attrs) {
return HYG_TEXTBOX_DROPDOWN_TEMPLATE;
},
require: "?ngModel",
scope: {
inputPlaceholderText: "#",
isEditable: "=",
items: "=",
ngModel: "=",
title: "#",
width: "#"
},
link: function (scope, element, attrs) {
scope.orderBy = $filter("orderBy");
if (scope.isEditable == null)
scope.isEditable = true;
$document.bind("click", function (e) {
var shouldHideSelectList = !Enumerable.From(e.target.classList).Any(function (x) { return x == "activate-textdown"; });
if (shouldHideSelectList) {
$timeout(function () { scope.selectActive = false; }, 0);
}
});
scope.destroy = function () {
if (scope.handler != null)
scope.handler();
};
scope.isFocused = function () {
return scope.focus;
};
scope.isSelectActive = function () {
return scope.selectActive;
};
scope.isValid = function () {
return scope.input == null || scope.input.length == 0 || scope.ngModel != null;
};
scope.onInputChanged = function () {
var input = scope.input == null ? null : scope.input.toLowerCase();
var item = Enumerable.From(scope.items).Where(function (x) { return x[scope.title].toLowerCase() == input; }).ToArray()[0];
scope.selectItem(item);
};
scope.onInputFocus = function ($event) {
scope.focus = true;
scope.selectActive = true;
};
scope.onInputBlur = function () {
scope.focus = false;
scope.selectActive = false;
};
scope.selectItem = function (item, $event) {
if (scope.isEditable) {
scope.ngModel = item;
if (item != null)
scope.selectActive = false;
}
};
scope.isSelected = function (item) {
return scope.ngModel == item;
};
scope.handler = scope.$watch("ngModel", function () {
if(scope.ngModel != null)
scope.input = scope.ngModel[scope.title];
});
}
}
}])
.controller("hygTextdownCtrl", ["$scope", function ($scope) {
$scope.focus = false;
$scope.handler = null;
$scope.selectActive = false;
}])
.filter("property", ["$filter", function ($filter) {
return function (array, propertyString, target) {
if (target == null)
return array;
var matched = [];
var toMatch = target.toLowerCase();
angular.forEach(array, function (item) {
if (item[propertyString].includes != undefined) {
if (item[propertyString].toLowerCase().includes(toMatch)) {
matched.push(item);
}
}
else
{
if (item[propertyString].toLowerCase().indexOf(toMatch) > -1) {
matched.push(item);
}
}
});
return matched;
}
}]);
Thanks,
Jibba
The reason why the ng-click is not firing is because before the option is clicked, blur event is fired on the input, which hides the options and your option never gets clicked.
You can try selecting option using ng-mousedown instead of ng-click.
I have a md-date-picker inside md-menu, but when I click on datepicker, md-menu closes. Here is codepen for this. It is related to this bug of ng-material. What can be workaround for this?
HTML:
<div class="md-menu-demo menudemoBasicUsage" ng-controller="BasicDemoCtrl as ctrl" ng-app="MyApp">
<div class="menu-demo-container" layout-align="center center" layout="column">
<h2 class="md-title">Month Select</h2>
<p>Select a month by clicking the input</p>
<md-menu>
<input md-menu-origin="" aria-label="Open phone interactions menu" ng-focus="ctrl.openMenu($mdOpenMenu, $event)" ng-model="ctrl.selectedMonth">
<md-menu-content width="4" ng-click="$event.stopPropagation()">
<md-datepicker ng-model="dateFilter" md-placeholder="Till date" md-min-date="dateFilter.fromDate"></md-datepicker>
</md-menu-content>
</md-menu>
</div>
</div>
JS:
angular
.module('MyApp')
.controller('BasicDemoCtrl', function DemoCtrl($mdDialog) {
var originatorEv;
this.openMenu = function($mdOpenMenu, ev) {
originatorEv = ev;
$mdOpenMenu(ev);
};
this.setMonth = function(val) {
this.month = val;
this.setSelectedMonth();
};
this.notificationsEnabled = true;
this.toggleNotifications = function() {
this.notificationsEnabled = !this.notificationsEnabled;
};
this.nextYear = function() {
this.year++;
this.setSelectedMonth();
};
this.preYear = function() {
this.year = this.year - 1;
this.setSelectedMonth();
};
}).directive('stopEvent', function() {
return {
restrict: 'A',
link: function(scope, element, attr) {
if (attr && attr.stopEvent)
element.bind(attr.stopEvent, function(e) {
e.stopPropagation();
});
}
};
});
I found a solution that works but is not the best answer.
HTML:
<md-datepicker id="myDatePicker"
ng-model="dateFilter"
md-placeholder="Till date"
md-min-date="dateFilter.fromDate">
</md-datepicker>
JS:
function setupDateButton()
{
var dateButtonFix = document.getElementById("myDatePicker").children;
for (var i = 0; i < dateButtonFix.length; i++)
{
if (dateButtonFix[i].tagName == 'BUTTON' || dateButtonFix[i].tagName == 'DIV')
{
if (dateButtonFix[i].tagName == 'DIV')
{
var child2 = dateButtonFix[i].children;
for (var j = 0; j < child2.length; j++)
{
if (child2[j].tagName == 'BUTTON')
{
child2[1].setAttribute("md-prevent-menu-close", "md-prevent-menu-close");
}
}
}
else
dateButtonFix[0].setAttribute("md-prevent-menu-close", "md-prevent-menu-close");
}
}
}
setupDateButton();
I'm sure there is a better way to do this but as of right now, it works.
Today I got to the same problem, and I created a class-restricted directive to solve this problem.
This is my directive's code:
const TRUE = 'true';
const PREVENT_CLOSE = 'md-prevent-menu-close';
class CalendarBtnFixDirective {
constructor() {
this.restrict = 'C';
this.require = '^^mdDatepicker'
}
link(scope, element, attrs, datePickerCtrl) {
const nativeElement = element[0];
const preventMenuClose = datePickerCtrl.$attrs.mdPreventMenuClose;
if ([TRUE, PREVENT_CLOSE].indexOf(preventMenuClose) !== -1) {
nativeElement.setAttribute(PREVENT_CLOSE, PREVENT_CLOSE);
}
}
}
export const MdCalendarFixModule = angular
.module('md.calendar.fix.module', [])
.directive('mdDatepickerTriangleButton', () => new CalendarBtnFixDirective())
.name;
Now in your md-datepicker you can use the md-prevent-menu-close attribute
Add md-prevent-menu-close="true" to the button or to the icon. This will prevent the menu from closing.
https://material.angularjs.org/1.0.3/api/directive/mdMenu