Dynamically loaded onclick function using in function data - javascript

Ok, my coding is a little weak, but I'm learning. I have a code simular to
$.post("test.php", {"data":"data"}, function(grabbed){
$.each(grab, function(i, item){
var place = document.getElementById("div");
var para = createElement("p");
para.innerHTML = item.name;
para.onclick = function(){
document.getElementById(?para?).innerHTML = ?item.price?;
}
place.appendChild(para);
});
}, "json");
My question is how would I use say, item.price in the onclick function. I haven't actually tried yet, but I'm pretty sure that referencing the data retrieved from the request won't work in the dynamically loaded onclick function.
My function works a little differently but the basic principle is on here. Any advise would be appreciated, or perhaps someone can point me in the right direction.
I was thinking of assigning ids to the div or para and using those in the function somehow. I also thought of using cookies to store the data I want, but I can't figure it out. Is there something else I need to know or try.

Your question concerns the concept of closures in JavaScript.
Closures are functions that refer to independent (free) variables. In
other words, the function defined in the closure 'remembers' the
environment in which it was created.
Consider the following code snippet:
var foo = (function(){
var remembered = "yea!";
return function(){
console.log("Do you remember? - " + remembered);
};
})();
foo(); // prints: Do you remember? - yea!
That said, looking at your code:
...
// the function assigned to `onclick` should remember
// the variable `item` in the current context.
para.onclick = function(){
document.getElementById(?para?).innerHTML = ?item.price?;
}
...
That works since you're using JQuery's $.each() for iteration. And here is a demonstration: http://jsfiddle.net/55gjy3fj/
However if you were doing the iteration with a plain for-loop, you would have to wrap the handler inside another closure:
var getHandler = function(para, item){
return function(){
document.getElementById(the_id_here).innerHTML = item.price;
};
};
para.onclick = getHandler(para, item);
...
This fiddle demonstrates the point: http://jsfiddle.net/63gh1gof/
Here's a fiddle that summarises both cases:

Since you're already using jQuery, this could be the perfect opportunity to get introduced to .data(). Use it to save an arbitrary object to the dom element, and then reference it later in the onclick-listener.
var para = $('<p/>').html(item.name).data('item', item);
para.click(function() {
$(this).data('item') // item!!!
});
$('#div').append(para);
Actually, here you could write a more efficient method using jQuery's delegated event handlers.
Just attach the event handler to the parent element:
$('#div').on('click', 'p', function() {
$(this).data('item') // item!!!
});

Related

Best way to attach an arbitrary callback function to a dom element?

