Dynamic HTML navigation bar from array - javascript

I outputted the following array to the equivalent JavaScript JSON variable, and I tried to create a dynamic based navigation bar.
$navArray = array(
array('id'=>1,'parent'=>0,'text'=>'1A','href'=>'1a'),
array('id'=>2,'parent'=>1,'text'=>'2B','href'=>'2b'),
array('id'=>3,'parent'=>1,'text'=>'3C','href'=>'3c'),
array('id'=>4,'parent'=>2,'text'=>'4D','href'=>'4d'),
array('id'=>5,'parent'=>2,'text'=>'5E','href'=>'5e'),
array('id'=>6,'parent'=>5,'text'=>'6F','href'=>'6f'),
array('id'=>7,'parent'=>5,'text'=>'7G','href'=>'7g'),
array('id'=>8,'parent'=>3,'text'=>'8H','href'=>'8h'),
);
The script (JavaScript/jQuery) should get the array and returns HTML based on the parent 'id' as follows:
<ul>
<li>
1A
<ul>
<li>
2B
<ul>
<li>
4D
</li>
<li>
5E
<ul>
<li>
6F
</li>
<li>
7G
</li>
</ul>
</li>
</ul>
</li>
<li>
3C
<ul>
<li>
8H
</li>
</ul>
</li>
</ul>
</li>
<ul>
The HTML screen result should look like this:
I tried doing something... but that didn't work.
$('li.dropdown').hover(
function(){
$(this).find('ul').slideDown();
},
function(){
$(this).find('ul').slideUp();
});
How do you dynamically create a multi dimensional level HTML navigation bar as described?

Sounds like a classic case for recursion, can you change the array objects to include their own children?
$navArray = array(
array('id'=>1,'text'=>'1A','href'=>'1a', 'children' => array(
array('id'=>2,'text'=>'2B','href'=>'2b', 'children' => array(
array('id'=>4,'text'=>'4D','href'=>'4d'),
array('id'=>5,'text'=>'5E','href'=>'5e', 'children' => array(
array('id'=>6,'text'=>'6F','href'=>'6f'),
array('id'=>7,'text'=>'7G','href'=>'7g'),
)),
)),
array('id'=>3,'text'=>'3C','href'=>'3c', 'children' => array(
array('id'=>8,'text'=>'8H','href'=>'8h')
)),
)),
);
Then if you output your array into JS (let arr = <?=json_encode($navArray)?>), you can feed it into your function which will run recursively through the children, something similar to this should work:
function createMenuItems(arr) {
let output = '<ul>'
for (let item of arr) {
output += `<li><a id="${item.id}" href="${item.href}">${item.text}</a></li>`
if (item.children && item.children.length) {
output += createMenuItems(item.children);
}
}
output += '</ul>'
return output
}

Related

Angular - Create component tree menu

I am creating a tree menu, visually it looks like this:
The tree has been created based on an array of objects obtained from a service, extracted from a date property.
Now, I have to get the tree menu to allow displaying and collapsing the: years, months and complete dates, in the style of this component:
https://angular2-tree.readme.io/
Ideally, I'd do this with typescript, but if it's hard for me, I'd try using an external component.
This is the html code:
<ul>
<li *ngFor="let y of tree | keyvalue">{{y.key}}
<ul>
<li *ngFor="let m of tree[y.key] | keyvalue">{{m.key}}
<ul>
<li *ngFor="let d of tree[y.key][m.key] | keyvalue">{{d.key}}
<ul>
<li *ngFor="let h of tree[y.key][m.key][d.key]"><a [ngClass]="{'hourSelected': (idSchedule === h.id || lastId === h.id),'hourUnSelected': idSchedule !== h.id}" (click)="loadMacroProcesses(h.id)">{{h.hour}}</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
This would be the solution, now I will refine it:
<ul>
<li id="year" *ngFor="let y of tree | keyvalue; let i=index" (click)="listClick($event, i)">{{y.key}}
<ul [hidden]="indexExpandedYear!=i?true:null">
<li id="month" *ngFor="let m of tree[y.key] | keyvalue; let j=index" (click)="listClick($event, j)">{{m.key}}
<ul [hidden]="indexExpandedMonth!=j?true:null">
<li id="day" *ngFor="let d of tree[y.key][m.key] | keyvalue; let k=index" (click)="listClick($event, k)">{{d.key}}
<ul [hidden]="indexExpandedDay!=k?true:null">
<li *ngFor="let h of tree[y.key][m.key][d.key]"><a [ngClass]="{'hourSelected': (idSchedule === h.id || lastId === h.id),'hourUnSelected': idSchedule !== h.id && lastId !== h.id}" (click)="loadMacroProcesses(h.id)">{{h.hour}}</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
This is the typescript method:
listClick(event, i) {
switch (event.srcElement.id) {
case "year":
this.indexExpandedYear = this.indexExpandedYear==i?-1:i;
event.stopPropagation();
break;
case "month":
this.indexExpandedMonth = this.indexExpandedMonth==i?-1:i;
event.stopPropagation();
break;
case "day":
this.indexExpandedDay = this.indexExpandedDay==i?-1:i;
event.stopPropagation();
break;
default:
break;
}
}
Can you recommend me a good one external component? Thanks.
NOTE: I am working with version 11 of Angular
NOTE: If you deploy one year, the rest of the years should be collpased back.
NOTE: Angular material is not valid for me
You could add a parameter for visibility and click event to the parent ul. How it would work is that they would have a boolean value on them for visibility that would change when you click the top ul element. You would have a method that would just switch between true/false and display if true hidden if false. Click event should be on the top li element and visibility on its child.
You should checkout the tree component provided by primeng. It has its own data format and can do your own customisation on top it.

