This is my code:
HTML
Card 1:
<!-- Shopping list -->
<div id='shopping' class='card'>
{{#each task}}
<ul class="card__task">
<li>{{taskText}}</li>
<i class="fa fa-minus delete-task"></i>
</ul>
{{/each}}
</div>
Card 2:
<!-- Medicine list -->
<div id='medicine' class='card'>
{{#each task}}
<ul class="card__task">
<li>{{taskText}}</li>
<i class="fa fa-minus delete-task"></i>
</ul>
{{/each}}
</div>
template.helper
task: function() {
return Tasks.find({}, {
sort: {
createdAt: -1
}
});
}
tasks collection example
Ex.1:
...
"taskGroup": "medicine",
"taskText": "medicine task 1",
...
Ex.2:
...
"taskGroup": "shopping",
"taskText": "shopping task 1",
...
So, I got a collection where I store tasks with the taskGroup corresponding to the textarea input id. For now each iterates over every task that I have in my tasks collection and those 2 cards show all tasks.
Question:
How can tell each block to only iterate over tasks that have taskGroup equal to this cards id?
Something like:
task: function() {
return Tasks.find({
taskGroup: *the card where each block is located*.getAttribute("id");
}, {
sort: {
createdAt: -1
}
});
}
You can create a blaze helper that does the filter by the taskGroup
tasks: function(taskGroup) {
return Tasks.find({
taskGroup: taskGroup;
}, {
sort: {
createdAt: -1
}
});
}
That way you can call it on the template like this
{{#each tasks 'medicine'}}
...
{{/each}}
Related
I'm returning a 3rd party object to handlebars but i've noticed that some property names are prefixed with unique namespaces. Here's a short extract:
var data={
"soapenv:Envelope":{
"soapenv:Body":{
"CaseDetails":[
{
"Status":"Open",
"Opened":"2018-02-19T10:56:03.783Z",
"ns1:CaseReference": {"id":"111111"}
},
{
"Status":"Closed",
"Opened":"2017-02-19T10:56:03.783Z",
"ns3:CaseReference": {"id":"222222"}
},
{
"Status":"Closed",
"Opened":"2016-02-19T10:56:03.783Z",
"ns8:CaseReference": {"id":"3333"}
}
]
}
}
};
I want to loop through this object and output the information. Is it possible to match these unique names: ns1:CaseReference, ns3:CaseReference and ns8:CaseReference?
{{#each data.soapenv:Envelope.soapenv:Body.CaseDetails}}
<td>{{Status}}</td>
<td>{{Opened}}</td>
<td>{{???.id}}</td>
{{/each}}
You can write a custom HandlebarsHelper, ifKeyHasText to check whether the key contains a specific string (CaseReference in this scenario).
Handlebars.registerHelper('ifKeyHasText', function(array, value, options) {
var key;
if(Object.keys(array).some(function(k){
if(~k.indexOf(value)){
key = k;
}
}));
if(key){
return options.fn(this[key]);
}
});
And in the template, you can pass the string CaseReference to the helper {{#ifKeyHasText}} along with the data JSON as this object.
{{#each soapenv:Envelope.soapenv:Body.CaseDetails}}
<td>{{Status}}</td>
<td>{{Opened}}</td>
<td>
{{#ifKeyHasText this 'CaseReference'}}
{{id}}
{{/ifKeyHasText}}
</td>
{{/each}}
Hope this helps.
I managed to get it based on this post: https://stackoverflow.com/a/21452230/1622376
var data={
"soapenv:Envelope":{
"soapenv:Body":{
"CaseDetails":[
{
"Status":"Open",
"Opened":"2018-02-19T10:56:03.783Z",
"ns1:CaseReference": {"id":"111111"}
},
{
"Status":"Closed",
"Opened":"2017-02-19T10:56:03.783Z",
"ns3:CaseReference": {"id":"222222"}
},
{
"Status":"Closed",
"Opened":"2016-02-19T10:56:03.783Z",
"ns8:CaseReference": {"id":"3333"}
}
]
}
}
};
Handlebar using 'this' to traverse the tree:
<ul>
{{#each soapenv:Envelope}}
{{#each this}}
{{#each this}}
<li>
{{this.Status}}
{{#each this}}
{{this.id}}
{{/each}}
{{this.Opened}}
</li>
{{/each}}
{{/each}}
{{/each}}
</ul>
The code below should insert the selected item value in the Tasks collection, retain the info for later use, and a headerLabel should show the task selected. I am not able to get the headerLabel to show the task when the click .menuItem function runs. Thanks
Tasks = new Mongo.Collection('tasks');
Template.mainMenu.events({
'click .menuItem': function(event){
Tasks.insert({menuItem: $(event.currentTarget).data('value')});
}
});
Template.header.helpers({
headerLabel: function( id ){
return Tasks.findOne({_id: id}).menuItem;
},
tasks: function(){
return Tasks.find();
}
});
<template name="mainMenu">
<div class="container">
<div class="row">
<section class="col-xs-12">
<div class="list-group">
{{#each menuItems}}
<a href="#" class="list-group-item menuItem" data-value={{menuItem}}>
<img src="/abc.png">
{{menuItem}} <span class="badge">></span>
</a>
{{/each}}
</div>
</section>
</div>
<template name="header">
<h1><button class="col-xs-2 mainMenu" type="button">☰</button></h1>
<h3>
<label class="col-xs-8 text-center">
{{#if headerLabel}} {{headerLabel}} {{else}} Select an item {{/if}}
</label>
</h3>
<h1><button class="col-xs-2" type="button">⋮</button></h1>
</template>
Assuming that the click can happen multiple times, you'll need to pass an _id for the appropriate task into your helper:
Tasks = new Mongo.Collection('tasks');
Template.mainMenu.events({
'click .menuItem': function(event){
Tasks.insert({menuItem: $(event.currentTarget).data('value')});
}
});
Template.header.helpers({
headerLabel: function( id ){
var task = Tasks.findOne({_id: id});
if( task ) {
return task.menuItem;
}
},
tasks: function() {
return Tasks.find();
}
});
So what I'm doing there is saying find one task's menuItem that has the ID passed to the helper. I also added a tasks helper to get all tasks. So your template might look something like:
<template name="mainMenu">
{{#each task in tasks}}
<h4>{{headerLabel task._id}}</h4>
<!-- Additional code here... -->
{{/each}}
</template>
You'll obviously need to customize the HTML to your specific situation, but that should give you the gist. Hope that helps!
I was after some advice please on the best way to set up data contexts for a MongoDB database with Iron Router.
To explain I'm working on a fairly basic film reviews project, and deployed to Modulus at http://reviews-48062.onmodulus.net/
This returns a list of reviews, but would like to use routes to create other pages. I've installed Iron Router locally and have a local MongoDB collection (called tasks) with some data in it.
Have re-written some code to include routing information. This works successfully in displaying the routes, but doesn't seem to pull in any data into the {{each}} statement.
My JS code is as follows:
// define Mongodb collection
Tasks = new Mongo.Collection("tasks");
// set up home route and database query
Router.route('/', function () {
this.render('Home', {
tasks: function () { return Tasks.find({}, {sort: {title: 1}, limit: 10}); }
});
});
// define routes
Router.route('/one');
Router.route('/two');
Router.route('/three');
The HTML code is:
<head>
<title>Iron router sandbox</title>
</head>
<body>
</body>
<template name="Home">
{{> Nav}}
<h1>Home</h1>
<p>This is a test</p>
{{#each tasks}}
<li>
<strong>{{title}}</strong>
<p>Directed by {{director}}</p>
<p>{{review}}</p>
<p>Available on: {{format}}</p>
</li>
{{/each}}
</template>
<template name="One">
{{> Nav}}
<h1>Page One</h1>
<p>Some more text.</p>
</template>
<template name="Two">
{{> Nav}}
<h1>Page Two</h1>
<p>A bit more text.</p>
</template>
<template name="Three">
{{> Nav}}
<h1>Page Three</h1>
<p>Even more text.</p>
</template>
<template name="Nav">
<ul>
<li>
Home
</li>
<li>
Page One
</li>
<li>
Page Two
</li>
<li>
Page Three
</li>
</ul>
</template>
My understanding is that the data context is set up for the "Home" template, so not sure what's wrong with the code.
Any advice much appreciated.
tasks is not a valid property to send to that object. You need to set the data context for IronRouter using data instead. Change tasks to data, then set up a helper on your template to get tasks data:
Template.Home.helpers({
tasks() {
return Tasks.find({}, {sort: {title: 1}, limit: 10});
}
});
You can return a data context with i-r but it needs to be named data:
Router.route('/', function () {
this.render('Home', {
data: function () {
return { tasks: Tasks.find({}, {sort: {title: 1}, limit: 10}); } };
});
});
This will return a data context to your template and then the tasks key will contain a cursor of tasks.
In Meteor, I have an app where I make a list of items grouped by tags, where non-tagged items come first and then tagged items are hidden under a "tag header" drop down.
I haven't touched this app since 0.8 came out, and I was using a block helper in a template which worked fine in pre-0.8...
See working jsfiddle here
Handlebars.registerHelper('eachItem', function(context, options) {
var ret = "";
for(var i=0, j=context.length; i<j; i++) {
if(!context[i].tag){
ret = ret + options.fn(context[i]);
} else {
if(i===0||!context[i-1].tag ||context[i-1].tag !== context[i].tag){
ret = ret + '<li class="list-group-item"><a data-toggle="collapse" data-target="#'+ context[i].tag +'"> Items tagged '+context[i].tag + '</a></li><div id="'+context[i].tag+'" class="collapse">';
}
ret = ret + options.fn(context[i]);
if(i+1<j && context[i+1].tag !== context[i].tag){
ret = ret + '</div>';
}
}
}
return ret;
});
But I'm struggling a bit to translate this into post-0.8 Meteor
The inserted HTML must consist of balanced HTML tags. You can't, for example,
insert " </div><div>" to close an existing div and open a new one.
One idea I had was to render the non-tagged items and also the containers in a vanilla {{#each}} loop (with 2 different templates), and then do something like this
Template.myListContainer.rendered = function(){
_.each(tags, function(tag){
var tagged_items = _.filter(items, function(item){ return item.tag == tag; });
_.each(tagged_items, function(item){
UI.insert(UI.RenderWithData(listItemTemplate, { item : item }), tagContainer);
});
});
}
Is there a simpler way to do this ? If the items come from a collection, will they keep their reactivity ?
Many thanks in advance !
If you haven't already, it's worth reading the Meteor wiki on migrating to blaze.
Anyhow, this one seems difficult to implement as a straight iteration.
If you don't need them in a specific order, I would just list items w/ and w/o tags, eg:
Template.example.helpers({
dataWithoutTags: function(){
return items.find({tag:{exists: false}});
},
tagList: function(){
// create a distinct list of tags
return _.uniq(items.find({tag:{exists: true}}, {tag: true}));
},
dataForTag: function(){
// use `valueOf` as `this` is a boxed-string
return items.find({tag: this.valueOf()});
}
});
Template:
<template name="example">
<div class='panel panel-default'>
<ul class='list-group'>
{{#each dataWithoutTags}}
<li class='list-group-item'>{{name}}</li>
{{/each}}
{{#each tagList}}
<li class="list-group-item">
<a data-toggle="collapse" data-target="#{{this}}"> Items tagged {{this}}</a>
</li>
<div id="{{this}}" class="collapse">
{{#each dataForTag}}
<li class='list-group-item'>{{name}}</li>
{{/each}}
</div>
{{/each}}
</ul>
</div>
</template>
If you do need them in a specific order - eg. grouping only consecutive items (as per your example). The only option would be to pre-process them.
eg:
Template.example.helpers({
itemsGrouped: function(){
dataGroups = [];
currentGroup = null;
items.find({}, {sort: {rank: 1}}).forEach(function(item){
if (currentGroup && (!item.tag || currentGroup.tag != item.tag)){
dataGroups.push(currentGroup);
currentGroup = null;
}
if (item.tag){
if (!currentGroup){
currentGroup = {
group: true,
tag: item.tag,
items: [item]
};
} else {
currentGroup.items.push(item);
}
} else {
dataGroups.push(item);
}
});
if (currentGroup){ dataGroups.push(currentGroup); }
return dataGroups;
}
});
Template:
<template name="example">
<div class='panel panel-default'>
<ul class='list-group'>
{{#each itemsGrouped}}
{{#if group}}
<li class="list-group-item">
<a data-toggle="collapse" data-target="#{{tag}}"> Items tagged {{tag}}</a>
</li>
<div id="{{tag}}" class="collapse">
{{#each items}}
<li class='list-group-item'>{{name}}</li>
{{/each}}
</div>
{{else}}
<li class='list-group-item'>{{name}}</li>
{{/if}}
{{/each}}
</ul>
</div>
</template>
If you log the value you are returning from the eachItem helper, you will see you are not closing the last div element. Fixing that may get it working again. However, you have div elements as direct children of the ul element, which you are not supposed to according to the HTML5 specification:
Permitted contents: Zero or more li elements
Also, I think it would be a better option for you to prepare your context in a format that makes it easier to let simple templates take care of the rendering. For example, using the same data from your jsfiddle, suppose you had it in the following format:
tagGroups = [{
names: ["item1", "item2"]
},{
tag: "red",
names: ["item3", "item4"]
},{
tag: "blue",
names: ["item5", "item6", "item7"]
}]
The following templates would give you the expected result in a valid html format. The ending result is a bootstrap panel containing collapsible list-groups:
<template name="accordion">
<div class="panel panel-default">
{{#each tagGroups}}
{{> tagGroup}}
{{/each}}
</div>
</template>
<template name="tagGroup">
{{#if tag}}
{{> namesListCollapse}}
{{else}}
{{> namesList}}
{{/if}}
</template>
<template name="namesList">
<ul class="list-group">
{{#each names}}
<li class="list-group-item">{{this}}</li>
{{/each}}
</ul>
</template>
<template name="namesListCollapse">
<div class="panel-heading"><a data-toggle="collapse" data-target="#{{tag}}">Items tagged {{tag}}</a></div>
<ul id="{{tag}}" class="panel-collapse collapse list-group">
{{#each names}}
<li class="list-group-item">
{{this}}
</li>
{{/each}}
</ul>
</template>
And here is an example of a helper to transform your data so you can make a quick test to see it working:
Template.accordion.tagGroups = function () {
var data = [{
name : 'item1'
},{
name : 'item2'
},{
name : 'item3',
tag : 'red'
},{
name : 'item4',
tag : 'red'
},{
name : 'item5',
tag : 'blue'
},{
name : 'item6',
tag : 'blue'
},{
name : 'item7',
tag : 'blue'
}];
data = _.groupBy(data, 'tag');
data = _.map(data, function (value, key) {
var obj = {};
if (key !== 'undefined') {
obj.tag = key;
}
var names = _.map(value, function (item) {
return item.name;
});
obj.names = names;
return obj;
});
//console.dir(data);
return data;
};
And yes, if the items come from a collection, you will get reactivity by default.
I am trying to get reactive-tables to work but I am having no luck following the instructions on GitHub.
This is what I have:
In my Main.html:
{{> courseTable }}
in my course_table.html:
<template name="courseTable">
<div id="table">
{{ > reactiveTable collection=Courses}}
</div>
</template>
in courses.js:(works with autoForm)
Courses = new Meteor.Collection("courses", {
schema: {....
Is there something I am missing? From my understanding once these commands are used, the rest is done from within the package. I can't find any more information on this package anywhere.
What I have now just shows a blank screen.
Thanks in advance!
This is what I have: (I'm using Meteor framework and bootstrap-3 package)
in index.html
<template name="clientes">
<div class="container">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Clientes</h3>
</div>
<div class="panel-body">
{{> reactiveTable collection=tables settings=tableSettings}}
</div>
</div>
</div>
</template>
in index.js
var Clientes = new Meteor.Collection('clientes')
Template.clientes.tables = function () {
return Clientes;
}
Template.clientes.tableSettings = function () {
return {
rowsPerPage: 10,
showFilter: false,
showNavigation: 'auto',
fields: [
{ key: 'nombre', label: 'Nombre' },
{ key: 'apellido', label: 'Apellido' },
{ key: 'correoe', label: 'E-mail' }
],
useFontAwesome: true,
group: 'client'
};
}
With this I can display all the records in the collection.
I hope this help you to go any further.
Courses is the collection object. To get some courses, you need to query the courses with find:
Courses.find()
However, to make this accessible in the template, you need a helper function.
//course_table.js
Template.courseTable.helpers({
courses: function () {
return Courses.find()
}
});
Then you can can set the table collection using the helper method (I used a lowercase "courses" for the helper method for clarity):
{{> reactiveTable collection=courses}}