Meteor retrieving document _id from reactive table - meteor

I am using Reactive-Table to display data saved in my Meteor app as shown from the code below, in each row of the table there is a link to edit the document related to this row. I am trying using the edit link 'click event' to capture the _id of the document related to the row being selected but can't seem to get the _id, can someone please check my code and let me know what I am missing / doing wrong here and how to capture the _id? Thanks
customerslist.html
<template name="customerslist">
<div class="customerslist">
<div class="page-header">
<h1>Customers List</h1>
</div>
<div>
{{> reactiveTable class="table table-bordered table-hover" collection=customersCollection settings=settings}}
</div>
</div>
</template>
customerslist.js
Template.customerslist.helpers({
customersCollection: function () {
return Customers.find({},{sort: {title: 1}});
},
settings: function () {
return {
rowsPerPage: 10,
showFilter: true,
showColumnToggles: false,
fields: [
{ key: 'name', label: 'Customer Name' },
{ key: 'email', label: 'Email' },
{ key: 'phone', label: 'Phone' },
{ key: '_id', label: 'Action', sortByValue: false, fn: function(_id){ return new Spacebars.SafeString('<a name="' + _id +'" class="edtlnk" target="_blank" href="' + _id + '/edit"> Edit </a>'); } }
]
};
}
});
Template.customerslist.customers = function () {
return Customers.find({},{sort: {title: 1}});
};
Template.customerslist.events({
'click .edtlnk': function(e) {
var cust = this;
event.preventDefault();
console.log('Customer ID: '+cust._id);
}
});

The way the package sets up data contexts, this will only be set to the customer object if the event selector matches the tr element. That makes event.currentTarget the tr, but event.target is still the edit link.
You could try something like this:
Template.customerslist.events({
'click .customerslist tr': function(e) {
if ($(e.target).hasClass('edtlnk')) {
var cust = this;
e.preventDefault();
console.log('Customer ID: '+cust._id);
}
}
});

I don't know Meteor though I am starting to play around with it so I don't care about up or down votes at all but learning the answer your question.
I found the Event Maps docs which I am sure you saw as well:
https://docs.meteor.com/#/full/eventmaps
This was listed in the doc:
{
'click p': function (event) {
var paragraph = event.currentTarget; // always a P
var clickedElement = event.target; // could be the P or a child element
}
}
If a selector matches multiple elements that an event bubbles to, it will be called multiple times, for example in the case of 'click
div' or 'click *'. If no selector is given, the handler will only be called once, on the original target element.
The following properties and methods are available on the event object passed to handlers:
type String
The event's type, such as "click", "blur" or "keypress".
target DOM Element
The element that originated the event.
currentTarget DOM Element
The element currently handling the event. This is the element that matched the selector in the event map. For events that bubble, it may be target or an ancestor of target, and its value changes as the event bubbles.
It seems to me that since target and currentTarget are DOM elements can't you get what you need from those or are you referring to the _id available in Meteor on an insert callback?

Related

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

Meteor Autoform - Prevent Field from Update

