2

Im stuck here, any hint would be nice.
I have an array of Objects objects[] and an array of divnames[]. My object has functions like play() and stop() on it. The Object and its' functions were tested in multiple situations, they are working. Now im Trying to iterate over the divnames[] and assign actions of the appropriate objects[] to mouseover, mouseout and click.
There was a closure problem, that i fixed with a solution that i found in another thread here on StackOverflow. That works. But the problem remaining is that the mouseover etc. actions are not assigned to divs that are loaded later on. They are working on objects that are on the page from the beginning.
Here is what i have:

$(function(){
    for (var i=0, len=divnames.length; i<len; i++) {
        if(divnames[i]){
            (function( ) { // anonymous function to fix closures
                var index = i; // also needed to fix closures
                $('#'+divnames[index]).on("click", function() {
                    objects[index].play();
                    loadContent(divnames[index]+".php");
                });
            })( ); // direct function execution to fix closures
        }
    }
});

As stated above, closure problem is fixed by the two commented lines, leaving them away will only ever apply the last execution of the for-loop. As it is now, that works.
But what doesnt work, is that the click function should also be applied to divs matching the selector, but are not yet loaded.
But that does work if the code with the same functionality is not iterated inside a for loop without the closure-fix, because thats expected .on() behaviour if i understand correctly.

So how do i get both desired functionalities to work?

Thanks for your time in advance.

----edit----

Additional information as requested:

var divnames = [];
divnames[0] = "home";
divnames[1] = "about";
divnames[2] = "projects";

function SpriteAnim (options) {
var timerId = 0; 
    var i = 0;
    this.status = 0;
this.init = function () {
    var element = document.getElementById(options.elementId);
    element.style.width = options.width + "px";
    element.style.height = options.height + "px";
    element.style.backgroundRepeat = "no-repeat";
    element.style.backgroundImage = "url(" + options.sprite + ")";
};
this.showFrame = function (which) {
    if (which < options.frames) {
                    i = which;
        element = document.getElementById(options.elementId);
        element.style.backgroundPosition = "0px -" + which * options.height + "px";
    }
};
this.play = function () {
            this.status = 2;
    timerId = setInterval(function () {
        if (i < (options.frames - 1)) {
                            i++;
            element = document.getElementById(options.elementId);
            element.style.backgroundPosition = "0px -" + i * options.height + "px";
                    } else {
                        clearInterval(timerId);
                        this.status = 1;
                    }
    }, 100);
};
}

As you probably have already guessed, the objects[] array contains 3 SpriteAnim objects in objects[0], objects[1], objects[2].

objects[0] = new SpriteAnim({
    width: 7,
    height: 7,
    frames: 8,
    sprite: "myanim1.png",
    elementId: "anim0"
});
objects[1] = new SpriteAnim({
    width: 7,
    height: 7,
    frames: 8,
    sprite: "myanim1.png",
    elementId: "anim1"
});
objects[2] = new SpriteAnim({
    width: 7,
    height: 7,
    frames: 8,
    sprite: "myanim2.png",
    elementId: "anim2"
});
1
  • Can you share your two arrays; this may help us provide a far better answer for you. Commented Oct 28, 2012 at 3:03

2 Answers 2

4

You need to use event delegation, and listen for these events on a parent element rather than on the elements themselves. This way, if you add new elements later on, you don't need to re-bind to them.

$("#buttons").on("click", ".button", function(){
    console.log("You clicked on a .button element.");
});

In this case, we bind to the click event on #buttons, but only when the invoking element matches the selector .button. Your markup would be something along the lines of this:

<div id="buttons">
    <a class="button">Foo</a>
    <a class="button">Bar</a>
</div>

When a click occurs on any of the .button elements, and bubbles up to the #buttons parent, it will be intercepted and the handler will be fired.

Sign up to request clarification or add additional context in comments.

2 Comments

Why does it perfectly work with a single element selector as shown when its not inside a for-loop & closure fix construct if THAT is the problem? Im trying to apply your solution, but since i have one object for each divname that needs to react independantly from the others its quite confusing.
@KarlKratzisheim Provide your arrays and perhaps I can get you a more tailored solution.
1

It's because index is declared outside of your binding statement. When the click goes into that call, it has no idea what objects[index] is.

If you want to keep the same structure, rework your function like this:

$(function(){
    for (var i=0, len=divnames.length; i<len; i++) {
        if(divnames[i]){
            (function( ) { // anonymous function to fix closures
                var index = i; // also needed to fix closures
                $('#'+divnames[index]).on("click", function(e) {
                    switch ($(e.id).attr('name')){
                        case 'home':
                            objects[0].play();
                            loadContent('home.php');
                            break;
                        case 'about':
                        // do same
                        case 'projects':
                        // do same
                        default:
                            break;
                    }
                });
            })( ); // direct function execution to fix closures
        }
    }
});

Realistically, you should do this:

$(document).on('click','div[name=home],div[name=projects],div[name=about]', function(){
    var name = $(this).attr('name');
    switch (name){
        case 'home':
            objects[0].play();
            break;
            // and so on
    }
    loadContent(name + '.php');
});

EDIT:

When you click your div, this is all it will be aware of:

objects[index].play();
loadContent(divnames[index]+".php");

7 Comments

i added var index2 = index; inside of the binding, and have objects[index2].play(); instead. still doesnt work, the late-loaded elements matching the selector do not react. or did i misunderstand how the solution should look like?
You misunderstand. The code inside this: $('#'+divnames[index]).on("click"... is all that will be fired, so the loop surrounding it will never be hit on your click. You have to determine which button was clicked in the proper context.
Instead of trying to fit your solution to the problem, you should focus on fitting the right solution to the problem. The right solution is binding the events as I showed you in the second code sample. I just wanted to explain to you why it wasn't working.
Thanks for your help. I only have one question: if i place a simple console.log("hello"); in the click event, even that is never showing up, even though it doesnt have to know anything to fire really. I guess thats because the $('#'+divnames[index]) inside of the for loop never knows its index on click as well?
Well, the $('#'+divnames[index]) is just a selector. It's a query against the dom that selects the element to bind the click event to. So, if you just did that with .on('click'), it should fire the log call, I would think.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.