AngularJS: ng-repeat, ng-if & JSON - html

I am having trouble with figuring out how to use ng-repeat and ng-if with JSON. I want a bar1 cluster to be displayed on the same line as a bar2 cluster if they have the same name.
The JSON looks like so:
{
"bar1" {
"name": "bar1"
"clusters": {
"1": {
"name": "Cluster 1"
}
...
}
}
"bar2" {
"name": "bar2"
"clusters": {
...
}
}
}
Right now I have a terrible solution where I nested the ng-repeat for bar2 inside the ng-repeat for bar1. The correct cluster is being displayed, but, unfortunately, this still generates the HTML for all of the clusters that are being looped through in bar2. At the very least, I don't want that extra HTML to be generated.
Angular
<div ng-repeat="(bar1clusterKey, bar1clusterValue) in blah.bar1.clusters">
<div>bar1 content</div>
<div ng-repeat="(bar2clusterKey, bar2clusterValue) in blah.bar2.clusters">
<div ng-if="bar1clusterValue.name == bar2clusterValue.name">
<div>bar2 content on the same line as bar1</div>
</div>
</div>
</div>
There are more bar1 clusters than bar2 clusters, and bar2 is a subset of bar1, so there aren't any unique bar2 cluster names.
I have a few questions: Is there any way to achieve my desired outcome without generating extra HTML with ng-repeat? Am I even using a sound nesting structure? Can I take care of this by using a service (I haven't even written an Angular service yet)?
EDIT: Here is a Plunk to show what I am talking about.

You could try angular filter to avoid ng-if
<div ng-repeat="(bar1clusterKey, bar1clusterValue) in blah.bar1.clusters">
<div>bar1 content</div>
<div ng-repeat="bar2 in blah.bar2.clusters| filter: {name: bar2clusterValue.name}">
<div>bar2 content on the same line as bar1</div>
</div>
</div>

If you put the ng-if on the same element as the ng-repeat, then the HTML generated for all the items that don't match the if will be commented out by the looks of it, which isn't totally ideal but it gets the job done. Perhaps a remove() could be piped in there somehow, but I didn't have any luck with that.
The final Plunk and the key HTML snippet:
<div class="flex-container" ng-repeat="(bar2clusterKey, bar2clusterValue) in
blah.bar2.clusters" ng-if="bar1clusterValue.name == bar2clusterValue.name">

Related

React renderDOM in multiple spots on same page?

I want react to render a DOM at multiple spots on my page. For example, if there's the class (.react-container), I want it to render the react component.
How can I do this if there are multiple spots on the page?
IMPORTANT: ArrayOne amount changes depending on where it's rendered.
APP.JS FILE
const App = () => {
return (
<div>
<div className="react-container">
{
ArrayOne.map(item=> (
<div className={
clsx({
'card': true,
'card-visible': state
})} >
<h2>{item.title}</h2>
<p>{item.text}</p>
</div>
))
}
</div>
</div>
);
};
INDEX.JS FILE
for (const el of document.querySelectorAll(".react-container")) {
ReactDOM.render(<App/>, el);
}
Code above is not running how I would like it to. It gives me two rows of only 1 card. When it should be 4 cards on row one and 1 card on row two. Console shows:
run query selector
running (4)
run query selector
running(1)
run query selector
running(1)
When I want it to do.
run query selector
running(4)
run query selector
running(1)
Interesting code. But i believe you are mixing some stuff.
get the layout working first, since seems you are not using React to do the layout.
<div>
<div className="react-container"></div>
<div className="react-container"></div>
<div className="react-container"></div>
<div className="react-container"></div>
</div>
render them into it with your lines.
for (const el of document.querySelectorAll(".react-container")) {
ReactDOM.render(<App/>, el);
}
Keep in mind, if you want to use your rest of code, you need to render it into a root, as in a typical React setup.
<div id="root"></div>
So pick one approach, either render once, but let react control everything underneath it, or render multiple times. You can't do both.

How to repeat same element in HTML without including them over and over?

I'm working with Angular v11.0.4 and TypeScript.
The idea is to show between one and eight progress spinner which represent different stock storages with different values.
The storages can be empty so I would like to show only the ones which actually have something inside, therefore I'm looking for someway to do that without repeating the same HTML code eight times.
This is the element I want to repeat.
<div class="col-sm">
<label><strong>Storage X:</strong></label>
<mat-progress-spinner
class="example-margin"
[color]="color"
[mode]="mode"
[value]="value">
</mat-progress-spinner>
</div>
If you have any idea or advise I'll appreciate your help. Thanks!
As described as a comment, the best way to not duplicate code, is to use *ngFor (Angular documation) and specify in your component what the values should be.
Here's a quick setup that should keep you moving.
Component.html
Add *ngFor="let progress of progressItems" to the surrounding div. It will now loop through all of the items that are in the component's defined array.
<div class="col-sm" *ngFor="let progress of progressItems">
<label><strong>Storage X:</strong></label>
<mat-progress-spinner class="example-margin" [color]="progress.color" [value]="progress.value" mode="determinate">
</mat-progress-spinner>
</div>
Component.ts
Define progressItems in your component, and give it values that belong to your needs (these are just random values for demonstration purposes only).
progressItems: progress[] = [
{
color: "primary",
value: 50
},
{
color: "accent",
value: 20
},
{
color: "primary",
value: 80
},
{
color: "warn",
value: 95
}
];
Since it's TypeScript and to make it more safe, I added a type of progress to it and declared that interface as well.
interface progress {
color: string;
value: number;
}
Two things to mention: do no not forget to import MatProgressSpinnerModule into your app's module and also add a Material theme to the CSS file, like below to see it actually working.
#import "~#angular/material/prebuilt-themes/indigo-pink.css";
See this working example on StackBlitz for the overview of the steps we just did.

How to implement LinkedIn Hopscotch

I would like to know how to implement a simple LinkedIn HopScotch.
I tried to implement, but was unsuccessful in getting it done.
Below is my attempted implementation;
<h1 id="header">My First Hopscotch Tour</h1>
<div id="content">
<p>Content goes here...</p>
</div>
<button id="myBtn">Click Me</button>
<script>
var tour = {
id: "hello-hopscotch",
steps: [
{
title: "My Header",
content: "This is the header of my page.",
target: "header",
placement: "right"
},
]
};
$("#myBtn").click(function() {
hopscotch.startTour(tour);
});
</script>
Should I add a <div> with an id as hello-hopscotch as per tour object?
http://cdnjs.com/libraries/hopscotch is the source of my libraries; I've implemented hopscotch.min.css and hopscotch.min.js.
What am I doing wrong and how can I fix it?
Actually, you have set the placement to the "right" which is fine, but it is displaying the step off the screen. Because the step is displayed to the right of the element, which happens to be a block element. Switch it to bottom and you will see the item.
You can configure the placement per the documentation.
As I know from last day I've started with this plugin, hopscotch will not render if element that you targeted not found. If your target is an element with Id, you just need to set target with "#".

Map variables not usable in Scala Play HTML template

I have the following code in one of my Play .scala.html templates:
#formats.map(format => {
<div id="#format">
{format}
</div>
})
formats is a Seq of enumerations. The divs are created, with the proper "format" content (each contains a different format string), however, the ids are never set correctly. The id of each div is literally set to "#format", like this:
<div id="#format">
OneOfTheFormats
</div>
<div id="#format">
AnotherFormat
</div>
I've tried making the code <div id="{format}">, <div id={format}>, and <div id=#format> with no luck. It's odd, because I have done similar things in my other templates, but perhaps it's not working because of the special map case... maybe because format is a created argument, and not passed into the template?
UPDATE:
I tried the following, as someone below suggested:
#{
def createDiv(f: String) = {
<div id="#f">
{f}
</div>
}
formats.map(f => {
createDiv(f.toString)
})
}
Again, The formats are printed correctly inside the div, but the ID is never set. I'm beginning to think this isn't possible. I've also tried <div id="#f">, <div id="{f}">, and <div id="#{f}"> with no luck. Oddly enough, in order to print the format inside the div, I have to use {f}, and not #f. Still struggling here...
UPDATE 2:
It works if I do the following: <div id={f}> ... no quotes! God damn.
As I know, there are some limitations for declaring new variables in new templates, but you can use such a workaround:
#createDiv(format: String) = {
<div id="#format">
#format
</div>
}
And use it in your code like this:
#formats.map(format => {
createDiv(format.toString)
})
This worked for me. Hope this solution suits you.
it seems that there is name collision then you use "format" as a variable name probably because of String.format, try with different name
#formats.map{f =>
<div id="#f">
#f
</div>
}
The following worked for me:
#{
def createDiv(format: String) = {
<div id={format}>
{format}
</div>
}
formats.map(format => {
createDiv(format.toString)
})
}
Note the enclosed #{ } block, and that there are no quotes around the id part of <div id={format}>.
UPDATE:
I ended up doing something a bit cleaner - I used a separate template file. The code looks a bit like this now:
#formats.map(f => {
// do some other stuff
// render format subview
formatSubView(f, otherStuff)
})
And the sub-view template looks like the following:
#(f: theFormatEnum,
otherStuff: lotsOfOtherStuff)
<div id="#f">
<img src="#{routes.Assets.at("images/" + f + ".png")}"/>
// etc, etc
</div>

