views:

76

answers:

4

I have a loop that when it is called, creates a div with some form elements in them. Each div is based on a variable, "i" to give unique names to the fields and divs. Is there a way I can store what the variable was at the point of creating the div?

For example, div1 is created and everything in it has 1 (the variable) attached to the name. The form elements rely on each other and are called by ID. Problem is, when I create a new div and the variable (i) is changed to 2, the first set of form elements try to use 2 instead of 1.

Make sense?

Edit: Here's some code. It's pretty messy so I apologize in advance.

    var i = 0;

    $('a#add-product').click(function(event){
        i++;
        $('<div />').addClass('product').attr('id', 'product'+i)
            .append($('<h2><img src="<?php echo base_url();?>img/product.png" alt="" />Product '+i+'</h2>'))
            .append($('<div class="info-line"><label>Division</label><p><select id="selection-'+i+'" class="selection"><option value="">- Select a Division -</option><option value="abrasives">Abrasives</option><option value="tapes">Bonding, Surface Protection &amp; Tapes</option><option value="packaging">Packaging</option></select></p></div>'))
            .append($('<div class="info-line"><label>Category</label><p><select id="selectionresult-'+i+'" name="selectionresult-'+i+'" class="selectionresult"></select><span id="result-'+i+'" class="result">&nbsp;</span></p></div>'))
            .append($('<div class="info-line"><label>Product</label><p><select id="selectionresult2-'+i+'" name="selectionresult2-'+i+'" class="selectionresult2"></select><span id="result2-'+i+'" class="result2">&nbsp;</span></p></div>'))
            .append($('<a class="remove" href="#add-product" id="remove-product'+i+'"><img src="<?php echo base_url();?>img/remove-product.jpg" alt="" />Remove Product</a>'))
            .appendTo("#products");

            // START OF ADDITIONAL PRODUCT DROP DOWNS

                    $("#selectionresult-"+i).hide();
                    $("#selectionresult2-"+i).hide();

                    $("#selection-"+i).change( function() {

                        $(this).next(".selectionresult").hide();
                        $(this).next(".selectionresult2").hide();
                        $("#result-"+i).html('Retrieving ...');
                        $.ajax({
                            type: "POST",
                            data: "data=" + $(this).val(),
                            url: "<?php echo base_url();?>dropdown.php",
                            success: function(msg){
                                if (msg != ''){
                                    $("#selectionresult-"+i).html(msg).show();
                                    $("#result-"+i).html('');
                                }
                                else{
                                    $("#result-"+i).html('<em>No item result</em>');
                                }
                            }
                        });

                    });
                    $("#selectionresult-"+i).change( function() {
                        $(this).next(".selectionresult2").hide();
                        $("#result2-"+i).html('Retrieving ...');
                        $.ajax({
                            type: "POST",
                            data: "data=" + $(this).val(),
                            url: "<?php echo base_url();?>dropdown.php",
                            success: function(msg){
                                if (msg != ''){
                                    $("#selectionresult2-"+i).html(msg).show();
                                    $("#result2-"+i).html('');
                                }
                                else{
                                    $("#result2-"+i).html('<em>No item result</em>');
                                }
                            }
                        });
                    });
    });
+1  A: 

You can place the code that needs to reference the correct version of i in a closure like this:

var i = 0;

