Minimalizing a menu with ng-repeat - angularjs

I know the question title is a little vague but I don't know what else to call it. Basically, this is my problem:
I have an angular menu with submenus. I have each menu item separate and the submenu items being injected with ng-repeat. That is fine, but the problem is I would like to run the entire menu in one ng-repeat and have the json file hold, not only the submenu items, but the menu items as well. This is what I have:
<div class="cnt">
<div class="menu-item" ng-click="toggle(1); open1=!open1">
<md-list layout="row" layout-padding="" class="layout-row" layout-align="start center" flex>
<span class="title flex" flex=""> Menu Item</span>
<i class="fa fa-chevron-down" ng-class="{'rotate180': open1, 'rotate-back': !open1}"></i>
</md-list>
<div class="sub-menu" ng-animate="'animate'" >
<md-menu-item ng-if="menuIsOpen===1" ng-repeat="item in data" >
<md-button>
<div layout="row" flex="">
<a ui-sref="{{item.link}}">
<p flex=""><i class="fa fa-{{item.icon}}"></i> {{item.title}}</p>
</a>
</div>
</md-button>
</md-menu-item>
</div>
</div>
<div class="menu-item" ng-click="toggle(2); open2=!open2">
<md-list layout="row" layout-padding="" class="layout-row" layout-align="start center" flex>
<span class="title flex" flex=""> Menu Item 2</span>
<i class="fa fa-chevron-down" ng-class="{'rotate180': open2, 'rotate-back': !open2}"></i>
</md-list>
<div class="sub-menu" ng-animate="'animate'" >
<md-menu-item ng-if="menuIsOpen===2" ng-repeat="item in data2">
<md-button>
<div layout="row" flex="">
<a ui-sref="{{item.link}}">
<p flex=""><i class="fa fa-{{item.icon}}"></i> {{item.title}}</p>
</a>
</div>
</md-button>
</md-menu-item>
</div>
</div>
</div>
with a json file that looks like this:
$scope.data =
[{
title: 'Home',
icon: 'home',
link: '/page1/'
}, {
title: 'Email Us',
icon: 'envelope',
link: '/page2/'
}, {
title: 'Profile',
icon: 'user',
link: '/page3/'
}, {
title: 'Print',
icon: 'print',
link: '/page4/'
}];
$scope.data2 =
[{
title: 'Home 2',
icon: 'home',
link: '/page1/'
}, {
title: 'Email Us 2',
icon: 'envelope',
link: '/page2/'
}, {
title: 'Profile 2',
icon: 'user',
link: '/page3/'
}, {
title: 'Print 2',
icon: 'print',
link: '/page4/'
}];
As you can see, if I want 2 menu items, I have to build the code for 2 menu items, add the data to the json, and run the bindings. And if I want 3, then I have to do this 3 times. But what if I want 20? Ideally, I would like to have 1 single HTML structure that pulls the data from the json to create as many menu items with as many submenu items as it finds in the json. But I am having problems trying to achieve this:
This is what makes sense in my head:
<div class="cnt" ng-repeat="item in data">
<div class="menu-item" ng-click="toggle({{item.pos}}); open{{item.pos}}=!open{{item.pos}}">
<md-list layout="row" layout-padding="" class="layout-row" layout-align="start center" flex>
<span class="title flex" flex=""> {{item.name}}</span>
<i class="fa fa-chevron-down" ng-class="{'rotate180': open{{item.pos}}, 'rotate-back': !open{{item.pos}}}"></i>
</md-list>
<div class="sub-menu" ng-animate="'animate'" >
<md-menu-item ng-if="menuIsOpen==={{item.pos}}" >
<md-button>
<div layout="row" flex="">
<a ui-sref="{{item.item1[0].linkto}}">
<p flex=""><i class="fa fa-{{item.icon}}"></i> {{item.item1[0].title}}</p>
</a>
</div>
</md-button>
</md-menu-item>
</div>
</div>
</div>
I have a CODEPEN that you can see the working code in.
See this CODEPEN for the desired effect. This is how I have it working now and how it should work ultimately.
Thanks fellas!