Razor HTML Conditional Output

I have a list of items I want to output as the contents of a main (the main in not included below). Each Item has 3 attributes: a Section Name, a Label and a Value. Each item is enclosed in a and everytime the Section Name changes I have to open a (and close the previous one, if any). I'm using a Razor view with this code:
#foreach (LocalStorageItem lsi in Model) {
string fld_name = "f_" + lsi.ItemName;
if (lsi.SectionName != sn) {
if (sn != "") {
Html.Raw("</fieldset>");
}
sn = lsi.SectionName;
<h2>#sn</h2>
Html.Raw("<fieldset>");
}
<div class="row">
<div class="ls_label">#lsi.ItemName</div>
<div class="ls_content" name="#fld_name" id="#fld_name">.</div>
</div>
}
#if (Model.Count != 0) {
Html.Raw("</fieldset>");
}
The problem is: each time the Section Name changes no fieldset tag (open and/or close) is generated. Where am I wrong? If I don't use Html.Raw (or #: as an alternative) the VS2010 parser signals an error.
Calling Html.Raw returns an IHtmlString; it doesn't write anything to the page.
Instead, you should write
#:</fieldset>
Using #: forces Razor to treat it as plain text, so it doesn't need to be well-formed.
However, your code can be made much cleaner by calling GroupBy and making a nested foreach loop.
I really think that the use of #: to work around such code is an abuse of that escape sequence. The problem should be addressed instead by correctly refactoring the code so that balanced tags can be easily written:
#foreach(var section in Model.GroupBy(i => i.SectionName)) {
<h2>#section.Key</h2>
<fieldset>
#foreach(LocalStorageItem lsi in section) {
string fld_name = "f_" + lsi.ItemName;
<div class="row">
<div class="ls_label">#lsi.ItemName</div>
<div class="ls_content" name="#fld_name" id="#fld_name">.</div>
</div>
}
</fieldset>
}
12 lines of code instead of 18