AngularJS : Recursive tree style - angularjs

as the name suggests i am trying to build a recursive tree directive I has to mimic the structure from the expression builder as that's how its been setup and i want to replace it with an angular component. I have had a look at the other posts on the forum but mine is not exactly the same.
the fiddle thanks to julien for showing me how to fix it.
the model which is based off the expression builder has a structure like a condition object inside a field then an expression array and then another array which will be called nested expression but essentially will contain a condition object and this can be nested as deep as it wants to go.
Anyway so i created a directive to create a list entry for each entry in the expression builder (ng-repeat) then i created a link function to find out when its at the last loop and after that repeat the loop for every item in the nested expression array (there must be a better way to do this).
It works for the nestings but now i want to assign a button at each root level ul so that you add a new entry(expression) to the array another but when i try it seems to duplicate the array and then attach the new value.
The other tasks is to:
And a root level button so that you can add a second nested expression array (i,e, two ul's at the same level) , a root level delete so that you can delete a whole ul nesting and a delete at each li so that you can delete just one expression.
I will keep at the fiddle but maybe someone can solve it before i can get the fiddle working.
Any help will be greatly appreciated
Regards
angular.module("expressionBuilderApp", [])
.controller('ExpressionBuilderController', ['$scope', function($scope) {
$scope.conditions = {
operator : "and",
expressions : [
{
name : "moo"
},
{
name : "mooest"
},
{
name : "mooster"
}
],
nestedexpressions : [
{
operator : "or",
expressions : [
{
name : "bow wow"
},
{
name : "woof woof"
}
],
nestedexpressions : [
{
operator : "or",
expressions : [
{
name : "meow"
},
{
name : "meeew"
}
],
nestedexpressions : [
{
operator : "And",
expressions : [
{
name : "oink"
},
{
name : "squeel"
}
],
nestedexpressions :
[
]
},
{
operator : "Or",
expressions : [
{
name : "hoot hoot"
},
{
name : "stares with yellow eyes"
}
],
nestedexpressions :
[
]
}
]
}
]
}
]
};
}])
.directive('expressionBuilder', function(){
return {
restrict: 'E',
template : '<ul><button ng-click="addNewCondition(conditions.expressions)">add</button><li nested-expressions="" conditions="conditions.nestedexpressions" ng-repeat="expression in conditions.expressions" >{{expression.name}}</li></ul>',
replace: true,
scope: {conditions: "="},
controller: function($scope){
$scope.addNewCondition = function(arrayToPushInto){
var newCondition = {name: "testing"};
arrayToPushInto.push(newCondition);
}
}
}
})
.directive('nestedExpressions', function($compile) {
return {
restrict: 'A',
replace: true,
link: function(scope, element, attrs){
if (scope.$last){
var conditions = scope.conditions;
console.log(conditions) ;
if(conditions.nestedexpressions.length > 0){
element.after($compile('<li ng-repeat="nestedexpression in conditions.nestedexpressions"><expression-builder conditions="nestedexpression"></expression-builder></li>')(scope));
}
}
}
}
});

I don't know why to be honest but i based my code of this so thanks buddy whoever you are.
Ng-include seems to just use the scope of the array passed in and does not duplicate the functions through directive nesting see the [fiddle] (http://jsfiddle.net/cerebral86/g62KP/5/)
Hopefully this helps someone else.
<script type="text/ng-template" id="conditionExpressionBuilder.html">
<div>
<div>
<button ng-click="addNestedCondition(conditions.nestedexpressions)">Add ()</button>
<button ng-click="addNewCondition(conditions.expressions)">Add node</button>
</div>
<div>
<select>
<option ng-selected="{{conditions.operator == operator.value}}" ng-repeat="operator in operators" value="{{operator.value}}">{{operator.display}}</option>
</select>
</div>
<div>
<ul>
<li ng-repeat="expression in conditions.expressions">
<div>
<span>
<button ng-click="delete(expression, conditions.expressions)" ng-disabled="$first">Delete Item</button>
</span>
<span>
<select>
<option ng-selected="{{filterfield.value == expression.filterField}}" ng-repeat="filterfield in filterfields" value="{{filterfield.value}}">{{filterfield.display}}</option>
</select>
</span>
<span>
<select>
<option ng-selected="{{comparator.value == expression.comparator}}" ng-repeat="comparator in comparators" value="{{comparator.value}}">{{comparator.display}}</option>
</select>
</span>
<span><input type="text" value="{{expression.expressionValue}}" /></span>
</div>
</li>
<li ng-repeat="conditions in conditions.nestedexpressions" ng-if="conditions.expressions.length > 0">
<button ng-click="removeNestedCondition($index, $parent.$parent.conditions.nestedexpressions)">Remove ()</button>
<ng-include src="'conditionExpressionBuilder.html'"></ng-include>
</li>
</ul>
</div>
</div>
</script>
angular.module("expressionBuilderApp", [])
.controller('expressionBuilderController', ['$scope', function($scope) {
$scope.operators = [
{
display: "And",
value: "and"
},
{
display: "Or",
value: "or"
}
];
$scope.filterFields = [
{
display: "Code",
value: "code"
},
{
display: "Capital",
value: "capital"
},
{
display: "Government",
value: "government"
},
{
display: "Population",
value: "population"
}
];
$scope.comparators = [
{
display: "contains",
value: "contains"
},
{
display: "Starts With",
value: "startswith"
},
{
display: "Ends With",
value: "endswith"
},
{
display: "Doest not Contain",
value: "doesnotcontain"
}
];
$scope.conditions = {
operator: "and",
expressions: [
{
filterField: "code",
comparator: "contains",
expressionValue: "moo"
},
{
filterField: "capital",
comparator: "startswith",
expressionValue: "mooest"
},
{
filterField: "government",
comparator: "endswith",
expressionValue: "mooster"
}
],
nestedexpressions: [
{
operator: "or",
expressions: [
{
filterField: "code",
comparator: "endswith",
expressionValue: "bow wow"
},
{
filterField: "government",
comparator: "doesnotcontain",
expressionValue: "woof woof"
}
],
nestedexpressions: [
{
operator: "or",
expressions: [
{
filterField: "code",
comparator: "contains",
expressionValue: "meow"
},
{
filterField: "code",
comparator: "contains",
expressionValue: "meeew"
}
],
nestedexpressions: [
{
operator: "And",
expressions: [
{
filterField: "government",
comparator: "doesnotcontain",
expressionValue: "oink"
},
{
filterField: "code",
comparator: "endswith",
expressionValue: "squeel"
}
],
nestedexpressions:
[
]
},
{
operator: "Or",
expressions: [
{
filterField: "code",
comparator: "doesnotcontain",
expressionValue: "hoot hoot"
},
{
filterField: "code",
comparator: "contains",
expressionValue: "stares with yellow eyes"
}
],
nestedexpressions:
[
]
}
]
}
]
}
]
};
}])
.directive('conditionExpressionBuilder', function () {
return {
restrict: 'E',
replace: true,
templateUrl: 'conditionExpressionBuilder.html',
scope: {
conditions: "=",
operators: "=",
filterfields: "=",
comparators: "="
},
controller: function($scope)
{
$scope.addNewCondition = function (arrayToPushInto) {
var newCondition = { name: "testing" };
arrayToPushInto.push(newCondition);
};
$scope.delete = function (expressionItem, expressionArray) {
var index = expressionArray.indexOf(expressionItem);
expressionArray.splice(index, 1);
};
$scope.removeNestedCondition = function (index, nestedExpressionArray) {
nestedExpressionArray.splice(index, 1);
};
$scope.addNestedCondition = function (nestedExpressionsArray) {
var nestedConidtion = { operator: "", expressions: [{}], nestedexpressions: [{}] };
nestedExpressionsArray.push(nestedConidtion);
};
}
}
});

Related

AngularJS ng-repeat for object contains objects and arrays

I have an object, which has objects inside, and arrays in child objects. Here's the structure:
var keywords = {
"Animals" : {
"Pets" : [
"Guppy", "Parrot", "Goldfish", "Dog", "Cat"
],
"Wild animals" : [
"Tiger", "Ant", "Tetra", "Peafowl", "Mongoose"
],
"Domestic animals" : [
"Cow", "Pig", "Goat", "Horse"
]
},
"Food" : {
"Fast food" : [
"Cheeseburger", "Hamburger"
],
"Dessert" : [
"Chocolate", "Cookie", "Cake", "Pie"
]
},
"Vehicle" : {
"Motorcycle" : [
"Harley Davidson"
],
"Car" : [
"Lamborghini", "Ferrari", "Bugatti", "BMW", "Mercedes"
]
},
"Movie" : {
"Science fiction" : [
"Sunshine", "Interstellar", "The Moon", "Oblivion", "Star Trek", "Star Wars"
]
}
};
I've made a foreach loop for looping through the elements inside and print them on screen:
angular.forEach(keywords, function(value, key) {
console.log(key);
angular.forEach(value, function(value, key) {
console.log(key);
angular.forEach(value, function(value, key) {
console.log(value);
})
})
})
Now, I'm trying to do the same with ng-repeat directive on a div (that has a div in it, and another in the child div), but can't make it work, have no clue how to start it. Any idea?
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.keywords = {
"Animals": {
"Pets": [
"Guppy", "Parrot", "Goldfish", "Dog", "Cat"
],
"Wild animals": [
"Tiger", "Ant", "Tetra", "Peafowl", "Mongoose"
],
"Domestic animals": [
"Cow", "Pig", "Goat", "Horse"
]
},
"Food": {
"Fast food": [
"Cheeseburger", "Hamburger"
],
"Dessert": [
"Chocolate", "Cookie", "Cake", "Pie"
]
},
"Vehicle": {
"Motorcycle": [
"Harley Davidson"
],
"Car": [
"Lamborghini", "Ferrari", "Bugatti", "BMW", "Mercedes"
]
},
"Movie": {
"Science fiction": [
"Sunshine", "Interstellar", "The Moon", "Oblivion", "Star Trek", "Star Wars"
]
}
};
});
.section1{
background-color: CornflowerBlue;
}
.section2{
background-color: lightblue;
}
.section3{
background-color: Azure ;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp" ng-controller="myCtrl">
<div ng-repeat="(keyword, list) in keywords" class="section1">
{{keyword}}
<div ng-repeat="(name, items) in list" class="section2">
{{name}}
<div ng-repeat="item in items" class="section3">
{{item}}
</div>
</div>
</div>
</body>
I think we are not using ng-repeat with this type of array.
You should go throw with this logic.
$scope.html ="";
angular.forEach(keywords, function(value, key) {
$scope.html += "<div>"+ key ;
angular.forEach(value, function(value, key) {
$scope.html += "<div>"+ key ;
angular.forEach(value, function(value, key) {
$scope.html += "<div>"+ key + "</div>";
})
$scope.html += "</div>";
})
$scope.html += "</div>";
})
and Print 'html' scope into your HTML page.
{{html}}
I know this is not perfect solution but it's trick to fix you current problem.

Highcharts using Directives in AngularJS

I am new to Angular and just getting used to how everything fits together. I want to display a chart through a directive. This chart will be static data so will neither be dynamically loaded nor updated, it is taken from a JSON.
I have referenced here but the main difference is that I am not loading Highcharts in the view. I am loading it globally in the .js file from my bower_components directory. This is my code:
.js
/* global Highcharts */
angular.module("fusoDataLoggerChart", [])
.controller("fusoDataLoggerChartController", ["$http", "$scope", function($http, $scope) {
"use strict";
$scope.data = {
"activities": [
{},
{},
{
"title": "Engine speed",
"type": "DATA_LOGGER",
"result": {
"Engine speed": {
"data": [
{
"timestamp": 0,
"value": {
"type": "QUANTITY",
"unit": "rpm",
"value": 900
}
},
{
"timestamp": 1000,
"value": {
"type": "QUANTITY",
"unit": "rpm",
"value": 1000
}
},
{
"timestamp": 2000,
"value": {
"type": "QUANTITY",
"unit": "rpm",
"value": 2000
}
}
]
},
"Accelerator pedal position": {
"data": [
{
"timestamp": 0,
"value": {
"type": "QUANTITY",
"unit": "%",
"value": 0
}
},
{
"timestamp": 1000,
"value": {
"type": "QUANTITY",
"unit": "%",
"value": 10.6
}
},
{
"timestamp": 2000,
"value": {
"type": "QUANTITY",
"unit": "%",
"value": 11
}
}
]
}
}
}
]
}
}])
.directive("fusoDataLoggerChart", function() {
"use strict";
return {
scope: {},
restrict: 'E',
link: function(scope) {
scope.dataLoggerData = scope.data["activities"][2]["result"];
scope.timestamps = getData()[0];
scope.engineSpeeds = getData()[1];
scope.pedalPositions = getData()[2];
var chart = new Highcharts.Chart({
chart: {
type: 'line',
animation: false,
renderTo: "DataLoggerChartContainer",
zoomType: 'x'
},
credits: {
enabled: false
},
title: {
text: null
},
xAxis: {
type: "linear",
title: {
text: "Timestamp"
},
min: 0,
categories: scope.timestamps
},
yAxis: [{ //Engine Speed
title: {
text: 'Engine Speed'
},
labels: {
format: '{value} RPM'
},
opposite: true
}, { //Accelerator Pedal Position
title: {
text: 'Accelerator Pedal Position'
},
labels: {
format: '{value} %'
},
min: 0,
max: 100,
// FIXME: 'allowDecimal' may not work
allowDecimal: true
}],
series: [{
name: 'Engine Speed',
type: 'spline',
yAxis: 1,
tooltip: {
valueSuffix: ' RPM'
},
data: scope.engineSpeeds
}, {
name: 'Accelerator Pedal Position',
type: 'spline',
yAxis: 2,
tooltip: {
valueSuffix: ' %'
},
data: scope.pedalPositions
}]
});
function getData() {
var timestamps = [],
engineSpeeds = [],
pedalPositions = [];
var engineSpeedData = scope.dataLoggerData["Engine Speed"]["data"],
pedalPosData = scope.dataLoggerData["Accelerator Pedal Position"]["data"];
for (var i in engineSpeedData) {
timestamps.push(engineSpeedData[i].timestamp);
}
for (var j in engineSpeedData) {
engineSpeeds.push(engineSpeedData[j].value.value);
}
for (var k in pedalPosData) {
pedalPositions.push(pedalPosData[k].value.value);
}
return [timestamps, engineSpeeds, pedalPositions];
}
}
}
});
HTML
<fuso-data-logger-chart>
<div id="DataLoggerChartContainer"></div>
</fuso-data-logger-chart>
When I go to where the chart is to be rendered, nothing is shown (and only the HTML code set manually is displayed with nothing inside when I check in DevTools).
N.B: I realize I do not need the controller yet but it is there as in future the JSON will be got from a rest call from within the controller
Any help appreciated.
This isn't a full answer to your question, but I noticed a way you can make your HTML directive a little cleaner so that you could instead use this format:
<fuso-data-logger-chart></fuso-data-logger-chart>
You don't need the <div id="DataLoggerChartContainer">. You can embed that in the directive definition as a template:
.directive("fusoDataLoggerChart", function() {
"use strict";
return {
scope: {},
restrict: 'E',
transclude: true,
template: '<div id="DataLoggerChartContainer"></div>'
link: function(scope) {
... your other code ...
}
}
});
I have figured out the problem. There were multiple errors but I'll leave this here so maybe someone else can find it useful:
Highcharts and my FusoDataLoggerChart.js were not loaded into index.html (<script src="modules/readVehicleData/FusoDataLoggerChart.js"></script>) meaning that they couldn't be seen by Angular.
In my directive, I was trying to access the local scope (scope.data) in the link() function instead of trying to access the parent scope in the controller where the data is set. To access the parent scope, scope.$parent.data worked for me (N.B: was only 'data' for me as that's the value I set)

Angular filtering object based on array of strings

I'd like to be able to filter an entire object based on an array of strings. Currently the default filter will search an entire object based on a single string value, but not with an array of strings.
current jsfiddle
<div ng-controller="myController">
<div>
<ul determine-filtering>
<li get-filter-tag active-state="false" tag="{{button}}"
ng-repeat="button in buttons">
{{button}}
</li>
</ul>
<hr/>
<ul>
<li ng-repeat="item in content | filter:filterArray">
{{item.data.headline}}
</li>
</ul>
</div>
</div>
app.js
var testapp = angular.module('testapp', [])
.filter('inArray', function($filter){
return function(list, arrayFilter){
if(arrayFilter.length > 0){
console.log(arrayFilter);
return $filter("filter")(list, function(listItem){
return arrayFilter.indexOf(listItem) != -1;
});
}else{
return $filter("filter")(list, function(listItem){
return arrayFilter.indexOf(listItem) == -1;
});
}
};
})
.directive('getFilterTag', function () {
return {
link: function postLink(scope, element, attrs) {
element.on('click', function(){
var tag = attrs.tag;
var filterArray = scope.filterArray;
if(filterArray.indexOf(tag) === -1){
scope.filterArray.push(tag);
}else{
filterArray.splice(filterArray.indexOf(tag), 1);
}
scope.$apply();
});
}
};
})
.directive('activeState', function () {
return {
link: function (scope, element, attrs) {
element.on('click', function(){
attrs.activeState = !attrs.activeState;
if(!attrs.activeState){
$(this).addClass('active');
}else{
$(this).removeClass('active');
};
});
}
};
})
.controller('myController', function($scope){
$scope.filterArray = [];
$scope.buttons = ['corn', 'vegetable', 'onion'];
$scope.content = [
{
"type": [
"recipe"
],
"data": {
"prepTimeInMinutes": 10,
"serves": "6 to 8",
"headline": "North Carolina Piedmont Slaw",
"ingredients": [
{
"item": "medium head cabbage",
"quantity": {
"number": 1
},
"notes": "cored and chopped (5 to 6 cups)"
},
{
"unit": "cup",
"item": "ketchup",
"quantity": {
"number": 1
}
},
{
"unit": "tbsp.",
"item": "sugar",
"quantity": {
"number": 3
}
},
{
"unit": "tbsp.",
"item": "apple cider vinegar",
"quantity": {
"number": 1
}
},
{
"unit": "tsp.",
"item": "kosher salt",
"quantity": {
"fraction": {
"display": "½",
"denominator": 2,
"numerator": 1
}
}
},
{
"unit": "tsp.",
"item": "black pepper",
"quantity": {
"fraction": {
"display": "½",
"denominator": 2,
"numerator": 1
}
},
"notes": "freshly ground"
},
{
"item": "Generous dash hot sauce, such as Texas Pete Hot Sauce or Tabasco brand"
}
],
"description": "",
"cookTimeInMinutes": 180,
"categories": {
"Dish Type": [
"Side"
],
"Main Ingredient": [
"Vegetable"
]
},
"cookingDirections": [
{
"step": "Place the cabbage in a large bowl."
},
{
"step": "Combine the ketchup, sugar, vinegar, salt, pepper and hot sauce in a liquid measuring cup. Pour over the cabbage and toss to coat thoroughly. Cover and refrigerate for at least 3 hours, and preferably overnight, before serving."
},
{
"step": "Serve on top of the pulled pork"
}
]
}
}
]
} );
I have a custom filter which worked with arrays, but not with arrays with nested objects. What is the best approach to do this kind of filtering? Is there anything the $filter service already provides for searching based on arrays?
If you need to filter an object based on an array of strings (matching the objects properties) I would look into lodash#omit.
Usage:
var obj = { a: 'a', b: 'b', c: 'c' };
var arr = ['a', 'c'];
_.omit(obj, arr); // { b: 'b' }
I'm not sure whether omit supports deep/nested objects. If not, you could compose it with an iterator to determine whether the given key of an object is an object itself and run another omit on that.

scope of event arguments in angular directive

I have the following angular app to create a menu of sections/products.
at present when rendered and hitting the 'add' button that is rendered within each li I want to add a section/product as a sub of that section however multiple new children are created.
ultimately I wish to display a form which when submitted will create the child but that is the next step. Right now I need to limit the scope to the current section and not have multiple bound clicks.
If you need more information please state and I will post in an edit.
Some sample data data.
{
"sections":[
{
"name":"Flags",
"sections":[
{
"name":"Europe",
"sections":[],
"products":[
{ "name": "France" },
{ "name": "Germany" },
{ "name": "Ireland" },
{ "name": "England" }
]
},
{
"name": "Africa",
"sections":[],
"products":[
{ "name": "Egypt" },
{ "name": "Nigeria" },
{ "name": "Chad" }
]
},
{
"name": "South America",
"sections":[],
"products": [
{ "name": "Brasil" },
{ "name": "Argentina" },
{ "name": "Peru" }
]
}
],
"products":[]
},
{
"name": "Maps",
"sections":[
{
"name": "Africa",
"sections":[],
"products":[
{ "name": "Egypt" },
{ "name": "Nigeria" },
{ "name": "Chad" }
]
},
{
"name": "South America",
"sections":[],
"products": [
{ "name": "Brasil" },
{ "name": "Argentina" },
{ "name": "Peru" }
]
}
],
"products":[]
}
],
"products":[]
}
The app.
'use strict';
var menuApp = angular.module('menuApp', []);
menuApp
.directive('sections', function () {
return {
restrict: "E",
replace: true,
scope: {
sections: '='
},
template: '<ul><section ng-repeat="section in sections" section="section" /></ul>'
};
})
.directive('section', function ($compile) {
return {
restrict: "E",
replace: true,
scope: {
section: '=section'
},
template: '<li class="section">{{section.name}} <button ng-click="addSub(section)">Add</button></li>',
link: function (scope, element, attrs, controller) {
if (angular.isArray(scope.section.sections)) {
element.append("<sections sections='section.sections'></sections>");
$compile(element.contents())(scope);
}
if(angular.isArray(scope.section.products)){
element.append("<products products='section.products'></products>");
$compile(element.contents())(scope);
};
},
controller : function($scope){
console.log($scope);
$scope.addSub = function (section){
//console.log(section,'Adding Sub');
section.sections.push({"name":"Section","sections":[],"products":[]});
};
}
};
})
.directive('products', function () {
return {
restrict: "E",
replace: true,
scope: {
products: '='
},
template: '<ul><product ng-repeat="product in products" product="product"></product></ul>'
};
})
.directive('product', function ($compile) {
return {
restrict: "E",
replace: true,
scope: {
product: '='
},
template: '<li class="product">{{product.name}}</li>'
};
});
menuApp.controller('menuCtrl', function menuCtrl($scope,$http) {
$http.get('/ajax/getvenuesmenu?venueID='+venueMenu.venueId).success(function(resp) {
$scope.sections = resp;
});
$scope.add = function(data){
data.push({"name":"Section","sections":[]});
};
});
Took me a bit to figure it out but here's the basic problem, you are compiling the full contents of section 2 extra times and each compile seems to add a new event handler.
Instead of compiling the contents of element each time you make an append of new template, compile the template itself (outside of the DOM) and then append the compiled template. This way the ng-click handler doesn't get compiled again other than initial scope creation
Here's an abbreviated version with one template appended:
link: function (scope, element, attrs, controller) {
if (angular.isArray(scope.section.sections)) {
/* compile outside of the DOM*/
var subsections = $compile("<sections sections='section.sections'></sections>")(scope);
/* append compilation*/
element.append(subsections);
}
DEMO
Another approach would be to create a complete template string in link by checking for subsections and products, then compiling everything all at once....instead of using template option
Code for alternate approach compiling complete section at once:
.directive('section', function ($compile, $timeout) {
return {
restrict: "E",
scope: {
section: '=section'
},
link: function (scope, element, attrs, controller) {
var template = '<li class="section">{{section.name}} <button ng-click="addSub(section)">Add</button>';
if (angular.isArray(scope.section.sections)) {
template += "<sections sections='section.sections'></sections>";
}
if (angular.isArray(scope.section.products)) {
template += "<products products='section.products'></products>";
};
template += '</li>';
var compiledTemplate = $compile(template)(scope);
element.replaceWith(compiledTemplate);
scope.addSub = function (section) {
section.sections.push({ "name": "Section", "sections": [], "products": []
});
};
}
};
})
DEMO-Alt

Create an unordered list n-levels deep using AngularJS

While going through some AngularJS examples, I see how easy it is to repeat and create structures. However, I couldn't figure out how to do the following.
Assume we have a json structure like
{
"Asia": {
"India": {
"Bangalore": {},
"Mumbai": {},
"New Delhi": {}
},
"China": {
"Beijing": {},
"Shanghai": {}
}
},
"Europe": {
"France": {
"Paris": {}
},
"Germany": {
"Berlin": {}
}
}
}
What I want to do is - Convert this JSON structure to an Unordered list - The depth of this kind of structure is not known, and can possibly go deeper. How do I perform repeats dynamically using Angular JS?
Your JSON is poorly structured, you're using property names to carry data.
What you really want is something like this:
$scope.continents = [
{
name: 'Asia',
countries: [
{
name: 'India',
cities: [
'Bangalore',
'Mumbai',
'New Delhi'
]
},
{
name: 'China',
cities: [
'Beijing',
'Shanghai'
]
},
]
},
{
name: 'Europe',
countries: [
{
name: 'France',
cities: [
'Peris'
]
},
{
name: 'Germany',
cities: [
'Berlin'
]
},
]
}
];
That said... what it sounds like you're looking to do is create a recursive tree directive of some sort. That gets a little tricky. You'll need to normalize your structure a bit so you can recursively examine it. Then you'll have to create two directives. One for a list, and one for an item:
Here is an example of what I mean...
function Item(name, items) {
this.name = name;
this.items = items || [];
}
app.controller('MainCtrl', function($scope) {
$scope.items = [
new Item('test'),
new Item('foo', [
new Item('foo-1'),
new Item('foo-2', [
new Item('foo-2-1'),
new Item('foo-2-2')
])
]),
new Item('whatever')
];
});
app.directive('tree', function() {
return {
template: '<ul><tree-node ng-repeat="item in items"></tree-node></ul>',
restrict: 'E',
replace: true,
scope: {
items: '=items'
}
};
});
app.directive('treeNode', function($compile) {
return {
restrict: 'E',
template: '<li>{{item.name}}</li>',
link: function(scope, elm, attrs) {
//MAGIC HERE!!!: this will do the work of inserting the next set of nodes.
if (scope.item.items.length > 0) {
var children = $compile('<tree items="item.items"></tree>')(scope);
elm.append(children);
}
}
};
});
In case anyone is interested in the "least-effort" way to do this without creating a directive (not that you shouldn't, but just offering a variation), here is a simple example:
http://jsbin.com/hokupe/1/edit
Also here's a blog post and a 10-15 minutes video on how it works:
http://gurustop.net/blog/2014/07/15/angularjs-using-templates-ng-include-create-infinite-tree/
Sample Code:
<script type="text/ng-template" id="treeLevel.html">
<ul>
<li ng-repeat="item in items">
<input type="checkbox"
name="itemSelection"
ng-model="item._Selected" />
{{item.text}}
<div ng-include=" 'treeLevel.html'"
onload="items = item.children">
</div>
</li>
</ul>
</script>
<div ng-include=" 'treeLevel.html' "
onload="items = sourceItems">
</div>

Resources