This answer is not directly addressing the parsing issue, but a solution to what you are trying to achieve.
Instead of using open{{item.pos}} to identify the menu item, which is resulting in parsing error, use the the menuIsOpen,which holds the position of the toggled item. Based on the desired effect.. codepen sample
script additions
//Not needed
// $scope.open1 = false; //initial value
//$scope.open2 = false; //initial value
//$scope.open3 = false; //initial value
//$scope.open4 = false; //initial value
$scope.toggle = function(itemPos) {
if ($scope.menuIsOpen === itemPos) {
$scope.menuIsOpen = 0;
}
else {
$scope.menuIsOpen = itemPos;
}
}
html : just need toggle the item we pass the position property
<div class="cnt" ng-repeat="item in data">
<div class="menu-item" ng-click="toggle(item.pos);">
<md-list layout="row" layout-padding="" class="layout-row" layout-align="start center" flex>
<span class="title flex" flex=""> {{item.name}}</span>
<i class="fa fa-chevron-down" ng-class="{'rotate180': item.pos==menuIsOpen, 'rotate-back': !menuIsOpen}"></i>
</md-list>
<div class="sub-menu" ng-animate="'animate'" >
<md-menu-item ng-if="menuIsOpen===item.pos" >
<md-button>
<div layout="row" flex="">
<a ui-sref="{{item.item1[0].linkto}}">
<p flex=""><i class="fa fa-{{item.icon}}"></i> {{item.item1[0].title}} a</p>
</a>
</div>
</md-button>
</md-menu-item>
</div>
</div>
</div>
Let us know
Update : based on your comment, the current setup only deals with item at postion 0 (only 1 item). The JSON (if you do not have control) and you need the item1 item2 etc.. to be displayed you can extend the ng-repeat by going over the entire object and iterate ONLY the ARRAYS. This is not going for the optimal solution just a work around.. sort of..
If there is another array of links inside the array.. this solution will not work.
html
<div class="sub-menu" ng-animate="'animate'" >
<div ng-repeat='(k,v) in item track by $index'>
<div ng-if="isArray(v)">
<md-menu-item ng-if="menuIsOpen===item.pos" ng-repeat='v1 in v track by $index'>
<md-button>
<div layout="row" flex="">
<a ui-sref="{{v1.linkto}}">
<p flex=""><i class="fa fa-{{v1.icon}}"></i> {{v1.title}}</p>
</a>
</div>
</md-button>
</md-menu-item>
</div>
</div>
</div>
script
$scope.isArray = function(val) {
return Array.isArray(val);
}

Related

preventing ng-click event with angular-material

