Build category menu with angularJS - html

I'm having problem with logic of building category menu with AngularJS
I wan't to print all categories with parent category id 0. Once this is done i wan't to print all sub categories which belong to its parent category.
End category menu should look like this:
- Audio, video & photo
--- Music players
--- Musical instruments
--- ...
--- Rest
- Cars
--- Whole cars
This is how my categories object looks like
0: Object
alias: "audio-video-and-photo"
category: "AUdio, video & photo"
id_category: "1"
parrent: "0"
__proto__: Object
1: Object
alias: "music-players"
category: "Music players"
id_category: "2"
parrent: "1"
__proto__: Object
2: Object
alias: "musical-instruments"
category: "Musical instruments"
id_category: "3"
parrent: "1"
__proto__: Object
3: Object
alias: "music-accessories"
category: "Music accessories"
id_category: "4"
parrent: "1"
__proto__: Object
4: Object
alias: "hi-fi"
category: "Hi-Fi"
id_category: "5"
parrent: "1"
__proto__: Object
5: Object
alias: "home-cinema"
category: "Home cinema"
id_category: "6"
parrent: "1"
__proto__: Object
6: Object
alias: "tv"
category: "TV's"
id_category: "7"
parrent: "1"
__proto__: Object
7: Object
alias: "rest"
category: "Rest"
id_category: "8"
parrent: "1"
__proto__: Object
8: Object
alias: "cars"
category: "Cars"
id_category: "9"
parrent: "0"
__proto__: Object
9: Object
alias: "whole-cars"
category: "Whole cars"
id_category: "10"
parrent: "9"
__proto__: Object
....
And this is how my HTML looks like
<div ng-repeat="c in categories track by $index">
<h4 ng-if="c.parrent == 0">{{c.category}}</h4>
<ul>
<li>{{c.category}}</li>
</ul>
</div>
End HTML should look similar to this
<div>
<h4>Audio, video photo</h4>
<ul>
<li>Music players</li>
<li>Music instruments</li>
<li>Music accessories</li>
<li>Hi-fi</li>
<li>Home cinema</li>
<li>Rest</li>
</ul>
<h4>Cars</h4>
<ul>
<li>Whole cars</li>
<li>Car accessories</li>
<li>Car parts</li>
<li>Rest</li>
</ul>
</div>
I hope you guys can help. Thanks in advance.

You can use ng-if to handle like this. Here is a solution with a nested ng-repeat.
<div ng-repeat="c in categories track by $index">
<h4 ng-if="c.parrent == 0">{{c.category}}</h4>
<ul ng-if="c.parrent != 0"
ng-repeat="sub_c in categories track by $index">
<li ng-if="sub_c.parrent == c.id_category">
{{sub_c.category}}
</li>
</ul>
</div>
Note that it will be better to organize your data before rendering or using a helper method to filter sub-category items. This will be efficient than do ng-repeat twice.
Update 1:
As requested, I post the idea how to organize data before rendering. I'll use with the library underscore (or lodash).
function build_category(categories) {
var result = _.filter(categories, function(c) {
return c.parrent == 0;
});
_.forEach(result, function(c) {
c.sub_categories = _.filter(categories, function(c2) {
return c2.parrent == c.id_category;
});
});
return result;
}
var grouped_categories = build_category(categories);
<div ng-repeat="c in grouped_categories track by $index">
<h4>{{c.category}}</h4>
<ul ng-if="c.sub_categories"
ng-repeat="sub_c in c.sub_categories track by $index">
<li>{{sub_c.category}}</li>
</ul>
</div>
If you have a large category, you will see some performance difference.

Related

How I can parse object to array in angular?

