How to render an arbitrarily deep nested list? - javascript

Suppose that I have the following JSON array:
tree = [{
name: "A",
children: [{
name: "AA",
children: []
}, {
name: "AB",
children: []
}]
}, {
name: "B",
children: [{
name: "BA",
children: []
}, {
name: "BB",
children: []
}]
}]
Moreover, suppose that I want to construct the following HTML:
<ul>
<li>
<label>A</label>
<ul>
<li>
<label>AA</label>
<ul></ul>
</li>
<li>
<label>AB</label>
<ul></ul>
</li>
</ul>
</li>
<li>
<label>B</label>
<ul>
<li>
<label>BA</label>
<ul></ul>
</li>
<li>
<label>BB</label>
<ul></ul>
</li>
</ul>
</li>
</ul>
I can achieve this using Knockout as follows:
<ul data-bind="foreach: tree">
<li>
<label data-bind="text: name"></label>
<ul data-bind="foreach: children">
<li>
<label data-bind="text: name"></label>
<ul></ul>
</li>
</ul>
</li>
</ul>
However, this does not work for arbitrarily-deep nested lists. What can I do?

Use a recursive template, e.g. like this:
var tree = {
subItems: [
{
name: "A",
subItems: [ { name:"AA", subItems: [] }, { name:"AB", subItems: [] }, { name:"AC", subItems: [] } ]
},
{
name: "B",
subItems: [ { name:"BA", subItems: [] }, { name:"BB", subItems: [{name:"BB1 (etc)", subItems: []}] } ]
}
]
};
ko.applyBindings(tree);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script type="text/html" id="myTemplate">
<ul data-bind="foreach: $data">
<li>
<label data-bind="text: name"></label>
<div data-bind="template: { name: 'myTemplate', data: subItems }"></div>
</li>
</ul>
</script>
<div data-bind="template: { name: 'myTemplate', data: $root.subItems }"></div>

Related

Target clicked element in Vue

In Vue.js how do you target/detect the clicked element to perform a basic toggle class?
I've written this which toggles successfully but when you click an a the class is applied to to all li's
HTML
<ul id="app" class="projects">
<li v-bind:class="dyClass">Project A</li>
<li v-bind:class="dyClass">Project B</li>
<li v-bind:class="dyClass">Project C</li>
<li v-bind:class="dyClass">Project D</li> </ul>
JS
new Vue({
el: '#app',
data: {
show: false,
},
computed: {
dyClass: function() {
return {
show: this.show
}
}
}
})
I'm new to Vue.
EDIT.
I managed to get it working with a mix of help from below, but I dont seem to be able to get the toggle effect.
<ul id="app" class="projects">
<li :class="{show:selected == 1}">
<a href="" #click.prevent.stop="selected = 1" >Exposure</a>
</li>
<li :class="{show:selected == 2}">
<a href="" #click.prevent.stop="selected = 2" >Exposure</a>
</li>
<li :class="{show:selected == 3}">
<a href="" #click.prevent.stop="selected = 3" >Exposure</a>
</li>
</ul>
and
new Vue({
el: '#app',
data: {
selected: false,
}
});
You can pass element to function ($event):
:click=dyClass($event) Then in computed:
dyClass: function(event) {
event.target.toggleClass('whatever')
}
As i understand you have some li items and you want to add an active class when a specific li gets clicked.
Solution:
the "html" part:
<div id="app">
<ul class="projects">
<li v-for="project in projects"
:key="project.id"
:class="{ active: project.id === activeProjectId }"
#click="activeProjectId = project.id"
>
{{ project.name }}
</li>
</ul>
</div>
The "vue" part
new Vue({
el: "#app",
data: {
projects: [
{ id: 1, name: 'Project A' },
{ id: 2, name: 'Project B' },
{ id: 3, name: 'Project C' },
{ id: 4, name: 'Project D' },
],
activeProjectId: 1
},
})
Then add some css to the 'active' class.
For more, See the fiddle

Looping through 2 different objects in AngularJS

