Ember use separate child template files - javascript

I have the below template file (actually used for my component)
{{#each details.secs as |sec|}}
<div class="row">
{{#each secs.flds as |fld|}}
// if fld.id is 'abc', use abc.hbs
// if fld.id is 'xyz', use xyz.hbs
{{/each}}
</div>
{{/each}}
My question is how can I use separate sub-template files and include them in the above parent file (based on condition)
Thus if field.id is 'abc', it should use rendering logic from abc.hbs
Also abc.hbs would need the 'field' model input for rendering purpose (It's output should get appended to the main template)

With Ember 2.0, creating and registering our own eq (equals) helper is super easy:
ember generate helper eq
Include our equality function:
// app/helpers/eq.js
import Ember from 'ember';
export function eq(params, hash) {
return (params[0] === params[1]);
}
export default Ember.Helper.helper(eq);
And use it!
{{#each details.sections as |section|}}
<div class="row">
{{#each section.fields as |field|}}
{{#if (eq field.id "abc")}}
{{abc-component data=field}}
{{/if}}
 {{#if eq field.id "xyz)}}
{{xyz-component data=field}}
{{/if}}
{{/each}}
</div>
{{/each}}

Related

Express Handlebars getting first letter of every string

Some discord servers don't have icons, and discord renders the icons as the first char of each word in the guilds name. I am trying to do that in handlebars with express. I did the following, but it errored.
<h1>Welcome, {{username}}!</h1>
<ul>
<h2>Your guilds</h2>
{{#each guilds as |key value|}}
<li>
{{#if key.icon}}
<img src="https://cdn.discordapp.com/icons/{{key.id}}/{{key.icon}}?size=64" alt="profile"> </p>{{key.name}}</p>
{{else}}
<div>
<h3>{{ key.name.match(/\b\w/g).join('') }}</h3>
<p>{{key.name}}</p>
</div>
{{/if}}
</li>
{{/each}}
</ul>
Error
Error: Parse error on line 10:
... <h3>{{ key.name.match(/\b\w/g).join(
-----------------------^
Expecting 'ID', got 'INVALID'
In Handlebars, you can work with custom helpers functions that handle javascript logic outside the templates. You should make two changes in your project to achieve that:
create a file with custom helpers, normally inside helpers folder in your project:
./helpers/hbs.js
function getFirstLetter(name) {
return name.match(/\b\w/g).join("");
}
module.exports = { getFirstLetter };
Modify handlebars middleware configuration in the app.js to use custom helpers created by you:
./app.js
/**
* Import Handlebars
*/
const exphbs = require("express-handlebars");
/**
* Import Custom Helpers
*/
const hbs = require("./helpers/hbs");
/**
* Template Engine
*/
app.engine(
".hbs",
exphbs({
... all configuration here...
helpers: hbs, // add custom helpers.
})
);
Now you can use custom helper inside your template like this:
<h1>Welcome, {{username}}!</h1>
<ul>
<h2>Your guilds</h2>
{{#each guilds as |key value|}}
<li>
{{#if key.icon}}
<img src="https://cdn.discordapp.com/icons/{{key.id}}/{{key.icon}}?size=64" alt="profile"> </p>{{key.name}}</p>
{{else}}
<div>
<h3>{{getFirstLetter key.name}}</h3>
<p>{{key.name}}</p>
</div>
{{/if}}
</li>
{{/each}}
</ul>

Toggle row independently of others in an {{#each}} - Ember

I've got this chunk of code in my controller to output everything I'd like to in the first row. The second row is going to be available upon a click action. However, I'm trying to make it so that when I click any row shown, it'll only display the 2nd row that given row. Not the 2nd row of all the others.
This is in my template.
{{#each chosenSomething as |something|}}
<tr class='main' {{action 'toggleHelp'}}>
<td>{{something.this}}</td>
<td>{{something.that}}</td>
<td>{{something.hey}}</td>
<td>{{something.you}}</td>
</tr>
{{#if isDisplay}}
<tr class='help'>
<td class='instruction'></td>
<td class='video'></td>
</tr>
{{/if}}
{{/each}}
This is my JS for the action.
toggleHelp() {
this.toggleProperty('isDisplay');
},
I know how I would approach this using jQuery, but I'd like to know the Ember way of doing such action. I thought about including index, but unsure how it should be written.
I would suggest making a component and handing the toggle within.
https://ember-twiddle.com/e6ccad3d2ffdc4a9d627db12a1317fa5?openFiles=templates.components.toggle-thing.hbs%2Ctemplates.components.toggle-thing.hbs
// toggle-thing.hbs
<header {{action 'toggleAnswer'}}>
{{data.question}}
</header>
{{#if open}}
<footer>
{{data.answer}}
</footer>
{{/if}}
// toggle-thing.js
import Ember from 'ember';
export default Ember.Component.extend({
// tagName: 'tr', // or whatever if you like
// classNames: ['thing', 'row', 'etc'], // smooth out the template a bit later...
open: false, // default
actions: {
toggleAnswer() {
this.toggleProperty('open');
console.log( this.get('open') );
},
},
});
// application.hbs or wherever you want to list the things
<ul class='thing-list'>
{{#each model as |thing|}}
<li class='thing'>
{{toggle-thing data=thing}}
</li>
{{/each}}
</ul>
has a 'thing' model and is pulling in some question answers to the model hook for example.
import Ember from 'ember';
var data = [
{
question: "How to be funny",
answer: "Stand on your head",
},
{
question: "How to be smart",
answer: "Just listen more often.",
},
];
export default Ember.Route.extend({
model() {
return data;
},
});
The Ember guide has a toggle example: https://guides.emberjs.com/v2.13.0/tutorial/simple-component/
Augment each element of chosenSomething to include the open/close state.
You'll then be able to have:
{{#each chosenSomething as |something|}}
<tr class='main' {{action 'toggleHelp' something}}>
<td>{{something.this}}</td>
<td>{{something.that}}</td>
<td>{{something.hey}}</td>
<td>{{something.you}}</td>
</tr>
{{#if something.isDisplay}}
<tr class='help'>
<td class='instruction'></td>
<td class='video'></td>
</tr>
{{/if}}
{{/each}}
and the JS:
toggleHelp(something) {
Ember.set(something, 'isDisplay', !something.isDisplay);
}
If you don't want to modify the original data, you can have a computed property generate the appropriate data such as:
chosenSomethingWithState() {
return (this.get('chosenSomething') || []).map((item)=>{isDisplay:false, item})
}.property('chosenSomething')
and adjust the template accordingly

Why does nested each in template output nothing

While this is rendered as expected:
{{#each Items}} // a collection
{{title}}
{{/each}}
{{#each types}} // another ollection
{{refId}}
{{/each}}
if I put it together:
{{#each Items}}
{{title}}
{{#each types}}
{{refId}}
{{/each}}
{{/each}}
The #each types is empty.
The template helpers:
Template.menuItems.helpers({
restMenuItems: function() {
return RestMenuItems.find({restRefId: this._id}, {sort:Template.instance().sort.get()});
},
restMenuSideItems: function() {
return RestMenuSideItems.find({restRefId: this._id}, {sort:Template.instance().sort.get()});
}
});
Template.menuItems.onCreated( function(){
this.subscribe('restMenuItems');
this.subscribe('restMenuSideItems');
});
And some of the template code:
{{#each restMenuItems}}
<div>
<select class="list-group select_side_addons">
{{#each restMenuSideItems}}
<option>{{restRefId}}</option>
{{/each}}
</select>
</div>
{{/each}}
Even when replacing {{#each restMenuSideItems}} by {{#each ../restMenuSideItems}}, nothing appears.
What's wrong?
Because the #each clause changes the data context to the current item.
If you want a list of types inside each of the Items, you can get the parent data context using ...
If types is an attribute of the parent context:
{{#each Items}}
{{title}}
{{#each ../types}}
{{refId}}
{{/each}}
{{/each}}
If types is a template helper, you can pass the parent context as an argument to it:
{{#each Items}}
{{title}}
{{#each types ..}}
{{refId}}
{{/each}}
{{/each}}
and use the context for your query.
Template.myTemplate.helpers({
types: function(parent) {
// you now have the parent context here.
// use parent._id etc. where you used this._id in the
// "flat" version.
}
});

Ember CLI- conditional output within an each loop using a component instead of itemController

In my Ember CLI app, I'm using {{#each}} helpers to output the rows of a table. 'name' 'created_date' and 'type' are all defined in the related model.
{{#each model as |job|}}
<tr>
<td>{{job.name}}</td>
<td>{{job.created_date}}</td>
<td>{{job.type}}</td>
<td>
{{#if typeZero}}
<p>Content that will display if the value of 'type' is 0.</p>
{{/if}}
</td>
</tr>
{{/each}}
In the fourth table cell of each row, I'd like to display certain content if that value of 'type' for that record is 0.
I first tried adding an itemController to the each helper:
{{#each job in model itemController="jobrowcontroller"}}
......
{{/each}}
This gave me an error: "Uncaught Error: Assertion Failed: The value that #each loops over must be an Array. You passed ***#controller:array:, but it should have been an ArrayController"
I found that itemController is now deprecated, and components should be used instead.
I created a component named job-table-row, and updated the page template:
{{#each model as |job|}}
{{#job-table-row model=job as |jobTableRow|}}
<tr>
<td>{{job.name}}</td>
<td>{{job.created_date}}</td>
<td>{{job.type}}</td>
<td>
{{#if typeZero}}
<p>Content that will display if the value of 'type' is 0.</p>
{{/if}}
</td>
</tr>
{{/job-table-row}}
{{/each}}
In the component handlebars file, I simply use {{yield}} and everything displays fine.
In the component js file, I have:
import Ember from 'ember';
export default Ember.Component.extend({
tagName: '',
typeZero: function() {
var currentStatus = this.get('model.status');
if (currentStatus === 0) {
this.set('typeZero', true);
} else this.set('typeZero', false);
}.on('didInsertElement'),
});
The problem with this is that the function 'typeZero' doesn't run. Is it possible to achieve this with a component, or do I need to use a different method altogether?
You cannot yield because typeZero only exists inside the component. Instead, move the template to the component:
// templates/components/job-table-row.hbs
<td>{{model.name}}</td>
<td>{{model.created_date}}</td>
<td>{{model.type}}</td>
<td>
{{#if statusZero}}
<p>Content that will display if the value of 'status' is 0.</p>
{{/if}}
</td>
And simplify your template outside:
<table>
<tbody>
{{#each model as |job|}}
{{job-table-row model=job}}
{{/each}}
</tbody>
</table>
Also, you can replace your complex method with a computed property:
// components/job-table-row.js
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'tr',
statusZero: Ember.computed.equal('model.status', 0)
});
See it all working at http://ember-twiddle.com/de8a41b497ef4f116bab

How to access another collection by ID in Meteor template?

Let's say I have a Posts collection, the data for which looks like this:
var post1 = {
title: "First post",
post: "Post content",
comments: [<comment id>, <another comment id>, <etc>]
};
And I have a corresponding Comments collection. I've published and subscribed to both collections and want to display a post with it's comments.
How do I go about displaying the comments for that specific post?
<template name="post">
<h1>{{title}}</h1>
<p>{{post}}</p>
{{#each comments}}
// Only have access to the ID
{{/each}}
</template>
I could create a helper like this:
Template.post.helpers({
displayComment: function(id) {
return Comments.findOne(id).fetch().comment;
}
});
and do:
<template name="post">
<h1>{{title}}</h1>
<p>{{post}}</p>
{{#each comments}}
{{displayComment #index}}
{{/each}}
</template>
But then I'd have to create a helper for each of the comment object's properties, etc.
What's the clean way to do it? I don't want to populate the comments field on the post object, since that would mean calling .fetch(), and posts would no longer be reactive.
A couple of suggestions:
<template name="post">
<h1>{{title}}</h1>
<p>{{post}}</p>
{{#each comments}}
{{#with commentDetails}}
{{userName}} //
{{content}} // These are properties of a comment document
{{upvotes}} //
{{/with}}
{{/each}}
</template>
Template.post.helpers({
commentDetails: function() {
return Comments.findOne(this);
}
});
That will set the data context within each with block to be the comment object returned by looking up the current _id (which is the value of this in the commentDetails helper within the each block).
An alternative would be to just use an each block on its own, but have it iterate over a cursor:
<template name="post">
<h1>{{title}}</h1>
<p>{{post}}</p>
{{#each commentCursor}}
{{content}}
... // other properties
{{/each}}
</template>
Template.post.helpers({
commentCursor: function() {
return Comments.find({_id: {$in: this.comments}});
}
});

Categories