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

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.

Related

meteor simple reactive var

I'm working in meteor trying to use a reactive var to switch the content in the main panel between two tabs. I've been able to test the content successfully on it's own so I'm fairly confident the issue lies in the reactive var code. Specifically I think the issue is with the tab: function() but after many searches and reading documentation I haven't found a solution.
The relevant js:
Template.content.onCreated( function() {
this.currentTab = new ReactiveVar('form');
});
Template.content.helpers({
tab: function() {
return Template.instance().currentTab.get();
}
});
Template.content.events({
'click .nav li': function (event, template) {
var currentTab = $( event.target ).closest( "li" );
currentTab.addClass( "active" );
$( ".nav li" ).not( currentTab ).removeClass( "active" );
Template.currentTab.set();
}
});
The relevant html:
<template name ="content">
<ul class ="nav">
<li data-template="form">Form</li>
<li data-template="results">Results</li>
</ul>
{{ > Template.dynamic template=tab}}
</template>
{{ > Template.dynamic template=tab}}
This is calling the tab helper to get a string that is the name of the template you want to show here. It should work the first time because you start out by setting the value of currentTab to "form".
To change the template that gets shown, you need to change the value of currentTab to a string matching the name of the new template. You're not doing that.
Template.currentTab.set();
This is where you should be doing that. Instead you're calling set() on the currentTab property of Template, which I don't think exists. Template with a capital T is a Meteor object, not the template instance that I think you're trying to refer to. And to set a new value for currentTab, you actually need to provide a value. Like so:
Template.content.events( {
'click .nav li': function(event, instance) {
//logic to decide which template you want to show
//and put the name of that template in templateName
instance.currentTab.set(templateName)
}
});

Polymer two way binding

Banging my head on how to use polymer two way binding.
I have a home made Polymer element that defines a boolean property through
Polymer({
is: "test-element",
ready: function() {},
properties: {
propEnabled: {
type: Boolean,
notify: true,
value: false,
observer: "propEnabledChanged"
}
},
// Called when aoEnabled is changed
propEnabledChanged: function() { console.log("propEnabled value switched to " + this.propEnabled); },
});
Now I'm using this in an HTML page
<body>
<template id="t" is="dom-bind">
<test-element id="testElement"></test-element>
<paper-toggle-button checked="{{Model.propEnabled}}">prop. enabled</paper-toggle-button>
<button id="switchInternalState">switch state</button>
</template>
</body>
<script>
var t = document.querySelector('#t');
document.addEventListener('WebComponentsReady', function() {
console.log('WebComponentsReady');
// We have to bind the template with the model
var t = document.querySelector('#t');
t.Model = document.getElementById("testElement");
// chaging the property directly does not reflect in the GUI... :-(
var button = document.getElementById("switchInternalState");
button.addEventListener("click", function() {
t.Model.set("propEnabled", !t.Model.propEnabled);
});
});
</script>
But when clicking on the switch state button...
I get the log propEnabled value switched to true
But the toogle button on the page does not change...
If I add a simple
<label>{{Model.propEnabled}}</label>
The label does not change either...
To me it looks a bit like one way binding where it should be 2 way as
toggling the button fire the log and properly change the component propEnabled value. So it really looks like one way binding to me.
So... How can we actually benefit from two way binding with Polymer templates ????
You need to assign the propEnabled property from dom-bind to the test-element through html.
<test-element id="testElement" prop-enabled={{propEnabled}}></test-element>
<paper-toggle-button checked="{{propEnabled}}">prop. enabled</paper-toggle-button>
Also you don't need the variable t.Model in your script. You can remove it. The event listener should be like below
button.addEventListener("click", function() {
t.propEnabled = !t.propEnabled;
});
The following plunker has the working code: http://embed.plnkr.co/13QJ7QFETIBg4bEiCMS7/preview

Gridster add_widget with Meteor

I have some troubles wihth gridster & meteor. At first, I loaded the whole widgets into my template and then recalculate the grid with a method below.
I have a template named dashboard, in this template I do a loop through my widgets and call a second template called widgetTmpl that contains all the formatted html
<template name="dashboard">
<div id="dashboardBody">
<button id="configMode" class="btn btn-primary"> <i class="fa fa-pencil"></i>Configuration </button>
<div class="gridster">
<ul id="widgetItemList" class="widget_item">
{{#each activeWidgets}}
{{> widgetTmpl}}
{{/each}}
</ul>
</div>
</div>
</template>
I execute this code with the callback onRendered
Template.dashboard.onRendered(function(){
var gridsterUl = jQuery(".gridster ul");
gridsterUl.gridster({
widget_margins: [5, 5],
widget_base_dimensions: [25, 25],
resize : {
enabled : true
},
draggable: {
start: overlay_fix_start,
stop: overlay_fix_stop
},
serialize_params : function($w, wgd){
return {
id : $w.prop('id'),
col : wgd.col,
row : wgd.row,
size_x : wgd.size_x,
size_y : wgd.size_y
};
}
});
});
This works fine, but when I add or reload a widget, I have to refresh the page due to the onRedered callback.
I heard about a gridster.add_widget methods, that does the perfect job but I don't know how to implement it to my code.
Where should I use the gridster.add_widget ?
There is methods like Blaze.insert and Blaze.renderWithData but I have no idea how to use it
I've never tried using Gridster, however I have integrated d3 into quite a few of my meteor apps. When I want the d3 context to reactivity change without a page refresh, I use a Tracker.autorun in my onRendered callback with a reactive datasource. For example my simplified code would look like this:
Template.d3Plot.onRendered( function () {
Tracker.autorun(function() {
// Function That Draws and ReDraws due to Tracker and Reactive Data
drawPlot(Plots.find());
});
});
drawPlot = function (plotItems) {
.....
}
Where Plots is my mongo collection so that whenever a new item is inserted/updated in the collection, the drawPlot function re-fires.

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.

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