Access reactive-table row data in template/helper? - meteor

For Tabular:
“In your template and helpers, this is set to the document for the
current row”
Is there an equivalent of this for reactive-table? I need to access a rows data in my template/helper but am just struggling to find a way to access it, with tabular it was as easy as using this.
I'm using a template for a column called "Status" and in this there are different types of labels, depending on what the row data returns it will be a different type of label. The code below works for Tabular but I'm not really sure how to make this work for reactive-table?
Example.html
<template name="ApplicationStatus">
<div class="row">
{{#if statusPending}}
<label class="label label-warning label-xs">"Pending</label>
{{/if}}
{{#if statusConnected}}
<label class="label label-primary label-xs">Connected</label>
{{/if}}
</div>
</template>
Example.js
Template.ApplicationStatus.helpers({
statusPending: function() {
if (this.applications.app_status === 'Pending')
return true;
else
return false;
},
statusConnected: function() {
if (this.applications.app_status === 'Connected')
return true;
else
return false;
}
});
I am currently adding it to my reactive-table by doing this:
{ tmpl: Template.ApplicationStatus, label: 'Status' }
Any info is greatly appreciated or if there's a better way to achieve what I'm trying to achieve I would love to hear that as well!

TL; DR
Try Template.instance().data instead of this.
Little explanation
I am not shure what happenes when you miss key definition, but accoriding to docs:
You can specify a template to use to render cells in a column, by
adding tmpl to the field options.
{ fields: [
{ key: 'name', label: 'Name', tmpl: Template.nameTmpl },
{ key: 'location', label: 'Location', tmpl: Template.locationTmpl }
] }
The template's context will be the full object, so it will have access
to all fields.
So inside helpers and event handlers you can get access to full row object via Template.instance().data.

Related

Search and Sort on the same page

I'm trying to implement sort and search to my items, so i started with sort and it works:
Template
<button class="sort">Sort</button>
{{#each cvs}}
{{> Interviu}}
{{/each}}
JS:
Template.Interviuri.onCreated(function () {
var self = this
self.autorun(function () {
self.sortOrder = new ReactiveVar(-1)
})
Template.Interviuri.helpers({
cvs() {
const instance = Template.instance()
return Cvs.find({}, { sort: { createdAt: instance.sortOrder.get() } })
},
})
Template.Interviuri.events({
'click .sort'(event, instance) {
instance.sortOrder.set(instance.sortOrder.get() * -1)
Next i wanted to implement Search on the same page. So the best way i could found was EasySearch.
But using EasySearch, it means i must change the way my items are being displayed. And then the sort doesn't work anymore.
Template
<div class="searchBox pull-right">
{{> EasySearch.Input index=cvsIndex attributes=searchAttributes }}
</div>
{{#EasySearch.Each index=cvsIndex }}
{{> Interviu}}
{{/EasySearch.Each}}
Collection
CvsIndex = new EasySearch.Index({
collection: Cvs,
fields: ['name'],
engine: new EasySearch.Minimongo()
})
JS
cvsIndex: () => CvsIndex,
How can i have both search and sort working at the same time?
With EasySearch you can use two methods on your index, namely getComponentDict() and getComponentMethods().
With getComponentDict() you can access search definition and options:
index.getComponentDict().get('searchDefinition');
index.getComponentDict().get('searchOptions');
You also have the corresponding setters to change the search definition/option.
getComponentMethods has mehods like
index.getComponentMethods().loadMore(integer);
index.getComponentMethods().hasMoreDocuments();
index.getComponentMethods().addProps(prop, value);
index.getComponentMethods().removeProps([prop])
From that you can set your prop, say index.getComponentMethods().addProp('sort', -1) and then on the index definition, in your MongoDB engine, set the sort from that prop:
index = new EasySearch.index({
// other parameters
engine: new EasySearch.MongoDB({
sort: function(searchObject, options) {
if(options.search.props.sort) {
return parseInt(options.search.props.sort);
}
return 1;
}
})
});
See EasySearch Engines for more info.

Meteor: Publish a collection and attach it to the appropriate user

I have a list of student users and collection of classes. When I publish the list of students I want it to display the classes they are attending. My code currently displays all the classes that every student is attending under each student instead of the classes that are relevant to the individual student.
How can I get it to display the right classes under the right stendents.
Path: classes.js
Template.classes.helpers({
studentList: ()=> {
return Meteor.users.find({_id: { $ne: Meteor.userId() }});
},
classes: ()=> {
return Classes.find({_id: { $ne: Meteor.userId() }});
},
});
Path: classes.html
{{#each studentList}}
{{profile.firstName}}
{{#each classes}}
{{class}}
{{/each}}
{{/each}}
Path: Classes.js
Schemas.Classes = new SimpleSchema({
class: {
type: String
},
teacherUserId: {
type: String,
autoValue: function() {
return this.userId
},
autoform: {
type: "hidden"
}
},
studentUserId: {
type: String,
optional: true,
autoform: {
type: "hidden"
}
}
});
The code above means: "Find all classes where the id of the class isn't equal to the current user's id." I think you want: "Find all classes where the current user's id is in the list of students."
Assuming classes have a students field, which is an array of user ids, you'd do this:
return Classes.find({students: Meteor.userId()});
Thanks to everyone's input my problem is solved! Tim hit the nail on the head. For those of you encountering a similar problem, check out the code below. I hope it helps. Thanks again Tim.
Path: classes.js
Template.classes.helpers({
classes: ()=> {
return JobOffers.findOne({candidateUserId: this._id});
},
});
Path: classes.html
{{#each studentList}}
{{profile.firstName}}
{{#with classes}}
{{class}}
{{/with}}
{{/each}}
When you are defining classes: () => Classes.find({_id: { $ne: Meteor.userId() }}) what you are telling the computer is:
Every time I ask you for the box labeled 'classes' I want you to go
through the box we called 'Classes' and fill 'classes' with everything
you find that doesn't have the '_id' property set to whatever you find
when you look inside of the box that 'Meteor.userId()' gives you.
That is not what you are wanting to ask your worker for. You want to ask your worker:
Every time I ask you for the box labeled 'classes' I want you to go
through the box we called 'Classes' and fill 'classes' with everything
that you find where the '_id' is set to a certain string that I am passing
you.
Which, without really trying to write it for you, it might have something to do with this being used somewhere in your helper function

Use Flow Router Param in Autoform

Friends,
I'm working on my first app in Meteor and hitting my head against the wall on something...
I have a scenario similar to a blog + comments situation where I have one collection (call it 'posts') and want to associate documents from another collection (call it 'comments').
The best way I know to pass the post._id to the comments as a "postId" field is to use the Flow Router params, since the form is on the 'post/:id' view.
But for the life of me, I cannot figure out how to get "var postId = FlowRouter.getParam('postId');" to pass to Autoform so it populates. I've tried adding it as a function in the schema, as a hook, and as a hidden field in the form on the page (obviously don't want to go that route).
Autoform is amazing and I want to use it, but may have to wire it up the hard way if I can't get this darn value to populate.
Any ideas? I've been hitting my head against the wall on this for a couple of days now.
Thanks!
First, just so we're on the same page, if you have your route is set up like this:
FlowRouter.route('/blog/:postId', {
action: function (params, queryParams) {
FlowLayout.render('layout', { body: 'postTemplate' });
},
});
You are able to call FlowRouter.getParam('postId') from inside the AutoForm hook
You'll need to use an AutoForm hook and have a complete schema. I'm using the package aldeed:collection2 for the schema set up. The postId field must be explicity declared. This code is running on both server and client.
Comments = new Mongo.Collection("comments");
Comments.attachSchema(new SimpleSchema({
comment: {
type: String,
label: "Comment"
},
postId: {
type: String
}
}));
Setting your form up like this is not what you want:
{{> quickForm collection="Comments" id="commentForm" type="insert"}}
That's no good because it will show the postId field in the HTML output. We don't want that, so you have to fully define the form like this:
{{#autoForm collection="Comments" id="commentForm" type="insert"}}
<fieldset>
{{> afQuickField name='comment' rows=6}}
</fieldset>
<button type="submit" class="btn btn-primary">Insert</button>
{{/autoForm}}
Then add the AutoForm hook. This code is running on the client.
var commentHooks = {
before: {
insert: function(doc){
var postId = FlowRouter.getParam('postId');
doc.postId = postId;
return doc;
}
}
};
AutoForm.addHooks(['commentForm'],commentHooks);
Make sure you have your allow/deny rules set up, and it should be working fine.
I was struggling with this same use case as well, and I found this on the Meteor forums: https://forums.meteor.com/t/use-flow-router-param-in-autoform/14433/2
If you're using a schema to build your form (either with the autoform or quickform tags) then you can put it right in there.
For example:
campaignId: {
type: String,
autoform: {
value: function() {
return FlowRouter.getParam('campaignId');
},
type: "hidden"
}
},

Meteor with ViewModel package not updating accross multiple child templates

I am new to meteor, and have a basic understanding of what is going on, but I am stuck with this example (the problem has been simplified as much as possible):
I have a template, and a child template:
<template name="test">
{{#each items}}
{{> testItem}}
{{/each}}
{{#each items}}
{{> testItem}}
{{/each}}
</template>
<template name="testItem">
<div {{ b "click: toggle"}}>{{value}}</div>
</template>
Template.test.viewmodel({
items: [],
onCreated: function() {
this.items().push({ value: 'test' });
}
})
Template.testItem.viewmodel({
toggle: function() {
this.value("changed");
}
});
The thing here is we have a single array of items in the viewmodel, and we render it through a child template multiple times.
When we toggle the item, it only toggles the single item template, not the other representation of it. It is behaving like it is copying the value, or some sort of scoping is taking place.
My expectation would be the second item to also change, but this is not the case - what am I missing, or misunderstanding here?
EDIT - Additional Investigation
If I change the item through the parent, and notify it has changed, the changes propogate throughout the child templates
Template.testItem.viewmodel({
toggle: function () {
this.parent().items()[0].value = "changed";
this.parent().items().changed();
}
});
Thanks!
You're right, when you do this.value("changed"); you're changing the value of the testItem view model, not the parent array. If you're going to modify the properties of objects in an array I highly recommend you use a client side Mongo collection instead. It will save you a lot of headaches.
Items = new Mongo.Collection(null);
Template.test.viewmodel({
items: function() {
return Items.find();
},
onCreated: function() {
Items.insert({ _id: "1", value: 'test' });
}
})
Template.testItem.viewmodel({
toggle: function() {
Items.update({ _id: this._id() }, { value: 'changed' });
}
});
btw, I rarely check SO. You will get quicker responses on viewmodelboard.meteor.com

in Meteor, how do i update a property on only one instance of a template?

If I have an {{# each}} binding in Meteor, and I want to update a property on only one instance of the template inside the #each. How would I do that? I've tried setting a value on the "template" object inside the events map, but that doesn't seem to be reactive. I've also tried binding to a Session property, but that will cause every instance to update instead of just the one I want...
for example:
{{#each dates}}
{{> dateTemplate}}
{{/each}}
<template name="dateTemplate">
{{date}}
<span style="color: red;">{{errorMsg}}</span> <--- how do i update errorMsg?
</template>
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = 'not valid'; <--- this doesn't do anything
}
});
EDIT TO ADDRESS ANSWER BELOW:
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = function() { return 'not valid';} <--- this also doesn't do anything
}
});
You don't have to use handlebars for this, because its not something that needs reactivity to pass the message through, reactive variables work best with db data, or data that would be updated by another client over the air.
You could use JQuery (included by default) to update it, it can also get a bit fancier:
<template name="dateTemplate">
{{date}}
<span style="color: red;display: none" class="errorMessage"></span>
</template>
Template.dateTemplate.events({
'click': function(event, template) {
$(template.find('.errorMessage')).html('Your Error Message').slideDown();
}
});
Ive edited it so the error is hidden by default, and slides down with an animation
I'm experimenting handling this by passing a different reactive object to each instance of the template. Then the template can bind to the reactive object (which is unique per instance) and we don't have any extra boilerplate.
It ends up looking like this:
Initial render:
Template.firstTemplateWithPoll(ContextProvider.getContext())
Template.secondTemplateWithPoll(ContextProvider.getContext())
// (I actually pass getContext an identifier so I always get the same context for the same template)
JS:
Template.poll.events = {
'click .yes' : function() {
this.reactive.set('selection', 'yes');
},
'click .no' : function() {
this.reactive.set('selection', 'no');
}
};
Template.poll.selection = function(arg) {
return this.reactive.get('selection');
}
Template:
<template name="poll">
<blockquote>
<p>
Your selection on this poll is {{selection}}
</p>
</blockquote>
<button class='yes'>YES</button>
<button class='no'>NO</button>
</template>
template.errorMsg should be a function that returns your error.
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = function() { return 'not valid'; };
}
});

Resources