I get an object from an array of elements, and I need to parse it as an array and display it as a list, can you tell me how to do this? I only know how to handle requests.
html:
<ul *ngFor="let filtered of reposFiltered">
<li>
{{filtered.name}}
{{filtered.description}}
{{filtered.language}}
{{filtered.html}}
</li>
</ul>
.ts:
// Required data
selectRepo(data){
this.reposFiltered = data;
console.log(data)
}
massive with objects:
0: cu {name: "30daysoflaptops.github.io", description: null, language: "CSS", html: "https://github.com/mojombo/30daysoflaptops.github.io"}
1: cu {name: "asteroids", description: "Destroy your Atom editor, Asteroids style!", language: "JavaScript", html: "https://github.com/mojombo/asteroids"}
etc
If you only wish to see the data in the rows you could use the following code:
// the data
const data = [
{name: "30daysoflaptops.github.io", description: null, language: "CSS", html: "https://github.com/mojombo/30daysoflaptops.github.io" },
{name: "asteroids", description: "Destroy your Atom editor, Asteroids style!", language: "JavaScript", html: "https://github.com/mojombo/asteroids"}
];
// Required data
selectRepo(data){
this.reposFiltered = data.map(row => Object.values(row));
}
//html
<!-- iterate through all data rows -->
<ul *ngFor="let row of reposFiltered">
<!-- iterate through all values -->
<li *ngFor="let value of row">
{{ value }}
</li>
</ul>
To iterate over the properties on an object you need to use the https://angular.io/api/common/KeyValuePipe
something like this:
<ul *ngFor="let filtered of reposFiltered | keyvalue">
<li>
{{filtered.value.name}}
{{filtered.value.description}}
{{filtered.value.language}}
{{filtered.value.html}}
</li>
</ul>
If you need to access the key:
{{filtered.key}}
This code is useful for debugging:
{{filtered.key | json}}
{{filtered.value | json}}

Angular 8 Nested Object Interpolation

