on click ajax result always updates last clicked element - javascript

Consider the following code, if I click 3 links in a row, the last clicked "section" is updated 3 times, the first 2 remane the same. (assuming you click 3 links before the ajax finishes)
<div><span class="show-records">click me</span></div>
<div><span class="show-records">click me</span></div>
<div><span class="show-records">click me</span></div>
$('.show-records').on('click', function(e) {
el = $(this).parent(); // get parent of clicked link
$.ajax({
url: "...",
method: 'POST',
data: $(this).data('data'),
beforeSend: function() {
el.html('<p style="margin-left: 24px;">loading <span class="loading-spinner"></span></p>'); // show a spinner
},
error: function() {
alert("{{ 'unknown error'|trans({}, 'alerts') }}");
}
}).then(function(html) {
el.html(html); // update html with table
});
});
I can only assume it is because my el variable is being updated with each click, and "then" always uses the latest el
How can I isolate the parent element between each click event?
I have tried using success & complete functions inside $.ajax but this & $(this) are no longer the clicked element.

The problem appears to be simply that the el variable was never formally declared anywhere. Using a variable without declaring it should throw an error, and does in "strict mode", but unless you opt in to that (highly recommended), the JS engine will implicitly declare any undeclared variables in the global scope.
Tha can cause all manner of nasty problems, but here the particular problem was simply that the three event handler functions were "sharing" the same el variable, and therefore overwriting it in the way you observed.
Whereas if you declare it as a local variable instead (this would work just as well with the "old-fashioned" var keyword instead, but let's be modern), with let el = ..., this problem goes away. el is recreated, pointing to the correct element, each time any of the 3 event handlers run, and none can affect the others.
[In fact, as pointed out in other comments/answers, const is better here than let]

If you make a new reference to el as a constant. It will fix your issue.
const myEl = $(this).parent(); // get parent of clicked link

Related

What is the returned value of a clicked <button>?

I want to get the returned value of a clicked button, and then use it to make an if statement. All the answers that I read here about people trying to do that are either very old using old script that doesn't work anymore, or not the same case.
function remove() {
if (document.getElementById("removing").value == true) {
document.getElementById("test").style.backgroundColor = "red";
}
}
<div id="test">test</div>
<button id="removing" onclick="remove()">Remove a word</button>
I have tried using the value property, and onclick, but non of them equal true when the button is clicked.
I tried using alert to display the value, but it displays nothing.
Does clicking a button actually returns a value, and if so, what is it?
DOM Events are handled by an EventListener's callback function.
Thus such a handler function, if triggered by an event and forwarded by an event listener's handleEvent method, always will be invoked with an event object as this function's single argument.
All information related to this event are carried by the event itself. Its properties can be read and some even can be written/changed.
It is obvious from the provided example that the OP wants to assure that an event handler has been triggered by just a specific html element. Thus any valid approach just needs to look into the event's currentTarget property ...
// the way the OP might want to handle the problem.
function handleRemoveWord(evt) {
const elmNode = evt.currentTarget;
// make sure the handler was
// triggered by the intended element ...
// ... here by comparing node properties.
if (
(elmNode.tagName.toUpperCase() === 'BUTTON')
&& (elmNode.id === 'remove')
) {
document
.getElementById('test')
.style.backgroundColor = 'red';
}
}
// another way the OP might want to handle the problem.
function handleRemoveAnotherWord(evt) {
// `this` referres to the element which got
// bound to the handler via `addEventListener`.
const targetNode = this;
// make sure the handler was
// triggered by the intended element ...
// ... here by comparing node references.
if (targetNode === evt.currentTarget) {
document
.getElementById('test')
.style.backgroundColor = 'cyan';
}
}
// an alternative way of solving the problem
// of always being assured about the correct
// element having triggering the event handling.
function handleRestoreWordWithBoundContext(evt) {
const context = this;
const { elmTest, elmRestore } = context;
// make sure the handler was
// triggered by the intended element ...
// ... here by comparing node references.
if (elmRestore === evt.currentTarget) {
elmTest.style.backgroundColor = '';
}
}
function initialize() {
// the way(s) the OP might want to handle the problem.
document
.getElementById('remove')
.addEventListener('click', handleRemoveWord);
document
.querySelector('#removeAnother')
.addEventListener('click', handleRemoveAnotherWord);
// an alternative way of soving the problem
// of always being assured about the correct
// element having triggering the event handling.
const elmTest = document.querySelector('#test');
const elmRestore = document.querySelector('#restore');
elmRestore.addEventListener(
'click',
handleRestoreWordWithBoundContext.bind({
elmTest,
elmRestore,
})
);
}
initialize();
<div id="test">test</div>
<button id="remove">Remove a word</button>
<button id="removeAnother">Remove another word</button>
<button id="restore">Restore a word</button>
As one might have noticed, the example features a third button with yet another way of implementing an event handler. This additional handler assumes its this context to carry additional information. This is possible by invoking bind on this handler function and providing exactly the information one wants to be present as the event handlers this context. Every time this function specific method is invoked it creates another function which does have access to the bound information via the this keyword.
Simply change background color onClick of button as:
function remove() {
document.getElementById("test").style.backgroundColor = "red";
}
<div id="test">test</div>
<button id="removing" onclick="remove()">Remove a word</button>
Dispatching Javascript event handlers always returns true, even if it returns false, which we all know is used to prevent default behaviour of an event. We don't usually use the return values of Event handlers or even return anything for that matter.
In your case, I think you're trying to acess the value of the currentTarget element(the button 'removing' in your case). For this you can use the event object, which gets passed on as parameter to your event handler.
event.currentTarget is a way of referencing the element on which an event is being dispatched(triggered) on. It's just like using 'this' inside the event handler, except it also works on arrow functions.
So do something like this:
function remove(event) {
let button = event.currentTarget;
if (buttton.value) {
document.getElementById("test").style.backgroundColor ="red";
}
}
and in HTML,
<div id="test">test</div>
<button id="removing" onclick="remove(event)">Remove a word</button>
Notice I've used remove(event).
Edit Based on comment below:
Using onclick will require you to create you a global 'remove' function.
If you do, '...onclick="remove(event)" what it basically does is creates the function below, a wrapper basically:
// In the global scope
[reference element].onclick = () => {
remove(event);
}
So you must have a global 'remove' function. So this won't work in modules cause each modules have their own top level scope. And you're gonna wanna have to use modules if you plan to work on sophisticated projects.
NOTE Using inline 'onclick' attributes in html has following disadvantages on heavy requests from a comment below:
-separation of concern : You usually don't want to mix up your UI logic(what happens on clicking a button) with presentation. You want a clear split between content, style and script.
-only one handler can be assigned using onclick.
-if an event is specified inline, the JS is specified as a string (attribute values are always strings) and evaluated when the event fires.(extra wrapper code builds internally).
-as I've mentioned before, you are faced with having to reference named functions. This is not ideal and has implications on the function needing to be global which will really bite you back when you use modules.
In short, handle events centrally via the dedicated addEventListener API.

Capture selected option returns not defined instead

I need to capture the selected user option and send that value in a post request.
Let's ignore the post part as it is not directly related to the question.
But right now the value appears as undefined.
CodePen:
https://codepen.io/ogonzales/pen/yLyQQaP
I've this JS, but lista_id, the value I need to capture, appears as undifined. Why?
I've tried to set this as a global variable. In the JS, you'll see an
alert, this alerts undefined for lista_id
JS:
<script>
$("#agregarProducto1").click(function () {
var lista_id;
$('#listas-de-usuario').change(function() {
var lista_id = $(this).find('option:selected').val();
});
alert(lista_id);
// $.post("{% url 'listas/agregar-producto/", {
// c_slug: "cuadernos",
// s_slug: "Cuadernos",
// product_slug: "cuadernos-rojos",
// lista_id: lista_id,
// });
});
</script>
You could declare the variable only once either globally or in the surrounding function and then not declare them inside the click or change handlers. This way, it is the same variable you are referring to.
Also, the code to bind change handler could be outside the click handler, otherwise it would be bound every time the button is clicked.
Example of global variable declaration:
CodePen
Example of variable declared inside document ready function of jQuery:
CodePen

JavaScript global variable in function and .hover

I have this code in JavaScript:
status = document.getElementById('status2');
$('#slider > img').hover(
function() {
stopLoop();
status.innerHTML = "paused";
},
function() {
startSlider();
status.innerHTML = "playing";
}
);
where I look for all the images in my html that have the id slider and when I hover on then I want to add a word (paused or playing) to a span tag that has the id status2. But I don't know why the global variable is not working, the only way that I make it work is putting a local variable inside each funcion like this:
function() {
stopLoop();
var status = document.getElementById('status2');
status.innerHTML = "paused";
},
function() {
startSlider();
var status = document.getElementById('status2');
status.innerHTML = "playing";
}
Can anyone me why?
NOTE: as I said before all works with the local variables but not setting it as a global variable.
Because by the time you run
status = document.getElementById('status2');
DOM was not ready so you get status as undefined and so it wont work further.
So either put the code in ready
$(document).ready(function(){
//code goes here
})
or
Put the script at the end of file.
Do add in a
$(document).ready(function(){
});
This waits to execute the code inside until everything has finished loading. That way it shouldn't return undefined.
ALSO
I couldn't help but noticing that you seem to be trying to give multiple items the same ID.
Don't use IDs for multiple elements. That's not how they are designed to be used, nor do they work that way.If you give multiple elements the same ID and then try and style them with CSS, it'll only style the first one. Use a class. If you use
document.getElementById();
to try and grab multiple elements with the same ID, then the script will ONLY grab the FIRST element with that ID, because, given that it is an ID, it expects only one element. If you want to work with multiple elements, use a class, and then use
document.getElementsByClassName();
this will grab ALL elements with that class. So for example,
say you have four span elements with the class "foo". To grab all these and change the text, do this:
elements=document.getElementsByClassName("foo");
for (i=0; i<elements.length; i++){
elements[i].innerHTML='insert your text here';
}
About global and local variables, a GLOBAL variable is declared this way:
global_variable='foo'
and a local variable is declared this way:
var local_variable='foo'
a Global variable can be declared anywhere in the script and be used anywhere inside the script(and even in other scripts that are attached to the same HTML file ), whereas a Local variable, if declared inside the function, can only be used inside the function, or if you declare it outside the function, it can't be accessed within the function unless you pass the variable to it.
Hope that helps!

Common JavaScript Closure issue on jQuery elements in an Array

I am working at building a widget that calls a particular plugin on each jQuery DOM element inside an array.
MyApp.forms is an array of Objects. Each Object has a jQuery wrapped DOM element.
I am doing the following:
$(MyApp.forms).each(function(i){
var individualForm = this;
/*
individualForm is an Object {
prop: 'value,
$el: somejQueryElement,
...
}
*/
individualForm.$el.thePlugin({
// options
})
.on('pluginEvent', function() {
individualForm; // refers to the last object in MyApp.forms
this; // refers to the last
$(this); // same problem
}).on('pluginEvent2', function() {
// same problem as above here.
});
});
The events pluginEvent and pluginEvent2 get attached to all individualForm's $el. But when they fire, I always get the last element.
I feel this is a common JavaScript Closure problem.
I tried using a for loop and creating an IIFE inside but it doesn't work, as the function executes when the event fires. And though both events fire on all elements, I only get the handler attached to last element executed.
Update:
Found The fix. But don't know why and how it worked.
Every individualForm.$el element is an input element with class="some-class".
Somewhere else in the code, another developer is doing $('.some-class').bind(... with an older version of jQuery. And then again with a newer version of jQuery (using noConflict $). There are 2 jQuery's on the page. The fix was to delete the first .bind.
Can you please try the following:
$(MyApp.forms).each(function(i){
var form = this;
(function(individualForm) {
individualForm.$el.on('something', function() {
individualForm; // refers to the last object in MyApp.forms
this; // refers to the last
$(this); // same problem
}).on('somethingElse', function() {
// same problem as above here.
});
})(form);
});
You should wrap individualForm in a closure. Otherwise the scope is changed and it points to the last element of the array.

jQuery Event Handler created in loop

So I have a group of events like this:
$('#slider-1').click(function(event){
switchBanners(1, true);
});
$('#slider-2').click(function(event){
switchBanners(2, true);
});
$('#slider-3').click(function(event){
switchBanners(3, true);
});
$('#slider-4').click(function(event){
switchBanners(4, true);
});
$('#slider-5').click(function(event){
switchBanners(5, true);
});
And I wanted to run them through a loop I am already running something like this:
for(i = 1; i <= totalBanners; i++){
$('#slider-' + i).click(function(event){
switchBanners(i, true);
});
}
In theory that should work, but it doesnt seem to once I load the document... It doesnt respond to any specific div id like it should when clicked... it progresses through each div regardless of which one I click. There are more event listeners I want to dynamically create on the fly but I need these first...
What am I missing?
This is a very common issue people encounter.
JavaScript doesn't have block scope, just function scope. So each function you create in the loop is being created in the same variable environment, and as such they're all referencing the same i variable.
To scope a variable in a new variable environment, you need to invoke a function that has a variable (or function parameter) that references the value you want to retain.
In the code below, we reference it with the function parameter j.
// Invoke generate_handler() during the loop. It will return a function that
// has access to its vars/params.
function generate_handler( j ) {
return function(event) {
switchBanners(j, true);
};
}
for(var i = 1; i <= totalBanners; i++){
$('#slider-' + i).click( generate_handler( i ) );
}
Here we invoked the generate_handler() function, passed in i, and had generate_handler() return a function that references the local variable (named j in the function, though you could name it i as well).
The variable environment of the returned function will exist as long as the function exists, so it will continue to have reference to any variables that existed in the environment when/where it was created.
UPDATE: Added var before i to be sure it is declared properly.
Instead of doing something this .. emm .. reckless, you should attach a single event listener and catch events us they bubble up. Its called "event delegation".
Some links:
http://davidwalsh.name/event-delegate
http://net.tutsplus.com/tutorials/javascript-ajax/quick-tip-javascript-event-delegation-in-4-minutes/
http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/
http://lab.distilldesign.com/event-delegation/
Study this. It is a quite important thing to learn about event management in javascript.
[edit: saw this answer get an upvote and recognized it's using old syntax. Here's some updated syntax, using jQuery's "on" event binding method. The same principle applies. You bind to the closest non-destroyed parent, listening for clicks ON the specified selector.]
$(function() {
$('.someAncestor').on('click', '.slider', function(e) {
// code to do stuff on clicking the slider. 'e' passed in is the event
});
});
Note: if your chain of initialization already has an appropriate spot to insert the listener (ie. you already have a document ready or onload function) you don't need to wrap it in this sample's $(function(){}) method. You would just put the $('.someAncestor')... part at that appropriate spot.
Original answer maintained for more thorough explanation and legacy sample code:
I'm with tereško : delegating events is more powerful than doing each click "on demand" as it were. Easiest way to access the whole group of slider elements is to give each a shared class. Let's say, "slider" Then you can delegate a universal event to all ".slider" elements:
$(function() {
$('body').delegate('.slider', 'click', function() {
var sliderSplit = this.id.split('-'); // split the string at the hyphen
switchBanners(parseInt(sliderSplit[1]), true); // after the split, the number is found in index 1
});
});
Liddle Fiddle: http://jsfiddle.net/2KrEk/
I'm delegating to "body" only because I don't know your HTML structure. Ideally you will delegate to the closest parent of all sliders that you know is not going to be destroyed by other DOM manipulations. Often ome sort of wrapper or container div.
It's because i isn't evaluated until the click function is called, by which time the loop has finished running and i is at it's max (or worse overwritten somewhere else in code).
Try this:
for(i = 1; i <= totalBanners; i++){
$('#slider-' + i).click(function(event){
switchBanners($(this).attr('id').replace('slider-', ''), true);
});
}
That way you're getting the number from the id of the element that's actually been clicked.
Use jQuery $.each
$.each(bannersArray, function(index, element) {
index += 1; // start from 0
$('#slider-' + index).click(function(event){
switchBanners(index, true);
});
});
You can study JavaScript Clousure, hope it helps

Categories