Meteor class being fired multiple times from JQuery.click() - javascript

I have a generic template that I use multiple times.
{{#each item}}
{{> genericTemplate}}
{{/each}}
Inside of this template I have a button that when it is clicked fires a hidden file input in the generic template.
$(".upload").click();
Unfortunately for each template the ".upload" class gets fired. So if I had four items, it would give me 4 file inputs. I can't give the buttons a unique id="" because then I would have to explicitly define each event for each id, negating the entire reason for creating the generic template in the first place. What is the proper way to achieve something like this?
EDIT:
My template events look like this:
Template.generic.events({
'click .fileUpload' : function () {
$(".upload").click(); // sets off the 4 templates .upload class
},
'change .upload' : function (e) {
console.log('upload')
}
})
HTML:
<template name="generic">
<!--Hidden Inputs that get fired on click events -->
<div class="hiddenFile">
<input type="file" class="upload"/>
</div>
<button class="btn btn-success fileUpload">UPLOAD FILE </button>
</template>

Try this trick :
Template.generic.events({
'click .fileUpload' : function (event,template) {
// fires only the template instance .upload
template.$(".upload").click();
},
'change .upload' : function (e) {
console.log('upload')
}
});

You can use Template.instance to fire the event in only the appropriate instance:
'click .fileUpload' : function () {
var template = Template.instance();
template.$(".upload").click(); // should only set off this specific upload input.
}
That said, is there really no way you can achieve the same effect without manufacturing an event in the DOM? It's up to you, but can't you just run the code that's going to replace console.log('upload') directly?

Maybe smth like this?
Template.generic.events({
'click .fileUpload' : function (event, target) {
e.currentTarget.parent().find(".upload").click(); // sets off the 4 templates .upload class
},
'change .upload' : function (e) {
console.log('upload')
}
})

Related

How to access outside .this in object

I'm trying to rewrite my functional code to module pattern js, and I have this issue - When I try to delete input field which is dynamically created, I use jQuery $(this) to access dom element and delete its parent 'div'. But this refers to the Modal object, not the component I clicked. How to solve it, without making some field counter and creating fields with unique ID's, then catching ids on click and deleting that input field?
My modal:
var s,
Modal = {
settings: {
addInputBtn: $("#add-input"),
inputContainer: $("#modal-input-form"),
checkBoxesList: $(".check-box"),
inputFieldsList: $(".form-control"),
inputFieldsOptionalList: $(".optional-modal"),
inputHtml: `
<div class="input-group mb-3 optional-modal">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="checkbox" class="check-box">
</div>
</div>
<input type="text" class="form-control">
<button type="button" class="close">
<span>×</span>
</button>
</div>`
},
init: function () {
s = this.settings;
this.bindUIActions();
},
bindUIActions: function () {
s.addInputBtn.on("click", () => Modal.addInput());
s.inputContainer.on("click", ".close", () => Modal.deleteInput());
},
addInput: function () {
s.inputContainer.append(s.inputHtml);
},
deleteInput: function () {);
$(this).parent('div').remove();
}
}
Modal.init();
deleteInput: function (e) {);
$(e.target).parent('div').remove();
}
Event handlers are passed an event argument. Among its useful properties is target, which is the html element that the event originated on. Note that it could be different from the this of the event handler, which is the element that the event handler is attached to, as events bubble up through the DOM. So if you have this html:
<div id="parent">
<div id="child"></div>
</div>
then for this JS function:
$("#parent").on('click', function(e){
//this will equal <div id="parent"> unless you bind the handler to something else
//e.target will equal the clicked element:
//if the user clicked the child, it will equal the child;
//if the user clicked parent directly, it will equal parent.
$(e.target).closest('#parent').doSomething();
});
Did you try with:
deleteInput: function () {;
$(this.settings).parent('div').remove();
}
Also you have a typo at deleteInput: function () { ); ... the rest of the code, delete that extra ) after function. I suggest you trying $(this.settings).. because, this gets the Modal, and then you need to access the other object inside that object, which is settings. So your modal object, consists of other object, and I'm thinking this way you should be able to get it :)

