I want to showcase the shopping cart category like a tree structure. When I click on the parent ul>li
element it gets data from the server and dynamically adds the new set of data. I have n levels, initially when I click on the first level I create a dynamic children set and put it under the parent using its class name. The same function is called for the newly created child, but this time its updating the super parent of the element clicked. I have created a plunker for a detailed explanation.
When I click on the men category, its child elements are populated with:
When I click on "wrist watch for men", it should add the next level below it - but its over writing the existing data.
Please help me. I am new to angular.
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 src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<ul class="navigation">
<li ng-repeat='category in fullTree.category[0]' ng-class='category.category_id' ng-click='dropmenu(category.category_id,category.level,category.has_child_category,$event);event.stopImmediatePropagation()'>
<p>{{category.category_name}} <i class="ion-chevron-down"></i>
</p>
</li>
</ul>
</body>
</html>
script:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $sce, $compile) {
$scope.name = 'World';
$scope.html = "";
$scope.htmlElement = function() {
var html = "<input type='text' ng-model='html'></input>";
return $sce.trustAsHtml(html);
}
$scope.fullTree = {
"result": "true",
"success": 1,
"category": [
[{
"category_id": "prdcat01",
"category_name": "Men",
"level": "1",
"higher_level_catg_no": "0",
"total_level": "3",
"has_child_category": "true",
"total_product": "0"
}, {
"category_id": "prdcat02",
"category_name": "Women",
"level": "1",
"higher_level_catg_no": "0",
"total_level": "3",
"has_child_category": "true",
"total_product": "0"
}, {
"category_id": "prdcat03",
"category_name": "Kids",
"level": "1",
"higher_level_catg_no": "0",
"total_level": "1",
"has_child_category": "false",
"total_product": "0"
}],
[{
"category_id": "cat01c01",
"category_name": "Wrist Watch For Men",
"level": "2",
"higher_level_catg_no": "prdcat01",
"has_child_category": "true",
"total_product": "1"
}, {
"category_id": "cat02c01",
"category_name": "Wrist Watch For Women",
"level": "2",
"higher_level_catg_no": "prdcat02",
"has_child_category": "true",
"total_product": "0"
}, {
"category_id": "cat01c02",
"category_name": "Shirt for men",
"level": "2",
"higher_level_catg_no": "prdcat01",
"has_child_category": "false",
"total_product": "0"
}, {
"category_id": "cat02c02",
"category_name": "Shirt For Women",
"level": "2",
"higher_level_catg_no": "prdcat02",
"has_child_category": "false",
"total_product": "0"
}],
[{
"category_id": "cat0101c01",
"category_name": "Fastrack Watch For Men",
"level": "3",
"higher_level_catg_no": "cat01c01",
"has_child_category": "false",
"total_product": "0"
}, {
"category_id": "cat0201c01",
"category_name": "Fastrack Watch For Women",
"level": "3",
"higher_level_catg_no": "cat02c01",
"has_child_category": "false",
"total_product": "1"
}, {
"category_id": "cat0101c02",
"category_name": "Casio watch for men",
"level": "3",
"higher_level_catg_no": "cat01c01",
"has_child_category": "false",
"total_product": "1"
}]
],
"msg": "Data retrieved successfuly"
};
$scope.dropmenu = function(category_id, level, has_child_category, $event) {
$event.stopPropagation();
$scope.dummy = [];
for (var i = 0; i < $scope.fullTree.category[level].length; i++) {
if ($scope.fullTree.category[level][i].higher_level_catg_no == category_id) {
$scope.dummy.push($scope.fullTree.category[level][i]);
}
}
alert(JSON.stringify($scope.dummy))
var fieldHtml = "<ul class='navigation'><li ng-class={{category.category_id}} ng-repeat='category in dummy' ng-click='dropmenu(category.category_id,category.level,category.has_child_category,$event);event.stopPropagation()'><p>{{category.category_name}} <i class='ion-chevron-down'></i></p></li></ul>";
var compiledElement = $compile(fieldHtml)($scope);
$($event.target).closest("." + category_id).append(compiledElement);
}
});
Update for unknown number of levels
You can generalize what I wrote for each of the 3 levels into a template, and use it recursively to handle any number of subcategory levels.
template.html:
<ul ng-show="$parent.category.expand" class="navigation" ng-init="level = $parent.level+1">
<li ng-repeat="category in fullTree.category[level]" ng-if="category.higher_level_catg_no == $parent.category.category_id" ng-class="category.category_id">
<p>{{category.category_name}}
<i ng-click="category.expand = !category.expand" class="ion-chevron-down"></i>
</p>
<ng-include src="'template.html'" ng-if="fullTree.category[level+1]"></ng-include>
</li>
</ul>
There is a little duplication in index.html, which could probably be eliminated with more thought about the set up. But this works, and doesn't require any DOM manipulation.
Here is the updated demo: http://plnkr.co/edit/DJ3hXHwYmf3n1LHArzWn?p=preview
Original answer for 3 levels
I suggest reading this question: How do I "think in AngularJS" if I have a jQuery background?
Instead of trying to build the DOM in your controller, and then have to mess with compiling and inserting it using jQuery, you can nest ng-repeat
to create the list structure. Then use ng-if
to render the subcategories when the parent category is clicked.
<ul class="navigation">
<li ng-repeat="category in fullTree.category[0]" ng-class="category.category_id">
<p>{{category.category_name}}
<i ng-click="category.expand = !category.expand" class="ion-chevron-down"></i>
</p>
<ul ng-if="category.expand" class="navigation">
<li ng-repeat="subcategory in fullTree.category[1]" ng-if="subcategory.higher_level_catg_no == category.category_id">
<p>{{subcategory.category_name}}
<i ng-click="subcategory.expand = !subcategory.expand" class="ion-chevron-down"></i>
</p>
<ul ng-if="subcategory.expand" class="navigation">
<li ng-repeat="subcategory2 in fullTree.category[2]" ng-if="subcategory2.higher_level_catg_no == subcategory.category_id">
<p>{{subcategory2.category_name}}
</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
Here is a working demo: http://plnkr.co/edit/lgYb8vFsFYpdXqKcMDad?p=preview
Instead of using controllers to manipulate the DOM it is better to work with Angular directives - they are built with this purpose in mind.
You can wrap your content inside directives like in the example below:
app.directive('collection', function () {
return {
restrict: "E",
replace: true,
scope: {
collection: '=',
catTree: '='
},
template: '<ul><member full-tree="catTree" cat-tree="catTree[category.level]" ng-repeat="category in collection track by category.category_id" category="category"></member></ul>'
};
});
app.directive('member', function ($compile) {
return {
restrict: "E",
replace: true,
scope: {
member: '=category',
catTree: '=',
fullTree: '='
},
template: '<li class="ng-hide" ng-show="showItem">{{member.category_name}}</li>',
link: function (scope, element, attrs) {
scope.currentTree = {};
scope.currentTree.category = [];
scope.showItem = (scope.member.level == '1');
element.bind('click', function (ev) {
jQuery(ev.target).siblings('ul').children('li').toggleClass('ng-hide');
// Prevent Bubble
return false;
});
if (scope.member.has_child_category) {
for (var cat in scope.catTree) {
if (scope.catTree[cat].higher_level_catg_no == scope.member.category_id) {
scope.currentTree.category.push(scope.catTree[cat]);
}
}
element.append('<collection collection="currentTree.category" cat-tree="fullTree"></collection>');
$compile(element.contents())(scope);
}
}
};
});
While these directives are not by far optimized, their purpose is to demonstrate how you can dinamically add content to the DOM and how you can manipulate various items.
The full code can be tested here: http://plnkr.co/edit/W5K77XlvhcaAoYl0TEpJ?p=preview
You can find out more about directives here: http://www.sitepoint.com/practical-guide-angularjs-directives/