The following (vanillajs) code works fine
// library code:
let close_cb; // nasty global var...
...
let tree = document.createElement('ul');
tree.addEventListener('click', function(event) {
...
// let close_cb = tree.getAttribute('CLOSE_CB');
// let close_cb = tree.onchange;
close_cb(leaf.id, ', so there');
// user code:
function my_close_cb(id, msg) {
const footer = document.querySelector('footer');
footer.innerHTML = id + ' is closed' + msg;
}
// tree.setAttribute('CLOSE_CB',my_close_cb);
// tree.onchange = my_close_cb;
close_cb = my_close_cb;
However the commented-out s/getAttribute code fails, getAttribute puts full text of "function my_close_cb(..." in local close_cb.
The commented-out onchange hack actually works, but feels terribly dodgy to say the least, although it is certainly closer to what I'm after.
Note the "library code" is hand written and fully under my control, whereas "user code" is intended to be transpiled or otherwise machine-generated, so changing my_close_cb to accept a single event argument would be a complete non-starter.
What is the best way to attach an arbitrary callback function that accepts an arbitrary set of parameters to a dom element?
You can attach a plain json property to the DOM element.
document.body.callback = function cb(text) { console.log(text); };
document.body.callback("hello world");
Using tree.close_cb or any other property to store a function or anything else is totally fine, as the DOM is just a (persistent) tree of JavaScript objects. As such they behave like any other JS object, and properties can be added without any restrictions.

Variable as eventListener named function expression name

I'm writing an awesome IIFE and want this to be as easy as possible for my users who use it. So I was thinking since some of them don't know that to easily remove an eventlistener without it already being a function we can give that inline function a name
Example
document.addEventListener('click',function dood(){
//some function
},false);
document.removeEventListener('click',dood,false);
//instead of
function dood(){
//some function
}
document.addEventListener('click',dood,false);
document.removeEventListener('click',dood,false);
But since they shouldn't know the name exactly I was wondering if we could do
var k = "name_of_function";
document.addEventListener('click',function window[k](){
//the function
},false);
Though I know this does not work is there a way to do this? I'd like to make it so they can easily do this
object.cancel('name_of_function') //which will be the name of the instance
// they created earlier if they gave that instance a name
object={
cancel:function(nm){
document.removeEventListener(self.trigger,window[nm],false);
//self.trigger really is this.trigger which they assign as either scroll,click,mousemove,etc.
}
};
Any ideas? Or is this not possible at all?
usage is:
scrollex('element',{
max:500,
min:500,
pin:200,
offset:0.5,
name:'google',//this then would be used in multiple instances
});
scrollex.events //shows all events and their names
scrollex.listen('google'); //it'll console log all info for this event
scrollex.cancel('google');
I think you're on the right track. But you should not use window, and some local object instead. And dynamically naming function expressions (or whatever that function window[k](){} was supposed to mean) is impossible a pain - don't try this. Just let them stay anonymous, and reference them only via property names / variables.
var object = (function() {
var listeners = {
name_of_function: function dood(){…}
};
document.addEventListener('click', listeners.name_of_function, false);
return {
cancel: function(nm) {
document.removeEventListener('click', listeners[nm], false);
}
};
}());
// now, you can
object.cancel('name_of_function')

How to flatten a touchmove listener in JavaScript?

Trickshot #29 shows how to define touch events in jQuery. I've reworked it to my style of rogue writing in this fiddle.
What the author does is define a touchmove listener whenever a touchstart event is fired.
request.dom.ball.on('mousedown touchstart',myTouchStart);
function myTouchStart(myEvent){
request.dom.ball.on('mousemove.myNameSpace touchmove.myNameSpace',myTouchMove);
function myTouchMove(myEvent) {
What I'd like to do is put myTouchMove outside of myTouchStart because my rogue style of JavaScript writing is to try to keep it as flat as possible, and not have functions inside of functions, if I can help it.
That might seem strange since I already wrap everything inside of:
(function() {
})();
to begin with, but I really don't want to have functions inside of functions inside of functions if I can help it.
One way to do this is to return the inner function from another function (called a closure) that will pass any variables that you were originally referencing to your original function.
As far as I can tell in your case that's only one variable - local - but if you ever add new variables, just add a new argument to the function definition and the function call.
Updated fiddle: http://jsfiddle.net/ynUHb/1/
The affected code:
request.dom.ball.on('mousedown touchstart',myTouchStart);
function myTouchStart(myEvent){
var local = {};
//...
request.dom.ball.on('mousemove.myNameSpace touchmove.myNameSpace',myTouchMove(local));
//...
};
function myTouchMove(local) {
return function(myEvent) {
var myCss = {};
myEvent = (myEvent.originalEvent.touches) ? myEvent.originalEvent.touches[0] : myEvent;
myCss.top = local.elementPosition.y + myEvent.pageY - local.startPosition.y;
request.dom.top.text(myCss.top);
myCss.left = local.elementPosition.x + myEvent.pageX - local.startPosition.x;
request.dom.left.text(myCss.left);
request.dom.ball.css(myCss);
};
};
See:
Forming Closures

Can I define a variable to replace the use of $(this) in this example?

I have the following code:
$('#detailData')
.on('click', '.gridLink', function () {
dialog(this);
return false;
})
function dialog(link) {
var $link = $(link);
var viewURL = $link.attr('data-href')
Am I correct in saying I can replace that with this?
$('#detailData')
.on('click', '.gridLink', function () {
var $gridLink = $(this);
dialog($gridLink);
return false;
})
function dialog($gridLink) {
var viewURL = $gridLink.attr('data-href')
I tried to place this onto code review at stackoverflow.com. Someone needs to fix the logon problems as I just could not connect with my stack account :-(
Yes, foo = $(this) is perfectly legal, and legit. In fact, it's not entirely uncommon. It's wise to do this when you feel the need to wrap this over and over in the jQuery object. This way, you wrap it once, and have a reference to work from which offers performance benefits.
Yes the way you are passing in both the cases is perfectly legal..
This also has the advantage of caching it and reusing it instead of trying to access it every single time you use.

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