Can Ractive events work in markup rendered with the triple-stash syntax? - ractivejs

If I have the following in my Ractive template:
<span on-click='handleClick'>click me</span>
Then I can listen for the click with this:
app.on({
handleClick:function() {
alert("clicked!") ;
}
})
But lets say I have that same markup stored in a string variable called clicklyspan:
app.set("clicklyspan", "<span on-click='handleClick'>click me</span>")
and I render it in the template using the triple-stash syntax:
{{{clicklyspan}}}
The handleClick listener no longer gets fired. Is there anything I can do to force some kind of update to the rendered template so that the listener works? Say, after I do that app.set() call?
Here's a fiddle demonstrating the problem.
Thanks,
Dave

I have never used Ractive, but I did some research and it seems you have to use partials, like this:
var app = new Ractive({
el: 'container',
template: '#template',
data: {
myFunction: function() {
var template = '<a on-click="handleClick">I can now be clicked as well!</a>';
if (!this.partials.myFunction) {
this.partials.myFunction = template;
}
else {
this.resetPartial('myFunction', template);
}
return 'myFunction';
}
}
});
You will also need to use this instead of the triple mustache:
{{> myFunction() }}
Here's the corresponding jsfiddle.
Of course, replace myFunction with whatever name you like.
Related question I found useful:
RactiveJS events on tripple mustache

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)
}
});

vue.js reference div id on v-on:click

Using v-on:click I'd like to set a variable with the id of the div in Vue.JS - how do I reference this?
<div id="foo" v-on:click="select">...</div>
<script>
new Vue({
el: '#app',
data: {
},
methods: {
select: function(){
divID = this.id // ??
alert(divID)
}
}
})
</script>
You can extend your event handler with the event object $event. That should fit your needs:
<div id="foo" v-on:click="select($event)">...</div>
The event is passed on in javascript:
export default {
methods: {
select: function(event) {
targetId = event.currentTarget.id;
console.log(targetId); // returns 'foo'
}
}
}
As mentioned in the comments, `$event` is not strictly necessary, when using it as the only parameter. It's a nice reminder that this property is passed on, when writing it explicitly.
However, nobody will stop you from writing the short notation:
<div id="foo" #click="select">...</div>
Beware that the method will not receive the `$event` object when you add another parameter. You need to explicitly add it at the position you will handle it in the listener. Any parameter order will work:
<div id="foo" #click="select(bar, $event)">...</div>
To find more options of the v-on directive, you can look through the corresponding entry in the vue documentation:
Vue API Documentation - v-on
Inspired by #nirazul's answer, to retrieve data attributes:
HTML:
<ul>
<li
v-for="style in styles"
:key="style.id"
#click="doFilter"
data-filter-section="data_1"
:data-filter-size="style.size"
>
{{style.name}}
</li>
</ul>
JS:
export default {
methods: {
doFilter(e) {
let curTarget = e.currentTarget;
let curTargetData = curTarget.dataset;
if (curTargetData) {
console.log("Section: " + curTargetData.filterSection);
console.log("Size: " + curTargetData.filterSize);
}
}
}
}
Just to highlight another option than the selected answer for the same question, I have a delete button on a record and want to perform an action with the record's unique id (a number). I could do the selected answer as before:
<button :id="record.id" #click="del">×</button>
This leaves the unfortunate reality that my del function needs to pull the id attribute out of the javascript event, which is more about the API (the DOM) than my domain (my app). Also using a number as an element id isn't ideal and could cause a conflict if I do it more than once in a view. So here's something that's just as clear and avoids any future confusion:
<button #click="()=>del(record.id)">×</button>
methods: {
del(id) {
fetch(`/api/item/${id}`, {method:"DELETE"})
}
}
You see, now my del function takes the record id instead of an event, simplifying things.
Note that if you do this wrong, you will invoke your delete function immediately, which is not what you want. Don't do this:~~
<button #click="del(record.id)">×</button>
If you end up doing that, Vue will call the del function every time this html fragment is rendered. Using the anonymous function ()=>del(record.id) will return a function that's ready to be executed when the click event happens.
Actually #nirazul proved this is fine. Not sure what my issue was.

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'; };
}
});

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.

dynamically inserting templates in meteor

Ok so I've got my template in its own file named myApp.html. My template code is as follows
<template name="initialInsertion">
<div class="greeting">Hello there, {{first}} {{last}}!</div>
</template>
Now I want to insert this template into the DOM upon clicking a button. I've got my button rendered in the DOM and I have a click event tied to it as follows
Template.chooseWhatToDo.events = {
'click .zaButton':function(){
Meteor.ui.render(function () {
$("body").append(Template.initialInsertion({first: "Alyssa", last: "Hacker"}));
})
}
}
Now obviously the $("body").append part is wrong but returning Template.initialInsertion... doesn't insert that template into the DOM. I've tried putting a partia {{> initialInsertion}}but that just errors out because I dont have first and last set yet... any clues?
Thanks guys
In meteor 1.x
'click .zaButton':function(){
Blaze.renderWithData(Template.someTemplate, {my: "data"}, $("#parrent-node")[0])
}
In meteor 0.8.3
'click .zaButton':function(){
var t = UI.renderWithData(Template.someTemplate, {my: "data"})
UI.insert(t, $(".some-parrent-to-append"))
}
Is first and last going into a Meteor.Collection eventually?
If not, the simplest way I know is to put the data into the session:
Template.chooseWhatToDo.events = {
'click .zaButton' : function () {
Session.set('first', 'Alyssa');
Session.set('last', 'Hacker');
}
}
Then you would define:
Template.initialInsertion.first = function () {
return Session.get('first');
}
Template.initialInsertion.last = function () {
return Session.get('last');
}
Template.initialInsertion.has_name = function () {
return Template.initialInsertion.first() && Template.initialInsertion.last();
}
Finally, adjust your .html template like this:
<template name="initialInsertion">
{{#if has_name}}
<div class="greeting">Hello there, {{first}} {{last}}!</div>
{{/if}}
</template>
This is the exact opposite solution to your question, but it seems like the "Meteor way". (Basically, don't worry about manipulating the DOM yourself, just embrace the sessions, collections and template system.) BTW, I'm still new with Meteor, so if this is not the "Meteor way", someone please let me know :-)
I think you may want to use Meteor.render within your append statement. Also, note that if you are passing data into your Template, then you must wrap Template.initialInsertion in an anonymous function, since that's what Meteor.render expects. I'm doing something similar that seems to be working:
Template.chooseWhatToDo.events = {
'click .zaButton':function(){
$("body").append(Meteor.render(function() {
return Template.initialInsertion({first: "Alyssa", last: "Hacker"})
}));
}
}
Hope this helps!
Many answer here are going to have problems with the new Blaze engine. Here is a pattern that works in Meteor 0.8.0 with Blaze.
//HTML
<body>
{{>mainTemplate}}
</body>
//JS Client Initially
var current = Template.initialTemplate;
var currentDep = new Deps.Dependency;
Template.mainTemplate = function()
{
currentDep.depend();
return current;
};
function setTemplate( newTemplate )
{
current = newTemplate;
currentDep.changed();
};
//Later
setTemplate( Template.someOtherTemplate );
More info in this seccion of Meteor docs

Resources