In my autoform the value of a field is the difference of two other input fields. It is not allowed to be updated by the user. Unfortuantly at the moment it is not possible to set a single field to readonly in a form. So my approach is to create an autoValue and a custom Validation to prevent an update
My code so far:
'SiteA.Settings.RXSignalODU1difference': {
type: Number,
label: "RX Signal [dBm] ODU1 difference (without ATPC +/- 3dbm)",
decimal: true,
autoform: {
type: "number"
},
autoValue: function() {
var ODU1gemessen = this.field("SiteA.Settings.RXSignalODU1");
var ODU1planned = this.field("SiteA.Settings.RXSignalODU1planned");
if (ODU1gemessen.isSet || ODU1planned.isSet) {
return ODU1gemessen.value - ODU1planned.value;
}
},
custom: function() {
var ODU1gemessen = this.field("SiteA.Settings.RXSignalODU1");
var ODU1planned = this.field("SiteA.Settings.RXSignalODU1planned");
var dif = ODU1gemessen.value - ODU1planned.value;
if (this.value !== dif) {
return "noUpdateAllowed";
}
}
},
My Simple.Schema message:
SimpleSchema.messages({noUpdateAllowed: "Can't be updated"});
Unfortunatly no message pops up.
EDIT
This method will create a disabled input box within your form that will automatically show the difference between two other input fields as the user types.
First, we define session variables for the values used in the calculation and initialize them to undefined.
Template.xyz.onRendered({
Session.set("ODU1gemessen", undefined);
Session.set("ODU1planned", undefined);
});
Then we define two events, that will automatically update these session variables as the user types.
Template.xyz.events({
'keyup #RXSignalODU1' : function (event) {
var value = $(event.target).val();
Session.set("ODU1gemessen", value);
},
'keyup #RXSignalODU1planned' : function (event) {
var value = $(event.target).val();
Session.set("ODU1planned", value);
}
});
Then we define a helper to calculate the difference.
Template.xyz.helpers({
RXSignalODU1difference : function () {
var ODU1gemessen = Session.get("ODU1gemessen");
var ODU1planned = Session.get("ODU1planned");
if (!!ODU1gemessen || !!ODU1planned) {
return ODU1gemessen - ODU1planned;
}
}
});
My HTML markup looks like this. Note, to still control the order of the form, I use a {{#autoform}} with a series of {{> afQuickfields }} rather than using {{> quickForm}}.
To display the calculated difference, I just create a custom div with a disabled text box.
<template name="xyz">
{{#autoForm collection="yourCollection" id="yourId" type="insert"}}
<fieldset>
<legend>Enter legend text</legend>
{{> afQuickField name="SiteA.Settings.RXSignalODU1" id="RXSignalODU1"}}
{{> afQuickField name="SiteA.Settings.RXSignalODU1planned" id="RXSignalODU1planned"}}
<div class="form-group">
<label class="control-label">RXSignalODU1difference</label>
<input class="form-control" type="text" name="RXSignalODU1difference" disabled value="{{RXSignalODU1difference}}">
<span class="help-block"></span>
</div>
</fieldset>
<button class="btn btn-primary" type="submit">Insert</button>
{{/autoForm}}
</template>
Original Answer - not recommended
If you are generating your form as a quickForm, you could do something like
{{>quickForm collection='yourCollection' omitFields='SiteA.Settings.RXSignalODU1difference'}}
This will leave this field off the form, so the user won't be able to update it.
If you still want to display the value somewhere along with the form as the user types in the other two values, you could define a helper in your client side js
something like
Template.yourFormPage.helpers({
diff: function () {
var ODU1gemessen = $('[name=SiteA.Settings.RXSignalODU1]').val();
var ODU1planned = $('[name=SiteA.Settings.RXSignalODU1planned]').val();
if (!!ODU1gemessen || !!ODU1planned) {
return ODU1gemessen - ODU1planned;
}
}
});
You'll want to double check how the field names are being rendered in your DOM. Autoform assigns the name attribute using the field names in your schema, but I don't know how it handles nested keys... (i.e. whether it names the element 'SiteA.Settings.RXSignalODU1' or just 'RXSignalODU1' )
And then just display the value somewhere in your html as :
{{diff}}

How to pass functions to capture events in custom component in Meteor with Blaze

I want to know how to bind/set template-passed-parameter-value to click event of an item in the template in Meteor.
I'm using Meteor 0.7.0.1 with Blaze UI package. My main idea is to build a re-usable custom components in Meteor with Blaze template engine.
I have the following component which is working fine at the moment but I want this to be more customizable and remove some dependencies.
This is my component template named postLinks
<template name="postLinks">
<div id="link-popover-wrapper" >
<ul class="link-popover">
{{#each linkOptions}}
<li><a tabindex="-1" class="link-action" id="link-{{value}}" href="#">{{label}}</a>
</li>
{{/each}}
</ul>
</div>
</template>
This postLinks component is used in the myPostItem helper.
Template.myPostItem.events({
'click .post-item-link-picker': function (evt, tmpl) {
var tmpPostId = this._id;
var tempData = {linkOptions:[{label:'Favorite', value : 'favorite'},{label:'Wish list', value : 'wishlist'},{label:'Later', value : 'later'}, {label:"Read", value:"read"}]};
var linkContent = Template.postLinks(tempData);
$(".item-link-picker").popover({
content: linkContent, html: true, placement: 'bottom', trigger: "manual",
template: "UI_POPOVER_TEMPLATE"});
$("#item-link-picker-"+tmpPostId).popover('show');
},
'click .link-action': function (evt, tmpl) {
//.... some code here to update link selection in db
}
});
Above code is working fine and I want to improve it to have following
Pass item click event externally to be bind to link-action like
After above two changes it will look like :
Template.myPostItem.events({
'click .post-item-link-picker': function (evt, tmpl) {
var tmpPostId = this._id;
var tempData = { itemClick:function(){}, linkOptions:[{label:'Favorite', value : 'favorite'},...]};
var linkContent = Template.postLinks(tempData);
$(".item-link-picker").popover({
content: linkContent, html: true, placement: 'bottom', trigger: "manual",
template: "UI_POPOVER_TEMPLATE"});
$("#item-link-picker-"+tmpPostId).popover('show');
}
});
I lack knowledge how/where to bind that passed event handling function to link-action elements in template or helper. I really appreciate if anybody could help to find a way to do that.
You go the other way around and use jQuery event triggering system, so
Template.myPostItem.events({
'click .link-action': function (evt, tmpl) {
$(evn.target).trigger('post-link-action', this /* extra data */);
},
});
This event can be easily catched in any parent template:
<template name="someOtherTamplate">
{{> myPostItem}}
</template>
Template.someOtherTemplate.events({
'post-link-action': function (evt, tmpl, extra) {
// the user of your component can define their custom behavior here
},
});
Please note that the event extra parameter will only be supported in the next Meteor release. Currently (0.8.0) it is included in the devel branch.

Meteor.js leaderboard example, how to change button value reactively?

I tried to play with the leaderboard example by adding a button to toggle sorting by name/score.
I succeed in adding event, but I also want to change the text value on the button.
I expect the added button text value(sort by name/score) get updated reactively(every time I click the button), however it fails to do so, only get updated when manually click on the player item.
Code added:
leaderboard.html(leaderboard template):
<template name="leaderboard">
<div class="leaderboard">
<input type="button" name="sort" value="Sort by {{sort_type}}" class='sort'/>
{{#each players}}
{{> player}}
{{/each}}
</div>
{{#if selected_name}}
<div class="details">
<div class="name">{{selected_name}}</div>
<input type="button" class="inc" value="Give 5 points" />
</div>
{{else}}
<div class="none">Click a player to select</div>
{{/if}}
</template>
leaderboard.js(client part):
if (Meteor.isClient) {
Meteor.startup(function(){
Session.setDefault('sort_order', {score: -1, name: 1});
Session.setDefault('sort', 'name');
});
Template.leaderboard.players = function () {
return Players.find({}, {sort: Session.get('sort_order')});
};
Template.leaderboard.selected_name = function () {
var player = Players.findOne(Session.get("selected_player"));
return player && player.name;
};
Template.leaderboard.sort_type = function() {
return Session.get('sort');
};
Template.player.selected = function () {
return Session.equals("selected_player", this._id) ? "selected" : '';
};
Template.leaderboard.events({
'click input.inc': function () {
Players.update(Session.get("selected_player"), {$inc: {score: 5}});
},
'click input.sort': function(){
var sort_order = Session.get('sort_order');
if (_.isEqual(sort_order, {score: -1, name: 1}) || _.isEqual(sort_order, {score: 1})) {
Session.set('sort_order', {name: 1});
Session.set('sort', 'score');
} else if (_.isEqual(sort_order, {name: 1})){
Session.set('sort_order', {score: 1});
Session.set('sort', 'name');
}
}
});
Template.player.events({
'click': function () {
Session.set("selected_player", this._id);
}
});
}
Thanks!
B.R.
Ian
This has to do with the preserve-inputs package. From the Meteor Docs:
By default, new Meteor apps automatically include the preserve-inputs package. This preserves all elements of type input, textarea, button, select, and option that have unique id attributes or that have name attributes that are unique within an enclosing element with an id attribute. To turn off this default behavior, simply remove the preserve-inputs package.
So basically because your button has a name attribute, Meteor is preventing it from changing when the template is re-rendered. There are three ways you can solve this:
Simply remove the name attribute from your input.sort attribute.
Remove the preserve-inputs package (not recommended if you're using the current template engine).
You can use the preview release of the new Meteor template engine, which no longer needs the preserve-inputs package because it automatically does more fine-grained DOM updates. You can run the app once with the preview release using:
meteor --release shark-1-29-2014-e
Or you can tell Meteor to update your app to this version by running:
meteor update --release shark-1-29-2014-e
Note that this new templating engine will be included in the Meteor core release by 1.0.

Is it possible to use session.get() as part of a selector in Meteor event maps?

I would like to add specific event only to elements that match a certain criteria, in this case, if the element is selected in the session or not.. Here's some example code:
Template.leaderboard.events({
'click Session.get("selected_team") .win': function () {
Teams.update(Session.get("selected_team"), {$inc: {won: 1, score : 5, played: 1}});
}
});
This looks for the selected team in the session & then updates that item. Does that make sense? Is there a better way to achieve what I want?
In the leaderboard example, the selected player is given a css class of "selected", so all you need to do is:
Template.player.events({
'click .selected': function () {
console.log('clicked on the selected player:', this.name);
}
});
You can use the same pattern for other elements that you might want to trigger events on conditionally: assign a particular css class to them (or not) depending on the condition.
If you prefer not to add a css class to elements through the templates (for whatever reason), you're better off simply checking your condition in Javascript:
Template.player.events({
'click': function () {
if (Session.get("selected_player") === this._id) {
console.log('clicked on the selected player:', this.name);
}
}
});
It won't work the way you're writing it since you've included the Session.get statement as part of the string. Something like this might work:
Template.leaderboard.events({
'click ' + Session.get("selected_team") + ' .win': function() {}
});
...but I wouldn't recommend it. Instead you should probably do something like this:
Template.leaderboard.events({
'click .team': function() {
Teams.update(this.id, {...});
}
});
Template.leaderboard.teams = function() {
return Teams.find({});
}
In your view:
<template name="leaderboard">
{{#each teams}}
<div class="team">{{team}}</div>
{{/each}}
</template>
Each .team still remembers its context within the leaderboard template, referred to as this inside the event handler, so you can just pass this.id to the query.

Resources