This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Event handlers inside a Javascript loop - need a closure?
I am getting some json object through an API call.It is of the folowing format:-
{data:[{id:"1",name:"some1",url:"someurl1"},{id:"2",name:"some2",url:"someurl2"}...]}
I get it through a jsonp result and it is parsed as below:-
function(results){
for(var i=0;i<10;i++){
item=document.createElement("div");
item.innerHTML=results.data[i].name;
item.onclick=function(){
console.log(results.data[i]); //--->this is where i am stuck
}
}
}
How do i pass the particular object from the loop to the onclick event. I mean the first div created should have an onclick event with the data of the first object and the second div should have the data from the second object..
Please ask me if any more clarification is required
Edit:-
If I do something like this:-
item.onclick=function(){
console.log(results.data[1])
}
I get that particular object in the onclick event of all the items , but that is not what i want
Edit:-
This is how i finally solved it. Thanks to the link pointed to by DCoder.
item.onclick =function(object){
return function(){
//do something with the object
}
}(obj);
Quick solution:
function(results){
for(var i=0;i<10;i++){
(function(index){ // create a closure, this makes a new scope
item=document.createElement("div");
item.innerHTML=results.data[index].name;
item.onclick=function(){
console.log(results.data[index]); //--->this is where i am stuck
}
})(i); // pass in i
}
}
You could always jQuery's data function to store and retrieve the data object:
function(results){
for(var i=0;i<10;i++){
item=document.createElement("div");
item.innerHTML=results.data[i].name;
// Store object in data
$(item).data("results", results.data[i]);
item.onclick=function(){
// Retrieve the data needed
var data = $(this).data("results");
console.log(data);
}
}
}
The onclick event is fired from jquery when someone clicks on 'item'. You cannot expect custom data to the event handler unless it is triggered using trigger function.
You're running into trouble with doing async calls inside of js. This is a common problem and it's describe here:
http://dojo-toolkit.33424.n3.nabble.com/Advice-on-closures-and-retaining-variable-values-inside-async-handlers-e-g-xhrGet-td3227726.html
Basically the value of i is equal to 9 by the time your console.log is actually ran.
There are many ways to solve this problem in general, but your specific solution should probably be to restructure things immensely. Consider this alternative (requires jQuery), but we could do the same without it very easily.
$.each(results, function(data) {
var $el = $("<div></div>").html(data.name).click(function() { console.log(data) });
})
But it would be even better to use jQuery.data() to store things and then use .on() or .delegate() to listen for the click events like this
$.each(results, function(data) {
var $el = $("<div></div>").addClass("yourThing").html(data.name).data("data", data);
})
// replace window with a closer parent if one exists
$(window).on("click", ".yourThing", function() {
console.log($(this).data("data")); // retrieve data from jquerys .data() store
});
Add a hidden field inside your div (created in loop), keep whatever you want in it.
then on click of div. find hidden field inside it(div) and read its value.
I'd personally use $.map so the function callback creates a new execution context for each iteration.
As you've tagged the question with jQuery, your code can be as simple as
$($.map(data, function(v, i) {
return $('<div>').html(data[i].name).click(function() {
console.log(data[i]);
});
})).appendTo('body'); //appending for demonstration purposes
Fiddle
Of course, you have to wrap it inside a function and pass the data array of objects as you were doing previously.
Related
I have a function, which at the end of its task assigns a button to a new id.
function altChecker() {
var doc = document,
out = [],
appButton = doc.getElementById('appButton'),
//re = /click-me/gi,
output = doc.createElement('p');
output.setAttribute('id', 'output');
EventUtility.addHandler(appButton, 'click', function(e) {
//I have not included all the function details to concentrate on the question
appButton.id = 'appButtonNextChecker';
var appButtonNextChecker = doc.getElementById('appButtonNextChecker');
nextChecker(appButtonNextChecker);
});
}
function nextChecker(newBtnName) {
EventUtility.addHandler(newBtnName, 'click', function(e) {
$('#output').innerHTML = "";
console.log('next Checker, button!')
});
}
So basically there is one button in the DOM assigned to appButton ID initially, and then I change it doing:
appButton.id = 'appButtonNextChecker';
when the altChecker function fires...
Then I assign the button to a new variable, and pass in the variable to the next function...
var appButtonNextChecker = doc.getElementById('appButtonNextChecker');
nextChecker(appButtonNextChecker);
While I can see the buttons' ID change in the DOM, and I see the console.log fire in the nextChecker function,
$('#output').innerHTML = ""; //doesn't fire
AND the altChecker function fires as well (again)?! Haven't I severed the connection to the click function when I reassigned the new ID?
Any help would be appreciated!
Javascript doesn't remember that you initially attached the event through it's id. The event is attached to the element itself, not the ID. It's not like CSS that way.
In fact your variables are still holding the same element as well, so there's no need to create a new variable after changing the ID, either. Since you're using jQuery you can just type $(appButton).unbind(); to remove the event handler. You may also want to look into .on() and .off()
The problem is that you're trying to use the innerHTML property in a jQuery's object.
That property belongs to Element, and it will not work in the way you're using it.
You can use the document.getElementById method, and it will work fine:
document.getElementById('output').innerHTML = '';
Or you can use jQuery's html method:
$('#output').html('');
And you can even use the first element of the jQuery's array, and use innerHTML again:
$('#output')[0].innerHTML = '';
It's up to you, but the first option will be faster, for sure.
A few weeks ago I was painfully able to dynamically add buttons to an HTML DOM object that has its own .on('click'.. handler, and use e.stopPropgation to stop these new child elements from firing the event.
The weird thing I did was call a function without any parenthesis. I have no idea why I did this or why it works, or why it does not work when I do attach parenthesis. I want to know if I am doing something by fluke and not design (and now I will add comments to it).
It goes as such:
//Container is the parent element
// var buttons stores the buttons with class 'buttons'
$('.container').on('click', function(){
$(buttons).appendTo($(this)).fadeIn(500).find('.buttons').click(tableButton);
});
function tableButton(e){
e.stopPropagation();
//do stuff
}
I can't figure out why I wrote the call to tableButton with no arguements or why it works perfectly. I tried to change the syntax to
.find('.buttons').on('click', function(e){
tableButton(e);
});
but then it no longer works.
Any help appreciated!
It works because you're passing a function to the click handler rather than calling the function yourself (the ()) An example of that:
var testFunction = function(msg) {
alert(msg);
}
var functionCaller = function(functionToCall) {
functionToCall('hello!');
}
functionCaller(testFunction);
functionCaller passes the message argument to testFunction(), but we only pass testFunction to functionCaller (without arguments)
For the part which doesn't work, isn't the function name tableButton() instead of tableButtons()?
See http://jsfiddle.net/g2PAn/
You don't actually call it, you just declare it and the arguments it accepts. The click callback is called with an argument indeed, but not by you.
The problem probably comes from the fact that jQuery calls your function with the element clicked bound as this, you could call table button like this:
.find('.buttons').on('click', function(e){
tableButton.call(this, e);
});
I'm trying to write a plugin that will select multiple elements and then apply some private methods to them (see code below). Then I also want to give the user the ability to trigger the activation of the plugin's methods manually with a .activate() function.
Here is my code :
MARKUP : https://github.com/simonwalsh/jquery.imagepox/blob/master/demo/index.html
JS : https://github.com/simonwalsh/jquery.imagepox/blob/master/dist/jquery.imagepox.js
Basically, when I select multiple items and then try to use the manual activation like so :
$(".pox-wrapper").imagepox({ // NOTE: selects two elements
manualActivation: true
});
var manual = $(".pox-wrapper").data('imagepox');
setTimeout(function(){
manual.activate();
}, 5000);
It will only apply the activate() method to the first element in the query...
This is my first jQuery plugin and I've been able to handle everything so far but I'm not sure about this one or even if it is the right way to effectively call a public method. I also tried using a custom event with an event listener in the plugin but it still only applies the methods on the first element in the page.
Thanks in advance :)
its not your plugin's fault. data does not work like that, it doesnt know how to return data from a collection of elements. Because think about it, each element in the collection contains its own data object!
So when you call data on a collection, it returns the data from the first one. The quick solution would be to change the innards of the setTimeout into a loop over all the elements in the set and call activate on them.
setTimeout(function(){
$(".pox-wrapper").each(function(){
$(this).data('imagepox').activate();
})
}, 5000);
It seems to me that you want to add functions to collections of jquery objects. This is the usecase of a jquery plugin. You can create a lightweight one like this:
$.fn.imagepox.activate = function(){ //do this after you create your plugin!
return this.each(function(){
var $this = $(this);
var data = $this.data('imagepox');
if(data){
data.activate();
}
});
};
now you can call it like this:
$(".pox-wrapper").imagepox.activate()
I'm trying to use jQuery to build a home-made validator. I think I found a limitation in jQuery: When assigning a jQuery value to a json variable, then using jQuery to add more DOM elements to the current page that fit the variable's query, there doesn't seem to be a way to access those DOM elements added to the page which fit the json variable's query.
Please consider the following code:
var add_form = {
$name_label: $("#add-form Label[for='Name']"),
$name: $("#add-form #Name"),
$description_label: $("#add-form Label[for='Description']"),
$description: $("#add-form #Description"),
$submit_button: $("#add-form input#Add"),
$errors: $("#add-form .error"),
error_marker: "<span class='error'> *</span>"
}
function ValidateForm() {
var isValid = true;
add_form.$errors.remove();
if (add_form.$name.val().length < 1 ) {
add_form.$name_label.after(add_form.error_marker);
isValid = false;
}
if (add_form.$description.val().length < 1) {
add_form.$description_label.after(add_form.error_marker);
isValid = false;
}
return isValid
}
$(function(){
add_form.$submit_button.live("click", function(e){
e.preventDefault();
if(ValidateForm())
{
//ajax form submission...
}
});
})
An example is availible here: http://jsfiddle.net/Macxj/3/
First, I make a json variable to represent the html add form. Then, I make a function to validate the form. Last, I bind the click event of the form's submit button to validating the form.
Notice that I'm using the jQuery after() method to put a span containing an '*' after every invalid field label in the form. Also notice that I'm clearing the asterisks of the previous submission attempt from the form before re-validating it (this is what fails).
Apparently, the call to add_form.$errors.remove(); doesn't work because the $errors variable only points to the DOM elements that matched its query when it was created. At that point in time, none of the labels were suffixed with error_marker variable.
Thus, the jQuery variable doesn't recognize the matching elements of it's query when trying to remove them because they didn't exist when the variable was first assigned. It would be nice if a jQuery variable HAD AN eval() METHOD that would re-evaluate its containing query to see if any new DOM elements matched it. But alas...
Any suggestions?
Thanks in advance!
You are correct that a jQuery object is not "live" – that is, the set of elements in the jQuery object is not dynamically updated. (This is a good thing.)
If you really want to update an arbitrary jQuery object, you can get the selector used to create the object from .selector:
var els = $('#form input');
els.selector // '#form input'
So you could do els = $(els.selector); to re-query the DOM.
Note, however, that if you modified the collection after the initial selector (functions like add, filter, children, etc.), or if the jQuery object was created without using a selector (by passing a DOMElement), then .selector will be pretty much useless, since the selector will be empty, incorrect, or even potentially invalid.
Better is to re-structure your code in such a way that you aren't holding on to a stale jQuery object; the other answers make some good suggestions.
Also, please make sure you're validating input server-side too!
For the objects that are going to be changing, instead of making the JSON object reference a static value, make it a function:
$errors: function() { return $("#add-form .error"); },
since it's a function, it will re-evaluate the error fields every time you call add_form.$errors().
Your approach to the problem has got many structural problems:
Avoid using global variables
There may not always be just one form on the page that you are validating. What if one day you will decide that you will have several forms. Your add_form variable is a global variable and therefore would be conflicting.
Do not use the submit button click event for detecting form submissions.
What if a form is submitted by a js call like $("form").submit(); or by the enter key?
Store the selectors instead of the DOM objects if you are not certain that the objects already exist at the creation time of the configuration object.
.live is deprecated. Use .on instead.
It is 3. that will solve your actual problem, but I strongly recommend addressing all 4 issues.
For 2, the best place to attach the validator is not on the submit button, but on submit event of the form. Something like this:
$("#add-form").submit(function(event) {
event.preventDefault();
if (validateForm(this))
$.ajax({
url: $(this).attr("action"),
data: $(this).serialize(),
//ETC
});
});
Note how the form is now also much easier to access. Your configuration object no longer needs to store a reference to the submit button.
Your configuration object could now be simplified to be something like this:
{
name_label: "Label[for='Name']",
name: "#Name",
description_label: "Label[for='Description']",
description: "#Description",
errors: ".error",
error_marker: "<span class='error'> *</span>"
}
Within validateForm, you can use these selector as follows:
var $name_label = $(configuration.name_label, this); //Finds the label within the current form.
Now, to allow different configuration parameters for each form use something like this:
function enableValidation(form, configuration) {
$.extend(configuration, {
//Default configuration parameters here.
});
function validateForm(form) {
//Your original function here with modifications.
}
$(form).submit(funciton(e) {
if (!validateForm(this))
e.preventDefault();
});
}
function enableAjax(form) {
$(form).submit(function(e){
if (!e.isDefaultPrevented()) {
e.preventDefault();
$.ajax(...);
}
});
}
$(function() {
enableValidation("#add-form", {/*specialized config parameters here*/});
enableAjax("#add-form");
});
In my JavaScript and Flex applications, users often perform actions that I want other JavaScript code on the page to listen for. For example, if someone adds a friend. I want my JavaScript app to then call something like triggerEvent("addedFriend", name);. Then any other code that was listening for the "addedFriend" event will get called along with the name.
Is there a built-in JavaScript mechanism for handling events? I'm ok with using jQuery for this too and I know jQuery makes extensive use of events. But with jQuery, it seems that its event mechanism is all based around elements. As I understand, you have to tie a custom event to an element. I guess I can do that to a dummy element, but my need has nothing to do with DOM elements on a webpage.
Should I just implement this event mechanism myself?
You have a few options:
jQuery does allow you to do this with objects not associated with the document. An example is provided below.
If you're not already using jQuery on your page, then adding it is probably overkill. There are other libraries designed for this. The pattern you are referring to is called PubSub or Publish/Subscribe.
Implement it yourself, as you've suggested, since this is not difficult if you're looking only for basic functionality.
jQuery example:
var a = {};
jQuery(a).bind("change", function () {
alert("I changed!");
});
jQuery(a).trigger("change");
I would implement such using MVVM pattern with knockjs library.
Just create an element, and use jquery events on it.
It can be just a global variable, doesn't even have to be connected to the DOM.
That way you accomplish your task easily and without any extra libs.
Isn't it possible to bind onchange events in addition to click events? For instance, if addFriend is called and modifies a list on the page, you could bind the change event to then invoke additional functionality.
$('#addFriendButton').click( function() {
// modify the #friendList list
});
$('#friendList').change( function() {
myOtherAction();
});
This is total Host independent, no need for jQuery or dom in this case!
function CustomEvents(){
//object holding eventhandlers
this.handlers_ = {};
}
//check if the event type does not exist, create it.
//then push new callback in array.
CustomEvents.prototype.addEventListner = function (type, callBack){
if (!this.handlers_[type]) this.handlers_[type] = [];
this.handlers_[type].push(callBack);
}
CustomEvents.prototype.triggerEvent = function (type){
//trigger all handlers attached to events
if (!this.handlers_[type]) return;
for (var i=0, handler; handler = this.handlers_[type][i]; i++)
{
//call handler function and supply all the original arguments of this function
//minus the first argument which is the type of the event itself
if (typeof handler === "function") handler.apply(this,arguments.slice(1));
}
}
//delete all handlers to an event
CustomEvents.prototype.purgeEventType = function(type){
return delete this.handlers_[type];
}
test:
var customEvents = new CustomEvents();
customEvents.addEventListner("event A", function(arg){alert('Event A with arguments' + arg);));
customEvents.triggerEvent("event A", "the args");
EDIT added arguments passing