Event fails to fire after DOM change in jQuery 1.9 - javascript

I am trying to code a cancel button for user input. The user will be able to edit an item after double-clicking on it and the cancel button will allow the user to cancel the action.
The double-clicking part of the code works great, as a text-input box appears with cancel button attached. But now since the DOM has changed, jQuery no longer select the new element, and therefore when the cancel button is clicked the event is not fired. To illustrate, the code are following:
<div id="main">
<ul class="todoList">
<li id="todo-1" class="todo">
<div class="text">New Todo Item. Doubleclick to Edit
</div>
<div class="actions"> Edit</div>
</li>
</ul>
</div>
var currentTODO;
$('.todo').on('dblclick', function () {
$(this).find('a.edit').click();
});
$('.todo a').on('click', function (e) {
e.preventDefault();
currentTODO = $(this).closest('.todo');
});
$('.todo a.edit').on('click', function () {
var container = currentTODO.find('.text');
if (!currentTODO.data('origText')) {
// Saving the current value of the ToDo so we can
// restore it later if the user discards the changes:
currentTODO.data('origText', container.text());
} else {
// This will block the edit button if the edit box is already open:
return false;
}
$('<input type="text">').val(container.text()).appendTo(container.empty());
container.append(
'<div class="editTodo">' +
'<a class="cancel" href="#">Cancel</a>' +
'</div>');
});
// The cancel edit link:
$('.cancel').on('click', function () {
alert("oops");
});
Or here: http://jsfiddle.net/S7f83/42/
My question is therefore, how can I 'bind' the event after DOM has changed? Thank you very much

In your example the events are bound to the controls which exist at the moment of binding, and match the selector. If you would like the actions to apply for newly created controls, you shall bind the event to the document itself. As it is the highest level DOM element, all the events from lower levels will propagate up to it.
At the second parameter you give the context, so only if the event is received from that context will the function fire.
$(document).on('dblclick', '.todo', function () {
$(this).find('a.edit').click();
});
I won't cite your whole code, but you will get the idea from this one.
Binding listeners to the document is encouraged as it doesn't create as many bindings as many controls you have, just one top level binding, which waits for events to propagate up on the DOM tree. More info on optimalization: http://24ways.org/2011/your-jquery-now-with-less-suck/

Use
$('.todo').on('click', '.cancel', function () {
alert("oops");
});
so you delegate the event to an existing element (the .todo)
instead of
$('.cancel').on('click', function () {
alert("oops");
});

2 Methods
Bind the event to the cancel button after it is created http://jsfiddle.net/S7f83/44/
Bind a live (delegated) event to the cancel button http://jsfiddle.net/S7f83/43/
In the 2nd case, I bind the event delegation to the nearest parent, so the event doesn't need to bubble to the document element to be executed. This could improve some performance. It depends on the use whether you want to place the cancel button anywhere else or not.
EDIT: each li has a different id, so it's better to attach to the class .todo
$('.todo').on('click', 'a.cancel', function () {
alert("oops");
});

Related

How to wrap multiple dynamic eventListeners into one?

