1

generarGrilla() creates an output that has some html buttons. Each button calls a method that exist in the same object guardarReserva()

When I load the page guardarReserva() is called without clicking any button, because this line console.log("whatever "+this.i); gets printed into the console.

If I do click one of the buttons that have the listeners, nothing happens.

var reservasAPP = {
    i:0,
    nombre: function () {
        return $('#h09').val();
    },    
    guardarReserva:function(){
        var reservaConfirmada = $('#horario'+this.i).html('--> '+this.nombre());
        console.log("whatever "+this.i);
        return false;
    },
    generarGrilla: function(){
        //it loads from html using jQuery(document).ready();
        for(this.i=1; this.i<13; this.i++){
            var impresionGrilla = $('#grilla').append("<button class=\"btn btn-primary btn-xs\" onclick=\"return "+this.guardarReserva()+"\">Reservar</button>");
        };
    },

}
Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339
Rosamunda
  • 14,620
  • 10
  • 40
  • 70
  • Your [original code](http://stackoverflow.com/q/35639060/1048572) was totally fine. You should use a local variable `i` for what you want to do, not that `this.i` property. Pass `i` as an argument, and use `.on()` to attach your event listeners. – Bergi Feb 25 '16 at 22:54

2 Answers2

1

Have you tried reservasAPP.guardarReserva() instead of this.guardarReserva() ?

mitsest
  • 305
  • 3
  • 19
1

You're actually running guardarReserva() within your .append() function. That is why you see the immediate console.log("whatever "+this.i); when you first open the browser.

I think you want the onclick of that button to call that function. For that (assuming reservasAPP is in the window namespace) you can do something like this:

var reservasAPP = {
    i:0,
    nombre: function () {
        return $('#h09').val();
    },    
    guardarReserva:function(){
        var reservaConfirmada = $('#horario'+this.i).html('--> '+this.nombre());
        console.log("whatever "+this.i);
        return false;
    },
    generarGrilla: function(){
        //it loads from html using jQuery(document).ready();
        for(this.i=1; this.i<13; this.i++){
            // ---Change is here ------------------------------------------------------------------------|
            //                                                                                           V
            var impresionGrilla = $('#grilla').append("<button class=\"btn btn-primary btn-xs\" onclick=\"return reservasAPP.guardarReserva();\">Reservar</button>");
        };
    },

}

Why I couldn't use "this" and I need to use "reservasAPP"

So, let's look at some of your original code (I'll slightly modify it so it is a bit shorter) and see why it wasn't working.

The line where the good stuff happens is within generarGrilla, within the for loop where you call the .append() function.

var i_g = $('#grilla').append('<button onclick="return ' + this.guardarReserva() + '">Reservar</button>');

Now you are correct in the idea that when that line of code executes, the this keyword is pointing at the reservasAPP object. However, what you are doing is not setting the onclick event for the button to run this.guardarReserva(), you are immediately running the this.guardarReserva() function.

Let's look at a small, semi-related example:

var n = function() {
    return 'John';
};

var str = 'Hello, ' + n();

Now, what is the value of our variable str? Well, we are doing two things:

  1. First, we have a string literal Hello, .
  2. We are then concatenating it using the + operator with what the function n() returns.

Notice how we are going to use what n() returns rather than the literal 'n()' (string or function). This is because we are actually calling the n() function.

So, at the end of the day:

// str = 'Hello, John';

So, let's go back and look at your original code. What is actually going on there? Well, you are selecting all elements with id set to grilla (I'm assuming there is only one of those) then calling the jQuery append() function. .append() can accept a string as its argument, and it'll take that string, and insert it into the DOM as HTML.

Your string is concatenating three different things.

  1. '<button onclick="return '
  2. The return value of this.guardarReserva()
  3. '">Reservar</button>'

Your guardarReserva function at the end returns false, which when concatenated with a string, uses its .toString() method, which in this case returns the actual word 'false'.

So, if you'd look at your HTML from before, you'd see your HTML code looked like:

<div id="grilla">
    <button onclick="return false">Reservar</button>
</div>

Which is not at all what you wanted.

Instead, to fix that issue, I had you pass in one long string (which includes the function name you do want to call). So, your HTML ended up looking like:

<div id="grilla">
    <button onclick="return reservasAPP.guardarReserva()">Reservar</button>
</div>

OK, so that's why the function was running right away, and why your button wasn't working. So we need to pass in the function as a string for the HTML button to know to run that function when it is clicked.

So, why can't you pass in the string:

'<button onclick="return this.guardarReserva()">Reservar</button>'

It has to do with how the browser evaluates the this keyword within that button.

In fact, let's do an experiment

<button onclick="console.log(this);">CLICK ME</button>

What happens when I click the button? You can do this experiment yourself. You'll see that it actually logs the literal button. Because within the buttons inline onclick, this refers to the button itself!

You can doubly verify this with this code:

    <button id="button-1" onclick="console.log(this.id);">CLICK ME</button>

And see that it logs the string "button-1" (aka, the button's id).

So, if this refers to the button, we can't leave that context to get at our reservasAPP object! By referencing the reservasAPP object directly (assuming it was declared in your main <script></script> tag, thus placing it in the accessible window object), we can access that object, and thus its inner properties.


SIDE NOTE:

I would use a different method altogether for attaching our onclick handler.

I'd use jQuery's .on() function. Here is an example of how you could define generarGrilla:

generarGrilla: function() {
        for (this.i = 1; this.i < 13; this.i++) {
            // Save our `this` object for later
            var self = this;
            var button = $('<button class="btn btn-primary btn-xs">Reservar</button>');
            
            // Set the `onclick` handler for our button (that isn't in the DOM yet)
            button.on('click', function(event) {
                // Use our saved `self` variable, which points to `reservasAPP`
                return self.guardarReserva();
            });

            // `onclick` is set, insert our button into the DOM
            var impresionGrilla = $('#grilla').append(button);
        };
    }

Now, when you run reservasAPP.generarGrilla();, it'll insert your buttons, and they'll all have onclick event handlers.

You can read this post on why'd some people think it's better to use attached event handlers vs inline JS on HTML attributes.

Community
  • 1
  • 1
romellem
  • 5,792
  • 1
  • 32
  • 64
  • Thanks! It does work!! BTW, Why I couldn't use "this" and I need to use "reservasAPP"? – Rosamunda Feb 25 '16 at 22:54
  • @Rosamunda, because you're attaching your event via the actual HTML onclick attribute, instead of say, via a JS method. In the context of the onclick attribute, `this` doesn't refer to that JS object you initialized. I'll write a more detailed response shortly within my answer that'll hopefully clear it up for you. – romellem Feb 25 '16 at 23:01
  • @Rosamunda, I went back and edited my answer. Hope that helps in further understanding the underlying issue. – romellem Feb 26 '16 at 16:08
  • 1
    Yes! I think I've grasped the concept MUCH better now. You've been very kind!! THANK YOU!! :) – Rosamunda Feb 26 '16 at 16:37