Handlebars #each fails with one item - javascript

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"
]
}
}]
};

Related

Maping inside a map - Vanilla Javascript

Coding noob here :)
I'm working on a project where I have prepared an array that I'm mapping over and printing. Inside the array I have another array, called "lang", that I can't reach from the map. I want to print each item in that array into an <li>, as the code is now all the items are printed as one <li> , which I don't want. I have tried maping inside the map in various ways but not gotten it to work.
Is the right way to do a map inside the map? In that case, how is it done?
Is there another way to do this?
Please see part of the array and the map function I have created down below. projectContainer, which I'm adding the innerHTML to, is a div inside the index.html file.
},
{
img: "./pictures/guess-who.png",
projectTitle: 'GUESS WHO?',
projectDescription: 'Recreation of the game "Guess who?" built with HTML, CSS and JavaScript.',
lang: [
'HTML', 'CSS', 'JavaScript'
],
url: '',
className: '',
id: 6
}
]
}
const printProjects = () => {
projectArray.projects.map((project) => {
projectContainer.innerHTML += `
<div class="project-container ${project.id}">
<img src=${project.img} alt="picture of guess who game"></img>
<h4>${project.projectTitle}</h4>
<p>${project.projectDescription}</p>
<ul>
<li>${project.lang} </li>
</ul
</div>
`
})
}
printProjects()
You can do something like this and use map(...).join('')
<ul>
${project.lang.map(el => `
<li>${el}</li>
`).join('')}
</ul>

Vue Router Setup work when visited directly via URL

I'm looking for advice on how to go about this routing scenario:
I have the following HTML that loops category and items in the category. The <router-view> is inside the category so that when an item is clicked it will open only in the category that related to that item.
<ul>
<li v-for="cat in categories">
<ul>
<li v-for="business in filteredByCat">{{business.name}}</li>
</ul>
<router-view v-if="..."></router-view>
</li>
</ul>
My routes are as follows:
{
path: '/businesses',
name: 'Directory',
component: Directory,
children: [{
path: ':listing',
name: 'Listing',
component: Listing
}]
}
Visualization:
How do I get the data of the item clicked to pass to the router-view? I assume I'd use props but that wouldn't work if the user visited details directly by URL?
I tried getting the item like so:
methods: {
finalItem ($route) {
var match = this.businesses.filter((business) => {
return business.link === $route.params.listing
})
return match[0]
}
}
This doesn't work, even if it did, this feels wrong. Is there a way to pass the data in a way that would preserve even when visited directly? This is my primary concern. (I understand the repeated <router-view> is bad code but am not sure how to get around doing that with my layout. Open to suggestions on that too though.)
The way you're using router-view, you might as well just drop a component in. As far as using multiple router-views goes, it's very common, so idk what #varbrad is talking about there. Child routes are fine.
The not-so-common part is using multiple router-view's in one component. The UI you're aiming for is nearly identical to Netflix. If you check out what they're doing, you'll see that they pass a movie ID (business id/shortname) as "jbv" and a row number (category name) as "jbr" in the route query.
Let's mimic this in your component:
I'm not sure what filteredByCat looks like, but the way you have it set up, it would list the same businesses for every category. Try something like this:
computed:{
businessesByCategory(){
let dictionary = {};
this.businesses.forEach(business=>{
business.categories.forEach(category=>{ // assuming each business has an array of categories
dictionary[category] = dictionary[category] || [];
dictionary[category].push(business)
})
})
return dictionary;
}
},
data(){
return {
activeBusiness:null
}
},
methods:{
setActiveBusiness(business){
this.activeBusiness = business;
},
setUrlQuery(listing, category){
this.$route.query.listing = listing;
this.$route.query.category = category;
},
openListing(business, category){
this.setActiveBusiness(business);
this.setUrlQuery(business.link, category);
}
}
-
<ul>
<li v-for="cat in categories">
<ul>
<li
v-for="business in businessesByCategory[cat]"
#click="openListing(business, cat)"
>
{{business.name}}
</li>
</ul>
<Listing :business="activeBusiness" v-if="$route.query.category == cat"></Listing>
</li>
</ul>

Rivets.js iterating over collection

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-).

Add and remove templates

Lets say I have this 'Model'
var lists = [
{
title: 'Default title',
image: '/img/default.jpeg',
section: [
{ name: 'Default Name',
description: 'Default Description'
}
],
activity: ['default']
}
];
Here is the view templates
<template name="main_list_view">
{{#each list.section}}
{{> section}}
{{/each}}
<a id="addSection" href='#'>Add Section</a>
</template>
<template name="section">
<li>{{section.name}}</li>
<li>{{section.description}}</li>
<a class="deleteSection" href='#'>Delete Section</a>
</template>
I need help w the logic for add more sections and remove a specific section from the colection.
I have checked mongoDB and looks like I have to use something like addToset and $unset for update the model, but I really only want to remove them from the view, cause I want the default model to remain the same always.
So how I remove and add Templates to the view when the user clicks Addcontent, and remove the specific view when the user clicks remove ? is there a Meteor way to do it ? So a user clicks addContent and a new section template is rendered or removed if clicks remove.
Removing one should be simple enough. First, it might help to add the id of the section in your delete link:
<a data-id="{{section._id}}" class="deleteSection" href='#'>Delete Section</a>
Template.section.events({
'click a.deleteSection': function(evt) {
Sections.remove($(evt.target).data('id'));
}
});
Adding one is a different story, because it depends on exactly what you want to happen when you click the addSection link. Does that link render a form that creates a new Section on submission? Or does the handler to that link create a Section object and fill in the values?
Edit
It was pointed out that Sections is not a collection. I should've seen that. Making it into a collection would be an obvious way to deal with that. So instead of:
{{#each list.section}}
{{> section}}
{{/each}}
You'd have:
{{#each sections list._id}}
{{> section}}
{{/each}}
And in your js file:
Template.main_list_view.helpers({
sections: function(list_id) {
return Sections.find({ list_id: list_id });
}
});
Alternatively, you could move the "section" template up to the parent and then you could add the list_id as a data attribute on the link::
{{#each list.section}}
<li>{{name}}</li>
<li>{{description}}</li>
<a data-id="{{list._id}}" data-name="{{name}}" data-description="{{description}}" class="deleteSection" href="#">Delete Section</a>
{{/each}}
Then in your js file:
Template.section.events({
'click a.deleteSection': function(evt) {
var list = Lists.find($(evt.target).data('id'));
var section = { name: $(evt.target).data('name'), description: $(evt.target).data('description') };
sections = list.sections;
sections.splice(sections.indexOf(section), 1);
Lists.update(list._id, $set: { sections: sections });
}
});

Dealing with an empty list in mustache.js

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.

Categories

Resources