I ran into a very strange issue in Meteor...
I have a function that renders a template and append it to a div, inside the added template there's a button that can trigger listener events.
The strange thing is the click event can be triggered when I render the template without any data object as argument, but as soon as I pass in a data object into the template, all javascript within the newly appended template stop working...
Ideally I want to be able to pass in data to the template and have the events fire correctly, does anyone know what's exactly going on here? Thanks a lot!
Failure Scenario with passing in data to template:
client.coffee
Template.detail.events {
'click .ol_self_help_btn': (event) ->
alert 'event fired' //This is never triggered
}
Template.room.events {
'click .ol_detail': (event) ->
element = $(event.currentTarget).closest('.ol_property')
element.append Meteor.render Template.detail # //Passing in "this" data object to the template
}
detail.html
<Template name="detail">
<div class="row ol_detail_panel">
<button style='button' class='ol_self_help_btn'>Click Me</button> //The button that the event is attached to
</div>
</div>
</Template>
Success Scenario without data object passing:
client.coffee
Template.detail.events {
'click .ol_self_help_btn': (event) ->
alert 'event fired' //This is triggered when clicking on the button
}
Template.room.events {
'click .ol_detail': (event) ->
element = $(event.currentTarget).closest('.ol_property')
element.append Meteor.render Template.detail //NOT Passing in data to the template function
}
detail.html
<Template name="detail">
<div class="row ol_detail_panel">
<button style='button' class='ol_self_help_btn'>Click Me</button> //The button that the event is attached to
</div>
</div>
</Template>
This should render the current # into the template.
Template.room.events
'click .ol_detail': (event) ->
element = $(event.currentTarget).closest('.ol_property')
template = Template.detail #
element.append Meteor.render(template)
This looks messy:
element.append Meteor.render Template.detail
To debug I would break it up into something like (excuse my non-Coffee Script syntax, I'm going old school with pure JS):
var my_render = Meteor.render(Template.detail);
element.append(my_render);
Then use lots of console.log() statements to figure out where the miss match is happening. i.e.
console.log("my_render: " + my_render);
console.log("element.append(my_render): " + element.append(my_render));
btw - your example doesn't show the room template, so assuming it's pretty simple.
Related
I have my app with tons of buttons/inputs/etc. with different events. I want to clearly identify each one of them which some event triggers on.
For example, when I have a piece of my app:
<div class="someClass">
<div>
<someOtherElement>
<div></div>
<div><button ng-click="someClickEvent($event)"></button></div>
</someOtherElement>
</div>
</div>
I want to identify somehow, which button I have just clicked:
function someClickEvent(e) {
// some identification code here
}
[edit]
Maybe I wrote this wrong... I want some identification like XPath or something that will point which button were triggered (for error logging purposes).
So when I click my button and some error occurs, I want to identify the button and log some information about it (e.g. div[0].someClass>div[0]>someOtherElement[0]>div[1]>button[0]).
You can get identify the button and log it by this:
$scope.clickFunc = function(event){
$scope.clickedElement = event.target.outerHTML;
};
DEMO: http://jsfiddle.net/rjdzuxaL/1/
Use ng-click instead on onclick
<button ng-click="myFunction($event)">test</button>
Working demo
Change HTML to:
<button ng-click="myFunction($event)">test</button> //onclick works on javascript. For Angularjs, use ng-click.
JS:
$scope.someClickEvent = function(e) {
// some identification code here
var element = e.target; // this will give you the reference to the element.
}
You should avoid handling DOM in the controller. Use directives for them.
I've created simple button:
<button type="button" class="btn btn-success link-accept" data-id="16">Accept</button>
with corresponding listener:
$(document).ready(function() {
$('.link-accept').on('click', function(event){
console.log('accepted');
});
});
Everything works okay.
Then I introduced AJAX-based dynamic (refreshless, you name it) navigation on my page using this function:
function ajaxLoadContent(pageurl){
$('#content').addClass("grey");
$.ajax({url:pageurl+'?rel=tab',success: function(data){
var title=$(data).filter('title').text();
var keywords=$(data).filter('meta[name=keywords]').attr('content');
var description=$(data).filter('meta[name=description]').attr('content');
var mdlTitle=$(data).find('.mdl-layout-title').html();
var content=$(data).find('#content').html();
var scripts=$(data).filter('#scripts').html();
document.title = title;
$('meta[name=keywords]').attr('content', keywords);
$('meta[name=description]').attr('content', description);
$('.mdl-layout-title').html(mdlTitle);
$('#content').html(content);
$('#scripts').html(scripts);
$('#content').removeClass("grey");
window.scrollTo(0, 0);
$(document).trigger("content-refreshed");
console.log('triggered content-refresh');
}});
}
And now my listener isn't working. I guess it's something about that dynamic refresh.
I've tried:
Listening to content-refreshed event - it fires, but isn't caught by my listener inside dynamically loaded page
Moved content of my $(document).ready anonymous function to separete function and called it at the bottom of the page - function is not defined.
Extracted content of my $('.link-accept').on('click') to separate function and called it with <button onclick="function()"> - function is not defined
I'm out of ideas now... It looks like it doesn't execute that code at all if loaded dynamically. What should I do? I guess eval is not an option here...
EDIT
I'm sure that dynamically loaded javascript isn't ran at all. Pure console.log at the top of <script> section doesn't work either. So I guess my primary task is to evaluate that code first - then it should work just right.
Change first snippet to:
$(document).on('click', '.link-accept', function(event){
console.log('accepted');
});
It will work then.
Otherwise it will just search for all .link-accept items first and then attach the handler once to the found objects. Nothing will update after your ajax request.
In my code snippets it is more dynamic. It binds the click to the document. If you click on any object on the document it will check if it is a .link-accept element. More dynamic and better performance.
On my meteor app I have comments and within those comments I have a button "reply" when I click on reply the form to leave an additional comment will open.
The problem is that when I click on a reply button it opens the form on all the other comments as well as the one clicked instead of only where I clicked.
this is my code
template.replyComments.events({
'click #replyToCommentButton2': function(e) {
$(".commentToShow").show();
},
//...
)};
and html
<button class="btn waves-effect waves-light" data-show="#form2" id="replyToCommentButton2">Reply
<i class="mdi-content-send right"></i>
</button>
Try using stopPropagation and narrowing down the .commentToShow class, maybe adding a data attr to the button you are clicking that will tell you the element to show:
HTML:
<a id="replyToCommentButton2" data-show="#form2">Reply</a>
JS:
template.replyComments.events({
'click #replyToCommentButton2': function(e) {
e.stopPropagation();
$(this).parent().find('.commentToShow').show();
},
)};
There are two things you'll need to do, one of them is to stop the propagation event from bubbling up the event chain and the second one is only reference the current object (referenced with this), because you're targeting all the classes instead of the current object.
template.replyComments.events({
'click #replyToCommentButton2': function(e) {
e.stopPropagation();
var targetedForm = $(this).data("show");
$(".commentToShow", targetedForm).show();
},
This should help to understand event propagation: What's the difference between event.stopPropagation and event.preventDefault?
So this is not really answer to your question, but unless you are using some third party lib that requires you to manipulate the DOM with jQuery there is a more Meteoric approach to this.
// comment.coffee
Template.comment.created = ->
# Keep the state of the comment in a ReactiveVar on the template instance
this.showReplyField = new ReactiveVar
Template.comment.events
"click [data-reply]": (e, tmpl) ->
e.stopPropagation()
# If the reply field is open then close it and vice verse
currentState = tmpl.showReplyField.get()
tmpl.showReplyField.set !currentState
Template.comment.helpers
reply: ->
# Get the state and expose it to the template. It will update reactively when the value changes and only for this template/comment.
Template.instance().showReplyField.get()
// comment.html
<template name="comment">
<p>{{text}} </p>
{{#if reply}}
{{> newComment}}
{{else}}
<button class="button" data-reply>reply</button>
{{/if}}
</template>
<template name="newComment">
<!-- Your new comment html here -->
</template>
Note that you will need to meteor add reactive-var to add the reactive var package.
This is what I'm using in my comments package for a blog. You can check out the source code here if you wish.
And if you dont like coffeescript you can translate it.
If you still prefer to use jQuery you should probably use the template optimised version that ships with Meteor. Within an event handler it's available as tmpl.$ and will only select elements within the template (so children will be included too).
This was the answer to the problem I was having (answered by someone on Meteor forums)
template.replyComments.events({
'click #replyToCommentButton2': function(e, template) {
template.$(".commentToShow").show();
},
//...
)};
I would like to set up a simple jQuery onClick event to make the UI dynamic on a handlebars template. I was wondering to addClass() after a specific click.
consider the HTML (generated by handlebars)
{{#if hasButton}}
<div id="container">
<button type="submit" class="myButton">Click me!</button>
</div>
{{/if}}
i.e: After a click within a button, its container will receive a loading class that will create the interaction using CSS.
$(".myButton").on("click", function(event){
$(this).parent().addClass("loading");
});
This code should goes on my handlebars-template or should I rewrite it into a specific helper for it? Is there any examples that could be provided so I can study it then develop something similar?
Thanks in advance!
there is no need to reattach the event handler on every dynamic DOM update if you're defining them at document level:
$(document).on('click','li',function(){
alert( 'success' );
});
Hope this helps! :-)
You have to refresh your Jquery listeners AFTER the insertion of nodes into your DOM HTML.
var source = "<li>{{label}}</li>";
var template = Handlebars.compile(source);
var context = {"uri":"http://example.com", "label":"my label"};
$("ul").append( template(context) );
// add your JQuery event listeners
$("li").click(function(){ alert("success"); });
I am not sure what your problem exactly is.
It's correct like this if you keep your JavaScript in a *.js file, perhaps using parent() instead on prev() in this specific case.
I am having trouble getting an HTML Select Options to work the way I think it should work in Meteor.
Here is an example of how it would work with buttons for a collection called Countries.
Template
{{#each get_countries}}
<button class="btn btnCountryTest">{{iso3}} - {{name}} </button><br />
{{/each}}
Client event handler
'click .btnCountryTest': function(event){
console.log('clicked .btnCountryTest this._id: ' + this._id);
}
Produces the correct output such as.
clicked .btnCountryTest this._id: 39cd432c-66fa-48de-908b-93a874323e2e
Now, what I want to be able to do is trigger other on page activity when an item is selected from an HTML Select Options drop down. I know I could put the ID in the options value and then use Jquery etc... I thought it would "just work" with Meteor but I can't figure out how to do it.
Here is what I am trying and it is not working.
Template
<select class="input-xlarge country" id="country" name="country">
{{#each get_countries}}
<option value="{{iso3}}">{{name}}</option>
{{/each}}
</select>
Handler
'click #country': function (event) {
console.log('Template.users_insert.events click .country this._id: ' + this._id);
}
Which produces
Template.users_insert.events click .country this._id: undefined
Clearly not what I had expected. Any ideas anyone before I resort to Jquery form processing?
Thanks
Steeve
In Meteor, each helper calls Meteor.ui.listChunk (please take a look at packages/templating/deftemplate.js), which treats the current variable as this context.
In other words, it's the global context in click #country, and you can only access this._id under each block, like in the click #country option event.
I think you can put the data in HTML data attribute, and access it using jQuery. Just like this -
Template
<select class="input-xlarge country" id="country" name="country">
{{#each get_countries}}
<option value="{{iso3}}" data-id={{_id}}>{{name}}</option>
{{/each}}
</select>
Handle
'click #country': function (event) {
console.log('Template.users_insert.events click .country this._id: ' + $(event.currentTarget).find(':selected').data("id"));
}
If you're trying to get to the data context, you need to give your event handler access to the template argument, and use it's data object. That is the top level data context.
'click #country': function (event, template) {
console.log('Template.users_insert.events click .country _id: ' + template.data._id);
}
should work. Here's the best documentation I can find on this: http://docs.meteor.com/#template_data
#StevenCannon, I had a similar issue and found a solution here - http://www.tutorialspoint.com/meteor/meteor_forms.htm. #DenisBrat is correct, listen for the change event instead of the click event.
Here's the solution modified for you:
'change select'(event) {
// Prevent default browser form submit
event.preventDefault();
var selectedCountry = event.target.value;
console.log('Template.users_insert.events change select iso3: ' + selectedCountry);
}
You're listening for the 'click' event of the select. You'd really like to subscribe to the 'change' event. 'Click' occurs when you click on a html element. On the other hand, 'change' event gets triggered after you select a value from the list.
Maybe you can also take a look at 'packages/liveui/liveui.js'. There is a findEventData function, which give the callback this object.