I just started to learn js and need a little help: I have the following function:
//SET CHAT BEHAVIOR
function chatSettings() {
console.log('ChatSettings called')
function BtnAndScrollBar(texteditor) {
console.log('BTNAndScrollBar called');
const sendBtn = $('.cl.active').find('.sendBtn');
const attachBtn = $('.cl.active').find('.attachBtn');
console.log(sendBtn)
}
function sendAndDeleteMessage(send) {
console.log(send);
}
var sendBtn = $('.cl.active').find('.sendBtn');
sendBtn.mousedown(function () {
sendAndDeleteMessage(this);
});
var textEditor1 = $('.cl.active').find('.chatTextarea');
textEditor1.on('focus change mousedown mouseout keyup mouseup', function (){
console.log(this);
BtnAndScrollBar(this)
});
}
$('document').ready(function () {
console.log('hello');
$('.tabs').tabs();
chatSettings();
});
I prepared a js.fiddle - As you can see from console.log when clicking into the textarea, the eventListener always listens to #cl1, even if .cl.active switches along with the according TAB.
The events in the textarea are just relevant, if .cl is active. My target is to wrap all three eventListener into one and apply the event to the textarea in the active stream, but all I tried went wrong... Can anyone help? #Dontrepeatyourself #DRY
$(".chatTextarea").on(
'focus change mousedown mouseout keyup mouseup',
function (this) {
//this.id can contain the unique id
greatFunction(this);
});
This will bind event individually with unique id found with this keyword and also wraps all event listener into one function but this is better when you want to process each event with same functionality
please let me know if this helps.
Peace
$(".cl textarea").on('focus change mousedown mouseout keyup mouseup', function () {
greatFunction(this)
});
Tada!
P.S. Is there a reason greatFunction is defined inside window.onload?
Try using $(document).ready function to load code when the page loads.
Also use $('textarea #cl1').on to get the textarea with the #cl1 or whichever id you want to use and then call the function after using the .on.
Hope this helps!
Let me know if it works!
$(document).ready(function () {
function greatFunction(elem) {
//do stuff
}
$('textarea').on('focus change mousedown mouseout keyup mouseup', function () {
greatFunction(this)
});
}
First off, I changed the onload to bind with jQuery, so all your logic is doing jQuery bindings, rather than swapping back and forth between jQuery and vanilla javascript. Also, doing an actual binding removes an inline binding.
Next, the binding has been condensed into a single delegate event listener. Since you eluded in your comments that it wasn't working for the active element after the active was moved or added, this reflected that you were dealing with dynamic elements. Delegate event listeners are one way to handle such things.
Delegate event listeners bind on a parent element of the elements that will change, or be created. It then waits for an event to happen on one of it's children. When it gets an event it is listening for, it then checks to see if the element that it originated from matches the child selector (second argument) for the listener. If it does match, it will then process the event for the child element.
Lastly, I added some buttons to swap around the active class, so you could see in the snippet that the event handler will start working for any element that you make active, regardless of it starting out that way.
$(window).on('load', function () {
function greatFunction (elem) {
console.log(elem.value);
}
$(document.body).on(
'focus change mousedown mouseout keyup mouseup',
'.cl.active .chatTextarea',
function () {
greatFunction(this);
}
);
$('.makeActive').on('click', function () {
$('.active').removeClass('active');
$(this).closest('div').addClass('active');
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="cl1" class="cl active"><textarea class="chatTextarea">aa</textarea><button class="makeActive">Make Active</button></div>
<div id="cl2" class="cl"><textarea class="chatTextarea">bb</textarea><button class="makeActive">Make Active</button></div>
<div id="cl3" class="cl"><textarea class="chatTextarea">cc</textarea><button class="makeActive">Make Active</button></div>

Click event on dynamic elements not working

In framework7, how to add click event on dynamic elements?
If I add my element first on my view, the click event works fine like below:
<div class="test">Click Me</div>
$$('.test').on('click', function () {
myApp.alert("Gotcha!");
});
But if I have dynamic elements, especially elements dynamically added to virtual-list, I cannot make the click event to work. What is the right way to do this?
I even tried inline function, ex: <div class="test" onclick="myFunction();">Click Me</div>, still this won't work.
You can use:
// Live/delegated event handler
$$(document).on('click', 'a', function (e) {
console.log('link clicked');
});
For your case:
$$(document).on('click', '.test', function(e){
console.log('Some code...');
});
Here is docs. Scroll until events section.
Use this for dinamically added elements:
$$(document).on('click', '.test', function () {
myApp.alert("Gotcha!");
});
All answers are good to go with. But if you are using this class 'test' for other elements of the page, you will end up firing some extra click event(when you click on any other element of same class). So if you wanna prevent that, you should add listener to that particular element.
if you're adding an element of class test to an existing element of id testId, then use
$('#testId').on('click', '.test', function(this){
}
In the function where you dynamically add the new elements you have to assign an event handler for them.
Lets say you have a function something like this
function addNewLines(){
//add the new lines here
// you have to run this again
$$('.test').on('click', function () {
myApp.alert("Gotcha!");
});
}

Remove and add a class in a button jquery, can't make it work

I have a button on the page with a class next-main-button.
On page load, a modal is displayed and I have a button with class look-task-again. If I click on it I want to remove next-main-button class from next button and add this class next-task-error
Here is the next button:
<button type="button" class="btn btn-default next-main-button" id="next-button" >Next</button>
This event is executed when the modal button is clicked, here next-main-button is removed and next-task-error is added.
$(".look-task-again").off("click").on("click", function(){
$('#error-popup-message').modal('hide');
$("#next-button").removeClass("next-main-button");
$("#next-button").addClass("next-task-error");
});
If I inspect element the class is changed but if I click in the next button, the event with removed class is executed:
$('.next-main-button').off("click").on("click", function(){
nextButtonClick();
});
Not this:
$(".next-task-error").off("click").on("click", function(){
errorButtonClick();
});
I tried to use unbind method and to bind the button again, I did it like this :
$("#next-button").removeClass("next-main-button").unbind( "click" );
$("#next-button").addClass("next-task-error").bind( "click" );
In this way the next button is deactivated, nothing happens! Can somebody help me, please?
The selector that you used to get the element to bind the click handler on (i.e. .next-task-error or .next-main-button) is only executed once, and delivers one or more elements.
Then you bind a click handler to those element(s), but that binding has no knowledge of the selector you used to get those elements. The binding is done on the elements directly, and never again is their class verified (or whatever other selector you could have used).
To make it more dynamic, you could either (1) use event delegation, or (2) test the clicked element's class at the moment of the event, or (3) change the handler when the condition changes.
1. Event delegation:
$(document).on("click", ".next-main-button", function(){
nextButtonClick();
});
$(document).on("click", ".next-task-error", function(){
errorButtonClick();
});
Shorter version of the same, using chaining:
$(document).on("click", ".next-main-button", nextButtonClick)
.on("click", ".next-task-error", errorButtonClick);
2. Test class within the handler:
$('#next-button').on("click", function(){
if($(this).is('.next-main-button')) {
nextButtonClick();
} else {
errorButtonClick();
}
});
3. Alter handler when status changes
This is like you attempted to do: re-bind the correct handler when the condition changes:
// set initial click handler
$('#next-button').on("click", function(){
nextButtonClick();
});
$(".look-task-again").on("click", function(){
$('#error-popup-message').modal('hide');
$("#next-button").removeClass("next-main-button");
$("#next-button").addClass("next-task-error");
// replace click handler:
$('#next-button').off("click").on("click", function(){
errorButtonClick();
});
});
Shorter version, using chaining:
// set initial click handler
$('#next-button').on("click", nextButtonClick);
$(".look-task-again").on("click", function(){
$('#error-popup-message').modal('hide');
$("#next-button").removeClass("next-main-button")
.addClass("next-task-error")
.off("click")
.on("click", errorButtonClick);
});

jQuery .html() not setting html at all

I have a simple thing. When a user clicks on the edit link it turns the previous element into an input element for editing and the edit into a cancel. If the user decides not to edit he can click on cancel and everything should revert to its initial state.
Right now this is not working at all:
$('.cancel').on('click', function() {
$(this).parent().html("<a href='#'>edit</a>");
});
HTML:
<div class='photo-section'>
<div class='photo-head'>
<div class='photo-info'>
Photo Name : <span class='photo-name'>Work selfie</span>
<span class='title-edit'><a href='#'>edit</a></span>
</div>
</div>
<div class='photo'>
<img src='' alt='' title=''>
</div>
<div class='tag-section'>
<div class='tags'>Photo Tags:
<span>#code#coffee#late#night</span>
<span class="tags-edit">edit</span>
</div>
</div>
</div>
CSS:
// JavaScript to handle photo operations
$(document).ready(function() {
// show/hide edit option
$('.photo-info, .tags').hover(function() {
$(this).find('.title-edit > a, .tags-edit > a').addClass('visible');
}, function() {
$(this).find('.title-edit > a, .tags-edit > a').removeClass('visible');
});
// show editable area
$('.title-edit, .tags-edit').on('click', function () {
edit(this);
});
});
function edit(elem) {
// change element into an input elemnt for editing
var $item = $(elem).prev();
var text = $item.text();
$item.html("<input type='text'>").find('input').attr('value', text);
// change edit to cancel if input element present
if ($('input').length) {
$item.next().html("<a href='#' class='cancel'>cancel</a>");
}
// change cancel back to edit
if ($('.cancel').length) {
$('.cancel').on('click', function() {
$(this).parent().html("<a href='#'>edit</a>");
});
}
}
Result: https://jsfiddle.net/hgwkxygz/6/
Any help would be great!
This is a very common case of attaching event at wrong time in javascript.
Actually you are removing and re-adding a DOM element. So the already attached event to .cancel won't work this time. You again have to write event listener on .cancel whenever you attach a new DOM element after clicking on edit button.
Basically it means whenever you do .html(), you have to re-add event listener for click.
There are various approach to solve this problem.
1) make a function which attach the DOM element as well as click event to that DOM element. Call that function only on click events.
2)Do event delegation.
3)Do not remove DOM elements on click events, rather hide and show elements so that you wont loose your event listeners.
4)If you really have to do remove and re-add DOM elements then in my opinion, best approach is to make a class, where you make DOM elements, add event listeners privately in that class, and on click events just make new instance of that class.
You can check out these options in detail on web.

Prevent parent event from triggering child events

So there are plenty of questions asking how to keep child events from triggering parent events, but I can't seem to find any for the opposite.
I toggle a parent event on and off like so:
var body_event = function(e){
console.log('test');
};
$('#btn').toggle(
function(e){
$('body').bind('click', body_event);
},
function(){
$('body').unbind('click', body_event);
}
);
Currently if I run this on a page like this:
<body>
<div id="btn">click to activate</div>
<div id="example" onclick="alert('do not show this when event is bound')">
try the body event by clicking here
</div>
</body>
the toggle works fine, but when I click on "try the body event by clicking here" the alert will still show up. I want to be able to ignore all child events when the body event is bound without individually referencing the child events.
In other words I'm looking for a better solution than doing something like this on toggle:
$('#example").attr('onclick', '');
This is close, but not quite there. It doesn't check whether the specific function is bound, just whether there are any events bound to the jQuery object.
There must be a way to query the events to find if one is bound to click, and then subsequently what function it points too. Hopefully this gets you started though.
http://jsfiddle.net/PhjB8/
var body_event = function(e) {
alert('test');
};
$('#btn').toggle(
function(e) {
$('body').addClass('active').bind('click', body_event);
}, function() {
$('body').removeClass('active').unbind('click', body_event);
});
$('#example').click(function() {
//debugger;
if ($('body').data('events') == undefined) {
alert('do not show this when event is bound');
}
});​
event.stopPropagation() can solve this problem, inside body_event?

Categories