This is my version of Angular CLI:
Angular CLI: 7.3.9
Node: 12.2.0
OS: win32 x64
Angular: 8.0.2
While making an Angular 8 application, I am trying to use nested FormGroups which correspond to the following object:
const Boilerplate: any = {
1: {
desc: "section1",
content: {
1: "question 1.a:",
2: "question 1.b:",
3: "question 1.c"
}
},
2: {
desc: "section2",
content: {
4: "question 2.a:",
5: "question 2.b:",
6: "question 2.c",
7: "question 2.d"
}
}
}
There is an inner FormGroup of FormControls for section 1 and section 2, and an outer FormGroup holding the two inner formgroups. This is defined in the component.ts.
In the component.html, I am trying to iterate through the outer FormGroup's inner FormGroups, and print the inner FormControls. This is the code I have so far:
<form [formGroup]="sectionGroup">
<div *ngIf="boilerplate">
<div *ngFor="let section of boilerplate | keyvalue">
{{ boilerplate[section.key].desc }}
<div formGroupName="{{section.key}}">
<div *ngFor="let question of boilerplate[{{section.key}}]">
<-- things -->
</div>
</div>
</div>
</div>
The line <div *ngFor="let question of boilerplate[{{section.key}}]"> fails with an error message of:
Unexpected token {, expected identifier, keyword, or string
I have tried the following solutions, none of which have worked for me:
<div *ngFor="let question of {{boilerplate}}.{{section.key}}">
<div *ngFor="let question of {{boilerplate[section.key]}}">
<div *ngFor="let question of {{boilerplate[{{section.key}}]}}">
<td *ngFor="let question of Section">{{boilerplate[[section.key]]}}</td>
I have tried a variety of other {} and [] combinations and orders, and I realize now that nested interpolation is non-parsable.
Does anyone have a suggestion of how I can achieve this? I am using nested FormGroups because it is possible I will have additional layers of sections in the future. The format of the Boilerplate object can be changed if it would make the problem solvable (because I defined it, myself).
EDIT
The following was the solution that resolved this issue:
<div *ngFor="let question of boilerplate[section.key].content | keyvalue">
{{question.value}}
</div>
I try like below,
<div [formGroup]="formGroup">
<div *ngIf="boilerplate">
<div *ngFor="let section of boilerplate | keyvalue">
{{ boilerplate[section.key].desc }}
<div>
<div *ngFor="let question of boilerplate[section.key].content | keyvalue">
{{ question | json }}
</div>
</div>
</div>
Output is like below,
section1
{ "key": "1", "value": "question 1.a:" }
{ "key": "2", "value": "question 1.b:" }
{ "key": "3", "value": "question 1.c" }
section2
{ "key": "4", "value": "question 2.a:" }
{ "key": "5", "value": "question 2.b:" }
{ "key": "6", "value": "question 2.c" }
{ "key": "7", "value": "question 2.d" }
You need to use a keyValue filter pipe then you could just have the following syntax, this will let you use ngFor* to iterate though objects rather than arrays.
<div *ngFor="let question of boilerplate | keyValue">
{{ question.key }} - {{ question.value }}
</div>
You can then do the same for the nested objects inside until you have the correct data displayed. This is not supported in all versions of Angular, but definitely fine in 8.
Where you have Objects with the key as a number, I would look to manipulate that into an array which would help you keep this a little more simple. Allowing you to use traditional *ngFor
The answer from schoolcoder is great, I just would like to post another example for people in the future with the same problem.
I have an object Block that holds a list of Transactions and I want to show it on my page using two *ngFor's
Block model class:
export class Block {
hash: string;
previousBlockHash: string;
transactions: Transaction[]; <<<<<<<<<<<
merkleRoot: string;
tries: number;
timestamp: number;
}
Transaction model class
export class Transaction {
hash: string;
text: string;
senderHash: string;
signature: string;
timestamp: number;
}
How I show it on my page:
Blocks:
<div class="container">
<ul class="list-group">
<li class="list-group-item" *ngFor="let block of blocks | keyvalue">
Hash: {{blocks[block.key].hash}}<br>
Previous block hash: {{blocks[block.key].previousBlockHash}}<br>
Merkle root: {{blocks[block.key].merkleRoot}}<br>
Tries: {{blocks[block.key].tries}}<br>
Timestamp: {{blocks[block.key].timestamp}}<br>
Transactions in this block:
<ul class="list-group">
<li class="list-group-item" *ngFor="let transaction of blocks[block.key].transactions">
{{[block.key]}}<br>
Hash: {{transaction.hash}}<br>
Text: {{transaction.text}}<br>
SenderHash: {{transaction.senderHash}}<br>
Signature: {{transaction.signature}}<br>
Timestamp: {{transaction.timestamp}}
</li>
</ul>
</li>
</ul>
</div>

new retrieved list of the selected item from a list

I have a list which retrieves data from an object from backend, and after selecting an item from that list, it should display its items but i don't know how to render that in html or in rxjs.
Here is my current state after selecting the item Test1 from the list, it retrieve its inner items that are info 1 and info 2.
List:
List: Array(2)
0: {name: "Test1", item: Array(2)}
1: {name: "Test2", item: Array(1)}
length: 2
__proto__: Array(0)
selectedItemData: Array(2)
0: {name: "info 1", item: Array(1)}
1: {name: "info 2", item: Array(4)}
But the list doesn't change, even though everything works concerning the retrieving part of each selected item.
Here is my html :
<ul class="unstyled" *ngFor="let i of (listObservable$|async).list.List">
<li><a (click)="selected(i.name)">{{i.name}}</a></li>
</ul>
When using the async pipe it is best to us an ngIf and map it to a view variable so you only render the element once the observable has emitted.
<ng-container *ngIf="listObservable$ | async as listObj">
<ul class="unstyled" *ngFor="let i of listObj.list.List">
<li><a (click)="selected(i.name)">{{i.name}}</a></li>
</ul>
</ng-container>

looping through JSON Keys and values

it seems quite simple but some how i can't loop through the items as i would like to
i have this json object
"meters":{
"Rozvadec M11":{
"0":{
"Name":"m11-mcu13_sm114",
"Title":"Svarovaci centrum Flexarc",
"Parent":"m11-mcu13",
"Status":"Running",
"State":false
},
"1":{
"Name":"m11-mcu13_sm115",
"Title":"Brousici centrum Solicad",
"Parent":"m11-mcu13",
"Status":"Running",
"State":false
}
},
"Rozvadec R1-L":{
"0":{
"Name":"r1-l-mcu1_sm100",
"Title":"Amada NCT1",
"Parent":"r1-l-mcu1",
"Status":"Device unavailable",
"State":false
},
"1":{
"Name":"r1-l-mcu1_sm101",
"Title":"Amada 1",
"Parent":"r1-l-mcu1",
"Status":"Device unavailable",
"State":false
},
"2":{
"Name":"r1-l-mcu1_sm102",
"Title":"Amada 2",
"Parent":"r1-l-mcu1",
"Status":"Device unavailable",
"State":false
}
},
what i am trying to do is to loop through all meters to have a list of
"Rozvadec M11"
"Rozvadec R1-L"
and under each have a list of different smartmeters names
i have this in ts
let list = this.http.get('EndpointURL');
list.subscribe (
(response: Response)=>
{this.meters.push(response);
console.log(this.meters);
})
and this in html (i was trying to loop only through the meters first before i nest the for loops)
<ul *ngFor="let meter of meters">
<li>{{ meter}}</li>
</ul>
You have most of your code right but the ngFor is not used in that way. The html tag with the ngFor is what will be repeated and you that's why you should use it in the li instead of the list tag ul. It would be like this for you:
<ul>
<li *ngFor="let meter of meters">
{{meter}}
</li>
<ul>
This exact example is covered here in the docs.
EDIT: If you want to access the key of your list, in this SO answer you can see the new way in Angular6 to retrieve it. It will end up like this for your example:
<ul>
<li *ngFor="let meter of meters | keyvalue">
{{meter.key}}
</li>
<ul>
You can read more about the keyValuePipe in the docs.

Ruby - Array of hashes - Nested HTML menu from hash values without writing duplicates

I have a relatively large hash where the values for all keys within are array of hashes (see below for sample layout). I have spent the last 4.5 hours attempting to write out HTML for my Rails application and I am seriously about to cry as I've gone round in circles with nothing really to show for it!
Any help is greatly appreciated and thank you in advance for your time!
Specific Problems Encountered
Chapters/verses appear for books that they do not align to.
I'm also not able to de-duplicate entries, so 'Chapter 4', for example, is appearing multiple times (instead of it appearing once, with mulitple chapter/verse references nested beneath)
Desired Solution Criteria
The HTML needs to be written in 3 layers/nested (1st layer/div containing Book Name (e.g. Genesis, Exodus, Leviticus), 2nd layer/div containing chapter and 3rd layer/div containing verse)
Chapters and verses must align (e.g. Exodus 2:7 should not be written to the Genesis chapter 2 menu).
Book names should be written in the order of hsh's keys (e.g. Genesis followed by Exodus followed by Leviticus as opposed to alphabetical order)
Data Format:
hsh = { :genesis =>
[
{:id => 1, :verse => 'Genesis 4:12'},
{:id => 1, :verse => 'Genesis 4:23-25'},
{:id => 2, :verse => 'Genesis 6:17'}
],
:exodus =>
[
{:id => 5, :verse => 'Exodus 2:7'},
{:id => 3, :verse => 'Exodus 2:14-15'},
{:id => 4, :verse => 'Exodus 12:16'}
],
:leviticus =>
[
{:id => 2, :verse => 'Leviticus 11:19-21'},
{:id => 7, :verse => 'Leviticus 15:14-31'},
{:id => 7, :verse => 'Leviticus 19:11-12'}
]
}
Desired Output HTML [Shortened for Brevity]
<div class="submenu">
Genesis
<div class="lvl-2">
<div>
<div class="submenu">
<a>Chapter 4</a>
<div class="lvl-3">
<div>
<a onclick="load('1')"><span>ID 1</span> Verse 12</a>
<a onclick="load('1')"><span>ID 1</span> Verse 23-25</a>
</div>
</div>
</div>
<div class="submenu">
<a>Chapter 6</a>
<div class="lvl-3">
<div> <a onclick="load('2')"><span>ID 2</span> Verse 17</a> </div>
</div>
</div>
</div>
</div>
<div class="submenu">
Exodus
<div class="lvl-2">
<div>
<div class="submenu">
<a>Chapter 2</a>
<div class="lvl-3">
<div>
<a onclick="load('5')"><span>ID 5</span> Verse 7</a>
<a onclick="load('3')"><span>ID 3</span>Verse 14-15</a>
</div>
</div>
</div>
<div class="submenu">
<a>Chapter 12</a>
<div class="lvl-3">
<div>
<a onclick="load('4')"><span>ID 4</span> Verse 16</a>
</div>
</div>
</div>
</div>
</div>
## Shortened for brevity (Leviticus references excluded)
</div>
</div>
Code
final_html = String.new
hsh.each do |book, verse_array|
verse_array.each do |reference|
book = reference[:verse].split(' ').first # => "Genesis"
full_verse = reference[:verse].split(' ').last # => "4:12"
chapter = full_verse.split(':').first # => "4"
verse = full_verse.split(':').first # => "12"
# Failing Miserably at appending the right HTML to the final_html string here...
# final_html << "<div class=\"submenu\">"
# final_html << ...
# final_html << ...
end
end
Finally, note that there are no duplicate chapter/verse combinations (e.g. Genesis 4:12 will never appear a second time). It's also worth noting that both chapters and verses need to be sorted numerically and ascending.
As is often the case with such problems, it becomes much easier to solve once you've put the data into a "shape" that closely resembles your desired output. Your output is a nested tree structure, so your data should be, too. Let's do that first. Here's your data:
data = {
genesis: [
{ id: 1, verse: "Genesis 4:12" },
{ id: 1, verse: "Genesis 4:23-25" },
{ id: 2, verse: "Genesis 6:17" }
],
exodus: [
{ id: 5, verse: "Exodus 2:7" },
# ...
],
# ...
}
And, setting aside the HTML for now, here's the structure we want:
Genesis
Chapter 4
ID 1 - Verse 12
ID 1 - Verse 23-25
Chapter 6
ID 2 - Verse 17
Exodus
Chapter 2
ID 5 - Verse 7
...
...
The first thing I notice about your data is that it has a level of nesting you don't need. Since the :verse values contain the book name, we don't the keys from the outer hash (:genesis et al) and we can just flatten all of the inner hashes into a single array:
data.values.flatten
# => [ { id: 1, verse: "Genesis 4:12" },
# { id: 1, verse: "Genesis 4:23-25" },
# { id: 2, verse: "Genesis 6:17" }
# { id: 5, verse: "Exodus 2:7" },
# # ... ]
Now we need a method to extract the book, chapter, and verse from the :verse strings. You can use String#split if you want, but a Regexp is a good choice too:
VERSE_EXPR = /(.+)\s+(\d+):(.+)$/
def parse_verse(str)
m = str.match(VERSE_EXPR)
raise "Invalid verse string!" if m.nil?
book, chapter, verse = m.captures
{ book: book, chapter: chapter, verse: verse }
end
flat_data = data.values.flatten.map do |id:, verse:|
{ id: id }.merge(parse_verse(verse))
end
# => [ { id: 1, book: "Genesis", chapter: "4", verse: "12" },
# { id: 1, book: "Genesis", chapter: "4", verse: "23-25" },
# { id: 2, book: "Genesis", chapter: "6", verse: "17" },
# { id: 5, book: "Exodus", chapter: "2", verse: "7" },
# # ... ]
Now it's easy to group the data by book:
books = flat_data.group_by {|book:, **| book }
# => { "Genesis" => [
# { id: 1, book: "Genesis", chapter: "4", verse: "12" },
# { id: 1, book: "Genesis", chapter: "4", verse: "23-25" },
# { id: 2, book: "Genesis", chapter: "6", verse: "17" }
# ],
# "Exodus" => [
# { id: 5, book: "Exodus", chapter: "2", verse: "7" },
# # ...
# ],
# # ...
# }
...and within each book, by chapter:
books_chapters = books.map do |book, verses|
[ book, verses.group_by {|chapter:, **| chapter } ]
end
# => [ [ "Genesis",
# { "4" => [ { id: 1, book: "Genesis", chapter: "4", verse: "12" },
# { id: 1, book: "Genesis", chapter: "4", verse: "23-25" } ],
# "6" => [ { id: 2, book: "Genesis", chapter: "6", verse: "17" } ]
# }
# ],
# [ "Exodus",
# { "2" => [ { id: 5, book: "Exodus", chapter: "2", verse: "7" },
# # ... ],
# # ...
# }
# ],
# # ...
# ]
You'll notice that since we called map on books our final result is an Array, not a Hash. You could call to_h on it to make it a Hash again, but for our purposes it's not necessary (iterating over an Array of key-value pairs works the same as iterating over a Hash).
It looks a little messy, but you can see that the structure is there: Verses nested within chapters nested within books. Now we just need to turn it into HTML.
An aside, for the sake of our friends with disabilities: The correct
HTML element to use for nested tree structures is <ul> or <ol>. If
you have some requirement to use <div>s instead you can, but
otherwise use the right element for the job—users who use
accessibility devices will thank you. (Many articles have been written
on styling such trees, so I won't go into it, but for a start you can
hide the bullets with list-style-type: none;.)
I don't have a Rails app at my disposal, so to generate the HTML I'll just use ERB from the Ruby standard library. It will look more-or-less identical in Rails except for how you pass the variable to the view.
require "erb"
VIEW = <<END
<ul>
<% books.each do |book_name, chapters| %>
<li>
<%= book_name %>
<ul>
<% chapters.each do |chapter, verses| %>
<li>
Chapter <%= chapter %>
<ul>
<% verses.each do |id:, verse:, **| %>
<li>
<a onclick="load(<%= id %>)">ID <%= id %>: Verse <%= verse %></a>
</li>
<% end %>
</ul>
</li>
<% end %>
</ul>
</li>
<% end %>
</ul>
END
def render(books)
b = binding
ERB.new(VIEW, nil, "<>-").result(b)
end
puts render(books_chapters)
And here's the result as an HTML snippet:
<ul>
<li>
Genesis
<ul>
<li>
Chapter 4
<ul>
<li>
<a onclick="load(1)">ID 1: Verse 12</a>
</li>
<li>
<a onclick="load(1)">ID 1: Verse 23-25</a>
</li>
</ul>
</li>
<li>
Chapter 6
<ul>
<li>
<a onclick="load(2)">ID 2: Verse 17</a>
</li>
</ul>
</li>
</ul>
</li>
<li>
Exodus
<ul>
<li>
Chapter 2
<ul>
<li>
<a onclick="load(5)">ID 5: Verse 7</a>
</li>
<li>
<a onclick="load(3)">ID 3: Verse 14-15</a>
</li>
</ul>
</li>
<li>
Chapter 12
<ul>
<li>
<a onclick="load(4)">ID 4: Verse 16</a>
</li>
</ul>
</li>
</ul>
</li>
<li>
Leviticus
<ul>
<li>
Chapter 11
<ul>
<li>
<a onclick="load(2)">ID 2: Verse 19-21</a>
</li>
</ul>
</li>
<li>
Chapter 15
<ul>
<li>
<a onclick="load(7)">ID 7: Verse 14-31</a>
</li>
</ul>
</li>
<li>
Chapter 19
<ul>
<li>
<a onclick="load(7)">ID 7: Verse 11-12</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
Finally, here it is in action on repl.it: https://repl.it/Duhm