I'm using mustache.js to render a template in javascript. I'd like to check if a list is empty or not to hide the <h2> tag in the following example. Is this possible or is mustache.js too logic-less?
This is the template:
<h2>Persons:</h2>
<ul>
{{#persons}}
{{name}}
{{/persons}}
</ul>
and this is the data:
{
"persons":[
{"name": "max"},
{"name": "tom"}
]
}
After struggling for half a day with this problem, I've finally found an easy solution!
Don't check for the list, but check if its first item is not empty!
Template:
{{#persons.0}}
<h2>Persons:</h2>
<ul>
{{#persons}}
<li>{{name}}</li>
{{/persons}}
</ul>
{{/persons.0}}
{{^persons.0}}No persons{{/persons.0}}
Data:
{
"persons":[
{"name": "max"},
{"name": "tom"}
]
}
Output:
<h2>Persons:</h2>
<ul>
<li>max</li>
<li>tom</li>
</ul>
Data:
{
"persons": []
}
Output:
"No Persons"
You could use persons.length. If it is a truthy value (i.e. greater than 0) then the block will be rendered.
{{#persons.length}}
<h2>Persons:</h2>
<ul>
{{#persons}}
<li>{{name}}</li>
{{/persons}}
</ul>
{{/persons.length}}
To keep your template logic-less, you can make this check before rendering the template:
// assuming you get the JSON converted into an object
var data = {
persons: [
{name: "max"}
, {name: "tom"}
]
};
data.hasPersons = (data.persons.length > 0);
Then your template will look like this:
<h2>Persons:</h2>
{{#hasPersons}}
<ul>
{{#persons}}
<li>{{name}}</li>
{{/persons}}
</ul>
{{/hasPersons}}
Use handlebars instead. It's a superset of Mustache which gives you that little bit more power that you need. The mustache community asked for this feature but the maintainer refuses to put it in.
In javascript you can check with {{#names.length}}{{/names.length}} or with {{#names.0}}
If you're outside of javascript (e.g. in pystache or Scalate), you're out of luck. The only solution then is to introduce a separate boolean, or nest your array in an object which you avoid entirely if you have an empty array, like maxbeatty suggested.
Related
I’ve been experimenting with this GitHub repo via a course on Lynda.com (https://github.com/planetoftheweb/learnangular) by Ray Villalobos -- it functions similarly to a basic web app that I’m hoping to build, but I’ve recently hit a bit of a road block.
In that repo linked above, in app/component.app.ts, is the following array:
var ARTISTS: Artist[] = [
{
"name": "Barot Bellingham",
"shortname": "Barot_Bellingham",
"reknown": "Royal Academy of Painting and Sculpture",
"bio": "Some bio here."
},
// etc...
]
This array is filtered by a pipe as seen in app/pipe.search.ts:
export class SearchPipe implements PipeTransform {
transform(pipeData, pipeModifier) {
return pipeData.filter((eachItem) => {
return eachItem['name'].toLowerCase().includes(pipeModifier.toLowerCase()) ||
eachItem['reknown'].toLowerCase().includes(pipeModifier.toLowerCase());
});
}
}
Here's the filter input:
<input class="search-input" [(ngModel)]="field1Filter" placeholder="type in search term here" (click)="showArtist(item); field1Filter=''">
And the code for the filter results:
<ul class="artistlist cf" *ngIf="field1Filter">
<li class="artistlist-item cf"
(click)="showArtist(item);"
*ngFor="let item of (artists | search: field1Filter)">
<artist-item class="content" [artist]=item></artist-item>
</li>
</ul>
<artist-details *ngIf="currentArtist" [artist]="currentArtist"></artist-details>
This all works perfectly, however, in my project, I would need to include three nested arrays, and have the ability to filter based upon the values in those arrays. A sample of the kind of array I need will look something like this:
var ARTISTS: Artist[] = [
{
"name": "Barot Bellingham",
"shortname": "Barot_Bellingham",
"reknown": "Royal Academy of Painting and Sculpture",
"bio": "Some bio here...",
"friends": [
"James",
"Harry",
"Bob",
"Liz",
"Kate",
"Jesse"
],
"emails": [
"bb#this.com",
"aa#this.com"
],
"car": [
"honda",
"scion",
"aston martin"
]
},
// etc...
]
Therefore, I hope to filter by “Harry,” and only display objects that contain “harry” in either “name,” “reknown,” “friends,” "emails," or "cars." Is this possible, and if so, how can I edit the pipe filter to do this? Thank you!!
(I'm pretty green at angular and JS in general, so I want to apologize in advance if I’ve used incorrect terminology or overlooked/misunderstood something basic.)
I deleted my prior answer because it was more confusing than helpful. I pasted example code without applying it to your variables/properties/objects and it was misleading. Let's try again:
export class SearchPipe implements PipeTransform {
transform(pipeData, pipeModifier) {
pipeModifier = pipeModifier ? pipeModifier.toLowerCase() : null;
return pipeModifier ? pipeData.filter(eachItem => {
eachItem['name'].toLowerCase().indexOf(pipeModifier) !== -1 ||
eachItem['reknown'].toLowerCase().indexOf(pipeModifier !== -1) : pipeData;
});
}
}
The first line of code in the transform method ensures that the modifier passed in is also lowercase so that the compare always compares lower case values. It also has a null check to ensure it does not try to lowercase it if it is null.
The second line of code also uses the "?" syntax to handle the case of a null pipeModifier.
I changed includes to indexOf. Includes checks arrays. Are these items, such as eachItem['name'], an array?
That should be closer.
NOTE: Without a provided plunker ... I did not check the syntax or correct execution of this code.
update: Plunker session added on request. https://plnkr.co/edit/0XjAoxzjIoSUelFcfmrx (no experience so will likely need help getting it to run on Plunker)
Building up a menu system using Node, Angular 2 and Typescript. Creating the output with manually added content works just fine, but having trouble wanting to mod this to iterate over a more complex array to create menu and sub-menu items. I see examples of for(var a in array) and for(var a of array), but perplexed as to which is the best way to go. Here is the current setup:
my menu.component.ts:
import {Component} from 'angular2/core';
export class MenuContent {
constructor (
public id: number,
public name: string
) { }
}
#Component({
selector: 'jimmmenu',
templateUrl: 'app/menu.html'
})
export class MenuComponent {
menuTitle = 'Menu!';
menuContent = [
new MenuContent(1, 'menu item'),
new MenuContent(2, 'menu item'),
new MenuContent(3, 'menu item'),
new MenuContent(4, 'menu item')
]
Menu = this.menuContent[0];
}
the menu.html template:
<div>
<h2>{{menuTitle}}</h2>
<ul>
<li *ngFor="#menu of menuContent">{{menu.name}} {{menu.id}}</li>
</ul>
</div>
and the index.ejs snippet where the menu is placed:
<body>
<jimmmenu></jimmmenu>
<jsTurfApp>Loading...</jsTurfApp>
<script src='/socket.io/socket.io.js'></script>
</body>
As I said, everything works fine like this, producing a simple list of menu items. But when I try to do something like:
menuData = {
"menu": [
{ "id": 0, "name": "DasnBoard", "image":"/Images/dashboard_on.gif", "link": "page0.html",
"subMenu": [
{ "id": 10, "name": "Main", "image": null, "link": "page1.html", "subMenu": null },
{ "id": 11, "name": "ET & Moisture", "image": null, "link": "page2.html", "subMenu": null }
]
},
]
};
for(var m in menuData) {
menuContent = [ new MenuContent(this.menuData.menu[m].id, this.menuData.menu[m].name); ]
}
...things start going awry, such as the Chrome Inspector crying 'Uncaught SyntaxError: Unexpected token ;' on the line with the for(). And yes I know that the loop would only add the last one... it's a bad example, for sure.
Essentially I'm trying to do the following, but looping through the menuData array instead of populating menuContent by hand with long drawn-out menu/submenu assignments:
menuContent = [
new MenuContent(this.menuData.menu[0].id, this.menuData.menu[0].name),
new MenuContent(this.menuData.menu[0].subMenu[0].id, this.menuData.menu[0].subMenu[0].name),
...
]
Admittedly my Typescript and Angular 2 knowledge is small and the available google-results seems sparse on similar ideas, which is why I turn to you all for assistance or prodding in the right direction. Appreciate any insights.
You could use this approach for simplicity:
<ul class="menu">
<li *ngFor="#item of items">
<a>{{item.name}}</a>
<ul class="sub-menu">
<li *ngFor="#subitem of item.subMenu">
<label>{{subitem.name}}</label>
</li>
</ul>
</li>
</ul>
Below is my JSON object which I would like to display the name in both the parent and child array.
$scope.result= [
{
"id": 1,
"name": "1002",
"parentArray": [
{
"id": 28,
"name": "PRODP1",
"shortCode": "PRODP1"
}
]
}
I want to display Name:1002 Parent_Name:PRODP1
I tried {{item.name}} which will only display 1002.But I need to display the name of parentArray as well.
Since the parentArray is also an array your going to need a nested ng-repeat.
If this is a large page then this may cause a performance issue.
<div ng-repeat="item in result">
{{item.name}}
<div ng-repeat="innerItem in item.parentArray">
{{innerItem.name}}
</div>
</div>
parentArray is an...array, so you need to access it using an index:
<div ng-repeat="item in result">
Name: {{ item.name }} Parent_Name: {{ item.parentArray.length ? item.parentArray[0].name : '' }}
</div>
That's under the assumption that there is one object in parentArray. You might need to iterate it, or you might need to check to see if it exists depending on your requirements.
I have a handlebars template that uses each statements, one nested inside the other.
It works just fine, until the inner each comes across an item in the data set that only has one item, in which case it doesn't output anything.
Here's my template:
<div class="container">
{{#each stories.story}}
<div class="story">
<h1 class="mask">
<span>
{{copy.heading}}
</span>
</h1>
<ul class="story-copy">
{{#each copy.body.text}}
<li class="mask">
<span>{{this}}</span>
</li>
{{/each}}
</ul>
</div>
{{/each}}
</div>
The interesting thing, as I said, is that when the ul is being output when copy.body.text has more than one text node, it works. If there is only ONE, it comes out empty.
There's gotta be something I'm missing. Can anyone help?
couldn't reproduce your bug.
can you post your data?
this one works for me: http://jsfiddle.net/Schniz/7v0qawbd/
var data = {
stories: {
story: [{
copy: {
heading: "hello",
body: {
text: [
"Hey"
]
}
}
}]
}
};
yet, even though I don't really know how your data looks, I think looks like your template should be kinda different: http://jsfiddle.net/Schniz/Ly8uh2u1/ for using with data that looks like:
var data = {
stories: [{
copy: {
heading: "hello",
body: [
"Hey"
]
}
}]
};
I was trying to perform iteration binding in Rivets.js as described in the documentation. However it seems that no binding occurs.
The template is defined as follows:
<section id="rivets">
<ul>
<li data-each-todo="list.todos">
<input type="checkbox" data-checked="todo.done">
<span data-text="todo.summary"></span>
</li>
<ul>
</section>
The binding is performed by:
var model = {
list: {
todos: [
{ done: true, summary: "Todo 1" },
{ done: false, summary: "Todo 2" }
]
}
}
rivets.bind(document.getElementById('rivets'), model)
I have created a fiddle for this issue. What am I missing?
My bad - the first paragraph of Rivets.js documentation made me use wrong name of Rivets.js attributes (data- instead of rv-).