3

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).

Community
  • 1
  • 1
David Thomas
  • 249,100
  • 51
  • 377
  • 410
  • 2
    `this.trigger.addEventListener('click', this.quote.bind(this));` – epascarello Jun 05 '14 at 02:29
  • 1
    What `this` refers to depends on how you call `clickhandler`. If you call it "normally", like `app.clickhandler()`, `this` refers to `app` and you should just use `.bind` as suggested by epascarello . See [How to access the correct `this` / context inside a callback?](http://stackoverflow.com/q/20279484/218196). Your problem has nothing to do with using object literals. – Felix Kling Jun 05 '14 at 02:30
  • http://jsfiddle.net/3yj7v/5/ – elclanrs Jun 05 '14 at 02:30
  • @epascarello: what I was trying to do (my question may not have been sufficiently clear, for which I apologise) is to bind the event-handler within the object itself, in order to *not* have to call the function. Essentially to create *just* the object and have the handling assigned without making any calls to the object or its methods. – David Thomas Jun 05 '14 at 02:33
  • 1
    No, that doesn't work. An object literal consists only of `key: value` pairs, where `value` can be any expression. But those expressions don't have access to the object itself since it does not exist yet. So in short: You can't reference the object itself during initialization. – Felix Kling Jun 05 '14 at 02:52

5 Answers5

1

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/

Michal
  • 13,439
  • 3
  • 35
  • 33
1

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.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

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.

xdazz
  • 158,678
  • 38
  • 247
  • 274
  • Is there a reason why you have one long expression using the comma operator, instead of using multiple expression statements? – Felix Kling Jun 05 '14 at 02:57
  • @FelixKling No, it should be `;`, I just copied the code from the op's fiddle and forgot to change it :) – xdazz Jun 05 '14 at 02:59
  • @Bergi Although the constructor could be leaked this way, but this is under your control, right? Unless you use `app.constructor`, there should be no problems. – xdazz Jun 05 '14 at 03:13
  • I'm not so much concerned with "loosing control", but more with memory consumption. There exist two completely superflouos objects which could so easily be avoided. E.g, omit the `new` and let the function end in `return this;}.call({})`. – Bergi Jun 05 '14 at 03:20
0

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;
}
Quentin Engles
  • 2,744
  • 1
  • 20
  • 33
0

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;
}
shibualexis
  • 4,534
  • 3
  • 20
  • 25