Meteor -- bind click handler to button inside a conditional

I have a Meteor template with 2 possible HTML blocks, depending on a conditional: already_facebook_authed. As the code below shows, already_facebook_authed is the result of a Session variable, and that Session variable is asynchronously set. It seems like when the template is rendered, the Session variable (and thus already_facebook_authed) is falsey so when trying to bind a click handler to #deauth_facebook_button, it does not exist yet because it is in the other block which is not yet rendered.
How can I bind a click handler to #deauth_facebook_button? Perhaps there is some callback for when a certain DOM element is rendered in which I can instantiate this click handler?
------------
-- auth.html
<template name="accounts_auth_with_facebook">
{{#if already_facebook_authed}}
<div class="col-md-4">
<button id="deauth_facebook_button" class="btn btn-primary"> Deauth Facebook </button>
</div>
{{else}}
<div class="col-md-4">
<div id="facebook_button"> Authenticate with FB </div>
</div>
{{/if}}
</template>
----------
-- auth.js
Template.accounts_auth_with_facebook.rendered = function () {
$('#facebook_button').unbind('click.auth').bind('click.auth', function() {
// some handler code
});
$('#deauth_facebook_button').unbind('click.deauth').bind('click.deauth', function() {
// some other handler code
});
};
Template.accounts_auth_with_facebook.already_facebook_authed = function() {
Meteor.call('get_composer_id', function (error, result) {
if (blah blah blah) {
Session.set('logged_in_with_facebook', true);
}
});
return Session.get('logged_in_with_facebook');
};
Do not use jQuery for setting up click handlers on Meteor templates, use the Meteor standard events mechanism :
Template.accounts_auth_with_facebook.events({
"click #facebook_button":function(event,template){
// some handler code
}
"click #deauth_facebook_button":function(event,template){
// some other handler code
}
});

Prototype event handler

I've defined the following HTML elements
<span class="toggle-arrow">▼</span>
<span class="toggle-arrow" style="display:none;">▶</span>
When I click on one of the elements the visibility of both should be toggled. I tried the following Prototype code:
$$('.toggle-arrow').each(function(element) {
element.observe('click', function() {
$(element).toggle();
});
});
but it doesn't work. I know everything would be much simpler if I used jQuery, but unfortunately this is not an option:
Instead of iterating through all arrows in the collection, you can use the invoke method, to bind the event handlers, as well as toggling them. Here's an example:
var arrows = $$('.toggle-arrow');
arrows.invoke("observe", "click", function () {
arrows.invoke("toggle");
});
DEMO: http://jsfiddle.net/ddMn4/
I realize this is not quite what you're asking for, but consider something like this:
<div class="toggle-arrow-container">
<span class="toggle-arrow" style="color: pink;">▶</span>
<span class="toggle-arrow" style="display:none; color: orange;">▶</span>
</div>
document.on('click', '.toggle-arrow-container .toggle-arrow', function(event, el) {
var buddies = el.up('.toggle-arrow-container').select('.toggle-arrow');
buddies.invoke('toggle');
});
This will allow you to have multiple "toggle sets" on the page. Check out the fiddle: http://jsfiddle.net/nDppd/
Hope this helps on your Prototype adventure.
Off the cuff:
function toggleArrows(e) {
e.stop();
// first discover clicked arow
var clickedArrow = e.findElement();
// second hide all arrows
$$('.toggle-arrow').invoke('hide');
// third find arrow that wasn't clicked
var arw = $$('.toggle-arrow').find(function(a) {
return a.identify() != clickedArrow.identify();
});
// fourth complete the toggle
if(arw)
arw.show();
}
Wire the toggle arrow function in document loaded event like this
document.on('click','.toggle-arrow', toggleArrows.bindAsEventListener());
That's it, however you would have more success if you took advantage of two css classes of: arrow and arrow-selected. Then you could easily write your selector using these class names to invoke your hide/show "toggle" with something like:
function toggleArrows(e) {
e.stop();
$$('.toggle-arrow').invoke('hide');
var arw = $$('.toggle-arrow').reject(function(r) {
r.hasClassName('arrow-selected'); });
$$('.arrow-selected').invoke('removeClassName', 'arrow-selected');
arw.show();
arw.addClassName('arrow-selected');
}

In Meteor how can I create a generic event handler?

I want to create a generic event handler that I can reuse on dom elements so I don't have to write boiler plate over and over again. I thought I had it figured out but I am getting errors.
The problem I am having is that I think the event handlers are bound at a different time than I need. Maybe at document.ready? Where I think I need to attach them with the .live() method? Though I may have no idea what I am talking about here.
Here is what I am trying to do:
Multi page application.
Multiple collections where data needs to be inserted.
Button code to show the insert form.
<button id="btnShowInsert" class="btn btn-success" rel="tooltip" title="add group">
<i id="btnIcon" class="icon-plus-sign icon-white"></i>
</button>
Template that shows the form based on the page (controller)
{{> groups_insert}}
Here is the form.
<template name="groups_insert">
{{#if acl_check}}
{{> alert}}
< p>
< form class="form-horizontal well hide" id="insert">
<fieldset>
< div class="control-group">
< label class="control-label" for="name">Name</label>
< div class="controls">
< input type="text" class="input-xlarge" id="name" name="name">
< /div>
< /div>
< div class="form-actions well">
< button id="btnReset" type="reset" class="btn btn-large">Reset</button>
< button id="btnSubmit" type="button" class="btn btn-primary btn-large">Submit</button>
< /div>
< /fieldset>
< /form>
< /p>
{{/if}}
< /template>
Here is the client code to implement the button that shows the form on the page.
Template.groups.events[ Meteor.eventhandler.btn_events('#btnShowInsert') ] = Meteor.eventhandler.make_btn_show_insert_form_click_handler();
Here is my generic event handler:
var EventHandler = Base.extend({
btn_events: function(selector) {
return 'click ' + selector; //, keydown '+selector+', focusout '+selector;
},
make_btn_show_insert_form_click_handler: function(){
//var click = options.click || function () {};
return function (event) {
if (event.type === "click") {
event.stopPropagation();
event.preventDefault;
try{
if ($('#btnIcon').hasClass('icon-plus-sign') ) {
$('#btnIcon').removeClass('icon-plus-sign');
$('#btnIcon').addClass('icon-minus-sign');
} else {
$('#btnIcon').removeClass('icon-minus-sign');
$('#btnIcon').addClass('icon-plus-sign');
}
$('#insert').slideToggle('slow', 'swing');
} catch(error) {
Alert.setAlert('Error', 'Critical Error: ' + error, 'alert-error');
}
}
}
},
});
Meteor.eventhandler = new EventHandler;
THE ERROR
Uncaught TypeError: Cannot call method 'btn_events' of undefined
BUT, if I define the event handler this way and call it this way it works.
Template.groups.events[ btn_events('#btnShowInsert') ] = make_btn_show_insert_form_click_handler();
var btn_events = function (selector) {
return 'click ' + selector; //, keydown '+selector+', focusout '+selector;
};
var make_btn_show_insert_form_click_handler =
function () {
//var click = options.click || function () {};
console.log( Meteor.request.controller );
return function (event) {
if (event.type === "click") {
event.stopPropagation();
event.preventDefault;
try{
if ($('#btnIcon').hasClass('icon-plus-sign') ) {
$('#btnIcon').removeClass('icon-plus-sign');
$('#btnIcon').addClass('icon-minus-sign');
} else {
$('#btnIcon').removeClass('icon-minus-sign');
$('#btnIcon').addClass('icon-plus-sign');
}
$('#insert').slideToggle('slow', 'swing');
} catch(error) {
Alert.setAlert('Error', 'Critical Error: ' + error, 'alert-error');
}
}
}
};
The Problem
I don't want to have to replicate code all over my site in order to implement a nice button that can slideToggle and form on any page. If I could get it abstracted then I should be able to have a Show Form type of button on all pages for any collection that I am rendering that allows data entry. As well, this leads into being able to create one form handler for all forms as well and then tying them to the controller through an action to the model.
Any ideas?
You can bind a high-level template to elements created with child templates. Then you only have to do the binding once. For example
HTML:
<template name="settings">
{{> login_settings }}
{{> account_settings }}
{{> data_settings }}
</template>
<template name="login_settings">
<btn class="slideToggle">Slide me for login!</btn>
</template>
<template name="account_settings">
<btn class="slideToggle">Slide me for account!</btn>
</template>
<template name="data_settings">
<btn class="slideToggle">Slide me for data!</btn>
</template>
JavaScript:
Template.settings.events {
'click .slideToggle': function() {
var clickedElement = event.target;
// add/remove CSS classes to clicked element
}
};
So if you end up creating 10 different template definitions under settings so you still only have to bind the handler to a single template.
I feel like you're overcomplicating things. Why not do this?
Template.someTemplate.events({
'click .button': buttonClicked
});
function buttonClicked(evt) {
// DRY code to handle a button being clicked
}
This has the right balance of separation: your event handler is defined once, but you can tell each template that you want its buttons to listen to some event. And if that's not good enough, you can further abstract it:
Template.someTemplate.events(genericEvents);
And possibly even merge genericEvents with specific events for that Template if you wanted.
Here is what I ended up doing. The example only shows the generic insert handler.
var EventHandler = Base.extend({
btnClickHandler: function(){
return function (event) {
event.preventDefault();
Meteor.eventhandler[event.currentTarget.id](event);
}
},
insert: function(event){
event.preventDefault();
var params = $('#insert-form').toJSON();
try{
window[Meteor.request.controller.capitalise()]['validateParams'](params);
var ts = new Date();
params.client_updated = ts;
var has_popup = params.has_popup;
delete params.has_popup;
window[Meteor.request.controller.capitalise()]['insert'](params, function(error, _id){
if(error){
Alert.setAlert('Error', error, 'alert-error', true, has_popup);
} else {
Alert.setAlert('Success', 'Record successfully created.', 'alert-success', true, has_popup);
$("#insert-form").reset();
Meteor.flush();
}
});
} catch(error) {
Alert.setAlert('Error', error, 'alert-error', true, params.has_popup);
}
}
});
Meteor.eventhandler = new EventHandler;
Now, I merely have to create handlebars templates without any significant javascript coding to handle generic events and wire them up as follows.
$(document).on("click", '#print', Meteor.eventhandler.btnClickHandler());
$(document).on("click", '#insert', Meteor.eventhandler.btnClickHandler());
$(document).on("click", '#remove', Meteor.eventhandler.btnClickHandler());
$(document).on("click", '#removeSubField', Meteor.eventhandler.btnClickHandler());
$(document).on("click", '#insertSubField', Meteor.eventhandler.btnClickHandler())
$(document).on("click", '#update', Meteor.eventhandler.btnClickHandler());
$(document).on("click", '#updateSubField', Meteor.eventhandler.btnClickHandler());
$(document).on("click", "#toggleActive", Meteor.eventhandler.btnClickHandler());
$(document).on("click", "#toggleChild", Meteor.eventhandler.btnClickHandler());
Now, I don't have to write any template event maps to handle basic CRUD. I can create any number of handlebars templates as long as the /route corresponds to the collection name. Although I do some tricky conversions from time to time. Basically, the generic event handler wires up the events, based on the route aka request.controller, to a collection and abstracts it through a client/server shared data model for validation and even access control alongside what is existing in Meteor.
It seems to work well and has reduced my code base significantly. I have dozens of collections where I haven't had to write any event maps handlers because basic CRUD is handled but abstracted enough that I can customize validation, security and other sanity checks on the client/server shared data model.
The approach I've taken to this problem in Meteor 1.0.2 is to use dynamic templates. See Dan Dascalescu's canonical answer and the docs.
Let's say you have a set of generic events attached to template "A" and you want to take advantage of them in templates "B", "C", and "D."
HTML:
<template name="A">
{{> Template.dynamic template=myTemplate}}
</template>
JS:
Template.A.events({
... your event code
})
You define a helper function for "A" that dynamically picks which of B, C, or D (...) you want to include:
Template.A.helpers({ // dynamically insert a template
myTemplate: function(){
if (...) return 'B'; // return a string with the name of the template to embed
if (...) return 'C';
if (...) return 'D';
}
})
The events defined in "A" will now be available in "B", "C", and "D."
Note that template "A" need not contain any HTML whatsoever.

Backbone.js button click event is fired for all instances of the button instead of just the one that is clicked. Why?

I am learning backbone.js and am quite new. I have a view that acts as a button:
simpleButton = Backbone.View.extend({
template: "<button class='${classes}'>${text}</button>",
el: $("body"),
events: {
"click": "onClick",
"focus": "onFocus",
"blur": "onBlur"
},
initialize: function (args) {
_.bindAll(this, 'render');
this.rendered = false;
this.text = args.text || 'button';
this.classes = args.classes || [];
this.classes.push('ui-button');
//console.debug("Wh.views.simpleButton.initialize classes ",this.classes);
if (args.autoRender === true) this.render();
},
render: function () {
//console.debug("Wh.views.simpleButton.render classes ",this.classes);
if (this.rendered === false) {
$.tmpl(
this.template, {
classes: this.classes.join(' '),
text: this.text
}
).appendTo(this.el);
this.rendered = true;
}
},
//event handlers
onClick: function (ev) {
console.debug(this);
alert("click on ", ev, this);
},
onFocus: function (ev) {
////console.debug(ev);
},
onBlur: function (ev) {
}
});
My problem is that if I create two buttons, and click just one of them, I get the alert box two times, and the debug showing me "this" shows the first button first, and the second button next.
Am I missing something?
The events you define are bound to the "el" property of your view. In your case it is "body" so when you fire up click with 2 simpleButton views instantiated, you have 2 of them listening for the same event.
Each view you instantiate should represent one and only one DOM element defined by the el property. So if you want to create a button view (not sure this is 'best practice' in a real program) you could have :
SimpleButton = Backbone.View.extend({
template : "<button class='${classes}'>${text}</button>",
tagName : "div", // defines the html tag that will wrap your template
className: ".buttonbox",
...
});
mybtn = new SimpleButton();
mybtn.render().appendTo('body')
That way your click event will only concern the one div.buttonbox inside of which your button lives.
Notice : Backbone idea of the render function is creating an html string you'll use afterwards to append prepend or whatever in the DOM. That way if you create many you can do it so you only refresh the DOM once (refreshing the DOM is expensive)...
Use this in your View .it will unbind the click events
initialize : function() {
$(this.el).unbind("click");
}
Just a thought that creating a Backbone.View for each and every button in your app could be a performance overkill and you can't leverage the "delegate" feature in jQuery. I'd instead create a Backbone.View for the parent element of those buttons instead.
Of course, if you have a few special buttons with complicated logic then they probably do deserve their own View classes. :)
Give your buttons unique ids, for example <button id="button1"> and <button id="button2">, then in your events hash, you need to specify the click event and the id of the button you want to handle that event for, e.g:
events : {
"click #button1" : "onClick",
"click #button2" : "doSomethingElse"
}
Now this will call onClick() only when you click on the button with id=button1 and call doSomethingElse() when you click on the button with id=button2

Categories