I have a series of toggles inside a custom angular dropdown(s). The problem I am having is, the toggles do toggle, but they close the dropdown as well. How can I prevent this? The dropdowns should still close when double clicking the drawer, that is the expected behavior, but not when the toggle is clicked.
This is the HTML:
<div class="cnt" ng-repeat="item in data">
<div class="menu-item" ng-click="toggle(item.pos);">
<md-list layout="row" layout-padding="" class="layout-row" layout-align="start center" flex>
<span class="title flex" flex=""> {{item.name}}</span>
<i class="fa fa-chevron-down" ng-class="{'rotate180': item.pos==menuIsOpen, 'rotate-back': !menuIsOpen}"></i>
</md-list>
<div class="sub-menu" ng-animate="'animate'" >
<div ng-repeat='(k,v) in item track by $index'>
<div ng-if="isArray(v)">
<md-menu-item ng-if="menuIsOpen===item.pos" ng-repeat='v1 in v track by $index'>
<md-button>
<div layout="row" flex="">
<md-switch class="md-primary" name="special" ng-model="project.special" required>
<span class="">
<p flex="">{{v1.title}}</p>
</span>
</md-switch>
</div>
</md-button>
</md-menu-item>
</div>
</div>
</div>
</div>
</div>
and the controller has this in it for functionality purposes and data population from json :
$scope.toggle = function(itemPos) {
if ($scope.menuIsOpen === itemPos) {
$scope.menuIsOpen = 0;
}
else {
$scope.menuIsOpen = itemPos;
}
}
$scope.isArray = function(val) {
return Array.isArray(val);
}
spoiler alert:adding md-prevent-menu-close="true" to the md-button didn't help
Here is a CODEPEN to try things out
Thanks guys
You should move your ng-click="toggle(item.pos);" into the <md-list>
<div class="cnt" ng-repeat="item in data">
<div class="menu-item">
<md-list layout="row" layout-padding="" class="layout-row" layout-align="start center" flex ng-click="toggle(item.pos);">
<span class="title flex" flex=""> {{item.name}}</span>
<i class="fa fa-chevron-down" ng-class="{'rotate180': item.pos==menuIsOpen, 'rotate-back': !menuIsOpen}"></i>
</md-list>
<div class="sub-menu" ng-animate="'animate'" >
<div ng-repeat='(k,v) in item track by $index'>
<div ng-if="isArray(v)">
<md-menu-item ng-if="menuIsOpen===item.pos" ng-repeat='v1 in v track by $index'>
<md-button>
<div layout="row" flex="">
<md-switch class="md-primary" name="special" ng-model="project.special" required>
<span class="">
<p flex="">{{v1.title}}</p>
</span>
</md-switch>
</div>
</md-button>
</md-menu-item>
</div>
</div>
</div>
</div>
</div>

open one dropdown at a time while using ngHide/ngShow angular directives

