how to recall a jquery function after html change - javascript

I am trying to figure out why my function stopped working after I changed html code.
I have a div:
<div class="float">
<div class="box" data-speed="3" data-direction="X"><h1>Hola</h1></div>
<div class="box" data-speed="2" data-direction="X"><h1>chau</h1></div>
</div>
And the jquery code :
$(function() {
$('.box').moveIt();
});
//move elements in different speeds
$.fn.moveIt = function () {
var win = $(window);
var it = $(this);
var instances = [];
$(this).each(function (){
instances.push(new moveItItem($(this)));
});
$('.parallax').on('scroll', function() {
instances.forEach(function(inst){
var wrap = inst.el.parents('.float');
var scrol = win.scrollTop()-wrap.offset().top;
inst.update(scrol);
});
});
}
var moveItItem = function(el){
this.el = $(el);
this.speed = parseInt(this.el.attr('data-scroll-speed'));
this.direction = this.el.attr('data-direction');
};
moveItItem.prototype.update = function(scrollTop){
var pos = scrollTop / this.speed;
this.el.css('transform', 'translate'+this.direction+'(' + -pos + 'px)');
};
ok until here everything working, when I scroll the elements .box translate accordingly.
But now I am trying to modify the html in class .float after an ajax call
//after ajax
$.ajax({
url: 'do_content.php'
}).done(function(result) {
//result = <div class="box" data-speed="3" data-direction="X"><h1>Como estas?</h1></div>
$('.float').html(result);
});
After when I fired the scroll again the function appear to look broken and I got this message:
Uncaught TypeError: Cannot read property 'top' of undefined
at http://localhost/ophelia/public/js/control.js?v=1487219951:197:45
at Array.forEach (native)
at HTMLDivElement.<anonymous> (http://localhost/ophelia/public/js/control.js?v=1487219951:195:13)
at HTMLDivElement.dispatch (http://localhost/ophelia/public/utilities/jquery/jquery-3.1.1.min.js:3:10315)
at HTMLDivElement.q.handle (http://localhost/ophelia/public/utilities/jquery/jquery-3.1.1.min.js:3:8342)
I understand that this message appear only if I change the elements with class .box (I tried to change the only the h1 and it doesnt break but I want to change everything to change also the speeds)
How can I re-fire the function?
I tried to call it again with $('.box').moveIt(); but still getting the same error
I know is a long question but didnt find another way to explain my problem

This happens because the html element tied to the listener has been replaced..
Like in this fiddle here.. The alert works but after the html is changed, it doesn't. This is because the old element has been replaced by the new element.
You can use the on function in jQuery to get past this like in this fiddle

As already pointed (but maybe not so clear), the problem is that you attach an event handler using elements existing in the page in a certain moment of time (I think to the instances var). Then you substitute them, but your handler is already set on scroll for element with class .parallax and already registered using that instance of instances and so on.
One way is to rewrite your code using delegate methods.
From http://api.jquery.com/on/
Event handlers are bound only to the currently selected elements; they
must exist at the time your code makes the call to .on().
Delegated events have the advantage that they can process events from
descendant elements that are added to the document at a later time
An event-delegation approach attaches an event handler to only one
element, the tbody, and the event only needs to bubble up one level
(from the clicked tr to tbody):
$( "#dataTable tbody" ).on( "click", "tr", function() {
console.log( $( this ).text() );
});
But It may be complex as you should deeply restructure your code.
Otherwise you could rewrite your function as follows (sorry I can't make fiddles)
$(function() {
$('.parallax').moveIt();
});
//move elements in different speeds
$.fn.moveIt = function () {
var win = $(window);
var it = $(this);
//REMOVED
//var instances = [];
// $(this).each(function (){
// instances.push(new moveItItem($(this)));
// });
$(this).on('scroll', function() {
$('.box').each(function(){
var inst=new moveItItem($(this));
var wrap = inst.el.parents('.float');
var scrol = win.scrollTop()-wrap.offset().top;
inst.update(scrol);
});
});
}
...... and so on

you could bind event on div.float and go through element.children to move every .box

Related

How can I observe changes to my DOM and react to them with jQuery?

I have this function where I toggle a class on click, but also append HTML to an element, still based on that click.
The problem is that now, I'm not listening to any DOM changes at all, so, once I do my first click, yup, my content will be added, but if I click once again - the content gets added again, because as far as this instance of jQuery is aware, the element is not there.
Here's my code:
(function($) {
"use strict";
var closePluginsList = $('#go-back-to-setup-all');
var wrapper = $('.dynamic-container');
$('#install-selected-plugins, #go-back-to-setup-all').on('click', function(event) {
$('.setup-theme-container').toggleClass('plugins-list-enabled');
if ( !wrapper.has('.plugins-container') ){
var markup = generate_plugins_list_markup();
wrapper.append(markup);
} else {
$('.plugins-container').hide();
}
});
//Below here, there's a lot of code that gets put into the markup variable. It's just generating the HTML I'm adding.
})(jQuery);
Someone suggested using data attributes, but I've no idea how to make them work in this situation.
Any ideas?
You could just do something like adding a flag and check for it before adding your markup.
var flag = 0;
$('#install-selected-plugins, #go-back-to-setup-all').on('click', function(event) {
$('.setup-theme-container').toggleClass('plugins-list-enabled');
if ( !wrapper.has('.plugins-container') ){
var markup = generate_plugins_list_markup();
if(flag == 0){
wrapper.append(markup);
flag = 1;
}
} else {
$('.plugins-container').hide();
}
});
If you want to add element once only on click then you should make use of .one() and put logic you want to execute once only in that handler.
Example :
$(document).ready(function(){
$("p").one("click", function(){
//this will get execute once only
$(this).animate({fontSize: "+=6px"});
});
$("p").on("click", function(){
//this get execute multiple times
alert('test');
});
});
html
<p>Click any p element to increase its text size. The event will only trigger once for each p element.</p>

Javascript - Create Dynamic Element then Do Something on Element's Click

How can I call a click event listener on a dynamically created element rendered in the DOM?
I have some scripts that dynamically create elements in the DOM, one of them being a button/a. I would like that button/a to do something once the user clicks it. Right now nothing happens but if I add a setTimeout on the things to happen upon a click, then it kind of works - only let's me do the something on the first element's click (button/a). However I can't rely on a setTimeout to make this chunk of code work.
Here is more or less what I have without the setTimeout method:
// This triggers the whole process
var mainBtn = document.querySelector('.mainBtn');
mainBtn.addEventListener('click', function(e){
e.preventDefault();
mainFunc();
});
// This creates and renders dynamic content in DOM
function mainFunc(){
var out = document.querySelector('.outputWrapper');
var mainArr = ['something ', 'another ', 'else ', 'last one.'];
var div = document.createElement("div");
var btn = document.createElement("a");
var btnText = document.createTextNode("Click Me");
btn.appendChild(btnText);
btn.className = "clickMeBtn";
for(a of probArr){
div.append(a);
div.append(btn);
}
out.append(div);
}
// This is what should happen on button/a click
var clickedBtn = document.querySelector('.clickMeBtn');
if( clickedBtn != null ){
clickedBtn.addEventListener('click', function(e){
e.preventDefault();
console.log('click');
});
}
Here's with the setTimeout method:
// This triggers the whole process
var mainBtn = document.querySelector('.mainBtn');
mainBtn.addEventListener('click', function(e){
e.preventDefault();
mainFunc();
});
// This creates and renders dynamic content in DOM
function mainFunc(){
var out = document.querySelector('.outputWrapper');
var mainArr = ['something ', 'another ', 'else ', 'last one.'];
var div = document.createElement("div");
var btn = document.createElement("a");
var btnText = document.createTextNode("Click Me");
btn.appendChild(btnText);
btn.className = "clickMeBtn";
for(a of probArr){
div.append(a);
div.append(btn);
}
out.append(div);
}
// This is what should happen on button/a click
setTimeout(function(){
var clickedBtn = document.querySelector('.clickMeBtn');
if( clickedBtn != null ){
clickedBtn.addEventListener('click', function(e){
e.preventDefault();
console.log('click');
});
}
}, 10000);
Again this kind of works...it let's me click only on the first instance of the clickedBtn variable.
Any suggestions on how to make this idea work?
Thanks a lot!!
document.querySelector('.clickMeBtn'); returns the first found element, or null.
Attach click event handler when you create the anchor element:
var btn = document.createElement("a");
btn.addEventListener('click', function(e){
e.preventDefault();
console.log('click');
});
I am not 100% sure of what is your question. But the problem I can see is that you cannot bind listeners to elements that are not yet created in the DOM.
So I can see 3 options here:
1- You build a wrapper on top of document.createElement() and a wrapper on top of addEventListener to bind the events to the elements after they are created. For example you build a map of event listeners to begin with, with the 'element selector' as Key and function to call as Value. Then you do a lookup of the listener once the element has been created and you bind it to it with addEventListener.
2- You use JQuery on() method like this:
// define the click handler for all buttons
$( document ).on( "click", "button", function() {
alert( "Button Clicked!" )
});
/* ... some time later ... */
// dynamically add another button to the page
$( "html" ).append( "<button>Click Alert!</button>" );
Source: this JQuery script is from [here][1]
(EDIT) 3- you just bind it after creation, as suggested. Although I thought you wanted to do more advanced stuff, like dynamically add elements asynchronously from the listeners.

jquery: how to add event handler to dynamically added element without adding it to existing element again

I'll try to explain my problem:
I have a website where the user dynamically adds elements. They all belong to the "toBuy" class. Whenever a new element is added to this class I need to attach a click-handler to only this element but not to all others. To keep my code clean I want to have a function that does this work. Here is what i've tried:
this is how the stuff is added:
$("#addItemButton").click(function(){
var item= $('#item').val();
$('#item').val("");
var quantity= $('#quantity').val();
$('#quantity').val("");
var comment=$('#addComment').val();
$('#addComment').val("");
//construct new html
var newitem="<div class='toBuyItem'><div class='item'>";
newitem+=item;
newitem+="</div><div class='quantity'>";
newitem+=quantity;
newitem+="</div><div class='comment'><img src='img/comment";
if(comment==""){
newitem+="_none"
}
newitem+=".png' alt='Comment'></div><div class='itemComment'>"
newitem+=comment;
newitem+="</div></div>";
$("#toBuyItems" ).prepend( newitem );
toggle("#addItemClicked");
initializeEventListeners();
});
then this is the initializeEventListeners function (which I also run when the page loads so that the existing elements have the event handlers already:
function initializeEventListeners(){
$(".toBuyItem").click(function(){
console.log($(this).html());
console.log($(this).has('.itemComment').length);
if($(this).has('.itemComment').length != 0){
console.log("toggling");
$(this).addClass("toggling");
toggle(".toggling .itemComment");
$(this).removeClass("toggling");
}
});
}
function toggle(item){
$( item ).slideToggle(500);
}
now apparently what happens is that when a new element is added the existing elements get a new event handler for clicking (so they have it twice). Meaning that they toggle on and off with just one click. Probably it's damn simple but I cannot wrap my head around it....
EDIT:
so this works:
$(document).on('click', '.toBuyItem', function(){
if($(this).has('.itemComment').length != 0){
console.log("toggling");
$(this).addClass("toggling");
toggle(".toggling .itemComment");
$(this).removeClass("toggling");
}
});
Use jquery's on method. This way you have to add event only once. This will be added automatically to dynamically added elements.
$(document/parentSelector).on('click', '.toBuyItem', function() {
// Event handler code here
});
If you are using parentSelector in the above syntax, it has to be present at the time of adding event.
Docs: https://api.jquery.com/on
You can use jQuery.on method. It can attach handlers to all existing in the DOM and created in future tags of the selector. Syntax is as follows:
$(document).on('click', '.toBuyItem', function(){
//do onClick stuff
})
As others have suggested, you can delegate click handling to document or some suitable container element, and that's probably what I would do.
But you could alternatively define a named click handler, which would be available to be attached to elements already present on page load, and (scope permitting) to elements added later.
You might choose to write ...
function buy() {
if($(this).has('.itemComment').length != 0) {
$(this).addClass("toggling");
toggle(".toggling .itemComment");
$(this).removeClass("toggling");
}
}
function initializeEventListeners() {
$(".toBuyItem").on('click', buy);
}
$("#addItemButton").on('click', function() {
var item = $('#item').val(),
quantity = $('#quantity').val(),
comment = $('#addComment').val();
$('#item', '#quantity', '#addComment').val("");
//construct and append a new item
var $newitem = $('<div class="toBuyItem"><div class="item">' + item + '</div><div class="quantity">' + quantity + '</div><div class="comment"><img alt="Comment"></div><div class="itemComment">' + comment + '</div></div>').prependTo("#toBuyItems").on('click', buy);// <<<<< here, you benefit from having named the click handler
$newitem.find(".comment img").attr('src', comment ? 'img/comment.png' : 'img/comment_none.png');
toggle("#addItemClicked");
});

pass through current target name to function

I'd like to dynamically create event listeners for multiple buttons, and subsequently, show a particular frame label depending on the button clicked, but I'm unsure what to pass through (FYI, this is will be used for HTML5 canvas in Flash CC, but principally the same should apply to a web page for showing divs etc). I currently have this:
var butTotal = 4;
var selfHome = this;
function createListeners () {
for (var i=0; i<butTotal; i++) {
selfHome["btn" + i].addEventListener('click', openPop);
}
}
function openPop () {
alert("test");
selfHome.gotoAndPlay("pop"+event.currentTarget.name.substr(3));
}
createListeners();
It creates the listeners fine, but I don't really know where to start with passing through the current button instance name to tell it which frame label to gotoAndPlay.
Based on the code that you have, I'd simply change the .addEventListener() to call a generic function (rather than openPop, directly), and pass it the reference to the button. So, this:
selfHome["btn" + i].addEventListener('click', openPop);
. . . would become this:
selfHome["btn" + i].addEventListener('click', function() {
openPop(this);
});
At that point, you would then have to update openPop to accept a parameter for the reference to the element that triggered it . . . something like:
function openPop (currentButton) {
At that point, you could reference the clicked button, by using currentButton in the openPop logic.
I'm not sure I totally understand your question. However if you just need to pass the button instance (in you case "selfHome["btn" + i]") you could call an anonymous function in your event handler which calls openPop() with the button instance as an arugment. Would this work for you?
var butTotal = 4;
var selfHome = this;
function createListeners () {
for (var i=0; i<butTotal; i++) {
var currentBtn = selfHome["btn" + i];
currentBtn.addEventListener('click', function(){openPop(currentBtn);} );
}
}
function openPop (btn) {
alert("test");
selfHome.gotoAndPlay(/*use button instance 'btn' to find frame*/);
}
createListeners();
When the event is triggered the this keyword inside the handler function is set to the element is firing the event EventTarget.addEventListener on MDN. If the button have the data needed to be retrieved just get it from the this keyword:
function openPop (btn) {
alert(this.name);
/* ... */
}
It looks like you expect it to contain the function gotoAndPlay() as well as the btn elements (which contain both an ID (of btn[number]) and a name with something special at substr(3) (I assume the same as the id). If those things were all true, it should work in chrome... in other browsers you'll need to add event to the openPop() method signature.
function openPop (event) {
alert("test");
selfHome.gotoAndPlay("pop"+event.currentTarget.name.substr(3));
}
I believe this is what you are looking for and adding that one word should fix your problem (assuming some things about your dom and what selfHome contains):
JSFiddle
You could also leave out the event from openPop() and replace event.currentTarget with this:
function openPop () {
alert("test");
selfHome.gotoAndPlay("pop"+this.name.substr(3));
}
JSFiddle

Changing from .live to .on

I know .live was depreciated and recently I was updating a page and realized I was using .live I would like to switch to .on but do not understand what to change. Here is my current code:
//Script for Choosing which form to display
$("#email-button, #text-button").live('click',
function(){
//figure out what button was clicked.
if(this.id === "email-button"){
var btnA = $(this);
var btnB = $("#text-button");
var divA = $('#email-form');
var divB = $('#text-form');
}
else{
btnA = $(this);
btnB = $("#email-button");
divA = $('#text-form');
divB = $('#email-form');
}
//make sure it is not already active, no use to show/hide when it is already set
if(btnA.hasClass('dark_button_span')){
return;
}
//see if div is visible, if so hide, than show first div
if(divB.is(":visible")){
divB.fadeOut("slow", function(){
divA.fadeIn("slow");
});
}
else{//if already hidden, just show the first div
divA.fadeIn("slow");
}
//Add and remove classes to the buttons to switch state
btnA.addClass('dark_button_span').removeClass('light_button_span');
btnB.removeClass('dark_button_span').addClass('light_button_span');
}
);
I had assistance writing the above script and do not know what to change. Simply changing .live to .on doesn't work.
Any help would be greatly appreciated!
Thanks!
The syntax for on is
$("containerElement").on("click", "targetElement(s)", function(){ });
So in your case it could be
$("body").on("click", "#email-button, #text-button", function(){ });
But being more specific than body is a good idea.
$(document).on('click', '#email-button, #text-button', function() {
// Your code
});
Should do the trick. See http://api.jquery.com/live/ and http://api.jquery.com/on/.
However, since you're using IDs, you probably don't even need .live() or delegating .on(). So the way I'd write that would be simply:
function doButtons(btnA, btnB, divA, divB) {
btnA = $(btnA); btnB = $(btnB); divA = $(divA); divB = $(divB);
// Make sure it is not already active, no use to show/hide when it is already set
if (btnA.hasClass('dark_button_span'))
return;
// See if div is visible, if so hide, then show first div.
if (divB.is(":visible")) {
divB.fadeOut("slow", function (){
divA.fadeIn("slow");
});
}
else // If already hidden, just show the first div.
divA.fadeIn("slow");
// Add and remove classes to the buttons to switch state.
btnA.addClass('dark_button_span').removeClass('light_button_span');
btnB.removeClass('dark_button_span').addClass('light_button_span');
}
$('#email-button').click(function () {
doButtons(this, '#text-button', '#email-form', '#text-form');
});
$('#text-button').click(function () {
doButtons(this, '#email-button', '#text-form', '#email-form');
});
jQuery's .on doesn't use event delegation unless you provide it a selector. In the above code, .live listens for events at the document, but that's far too much bubbling. If we were to implement it with .on though we would do the following:
var handler = function( e ) {
console.log( "Clicked" );
};
$( document ).on( "click", "#email-button, #text-button", handler );
Again though, it isn't really all that wise to listen for events on the document; ideally you would pick an element just above your selector. So if #email-button and #text-button have a common parent, you should use that in place of document.

Categories