$('a#add-product').click(function(event){
    i++;

    // Begin closure. When called (at the end of the closure) it receives
    //    the current value of "i". This value of "i" will be referenced
    //    throughout the closure as a local variable containing the value
    //    you expect, instead of the "shared" "i" variable outside the 
    //    closure.
    (function( i ) {

        // So basically we've created a new "scope" inside here. Now "i"
        //    is a separate local variable than the "i" variable ouside
        //    the closure. You could change the variable name by changing
        //    the parameter above. Like (function( my_i ) {...
        // If you did that, you would need to change the "i" in your .change()
        //    handlers to "my_i". The rest of them could stay the same, or you
        //    could change them. Either way would work.
        // This is because the .change() handlers are executed at a later time
        //    (and so are the AJAX callbacks) so they need to use the variable
        //    that is local to this closure.
        // The rest of the code, like $("#selectionresult-" + i) is executing
        //    immediately, so it could reference the "i" variable that is
        //    outside the closure, and still work properly.

        $('<div />').addClass('product').attr('id', 'product'+i)
            .append($('<h2><img src="<?php echo base_url();?>img/product.png" alt="" />Product '+i+'</h2>'))
            .append($('<div class="info-line"><label>Division</label><p><select id="selection-'+i+'" class="selection"><option value="">- Select a Division -</option><option value="abrasives">Abrasives</option><option value="tapes">Bonding, Surface Protection &amp; Tapes</option><option value="packaging">Packaging</option></select></p></div>'))
            .append($('<div class="info-line"><label>Category</label><p><select id="selectionresult-'+i+'" name="selectionresult-'+i+'" class="selectionresult"></select><span id="result-'+i+'" class="result">&nbsp;</span></p></div>'))
            .append($('<div class="info-line"><label>Product</label><p><select id="selectionresult2-'+i+'" name="selectionresult2-'+i+'" class="selectionresult2"></select><span id="result2-'+i+'" class="result2">&nbsp;</span></p></div>'))
            .append($('<a class="remove" href="#add-product" id="remove-product'+i+'"><img src="<?php echo base_url();?>img/remove-product.jpg" alt="" />Remove Product</a>'))
            .appendTo("#products");

        // START OF ADDITIONAL PRODUCT DROP DOWNS
        $("#selectionresult-" + i).hide();
        $("#selectionresult2-" + i).hide();

        $("#selection-" + i).change(function () {

            $(this).next(".selectionresult").hide();
            $(this).next(".selectionresult2").hide();
            $("#result-" + i).html('Retrieving ...');
            $.ajax({
                type: "POST",
                data: "data=" + $(this).val(),
                url: "<?php echo base_url();?>dropdown.php",
                success: function (msg) {
                    if (msg != '') {
                        $("#selectionresult-" + i).html(msg).show();
                        $("#result-" + i).html('');
                    }
                    else {
                        $("#result-" + i).html('<em>No item result</em>');
                    }
                }
            });

        });
        $("#selectionresult-" + i).change(function () {
            $(this).next(".selectionresult2").hide();
            $("#result2-" + i).html('Retrieving ...');
            $.ajax({
                type: "POST",
                data: "data=" + $(this).val(),
                url: "<?php echo base_url();?>dropdown.php",
                success: function (msg) {
                    if (msg != '') {
                        $("#selectionresult2-" + i).html(msg).show();
                        $("#result2-" + i).html('');
                    }
                    else {
                        $("#result2-" + i).html('<em>No item result</em>');
                    }
                }
            });
        });

     // End closure. Executes the closure function, passing in the
     //   current value of "i"
    })( i );
});

EDIT:

To explain what is happening, in javascript, variables passed to (or created in) a function body are local to that function, and they persist.

All I'm doing above is creating a function that accepts one parameter. Here I'll change the name of the parameter to perhaps make it more clear:

function( inner_i ) {
    // create your element with the new local variable "inner_i"
}

...but I'm also calling that function as soon as I create it:

(function( inner_i ) {
    // create your element with the new local variable "inner_i"
})( i )
//  ^------- call the function, passing in the "i" from your loop.

The syntax looks a little strange, but it is simply a way to call a function that you've just created.

It would be the same as doing:

function myNewFunction( inner_i ) {
    // creates your element with the new local variable "inner_i"
}

myNewFunction( i );  // Call the function we just created above, 
                     //   and pass the "i" from the loop into it