I have the following controller:
app.controller('MainController', ['$scope', function($scope) {
$scope.taskCategories = {
categories: [
'work',
'chores',
'learning'
]
};
$scope.tasklist = {
tasks: [{
title: 'Email Gregory',
category: 'work'
}, {
title: 'Clean the Kitchen',
category: 'chores'
}, {
title: 'AngularJS',
category: 'learning'
}, {
title: 'Hose Car',
category: 'chores'
}, {
title: 'Email Jethro',
category: 'work'
}
]
};
}]);
And am pulling the information through so far like this:
<div>
<li data-toggle="collapse" data-target="#work" class="nav_head workcat collapsed">
Work <span class="arrow"></span>
</li>
<ul class="sub-menu collapse" id="work">
<li ng-repeat="tasks in tasklist.tasks | orderBy:'title' | filter: {category: 'work'}">
{{ tasks.title }}
</li>
<li class="addwork">
<a href="">
<span class="fa-stack"> <i class="fa fa-2x fa-stack-2x fa-circle"></i><i class="fa fa-2x fa-stack-2x fa-plus-circle"></i>
</a>
</span>
</li>
</ul>
This would work fine doing a few times, one for each category, but I am looking to be able to add categories dynamically, and so I am looking for some way to go through the following steps:
So, I’ll need to loop all in categories.
During that loop, I’ll loop through the tasks and print out any task that matches the string of categories.index(1)
Then add 1 to category index and run again, till category.length runs out
I'm unfamiliar with looping inside a loop, and more unfamiliar again with doing it in angular. Anyone have any suggestions?
You could do an outer loop (ng-repeat) on the categories:
<ul class="sub-menu collapse" id="work" ng-repeat="cat in taskCategories.categories">
<li ng-repeat="tasks in tasklist.tasks | orderBy:'title' | filter: {category: cat}">
{{ tasks.title }}
</li>
<li class="addwork">
<a href="">
<span class="fa-stack"> <i class="fa fa-2x fa-stack-2x fa-circle"></i><i class="fa fa-2x fa-stack-2x fa-plus-circle"></i></span>
</a>
</li>
</ul>
Fiddle
Please refer below code snippet
angular.module('app',[]);
angular.module('app').controller('myController',function($scope){
$scope.taskCategories = {
categories: [
'work',
'chores',
'learning'
]
};
$scope.tasklist = {
tasks: [{
title: 'Email Gregory',
category: 'work'
}, {
title: 'Clean the Kitchen',
category: 'chores'
}, {
title: 'AngularJS',
category: 'learning'
}, {
title: 'Hose Car',
category: 'chores'
}, {
title: 'Email Jethro',
category: 'work'
}
]
};
});
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/js/bootstrap.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<body ng-app="app" ng-controller="myController">
<ul>
<li data-toggle="collapse" data-target=#{{c}} class="nav_head workcat" ng-repeat="c in taskCategories.categories">
<span class="arrow"> {{c}}</span>
<ul class="sub-menu collapse" id={{c}}>
<li ng-repeat="tasks in tasklist.tasks | orderBy:'title' | filter: {category: c}">
{{ tasks.title }}
</li>
</ul>
</li>
</ul>
</body>
Hope this helps!

angular.js ng-repeat division

I have array of categories that has this structure:
{
name: 'something',
main_category: 'A'
}
So each category has it's main category. I would like to display all categories in html like so:
<h1>A</h1>
<ul>
<li>list of categories that has main category A</li>
</ul>
<h1>B</h1>
<ul>
<li>list of categories that has main category B</li>
</ul>
how should I achieve it? The only way I found was doing something like this:
<h1>A</h1>
<ul>
<li ng-repeat="category in categories" ng-if="category.main_category == 'A'">..</li>
</ul>
<h1>B</h1>
<ul>
<li ng-repeat="category in categories" ng-if="category.main_category == 'B'">..</li>
</ul>
It works but I don't think it's a good idea.
You should use groupBy filter provided by https://github.com/a8m/angular-filter, and do something like this
var myApp = angular.module('mcApp', ['angular.filter']);
myApp.controller('mcController', ['$scope', function ($scope) {
console.log('asadsds');
$scope.myArray = [
{
name: 'something 1',
main_category: 'B'
},
{
name: 'something 2',
main_category: 'A'
},
{
name: 'something 3',
main_category: 'A'
},
{
name: 'something 4',
main_category: 'B'
},
{
name: 'something 5',
main_category: 'B'
}
];
}]);
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-example7-production</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.5/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-filter/0.5.5/angular-filter.js"></script>
<script src="app.js"></script>
</head>
<body ng-app="mcApp" ng-controller="mcController">
<p>{{name}}</p>
<div ng-repeat="object in myArray | orderBy: 'main_category'| groupBy:'main_category' |toArray: true" >
<h1>{{object.$key}}</h1>
<ul>
<li ng-repeat="value in object">
{{value.name}}
</li>
</ul>
</div>
</body>
</html>
See this answer for how to use Angular's filter: { prop: 'value' } syntax to achieve the same functionality as an ng-if in this use-case:
<h1>A</h1>
<ul>
<li ng-repeat="category in categories | filter: {main_category: 'A'}"></li>
</ul>
<h1>B</h1>
<ul>
<li ng-repeat="category in categories | filter: {main_category: 'B'}"></li>
</ul>
You can do it by creating a second main categories array. Using ng-repeat-start and ng-repeat-end you create the basic structure (h1 + ul). In the ul a second repeater of categories filtered by the main category renders the list items (fiddle):
<div ng-app ng-controller="MyCtrl">
<h1 ng-repeat-start="mainCategory in mainCategories | orderBy">{{mainCategory}}</h1>
<!-- main categories ngRepeat starts here -->
<ul ng-repeat-end>
<!-- main categories ngRepeat ends here -->
<li ng-repeat="category in categories | orderBy:'name' | filter:{ 'main_category': mainCategory}">{{category.name}}</li>
<!-- categories are filtered by mainCategory -->
</ul>
</div>
function MyCtrl($scope) {
var categories = [{
name: 'somethingA2',
main_category: 'A'
}, {
name: 'somethingB1',
main_category: 'B'
}, {
name: 'somethingB2',
main_category: 'B'
}, {
name: 'somethingA3',
main_category: 'A'
}, {
name: 'somethingB3',
main_category: 'B'
}, {
name: 'somethingA1',
main_category: 'A'
}];
$scope.categories = categories;
/** main categories - reduce the categories array to a dictionary object, and get the keys of the dictionary to get the unique main category names **/
$scope.mainCategories = Object.keys(categories.reduce(function (main, category) {
main[category['main_category']] = '';
return main;
}, {}));
}

