David Thomas
David Thomas

Reputation: 253446

Is it possible to protect the 'this' reference binding a function within an object?

Given the following JavaScript (the relevant HTML will be posted at the bottom of the question):

var app = {
    // other objects from 'messages' array removed for brevity
    'messages': [{
        'author': 'Maya Angelou',
        'quote': "If you don't like something, change it. If you can't change it, change your attitude."
    }],
    'textProp': 'textContent' in document.body ? 'textContent' : 'innerText',
    'outputTo': document.querySelector('#output'),
    'trigger': document.querySelector('#load'),
    'quote': function () {
        var n = Math.floor(Math.random() * this.messages.length),
            f = document.createElement('figure'),
            c = document.createElement('figcaption'),
            frag = document.createDocumentFragment();
        f[this.textProp] = this.messages[n].quote;
        c[this.textProp] = this.messages[n].author;
        frag.appendChild(f);
        frag.appendChild(c);
        this.outputTo.innerHTML = '';
        this.outputTo.appendChild(frag);
    }
};

We can call the quote() function from outside of the object using the following:

document.getElementById('load').addEventListener('click', app.quote.bind(app));

JS Fiddle demo.

Or by simply calling the function directly (not bound as a callback to an event-handler):

app.quote();

JS Fiddle demo.

However, I tried to create an event-handler within the object itself, using:

'clickhandler': function(){
    this.trigger.addEventListener('click', this.quote);
}

JS Fiddle demo.

This, of course, failed (as expected, since this here is (using an IIFE) this Window object).

I realise that this will, while the object is being created/prior to its initialisation, refer to the Window object, but is there a way I'm not seeing to create, and trigger, the event-handling within the object itself?

I realise that a large portion of my imaginary internet points comes specifically from JavaScript, but learning it accidentally leads to moments of utter confusion and inadequacy; this is not to excuse my ignorance but to explain it.

Finally, the HTML (such as it is):

<button id="load">Switch message</button>
<div id="output"></div>

Incidentally, I've looked at the following linked/suggested questions:

For clarity, I'm trying to create the object itself and have the event-handling created and assigned entirely within/'by' the object, without having to call its methods afterwards. That's the part where I'm stuck (and which I suspect may be impossible).

Upvotes: 3

Views: 92

Answers (5)

shibualexis
shibualexis

Reputation: 4744

A small change. Declaring the Object before initializing its properties might help your usecase.

var app = {};

app["messages"] = "test message";
app["textProp'] = 'textContent' in document.body ? 'textContent' : 'innerText';

app['quote']= function () {
        var n = Math.floor(Math.random() * this.messages.length),
            f = document.createElement('figure'),
            c = document.createElement('figcaption'),
            frag = document.createDocumentFragment();
        f[app.textProp] = app.messages[n].quote;
}

Upvotes: 0

xdazz
xdazz

Reputation: 160923

Instead of object literal, you could do the below:

var app = new function () {
    this.messages = [{
        'author': 'Maya Angelou',
            'quote': "If you don't like something, change it. If you can't change it, change your attitude."
    }, {
        'author': 'Richard Feynman',
            'quote': "Hell, if I could explain it to the average person, it wouldn't have been worth the Nobel prize."
    }, {
        'author': 'Eddie Izzard',
            'quote': "Cats have a scam going – you buy the food, they eat the food, they fuck off; that's the deal."
    }, {
        'author': 'George Carlin',
            'quote': "I would never want to be a member of a group whose symbol was a man nailed to two pieces of wood. Especially if it's me!"
    }];
    this.textProp = 'textContent' in document.body ? 'textContent' : 'innerText';
    this.outputTo =  document.querySelector('#output');
    this.trigger = document.querySelector('#load');
    this.quote = function () {
        var n = Math.floor(Math.random() * this.messages.length),
            f = document.createElement('figure'),
            c = document.createElement('figcaption'),
            frag = document.createDocumentFragment();
        f[this.textProp] = this.messages[n].quote;
        c[this.textProp] = this.messages[n].author;
        frag.appendChild(f);
        frag.appendChild(c);
        this.outputTo.innerHTML = '';
        this.outputTo.appendChild(frag);
    };
    this.trigger.addEventListener('click', this.quote.bind(this));
};

SEE THE WORKING DEMO.

Upvotes: 1

Bergi
Bergi

Reputation: 665130

At some point, you will need to .bind() the method to your app (unless you avoid the use of this and replace it with app everywhere). This is however not necessarily in the place where you pass the app.quote method (e.g. bind as the event listener), but might be directly after the declaration of the app object:

var app = {
    …,
    quote: function() {
        … this …
    }
};
app.quote = app.quote.bind(app);

If you have Underscore around, you might use the bindAll helper function for this:

var app = _.bindAll({
    …,
    quote: function() {
        … this …
    }
}, "quote");

If you are not in an object literal - it could be a constructor, IEFE, whatever - you can .bind() the function directly at the place of its declaration:

function App() {
    …
    this.quote = function() {
         … this …
    }.bind(this);
}

With coffeescript or ES6, you can also use the fat-arrow function syntax as a sugar for this.

Upvotes: 1

Quentin Engles
Quentin Engles

Reputation: 2832

The this variable is just referencing app. So just use app.

var app = {
    someVar: 'thing',
    someMethod: function(){
        alert(app.someVar);
    }
};

or you can do

function createApp(){
    var app = {};

    app.someVar = 'thing';
    app.someMethod = function(){
        alert(app.someVar);
    };
    return app;
}

Upvotes: 0

Michal
Michal

Reputation: 13649

As you specified if you just want to create a new object you probably need to go this way. I think what ever you do you still need to execute something - be it instantiate an object or run a specific init function that binds the click.

 var App = function App(){
    this.clickhandler()
    }

    App.prototype =
    {
        'messages': [{
            'author': 'Maya Angelou',
                'quote': "If you don't like something, change it. If you can't change it, change your attitude."
        }, {
            'author': 'Richard Feynman',
                'quote': "Hell, if I could explain it to the average person, it wouldn't have been worth the Nobel prize."
        }, {
            'author': 'Eddie Izzard',
                'quote': "Cats have a scam going – you buy the food, they eat the food, they fuck off; that's the deal."
        }, {
            'author': 'George Carlin',
                'quote': "I would never want to be a member of a group whose symbol was a man nailed to two pieces of wood. Especially if it's me!"
        }],
            'textProp': 'textContent' in document.body ? 'textContent' : 'innerText',
            'outputTo': document.querySelector('#output'),
            'trigger': document.querySelector('#load'),
            'quote': function () {
                console.log('hey')
            var n = Math.floor(Math.random() * this.messages.length),
                f = document.createElement('figure'),
                c = document.createElement('figcaption'),
                frag = document.createDocumentFragment();
            f[this.textProp] = this.messages[n].quote;
            c[this.textProp] = this.messages[n].author;
            frag.appendChild(f);
            frag.appendChild(c);
            this.outputTo.innerHTML = '';
            this.outputTo.appendChild(frag);
        },
        'clickhandler' : function(){
            this.trigger.addEventListener('click', this.quote.bind(this));
        }
    };
    //just create an object
    app = new App();

http://jsfiddle.net/LwrvT/

Upvotes: 1

Related Questions