patrick dw
Couldn't get this working, might be because I'm not using a for loop.
Carson
@Carson - You would use it the same way. I'll replace my answer at the top to demonstrate. For simplicity, I'm just going to wrap (almost) the *entire* code in the closure even though the selectors don't need it. Note that the `i++` will be *outside* the closure.
patrick dw
@Carson - I update my answer. To understand what's happening, read the code comments that give an explanation. If you have any questions, feel free to ask them here. Closures are probably one of the best features of javascript, so at some point, it will be worthwhile understanding a little bit about them. :o)
patrick dw
@Carson - ...one more comment. :o) Here's an example using a shortened version of your code. http://jsfiddle.net/9hBmG/ Click the `add product` text on the right a few times, then click the resulting "products". You'll get 2 popups, one showing the value of `i` referenced inside the handler, and the other showing the value inside the AJAX call. You'll see that they have the values you expect.
patrick dw
@patrick dw you're a genius! I tried what you said before but it didn't work because I left out the left bracket before function. I can't wait to get the hang of this a bit more to see the full functionality of this stuff.Thanks so much
Carson
@Carson - You're welcome. :o)
patrick dw
@patrick dw - just a follow up, now that I've implemented the above (and it works great), the drop down menus no longer work once 'i' becomes greater than 2. So the first two products work fine, but the third and beyond don't.. did I miss something?
Carson
@Carson - I'd be happy to take a look, but I'm just heading out the door and won't be back until tonight. Any chance there's a public server where I could see the code in action? I'll check back later and take a closer look. Don't know why that would happen.
patrick dw
@patrick dw - yea it's weird that it's happening. I looked briefly at it again but couldn't figure it out. You can see it here - http://www.carsonshold.com/temp/php/logins/Login with scott/scott and mess around. There are probably a few glitches I haven't gotten around to yet though so just a heads up.
Carson
@Carson - The issue is with `productPreview`. The console error I get is `TypeError: Result of expression 'productPreview' [null] is not an object.`. Looks like it is because the following line returns "null": `var productPreview = document.getElementById("product"+i+"image");`. When your page loads, as far as I can tell, there's a `#product1image` and a `#product2image`, but it stops there. So when the third "Product" looks for `#product3image` it gets `null` and fails when you try to set `null.src = "http://www.carsonhold.com/temp/php/logins/img/spacer.gif"`.
patrick dw
That makes sense, thanks!
Carson
@Carson - You're welcome. :o)
patrick dw
A: 

Use a separate variable to store the index in, using the loop to add to it.

Roughly speaking, the funtion with the loop (whether or not it calls another function with the index number or not is irrelevant) but this way you get the index value stored, and use your loops so there are no duplicates.

var uniqueIndex = 0;

function functionWithLoop(x)
{
    for (i=0; i<x; i++)
    {
        uniqueIndex++;
        createNewDivFunction(uniqueIndex);
    }
}
gabe3886
A: 

From what I can understand the elements in div1 refer to each other but not to the elements in div2. The question is, HOW do they refer to each other? If it is by events, for example a textbox's onchange event, then you can do the following:

var MAX = 10;
for (var i = 0; i < MAX; i++) {
    // Create textbox
    $("<input type='text'>").attr("name", "slave" + i);
    // Create a second textbox that, when changed, takes it's value,
    // makes it uppercase, and copies it to the first one
    $("<input type='text'>").attr("name", "master" + i)
    .change(
        (function(i) {
            function() {
                $("input[name=slave" + i + "]").text($(this).text());
            }
        })(i)
    );
}

What it does is creates a temporary scope that captures the value of i within itself:

        (function(i) {
            function() {
                // the value of i is frozen in time within here
            }
        })(i)
box9
A: 

Actually, a simpler answer would be to store the integer as a value to each element you're creating. Saves you from messing with closures and is a lot more maintainable. Within your loop just do:

newElement.data("myIndex", i);

And to retrieve the index again:

newElement.data("myIndex");

Or, if within an event handler:

$(this).data("myIndex");
box9
This looks like exactly what I was looking for just didn't know how to do it. I'll give it a try a bit later today. Thanks
Carson
@Carson - Keep in mind that `.data()` is a slow operation compared to using a closure. If you're just needing to reference the value of `i`, this will work, but I'd say it's overkill.
patrick dw