0

I'm building a slider/fader in JavaScript. I'm using JavaScript in a way I've never actually used it before by declaring an object thusly:

var Object
{
    // Statements & functions.
}

I'm having an issue retrieving the value of a variable (testing) inside the object from a function (change_slide()) also inside the object. I have this code:

var Fader = 
{
    // Fader properties.
    speed: 1,                                   // The time each interval will take.
    fade: 0.1,                                  // How much to change the slides opacity with each interval.
    divs: ["fade_1", "fade_2", "fade_3"],       // Array.
    callbacks: [],                              // Array.
    testing: 0123456798,

    // Initialise fader.
    initialise: function()
    {
        // Randomise the starting slide.
        this.start_div = Math.floor((Math.random() * this.divs.length) + 1);

        // Add pips and initialise display (block or none) for the slides.
        for(var i = 0; i < this.divs.length; i++)
        {
            /*
            *       Create pips.
            */
            var new_pip = document.createElement("div");

            new_pip.className = "item";
            new_pip.id        = "pip_" + (1 + i);
            document.getElementById("pips").appendChild(new_pip);

            /*
            *       Get current div number.
            */
            var extract_div_number = this.divs[i].replace("fade_", "");

            if(extract_div_number == this.start_div)
            {
                //this.current_slide = extract_div_number;

                document.getElementById("fade_" + extract_div_number).style.display = "block";
            }
            else
            {
                document.getElementById("fade_" + extract_div_number).style.display = "none";
            }
        }

        this.pip_controller();
    },

    pip_controller: function()
    {
        for(var i = 0; i < this.divs.length; i++)
        {
            this.callbacks[i] = this.add_event("pip_" + (1 + i));
        }
    },

    add_event: function(item)
    {
        if(window.addEventListener)
        {
            return document.getElementById(item).addEventListener("click", this.change_slide, false);
        }
        else
        {
            return document.getElementById(item).attachEvent("onclick", this.change_slide);
        }
    },

    change_slide: function()
    {
        // Always returns "undefined" despite 'testing' being defined previously.
        console.log(this.testing);
    },
}

Fader.initialise();

// This works:
// Fader.change_slide();

This is my HTML:

<div id="main_slide">

    <div id="fade_1"><h1>Slide 1</h1></div>
    <div id="fade_2"><h1>Slide 2</h1></div>
    <div id="fade_3"><h1>Slide 3</h1></div>

    <div id="pips"></div>

</div>

For the record "pips" are those little circle things at the bottom of the slider you can click to change slides.

So, can anybody tell me why testing is returning undefined, and how I can make my current code retrieve the actual value of testing?

George88
  • 101
  • 3
  • 13
  • It's a matter of function scope, and the fact that when `change_slide` is called it's no longer in the same scope as the others. Try changing `this.testing` to `Fader.testing` in the example and see it do what you want. Otherwise, this is honestly a dupe about how function scope works wrt `this`. – jcolebrand Feb 18 '13 at 17:51
  • @jcolebrand: Not "scope", but the value of the `this` pseudo-argument, which is a different thing from scope. And yeah, this question has been asked and answered in various forms several times. :-) – T.J. Crowder Feb 18 '13 at 17:54
  • I don't see how, in one case it's getting executed against one element, and in another place it's getting executed against another element (namely the Fader element vs the DOM specific element). – jcolebrand Feb 18 '13 at 17:55
  • Should I instead be saying the "function binding"? Most folks who don't understand javascript won't understand that either. I found that scope is a better indicator to most folks than binding @T.J.Crowder – jcolebrand Feb 18 '13 at 17:57
  • 1
    @jcolebrand: Right. But that's not "scope." "Scope" has a specific meaning, which relates to what free identifiers a function has access to (via the "scope chain", which -- unlike `this` -- is lexically determined). This isn't an issue of scope, it's the fact that `this` is not lexically bound, but runtime-bound. Sadly, there isn't a good word for it. "Scope" definitely isn't it, it's already used. Some people tried "context" for a while, but "context" is already dramatically over-used and it didn't catch on. So I just avoid trying to use a term for it at all. :-) – T.J. Crowder Feb 18 '13 at 17:59
  • I do concede those points. Just hard to explain it to someone who doesn't already understand function binding in the first place, especially given `.apply` vs `.call` and then adding in `.bind` ... – jcolebrand Feb 18 '13 at 18:01
  • @jcolebrand: Yeah. :-) – T.J. Crowder Feb 18 '13 at 18:37

1 Answers1

2

addEventListener calls the function you give it with this pointing at the DOM element, not the instance of your class, so this line (and others like it) is the problem:

return document.getElementById(item).addEventListener("click", this.change_slide, false);

There are a couple of ways to deal with that, for instance:

initialize: function() {
    var self = this;

    // ...

    return document.getElementById(item).addEventListener("click", function(e) {
        return self.change_slide(e);
    }, false);

    // ...
},

Or in an ES5-enabled environment (or if you have an ES5 shim loaded, because this is shim-able), you can use the new Function#bind:

return document.getElementById(item).addEventListener("click", this.change_slide.bind(this), false);

More (on my blog):

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • What are you thoughts on this approach? http://stackoverflow.com/a/1982952/109749 – jcolebrand Feb 18 '13 at 18:03
  • 1
    Now I realise why JavaScript was giving me the error `Object # has no method 'change_slide'` for a previously attempted work around, which I couldn't figure out. Thanks for the links, I'll be reading more on your blog. – George88 Feb 18 '13 at 18:18
  • @jcolebrand: Interesting! Whatever gets the job done, really. Since George88 *already* has an execution context handy, the `var self = this;` thing is low-impact. But still, interesting idea passing it as extra event data. – T.J. Crowder Feb 18 '13 at 18:36
  • @T.J.Crowder Aye, I had never contemplated that either, but in looking for a reasonable dupe (as this sort of question gets asked on SO quite frequently) I saw that interesting concept. Cheers! – jcolebrand Feb 18 '13 at 19:48