Angularjs: Nested repeat in angular doesn't work

Following this article
But it seemed not to work on my json structure. Here is my json
$scope.trucks = [{
id: 4,
truckNumber: '50LD 02456',
driverName: 'Dẻo',
shipments: [{
id: 1,
routeCode: "THC-VinhHao",
trip: 2
}, {
id: 2,
routeCode: "THC-VinhHao(R)",
trip: 3
}, {
id: 3,
routeCode: "THC2-Hiệp Thành HM",
trip: 3
}]
}, {
id: 5,
truckNumber: '61C 03948',
driverName: 'Hưng',
shipments: [{
id: 4,
routeCode: "TBC-VBL HMo",
trip: 1
}, {
id: 5,
routeCode: "THC2-Hiệp Thành HM",
trip: 4
}]
}];
<ul>
<li data-ng-repeat="truck in trucks">
{{truck.truckNumber}}
<br />
<ul>
<li data-np-repeat="shipment in truck.shipments">{{shipment.routeCode}}</li>
</ul>
</li>
</ul>
Any help are appreciated. Thanks for reading.
Please make sure you have everything correctly spelled out. ng-repeat was misspelled.
<ul>
<li data-ng-repeat="truck in trucks">
{{truck.truckNumber}}
<br/>
<ul>
<li data-ng-repeat="shipment in truck.shipments">{{shipment.routeCode}}</li>
</ul>
</li>
</ul>
Change np-repeat to ng-repeat then it will work.
<ul>
<li data-ng-repeat="truck in trucks track by $index">
{{truck.truckNumber}}
<br />
<ul>
<li data-ng-repeat="shipment in truck.shipments track by $index">{{shipment.routeCode}}</li>
</ul>
</li>
</ul>
<ul>
<li data-ng-repeat="truck in trucks">
{{truck.truckNumber}}
<br />
<ul>
<li data-np-repeat="shipment in truck.shipments">{{shipment.routeCode}}</li>
</ul>
</li>
</ul>
There is a typo.
<li data-ng-repeat="shipment in truck.shipments">{{shipment.routeCode}}</li>

Toggle active class on item with angular.js

