I've got an event handler set on document, listening for submit and change events, delegated to any form elements on the page. The idea is to update the server when an input is altered (since the event bubbles up to the form), while also allowing for submitting an entire form. I have a few overrides in place for inputs that need special handling, and this works great (since they're placed higher up in the code, I can block the main event handler with .stopImmediatePropagation() - all these handlers are on document.)
What I'm trying to do now is intercept the change event on a specific input, then instead of preventing, I want to re-trigger the primary handler, passing in some additional data (using jQuery's .trigger(event, [data]) syntax.)
To give a simplified example: http://jsfiddle.net/rrEmq/3/
My goals are to:
Maintain delegated events (the forms are loaded in dynamically, the JS is loaded once)
Avoid duplicating code
Avoid breaking out the main handler's functionality into separate functions
That third item is my initial approach, and I understand that it will get the job done, but I'd love to find a way to solve this without rewriting the main handler. Obviously, the real kicker is that, since everything's delegated to the same element, and since I'm using e.target, I can't simply trigger a handler higher up in the DOM tree.
Create a custom event and trigger with the extra data
Instead of
$(this).trigger("change", data);
Call
$(this).trigger("changemodified", data);
And create a handler for this event.
You can reuse handlers
var handleChange = function(e) { };
$("something").on("change", handleChange);
$("something").on("changemodified", handleChange);
Related
what is the best way to catch and handle a click event on "anything except specific DOM-node(s)" in a React app?
The handler of the click event is the easy part: this can be any method.
The registration of the event, and the trigger to invoke the handler, is the hard part.
There is no clean way to capture a "clicks outside ...." event.
There are however various (HTML/ CSS/ Javascript) tricks you could apply:
If it is a modal page/ popup, you could also render a full page background rectangle (e.g. slightly transparent grey), which is in front of the whole page, but behind the popup. Add a click-event-listener to this background to remove the modal + the grey background.
Another method is to use the focusout javascript event on your top-react component:
the top HTML component rendered by react should be able to get focus (needs to be an <a> or similar HTML, or - somewhat less clean - needs a tabindex=... to work)
give the element focus as soon as it is rendered (inside componentDidMount()`)
add a focusout event listener, which triggers the handler to do something with the click outside.
The focusout event is fired as soon as the component no longer has focus:
- if a child of the component gets focus (e.g. you click something inside the component) focusout is also fired: usually no problem for menu's, but undesired for popups with forms
- the focusout is also fired if the user presses TAB.
There's no React-specific way to do this; all React event handlers are tied to the component they're set on. The best way to accomplish this depends on the details of what you need to get done, but a fairly straightforward way to address this would be to add a delegated click handler to the body element, or the closest ancestor element that includes the area you want to capture clicks from. You'd attach this event handler either on the component's componentDidMount() or whenever it becomes relevant, for example, after toggling the component's state so that it shows a dropdown menu.
Attach the event handler however you normally would – element.addEventListener or jQuery's $().on or what-have-you - and evaluate the event target when it fires to determine whether you need to execute your custom logic.
Simple example, without jQuery:
componentDidMount() {
document.body.addEventListener('click', (e) => {
if (e.target !== [your dom node]) {
// do something
}
}
}
Attaching a single event handler on the body element shouldn't pose any performance issues, but best practice for most cases where you'd use something like this would be to remove the event handler when it's no longer needed.
I have a situation where I am using the data attribute named data-command in many instances throughout a specific section of a site and instead of binding tons of separate click events I decided to just use the one and use a switch such as:
$('[data-command]').on('click', function(event) {
// Prevent default click action
event.preventDefault();
// Get command
var command = $(event.target).data('command');
switch (command) {
// Do stuff...
}
// Prevent default click action (IE 8)
return false;
});
However it has just become an issue when trying to get it to work on data loaded via AJAX.
This obviously works..
$('#existing_element').on('click', '[data-command]', function(event) {
...but since it is supposed to work on many different pages in that section of the site the above wouldn't work on all pages.
I could just make sure to give a specific id to the parent wrapper where I load all my ajax data, but that would mean making two separate binding events with a bunch of the same code.
I also could do this to cover all bases..
$(document).on('click', '[data-command]', function(event) {
...but that's probably not such a wise idea binding an element to the document.
Edit: Html data is being loaded into the DOM via jQuery's html method.
Any clean way I can handle this or should I just create two different binding events to handle each situation?
Event delegation is the best approach to bind events on dynamically created elements. Since you don't want to use event delegation, use following approach to bind events.
$('[data-command]').off('click').on('click', clickHandler);
// Somewhere in the same scope
function clickHandler(e) {
// Handle click event here
}
Add this after the dynamically created elements are added using html().
off('click') will first unbind the click event handlers that are applied previously and then on('click', will bind the click handler on all the elements matching selector.
Edit
This seems to be repeating the same code again and again. Can't I keep it DRY?
Yes, you can keep the code DRY and clean by creating a function to bind events and call the same function when you want to bind event.
function clickHandler(e) {
// Handle click event here
}
function bindEvent() {
$('[data-command]').off('click').on('click', clickHandler);
}
$(document).ready(bindEvent);
...
$.ajax({
...
success: bindEvent
....
I have a template which has a key event
Template.layout.events({
'keyup': function (e, template) {
if (e.keyCode === 13) {
e.preventDefault();
// do some stuff here, isn't important for question
Router.go('Users.profile', { _id: id });
}
}
});
However, when I change pages, it seems the event is still bound to the enter button.
I imagine the first step is to use an onDestroyed callback on my template.
Template.layout.onDestroyed(function () {
// ???
});
How would you unbind key events on page change in meteor?
It doesn't look like Meteor natively supports removing events added via Template.name.events. There are a few resolutions I can think of in this case:
Handle the special case inside the Event handler, which means coupling the handler with the rest of the application logic. This may or may not suit your needs.
Attaching a (namespaced) event handler .onRendered using jQuery and then removing it explicitly .onDestroyed.
Bind the keyup event more tightly to the element in focus. If that element is removed from the DOM, the handler will also be removed.
Event handlers are destroyed automatically when the template is destroyed. But, with a name like layout, I'm guessing that template exists on multiple routes. If this is the case, add a console.log to onDestroyed() and see if it even gets destroyed when you want it to. To fix this, attach the keyup listener on a more specific template, for instance if you have this
<template name="layout">
{{>settingsMenu}}
</template>
and assuming you only want 'enter' do do something on the settings menu, use: Template.settingsMenu.events
Then, when settingsMenu is destroyed, so is the event handler.
If you bizarrely don't have one (ie the current route doesn't have a single unique template on it), create one:
<template name="settingsMenu">
</template>
{{>settingsMenu}}
Template.settingsMenu.events({...
Additionally, an ugly fix would be to add a conditional at the top of your event:
if (Router.current().route.getName() === 'settingsMenu')... I call this ugly because you still have a rogue event listener there, it's just harmless for all but your specified route.
I have some javascript that I inherited for my job. In this javascript we have a side bar that is constantly updated(every 1 - 10 or so minutes). In the script we parse and process the AJAX from the server and then we call an interesting function.
function renewClicks(){
$('.classElem').unbind('click');
$('.classElem2').unbind('click');
$('.classElem3').unbind('click');
$('.classElem').click(elm1funct);
$('.classElem2').clikc(elm2funct);
$('.classElem3').click(elm3funct);
}
Where .classElem is a css class selector that is appended to each image that is added to the page. And elmfunct is a function that is written to handle the click. This runs on each update (deauthorizing valid already added elements and then re adding them all). I want to know if there is a way I can possibly attach a listener on the body element in the DOM so that all of the image elements added to the page and that inherit the css class will already be handled and therefore not unregistered and re-registered on each update.
Thank you for any info you can provide.
You could try this:
$('body').on('click','.classElem',elm1funct)
.on('click','.classElem2',elm2funct)
.on('click','.classElem3', elm3funct);
From jQuery's docs:
Delegated events have the advantage that they can process events from
descendant elements that are added to the document at a later time. By
picking an element that is guaranteed to be present at the time the
delegated event handler is attached, you can use delegated events to
avoid the need to frequently attach and remove event handlers.
As #crush mentioned, use an event delegated approach to avoid unbinding and re-binding events:
$(document).on('click', '.classElem', elm1funct);
I'm in a situation where I make a lot of ajax calls to change the same portion of html. This html represent a grid. After changing the html in the ajax call, I attach a event handler to an event of the grid. When the user click on a refresh button, I execute the same ajax call that set new html code and also add again an event handler to listen of event of the grid.
I want to know if each time I refresh my grid and add a new event handler if the previous event handler is still in memory and if yes, what are the bests practices in this situation? (e.g. unbind the event handler if exist before putting new html)
Here is an example of what I do:
$.get(this.config.actionLoggingUserListUrl, viewModel, function (data) {
MyNamespace.ui.hideGridAnimation($("#LoggingActionUsersList"));
if (data.success) {
$("#validationSummary").html("");
$("#usersList").html(data.result);
$("#LoggingActionUsersList").click(function() {
console.log("Here is my event handler attached multiple times!");
});
}
else {
$("#validationSummary").html(data.result);
$("#usersList").html("");
}
});
Note that the event handler I'm talking in this case is:
$("#LoggingActionUsersList").click(function() {
console.log("Here is my event handler attached multiple times!");
});
Event handlers stack, so yeah, this is a memork leak. Probably a fairly insignificant one, but its more the principle than the effect. Unless for some reason you really do need to have dynamic event handlers (something that is pretty rarely used as there aren't very many realistic uses for it), I'd strongly suggest pulling the event handler assignment out of the ajax call.
If you do need the event handler to change, the clever way to do it would be to make your event handler smart enough to know a little bit about the object to which it is assigned. That way, instead of adding a new event each time, you can just have logic in the event handler do different things based on the current identity of the object.
why are you binding it every time you make the call?
You are adding onto the stack every time. You are not replacing it. Best solution is to use on and do it once.
Other solution is to unbind the click event, before you add click event. The problem with this solution is if anything else added the click event, you just removed it.