Angular - Make nested list from JSON - javascript

I have a set of JSON data that I would like to display in a nested list:
The JSON comes in the following format:
["Item 1", "Item 2", "Item 3", ["Nested Item 1", "Nested Item 2"]]
The html should be:
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>
<ul>
<li>Nested Item 1</li>
<li>Nested Item 2</li>
</ul>
</li>
</ul>
I don't have control of the JSON, and it may be deeper than 2 levels.
Of course, I tried this:
<ul>
<li ng-repeat="item in data">{{item}}</li>
</ul>
but it doesn't work because for nested items, it simply displays the json.
How can I achieve nested lists in AngularJs?
Fiddle: http://jsfiddle.net/m7ax7tsa/

Have a look at this nice blog post that has a complete example at the end. In short you need to build nested directives, where inner directive will have recursive call to outer directive:
<body>
<div ng-controller="IndexCtrl">
<collection collection='tasks'></collection>
</div>
</body>
angular.module('APP', [])
.directive('collection', function () {
return {
...
template: "<ul><member ng-repeat='member in collection' member='member'></member></ul>"
}
})
.directive('member', function ($compile) {
return {
...
template: "<li>{{member.name}}</li>",
link: function (scope, element, attrs) {
if (angular.isArray(scope.member.children)) {
element.append("<collection collection='member.children'></collection>");
$compile(element.contents())(scope)
}
}
}

I found a solution thanks to zsong and Blackhole.
Resulting HTML:
<div ng-app="testApp">
<div ng-controller="TestCtrl">
<script type="text/ng-template" id="/nestedList.html">
<ul>
<li ng-repeat="item in data">
<div ng-switch="isString( item )">
<div ng-switch-when="true">{{item}}</div>
<div ng-switch-when="false">
<!-- Recursive template!! -->
<div ng-include="'/nestedList.html'" ng-init="data = item">
</div>
</div>
</div>
</li>
</ul>
</script>
<div ng-include="'/nestedList.html'"></div>
</div>
</div>
I used a recursive template which included itself. It borrows heavily on the answer of Unknown number of sublists with AngularJS. In addition, I had to add the following code to the controller:
$scope.isString = function (item) {
return typeof item === "string";
};

Related

How to get menus and submenus data from an array?

I am creating a webpage using smart admin.I am getting left menu datas from an array.I need display menus and sub menus properly based on parent Id of each data using angular. But I don't know how to do it.Can anyone help me?please.
Script:
var app = angular.module('myApp', []);
app.controller('myController', ['$scope', '$http', function ($scope, $http) {
$scope.Menus = [];
$http.get('/list/GetSiteMenu').then(function (data) {
$scope.Menus = data.data.data.record;
}, function (error) {
alert('Error');
});
}]);
Html:
<nav ng-repeat="menuData in Menus">
<ul>
<li>
<ul>
<li><a></a></li>
</ul>
</li>
</ul>
</nav>
console.log($scope.Menus) will be in this format:
Need to iterate over menuData.menu_roles
<nav ng-repeat="menuData in Menus">
<ul>
<li>
<ul>
<li ng-repeat ="subMenu in menuData.menu_roles">
<a></a>
</li>
</ul>
</li>
</ul>
</nav>
You can use something like this according to your json
Use rootscope instead of scope to store Menus
<section class="sidebar">
<!-- sidebar menu: : style can be found in sidebar.less -->
<ul class="sidebar-menu">
<li class="{{menuItem.LiCssClass}}" ng-repeat="menuItem in $root.menuList" ng-class="{active:isActive('{{menuItem.NavigationURL}}')}">
<a ng-href="{{menuItem.NavigationURL}}" ng-click="sidebar()">
<i class="{{menuItem.ICssClass}}"></i>
<span class="{{menuItem.SpanCssClass}}"> {{menuItem.DisplayName}}</span>
<i class="{{menuItem.TreeViewIcon}}"></i>
</a>
<ul class="{{menuItem.UiCssClass}}">
<li id="{{subMenuItem.TagName}}" ng-repeat="subMenuItem in menuItem.SubMenu" ng-class="{active:isActive('{{subMenuItem.NavigationURL}}')}">
<a ng-href="{{subMenuItem.NavigationURL}}">
<i class="{{subMenuItem.ICssClass}}"></i><span>{{subMenuItem.DisplayName}}</span>
</a>
</li>
</ul>
</li>
</ul>
</section>
You can use ng-bootstrap-submenu
https://www.npmjs.com/package/ng-bootstrap-submenu
It's a module for add submenus items to parent menu items and it's easy to use

Filter matched elements by class name in Angular js.

Problem:
I have an unordered list of items which are returned from a json call and are output using ng-repeat. Each one of these items has a class name (there are about 9 categories).
I have a second unordered list which is simply a list of available categories.
Aim:
I want to be able to select one of the categories in the right hand list, which will apply a filter to the actual list of returned elements. This should be activated via a toggle (so click once: filtered, click again: filter removed). So it is simply looking to match the classname in the clicked element, to the elements that share the same classname in the list of json data.
I cannot use ng-model (as this is reserved for certain form elements).
For my jsfiddle I am simply using static html.
Here is my angular code:
/* angular custom filter on returned ajax api data */
var app = angular.module('app', []);
app.controller('main', function($scope) {
$scope.chFilters = {};
$scope.links = [
{name: 'atm'},
{name: 'internet'},
{name: 'mobile'},
{name: 'sms'},
{name: 'postal'}
];
$scope.channels = ["ATM", "INTERNET", "SMS", "POSTAL","MOBILE"];
});
(this is based on another question I found on SO). Unfortunately the fiddle is a bit messy and has some extraneous code in it.
HTML:
<div ng-app="app">
<div ng-controller="main">
<ul>
<li class="atm">Some stuff ATM</li>
<li class="internet">Some stuff INTERNET</li>
<li class="sms">Some stuff ATM</li>
<li class="atm">Some stuff ATM</li>
<li class="postal">Some stuff POSTAL</li>
<li class="atm">Some stuff ATM</li>
<li class="internet">Some stuff INTERNET</li>
<li class="postal">Some stuff POSTAL</li>
<li class="postal">Some stuff POSTAL</li>
<li class="atm">Some stuff ATM</li>
<li class="sms">Some stuff SMS</li>
<li class="mobile">Some stuff MOBILE</li>
<li class="internet">Some stuff INTERNET</li>
<li class="mobile">Some stuff MOBILE</li>
</ul>
<ul class="channel-filters">
<li ng-repeat="link in links | filter:chFilters" class="{{link.name | lowercase}}"><a ng-click="chFilters.name = link.name">{{link.name | uppercase}}</a></li>
<li class="last" ng-click="chFilters.name = ''">CLEAR FILTER</li>
</ul>
<ul>
<li ng-repeat="channel in channels | filter:chFilters">
<strong>{{channel}}</strong>
<a ng-click="chFilters = channel">{{channel}}</a>
</li>
</ul>
<!-- original -->
<ul>
<li ng-repeat="link in links | filter:chFilters">
<strong>{{link.name}}</strong>
<a ng-click="chFilters.name = link.name">{{link.name}}</a>
</li>
</ul>
</div>
</div>
This is the actual HTML from the application (with the call to the api).
<ul class="accordion">
<li class="search-text-channel">
<input type="textarea" ng-model="searchTextChannel.$" placeholder="Search"/>
</li>
<li ng-repeat="day in interactions.model.byDay | filter:searchTextChannel" ng-click="hidden = !hidden" ng-init="hidden = false" class="{{day.channel | removeSpace | lowercase}}" ng-class="{'closed': !hidden, 'open': hidden}">
<span class="date">{{day.date}}</span>
<span class="subheading">{{day.channel}}</span>
<ul ng-show="hidden">
<li ng-repeat="interaction in day.interactions">
{{interaction.time}} {{interaction.description | removeUnderscore}}
</li>
</ul>
</li>
<li class="load-more">
<i class="fa fa-plus"></i>LOAD MORE
</li>
</ul>
I have managed to recreate this functionality in jquery, but I think it would be better to implement an angular solution in an angular application.
I've tried researching and also attempted to implement show/hide as well as a custom filter, but so far no joy.
Here is my (messy) jsfiddle
<ul>
<li ng-repeat="channel in channels | filter:chFilters.name">
<strong>{{channel}}</strong>
<a ng-click="chFilters = channel">{{channel}}</a>
</li>
</ul>
<!-- original -->
<ul>
<li ng-repeat="link in links | filter:chFilters.name">
<strong>{{link.name}}</strong>
<a ng-click="chFilters.name = link.name">{{link.name}}</a>
</li>
</ul>
Update Plunker
Let me know if you have any question on this.
Here is part of the solution:
Suppose your items look like this:
$scope.items = [
{ class: "atm", label: "Some stuff ATM" },
{ class: "internet", label: "Some stuff INTERNET" },
{ class: "sms", label: "Some stuff SMS" },
{ class: "postal", label: "Some stuff POSTAL" },
...
];
To show a filtered list (only filtering by a single channel for now): create a separate list in the scope, with the filters applied:
$scope.click = function(name) {
$scope.chFilters.name = name;
$scope.filteredItems = $scope.items.filter(function(item) {
return item.class === $scope.chFilters.name;
});
};
Call this click handler from the bottom list:
...<a ng-click="click(link.name)">{{link.name | uppercase}}</a>....
And show filteredItems in the top list:
<ul>
<li ng-repeat="item in filteredItems" ng-class="item.class">{{item.label}}</li>
</ul>
So this is really just a starting point, it should be extended to handle multiple filters, etc...

How to use a control statement (if statement) in a nested ng-repeat directive?

I have the following code in my HTML View:
<div ng-repeat='shop in shops'>
<ul ng-repeat='item in items'>
<li>
<!-- {{Show items of the particular shop in here}} -->
</li>
</ul>
</div>
The items is a JSON Object. Every item has a shop_id that has to be used to do the sorting.
According to the documentation, I understand that I cannot use the if control statement in an Angular Expression to achieve what I the sort. How else can I use the control statement.
I tried this, but it obviously failed because we cannot return a continue.
//var resources.items and resources.shops are JSON objects output from a database
//This file is app.js
$scope.itemName = function(itemIndex, shopIndex) {
for (var i = 0; i < resources.items.length; i++) {
if (resources.items[itemIndex].shop_id == resources.shops[shopIndex].id)
return resources.items[itemIndex].name;
};
return continue;
}
And, this in the view:
<div class="tab-content" ng-repeat='provider in providers'>
<ul ng-repeat='item in items'>
<li>{{itemName($index, $parent)}}</li>
</ul>
</div>
I'm a beginner in Angular. Please help out.
Use the ngIf directive:
<div ng-repeat='shop in shops'>
<ul ng-repeat='item in items'>
<li ng-if="item.shop_id === shop.id">
<!-- {{Show items of the particular shop in here}} -->
</li>
</ul>
</div>

How to stack <li> into divs using ng-repeat

I am using ng-repeat to iterate over an array to generate <li>. But i want to stack every 2 li into div.
<li ng-repeat="item in items">{{item.name}}</li>
I want the output to be like this:
<ul>
<div>
<li>A</li>
<li>B</li>
</div>
<div>
<li>C</li>
<li>D</li>
</div>
</ul>
How should i achieve this with ng-repeat?
Thanks in advance for any help.
Although it is invalid to put div inside ul but to illustrate. i have made a function to split your array into group of 2 element array contained in wrapper array
Or You can also use splice method
Working Demo 1
angular.module('testApp',[]).controller('testCtrl', function($scope) {
$scope.items=['1','2','3'];
$scope.groupByTwo = function (array)
{
var newarray =[];
index=0;
for(a=0;a<array.length;a++){
if(a==0 || a%2==0){
newarray[index]=[];
newarray[index].push(array[a]);
}else{
newarray[index].push(array[a]);
}
if(a!=0)
index++;
}
return newarray;
}
$scope.items=$scope.groupByTwo($scope.items);
});
div {
background:pink;
border:1px dotted black;
margin-top:15px;}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="testApp" ng-controller="testCtrl">
<ul>
<div ng-repeat="item in items">
<li ng-repeat="i in item">{{i}}</li>
</div>
</ul>
Working Demo 2
You can also use splice method
angular.module('testApp',[]).controller('testCtrl', function($scope) {
$scope.items=['1','2','3'];
var i,j,temparray=[],chunk = 2;
for (index=0,i=0,j=$scope.items.length; i<j; i+=chunk,index++) {
temparray [index]= $scope.items.slice(i,i+chunk);
}
$scope.items =temparray;
});
div {
background:pink;
border:1px dotted black;
margin-top:15px;}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="testApp" ng-controller="testCtrl">
<ul>
<div ng-repeat="item in items">
<li ng-repeat="i in item">{{i}}</li>
</div>
</ul>
Apply ng-repeat on ul and li will act as a template which will be generated for each item.
Ex :
<ul ng-repeat="item in items">
<li>{{item.name}}</li>
</ul>
This will result in
<ul>
<li>name1</li>
<li>name2</li>
<li>name2</li>
</ul>
If you have a collection consisting of 10 items , you can split them in multiple collections each of size 2 and then apply ng-repeat with each collection on div to get the output you want.

Angular.js more complex conditional loops

The goal is to create this
<h3>11.4.2013</h3>
<ul>
<li>entry 1</li>
<li>entry 2</li>
<li>entry 3</li>
</ul>
<h3>10.4.2013</h3>
<ul>
<li>entry 4</li>
<li>entry 5</li>
<li>entry 6</li>
</ul>
from this
[
{
"name": "entry1",
"date": "11.4.2013"
},
{
"name": "entry2",
"date": "11.4.2013"
},
{
"name": "entry3",
"date": "11.4.2013"
},
{
"name": "entry4",
"date": "10.4.2013"
},
{
"name": "entry5",
"date": "10.4.2013"
},
{
"name": "entry6",
"date": "10.4.2013"
}
]
The problem is that ng-repeat would have to be on li so I wouldn't never be able to do this using ng-repeat, is that right? I found this http://jsfiddle.net/mrajcok/CvKNc/ example from Mark Rajnoc, but it's still pretty limiting..
What other choices do I have? Write my own ng-repeat like directive? Or is there another way to do it without writting one?
You could write your own filter that filters out the unique dates for an outer ng-repeat, something like:
filter('unique',function(){
return function(items,field){
var ret = [], found={};
for(var i in items){
var item = items[i][field];
if(!found[item]){
found[item]=true;
ret.push(items[i]);
}
}
return ret;
}
});
with the following markup:
<div ng-repeat="dateItem in items | unique:'date' | orderBy:'date'">
<h3>{{dateItem.date}}</h3>
<ul>
<li ng-repeat="item in items | filter:dateItem.date">
{{item.name}}
</li>
</ul>
</div>
Have a look at this working plnkr example -- or this updated example with adding items
However, if your going to have a lot of items (hundreds or thousands) this solution is not the most optimal. An alternative approach would be to create a more optimal data structure. You can even get this to work with your original data structure by adding a $watch - something like:
$scope.$watch('items',function(){
var itemDateMap = {};
for(var i=0; i<$scope.items.length; i++){
var item = $scope.items[i], key = item.date;
if(!itemDateMap[key]){
itemDateMap[key] = [];
}
itemDateMap[key].push(item);
}
$scope.itemDateMap=itemDateMap;
},true);
Works with this markup:
<div ng-repeat="(date,subItems) in itemDateMap">
<h3>{{date}}</h3>
<ul>
<li ng-repeat="item in subItems">
{{item.name}}
</li>
</ul>
</div>
Here is an example where you can add lots of random items.
When I have same needs like yours, I used Object instead of Array.
<div ng-repeat="item in data">
<h1>{{item.date}}</h1>
<ul ng-repeat="name in item.names">
<li>{{name}}</li>
</ul>
</div>
http://jsfiddle.net/shoma/DqDsE/1/
This question page would help you.
What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

Categories