Loop through Object in JSX

On a JSX file, I need to loop through two levels of an Object to render the information each inner Array stores. It looks something like: Object > Object > Array
My data is structured this way:
const data = {
group1: {
subgroup1: [{...}, {...},{...}],
...
},
...
}
So I'm trying to acomplish something like this:
return (
<ul>
for (group in data) {
<li>Group Name
<ul>
for(subgroup in group) {
<li>Subgroup Name
<ul>
subgroup.map()
</ul>
</li>
}
</ul>
</li>
}
</ul>
)
I know for loops are not allowed inside a JSX file but converting my object into multiple Arrays doesn't seem right either since I have no idea how many items might be coming form my API.
I solved this using Herohtar suggestion, extracting the key/value of each of the parent Objects in an Array format:
{
Object.entries(data).map(group => (
<ul key={group[0]}>
<li>{group[0]}
<ul>
{
Object.entries(group[1]).map(subgroup => (
<ul key={subgroup[0]}>
<li>{subgroup[0]}
<ul>
{
subgroup[1].map(item => (
<li key={item.code}>{item.name}</li>
)
)
}
</ul>
</li>
</ul>
)
)
}
</ul>
</li>
</ul>
)
)
}
I wish there was a more elegant way of doing this though.

querySelectorAll return an empty object

I'm using Chromy framework to scrap a page, so, this is the html:
<ul class="a">
<li class="b"></li>
<li class="b"></li>
<li class="b"></li>
<li class="b"></li>
</ul>
So, if I do on chrome console the selection like this:
document.querySelectorAll('.b')
It will return me a nodeList with 4 objects inside and that's right, but if I do the same selection inside chromy script it return an empty object with 4 empty objects { '0': {},'1': {},'2': {},'3': {}}.
Can you please help me to figure out why this is happening?
That's my Chromy script
chromy.chain()
.goto('https://localhost:8080/')
.evaluate(() => {
return document.querySelectorAll('.b');
})
.result((r) => console.log(r))
.end()
.then(() => chromy.close());

Array methods for DOM HTML? [duplicate]

This question already has answers here:
How to convert a DOM node list to an array in Javascript?
(7 answers)
Closed 5 years ago.
I need to make an array from li tags. The array must include their inner texts. Eache index in array with each inner texts of li tags.
I'm trying to call the array method slice by Array.prototype.slice(). But probably I making some wrong...
The result must be like:
arr = ["Animals", "0_", .... , "fish__"]
var bodyd = document.getElementsByTagName('li');
for (var i = 0; i < bodyd.length; i++) {
bodyd = Array.prototype.slice.call(bodyd, 1);
console.log(bodyd);
}
<ul>
<li>Animals
<ul>
<li>0_
<ul>
<li>1__</li>
<li>2__</li>
<li>3__</li>
<li>4__</li>
</ul>
</li>
<li>Other_
<ul>
<li>Slis__</li>
<li>Bird__</li>
<li>Repti__</li>
</ul>
</li>
</ul>
</li>
<li>Fish
<ul>
<li>Aqua
<ul>
<li>Aqua__</li>
<li>Aqua__</li>
</ul>
</li>
<li>fish_
<ul>
<li>fish__</li>
</ul>
</li>
</ul>
</li>
</ul>
try this
var bodyd = document.getElementsByTagName('li');
bodyd = Array.prototype.slice.call(bodyd).map(function(val) {
return val.firstChild.data.trim();
});
console.log(bodyd);

Hide parent element in DOM when children are empty