How can I toggle active class on element if I have the following code?
<div class="searchandfilter">
<span ng-repeat="taxonomy in taxonomies" class="tab-controller" ng-class="{'active': $index == 0}">
<ul>
<li class="tab" ng-click="onClickTab(taxonomy)">{{taxonomy.name}}</li>
</ul>
<span class="tab-content">
<span ng-repeat="child in taxonomy.children">
<input type="checkbox" checked="child.value" />{{child.name}}
</span>
</span>
</span>
On load I need to set the first tab-conroller span to active, which is now correct but I can't toggle the active class onclick. The number of tabs is dynamic, so it can be 1 or 10. Currently it generates the tabs like this:
<span class="tab-controller ng-scope active" ng-class="{'active': $index == 0}" ng-repeat="taxonomy in taxonomies">
<ul>
<li class="tab ng-binding" ng-click="onClickTab(taxonomy)">Tab 1</li>
</ul>
</span>
<span class="tab-controller ng-scope" ng-class="{'active': $index == 0}" ng-repeat="taxonomy in taxonomies">
<ul>
<li class="tab ng-binding" ng-click="onClickTab(taxonomy)">Tab2</li>
</ul>
</span>
You can do this by passing the index and setting a scope variable.
var app = angular.module('app', []);
app.controller('myController', function($scope) {
$scope.taxonomies = [
{name: 'a', children: [{name: 'a', value: false}]},
{name: 'b', children: [{name: 'a', value: true},
{name: 'b', value: false}]}
];
$scope.onClickTab = function(idx) {
$scope.selectedIndex = idx;
};
$scope.selectedIndex = 0;
});
.active {
color: green;
font-weight: bold;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<div ng-app='app' ng-controller='myController'>
<div class="searchandfilter">
<span ng-repeat="taxonomy in taxonomies" class="tab-controller" ng-class="{'active': $index == selectedIndex}">
<ul>
<li class="tab" ng-click="onClickTab($index)">{{taxonomy.name}}</li>
</ul>
<span class="tab-content">
<span ng-repeat="child in taxonomy.children">
<input type="checkbox" ng-model="child.value">{{child.name}}
</span>
</span>
</span>
</div>
</div>
The problem with that approach is if the taxonomies change, the selectedIndex may no longer match up with selected tab so instead of tracking index, you can just track the entire object:
var app = angular.module('app', []);
app.controller('myController', function($scope) {
$scope.taxonomies = [
{name: 'a', children: [{name: 'a', value: false}]},
{name: 'b', children: [{name: 'a', value: true},
{name: 'b', value: false}]}
];
$scope.onClickTab = function(selected) {
$scope.selected = selected;
};
$scope.selected = $scope.taxonomies[0];
});
.active {
color: green;
font-weight: bold;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<div ng-app='app' ng-controller='myController'>
<div class="searchandfilter">
<span ng-repeat="taxonomy in taxonomies" class="tab-controller" ng-class="{'active': taxonomy === selected}">
<ul>
<li class="tab" ng-click="onClickTab(taxonomy)">{{taxonomy.name}}</li>
</ul>
<span class="tab-content">
<span ng-repeat="child in taxonomy.children">
<input type="checkbox" ng-model="child.value">{{child.name}}
</span>
</span>
</span>
</div>
</div>
You need to have some property in your tabs to be able to compare against the $index in the ng-repeat.
For example:
<div class="tab-controller" ng-class="{'active': $index == currentTab}" ng-repeat="taxonomy in taxonomies">
<ul>
<li class="tab" ng-click="onClickTab(taxonomy.tab)">{{ taxonomy.name }}</li>
</ul>
</div>
And taxonomies should be an array of objects, something like:
var taxonomies = [
{ id: 0, name: 'Tab 1', tab: 'whatever you need here'},
{ id: 1, name: 'Tab 2', tab: 'whatever you need here'},
{ id: 2, name: 'Tab 3', tab: 'whatever you need here'}
];
And when you call your function onClickTab it should set the variable scope.currentTab to the id of the clicked tab.
Hope it helps.
sorry if am wrong,try this
http://plnkr.co/edit/FdueM4rAvd4k6hClK1HR?p=preview
your html will be
<html>
<head>
<script data-require="angular.js#*" data-semver="1.4.0-beta.6" src="https://code.angularjs.org/1.4.0-beta.6/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="app">
<div class="searchandfilter" ng-controller="tab-controller">
<span ng-repeat="taxonomy in taxonomies">
<ul ng-class="{'active': $index == selectedIndex}">
<li class="tab" ng-click="onClickTab(taxonomy.id)">{{taxonomy.name}}
<br/>
<span class="tab-content">
<span ng-repeat="child in taxonomy.children">
<input type="checkbox" ng-model="child.value" />{{child.name}}
</span>
</span>
</li>
</ul>
</span>
</div>
</body>
</html>
angular js code will be
var app = angular.module("app",[]);
app.controller('tab-controller',['$scope',function($scope){
$scope.taxonomies = [
{ id: 0, name: 'hello' , children:[{name:"test1",value:true},{name:"test2",value:false}]},
{ id: 1, name: 'how' , children:[{name:"test5", value: false}]},
{ id: 2, name: 'are you',children:[{name:"test4",value: true}]}
];
$scope.onClickTab= function(x){
$scope.selectedIndex= x;
};
}]);
css code will be
.active{
background-color:green;
}
body {
background-color: yellow;
}

Categories