I have a simple dropdown menu that I want to tweak a little. I need help with animating the dropdown so it's scaling down when it opens. But that is not my biggest concern. What I really need help with is opening only one dropdown at a time. So if I open one and another one had already been previously opened, it should close.
I'm using Angular-Material, so I am looking for an Angular Controller or directive solution please, one that uses ngHide/ngShow directives perhaps? Not sure about that part.
This is my HTML
<md-list ng-click="menuIsOpen = !menuIsOpen" layout="row" layout-padding="" class="layout-row" layout-align="start center" flex>
<span class="title flex" flex=""> Menu Item</span>
<i class="fa fa-chevron-down"></i>
</md-list>
<div class="sub-menu">
<ul ng-init="menuIsOpen= false" ng-show="menuIsOpen">
<md-menu-item ng-repeat="item in data">
<md-button>
<div layout="row" flex="">
<a ui-sref="{{item.link}}">
<p flex=""><i class="fa fa-{{item.icon}}"></i> {{item.title}}</p>
</a>
</div>
</md-button>
</md-menu-item>
</ul>
</div>
<md-list ng-click="menu2IsOpen = !menu2IsOpen" layout="row" layout-padding="" class="layout-row" layout-align="start center" flex>
<span class="title flex" flex=""> Menu Item 2</span>
<i class="fa fa-chevron-down"></i>
</md-list>
<div class="sub-menu">
<ul ng-init="menu2IsOpen= false" ng-show="menu2IsOpen">
<md-menu-item ng-repeat="item in data">
<md-button>
<div layout="row" flex="">
<a ui-sref="{{item.link}}">
<p flex=""><i class="fa fa-{{item.icon}}"></i> {{item.title}}</p>
</a>
</div>
</md-button>
</md-menu-item>
</ul>
</div>
I have a CODEPEN you can checkout. Thanks a lot!
In ng-click, set variable menuIsOpen to ng-click="menuIsOpen = 1" and as ng-show="menuIsOpen === 1 " Do the same thing with other menus.
Example
var app = angular.module("app", []);
app.controller("ctrl", function($scope) {
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<button ng-click="menuIsOpen = 1">About Page</button>
<button ng-click="menuIsOpen = 2">Help page</button>
<button ng-click="menuIsOpen = 3">Info Page</button>
<button ng-click="menuIsOpen = 4">Refrence page</button>
<div class="form-group" ng-show="menuIsOpen===1">
<p>About page</p>
</div>
<div class="form-group" ng-show="menuIsOpen===2">
<p>Help page</p>
</div>
<div class="form-group" ng-show="menuIsOpen===3">
<p>Info</p>
</div>
<div class="form-group" ng-show="menuIsOpen===4">
<p>Refrence</p>
</div>
</div>
<div ng-controller="ListBottomSheetCtrl" class="md-padding bottomSheetdemoBasicUsage" ng-cloak="" ng-app="MyApp">
<div class="cnt">
<md-list ng-click="menuIsOpen = 1" layout="row" layout-padding="" class="layout-row" layout-align="start center" flex>
<span class="title flex" flex=""> Menu Item</span>
<i class="fa fa-chevron-down"></i>
</md-list>
<div class="sub-menu">
<ul ng-init="menuIsOpen= false" ng-show="menuIsOpen === 1 ">
<md-menu-item ng-repeat="item in data">
<md-button>
<div layout="row" flex="">
<a ui-sref="{{item.link}}">
<p flex=""><i class="fa fa-{{item.icon}}"></i> {{item.title}}</p>
</a>
</div>
</md-button>
</md-menu-item>
</ul>
</div>
<md-list ng-click="menuIsOpen = 2" layout="row" layout-padding="" class="layout-row" layout-align="start center" flex>
<span class="title flex" flex=""> Menu Item 2</span>
<i class="fa fa-chevron-down"></i>
</md-list>
<div class="sub-menu">
<ul ng-init="menu2IsOpen= false" ng-show="menuIsOpen === 2 ">
<md-menu-item ng-repeat="item in data">
<md-button>
<div layout="row" flex="">
<a ui-sref="{{item.link}}">
<p flex=""><i class="fa fa-{{item.icon}}"></i> {{item.title}}</p>
</a>
</div>
</md-button>
</md-menu-item>
</ul>
</div>
</div>
</div>
I solved try following:
<div ng-controller="ListBottomSheetCtrl" class="md-padding bottomSheetdemoBasicUsage" ng-cloak="" ng-app="MyApp">
<div class="cnt" ng-init="menuIsOpen= false; menu2IsOpen= false">
<md-list ng-click="menuIsOpen = !menuIsOpen; menu2IsOpen = false" layout="row" layout-padding="" class="layout-row" layout-align="start center" flex>
<span class="title flex" flex=""> Menu Item</span>
<i class="fa fa-chevron-down"></i>
</md-list>
<div class="sub-menu">
<ul ng-show="menuIsOpen">
<md-menu-item ng-repeat="item in data">
<md-button>
<div layout="row" flex="">
<a ui-sref="{{item.link}}">
<p flex=""><i class="fa fa-{{item.icon}}"></i> {{item.title}}</p>
</a>
</div>
</md-button>
</md-menu-item>
</ul>
</div>
<md-list ng-click="menu2IsOpen = !menu2IsOpen; menuIsOpen = false" layout="row" layout-padding="" class="layout-row" layout-align="start center" flex>
<span class="title flex" flex=""> Menu Item 2</span>
<i class="fa fa-chevron-down"></i>
</md-list>
<div class="sub-menu">
<ul ng-show="menu2IsOpen">
<md-menu-item ng-repeat="item in data">
<md-button>
<div layout="row" flex="">
<a ui-sref="{{item.link}}">
<p flex=""><i class="fa fa-{{item.icon}}"></i> {{item.title}}</p>
</a>
</div>
</md-button>
</md-menu-item>
</ul>
</div>
</div>
Here is CODEPEN

How to use ng-show and ng-hide in ng-repeat loop

Here i have the code:
<ul dnd-list="list" ng-controller="Modalcontrol">
<li ng-repeat="item in list" ng-click="showAdvanced($event)">
{{item.card_name}}
</li>
</ul>
<span ng-click="$parent.selectedIndex=$index">Add </span>
<md-card class="card" ng-show="$parent.selectedIndex == $index" >
<form name="colorForm" class="addcontdrag" >
<md-input-container>
<label>Add text</label>
</md-input-container>
<span>
<md-button class="md-raised md-primary">Save</md-button>
<i class="material-icons" ng-click="$parent.selectedIndex=$index">clear</i>
</span>
</form>
</md-card>
In this code i want to hide the md-card when i click the close button. Show the md-card by using $parent.selectedIndex=$index. But i dont know how to hide the md-card using this one.
If anyone know please help me.
Thanks Advanced...
You will have to place your md-card inside li tag to get the $index.
<ul dnd-list="list" ng-controller="Modalcontrol">
<li ng-repeat="item in list" ng-click="showAdvanced($event)">
{{item.card_name}}
<span ng-click="$parent.selectedIndex=$index">Add </span>
<md-card class="card" ng-show="$parent.selectedIndex == $index" >
<form name="colorForm" class="addcontdrag" >
<md-input-container>
<label>Add text</label>
</md-input-container>
<span>
<md-button class="md-raised md-primary">Save</md-button>
<i class="material-icons" ng-click="$parent.selectedIndex=$index">clear</i>
</span>
</form>
</md-card>
</li>
</ul>

change variable only for one ng-repeat document angularjs

I'm trying to show more text when an user clicks a read more button:
<md-content class="md-padding" layout-xs="column" layout="row" layout-wrap>
<div flex-xs flex-gt-xs="50" layout="column" ng-repeat="paquete in paquetes">
<md-card>
<md-card-title layout="row" layout-xs="column">
<md-card-title-media layout-margin>
<img ng-src="{{paquete.img}}" class="md-card-image margin-auto" alt="image caption">
</md-card-title-media>
<md-card-title-text>
<span class="md-headline blue">{{paquete.title}}</span>
<span class="md-subhead dark-blue">{{paquete.desc | limitTo: limit.limit}}
<span class="threedots" ng-show="mas.mas">... </span>
<span style="cursor: pointer;" class="blue underlined"
ng-click="limitFunction(paquete.desc)">Leer
<span class="mas" ng-show="mas.mas">más</span>
<span class="menos" ng-show="menos.menos">menos</span>.
</span>
</span>
</md-card-title-text>
</md-card-title>
</md-card>
</div>
</md-content>
I'm limiting the initial descripction {{paquete.desc | limitTo: limit.limit}} and letting the user click to read more ng-click="limitFunction(paquete.desc)"
My controller:
$scope.limit = {limit: 200};
$scope.limitFunction = function (desc) {
$scope.limit.limit = desc.length;
};
But when I click on one of the ng-repeat elements, the $scope.limit changes to all of them instead of the one I'm clicking.
How do I change the $scope.limit to only the one that I'm clicking?
You could put the limit on the paquete object and pass that into your limit function
<span class="md-subhead dark-blue">{{paquete.desc | limitTo: paquete.limit}}<span class="threedots" ng-show="mas.mas">... </span><span style="cursor: pointer;" class="blue underlined" ng-click="limitFunction(paquete)">Leer <span class="mas" ng-show="mas.mas">más</span><span class="menos" ng-show="menos.menos">menos</span>.</span></span>
In the controller:
$scope.limitFunction = function (paquete) {
paquete.limit = paquete.desc.length;
};

md-menu inside md-list-item as a second action button

I have got the code below in my project and I have got this problem. When I will add the md-menu component into my dynamically generated (ng-repeat) md-list component, it will show this error in JS console:
Error: Invalid HTML for md-menu: Expected two children elements.
My HTML code:
<md-card>
<md-card-content>
<h2>Menu</h2>
<md-subheader class="md-no-sticky">List</md-subheader>
<md-list-item ng-repeat="playlist in playlists" ng-click="someAction()">
<p>{{playlist[1]}}</p>
<md-menu>
<md-icon aria-label="Action" ng-click="$mdOpenMenu($event)" class="md-secondary md-hue-3 material-icons">create</md-icon>
<md-menu-content>
<md-menu-item><md-button ng-click="">Edit</md-button></md-menu-item>
<md-menu-item><md-button ng-click="">Remove</md-button></md-menu-item>
</md-menu-content>
</md-menu>
</md-list-item>
</md-card-content>
</md-card>
Can you help me to solve this issue please?
I think, that the problem is when the code is builded, it looks different and then md-menu component has got 2 child inside (2 button), but I don't know, how to resolve this.
Here is the builded code:
<md-list-item class="md-with-secondary ng-scope md-clickable" tabindex="-1" role="listitem" ng-repeat="playlist in playlists">
<button tabindex="0" ng-click="someAction()" class="md-no-style md-button md-ink-ripple" ng-transclude="">
<div class="md-list-item-inner ng-scope">
<p class="ng-binding">test</p>
<md-menu class="md-menu ng-scope">
<md-menu-content>
<md-menu-item role="menuitem">
<button tabindex="0" type="button" ng-click="" class="md-button md-ink-ripple" ng-transclude="">
<span class="ng-scope">Edit</span>
</button>
</md-menu-item>
<md-menu-item role="menuitem">
<button tabindex="0" ng-click="" class="md-button md-ink-ripple" ng-transclude="">
<span class="ng-scope">Remove</span>
</button>
</md-menu-item>
</md-menu-content>
</md-menu>
</div>
<div style="" class="md-ripple-container"></div>
<div style="" class="md-ripple-container"></div>
</button>
<button tabindex="0" ng-click="$mdOpenMenu($event)" class="md-secondary-container md-icon-button md-button md-ink-ripple" ng-transclude="">
<md-icon tabindex="-1" aria-label="Open Chat" class="md-hue-3 material-icons ng-scope ng-isolate-scope">create</md-icon>
</button>
</md-list-item>
I also couldn't use md-menu inside md-list-item as a second action button when I was using Angular Material 1.0.0 RC1 but when I upgraded both js and css of Angular Material to 1.1.0 RC4 it worked. The working code is something like this and please notice the "md-secondary" class needs to be assigned to "md-menu":
<md-list flex>
<md-subheader class="md-no-sticky">sub header</md-subheader>
<md-list-item ng-click="goToPerson(person.name, $event)" class="md-2-line" ng-repeat="user in userManagement.users">
<img alt="{{ 'person.name' }}" ng-src="https://pixabay.com/static/uploads/photo/2014/03/25/16/54/user-297566_960_720.png" class="md-avatar"/>
<div class="md-list-item-text">
<h3>{{ user.firstName }} {{ user.lastName }} </h3>
<p>{{ user.email }} </p>
</div>
<md-menu md-position-mode="target-right target" class="md-secondary">
<md-button aria-label="Open demo menu" class="md-icon-button" ng-click="$mdOpenMenu($event)">
<md-icon md-menu-origin>menu</md-icon>
</md-button>
<md-menu-content width="4">
<md-menu-item ng-repeat="item in [1, 2, 3]">
<md-button ng-click="ctrl.announceClick($index)">
<div layout="row" flex>
<p flex>Option {{item}}</p>
<md-icon md-menu-align-target md-svg-icon="call:portable-wifi-off" style="margin: auto 3px auto 0;"></md-icon>
</div>
</md-button>
</md-menu-item>
</md-menu-content>
</md-menu>
</md-list-item>
</md-list>
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.
https://material.angularjs.org/latest/api/directive/mdMenu
I think your ng-click="$mdOpenMenu($event)" needs to be on a md-button element, not the md-icon
go to source code of angular-material.js and add/modify that part... I will generate menu as secondary-item outside the item.
// Check for a secondary item and move it outside
if ( secondaryItem && (
isMdMenu(secondaryItem) ||
secondaryItem.hasAttribute('ng-click') ||
( tAttrs.ngClick &&
isProxiedElement(secondaryItem) )
)) {
tEl.addClass('md-with-secondary');
tEl.append(secondaryItem);
}
function isMdMenu(el) {
var nodeName = el.nodeName.toUpperCase();
return nodeName == "MD-MENU";
}

Resources