<div>
<h1>Birds</h1>
<ul>
<li ng-if="bird.type === 'bird'"
ng-repeat="bird in creatures">{{bird.name}}</li>
</ul>
</div>
I have a response from the server and I want to put it into the list. But when the list is empty I need to hide this div container. For example if bird.type === 'bird' is not in the array - I want to hide div. But I want to use bird after ng-repeat so I cant make ng-if="bird.type === 'bird'" on div. Can I check if li is empty, then hide the div?
plunkr example
AngularJS ng-repeat handle empty list case - It's not the same. I don't want to hide li if it empty, I want to hide parent where I have h1 - when li is empty.
You could do the following:
<div ng-if="hasBirds(creatures)">
<h1>Birds</h1>
<ul>
<li ng-if="bird.type === 'bird'"
ng-repeat="bird in creatures">{{bird.name}}</li>
</ul>
</div>
And then in controller/directive you can add the hasBirds function.
$scope.hasBirds = function(list){
return list.filter(function(item){return item.type === 'bird'}).length > 0;
}
This hasBirds function would get called often, but would allow you to hide/show the heading of the birds exist.
In your example I advise you to use a filter instead of using "ng-if", you should create a filter like:
angular.module('moduleName').filter(birdsFilter);
function birdsFilter(creature) {
return creature.type == 'bird';
}
With the filter you can rewrite your code like this:
<div ng-hide="birds.length">
<h1>Birds</h1>
<ul>
<li ng-repeat="bird in birds = (creatures | filter:birdsFilter)">{{bird.name}}</li>
</ul>
</div>
IMO, several of these answers will work. But none of them are ideally optimized. I would recommend filtering your data in your controller/postlink function.
$scope.animals = {
dogs: $scope.creates.filter(function(a){return a.type == 'dog'}),
cats: $scope.creates.filter(function(a){return a.type == 'cat'}),
birds: $scope.creates.filter(function(a){return a.type == 'bird'}),
fishes: $scope.creates.filter(function(a){return a.type == 'fish'})
};
This way you would only ever process the array of creatures one time, in one spot. The digest cycle would never have to re-eval the array to see if it needed to update the DOM. Here is what you markup with look like then:
<div ng-if="animals.birds.length">
<h1>Birds</h1>
<ul>
<li ng-repeat="bird in animals.birds">{{bird.name}}</li>
</ul>
</div>
You should filter the list based on the type, store the filtered items in a scope property then use that property to show or hide the div.
<div ng-show="birds.length">
<h1>Birds</h1>
<ul>
<li ng-repeat="bird in creatures | filter:birdType as birds">{{bird.name}} </li>
</ul>
</div>
Then implement the birdType filter function in your controller:
$scope.birdType = function(creature) {
return creature.type === 'bird';
};
Using ng-show="cats.length" to make div's disappear if length is zero.
Filter inline based on object property like cat in creatures | filter:{type: 'cat'} as cats as discussed in this SO post.
WORKING EXAMPLE:
var app = angular.module('App', []);
app.filter(birdsFilter);
function birdsFilter(creature) {
return creature.type == 'bird';
}
app.controller('Ctrl', function($scope) {
$scope.creatures = [
{
name : 'Cho-cho',
type : 'bird'
},
{
name : 'Floo-floo',
type : 'dog'
},
{
name : 'Pou-pou',
type : 'bird'
},
{
name : 'Oop-flup',
type : 'bird'
},
{
name : 'Chio-mio',
type : 'cat'
},
{
name : 'Floo-floo',
type : 'dog'
},
{
name : 'Loo-Li',
type : 'dog'
},
{
name : 'Pops-Mops',
type : 'bird'
},
{
name : 'Boo-Moo',
type : 'dog'
},
{
name : 'Iop-Pio',
type : 'dog'
},
{
name : 'Floop-cho',
type : 'bird'
}
]
});
<!DOCTYPE html>
<html ng-app="App">
<head>
<script data-require="angularjs#1.5.7" data-semver="1.5.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-controller="Ctrl">
<div ng-show="birds.length">
<h1>Birds</h1>
<ul>
<li ng-repeat="bird in creatures | filter:{type: 'bird'} as birds">{{bird.name}} </li>
</ul>
</div>
<div ng-show="dogs.length">
<h1>Dogs</h1>
<ul>
<li ng-repeat="dog in creatures | filter:{type: 'dog'} as dogs">{{dog.name}} </li>
</ul>
</div>
<div ng-show="cats.length">
<h1>Cats</h1>
<ul>
<li ng-repeat="cat in creatures | filter:{type: 'cat'} as cats">{{cat.name}} </li>
</ul>
</div>
<div ng-show="fishes.length">
<h1>Fish</h1>
<ul>
<li ng-repeat="fish in creatures | filter:{type: 'fish'} as fishes">{{fish.name}} </li>
</ul>
</div>
</body>
</html>

Categories

Resources