0

I'm practicing MVC in javascript and I'm trying to attach an event handler in a controller to a button. First I create the view and in the contructor of it, I load external HTML. Then, in the controller constructor, I try to do find("#myButton") in order to find my button and then attach an event listener to it. Here's my attempt:

index.html:

<div id="welcome"></div>

js/app.js:

var welcome = $("#welcome");
var welcomeView = new WelcomeView(welcome, model);
var welcomeController = new WelcomeController(welcome, model, generalController);

js/view/welcomeView.js:

var WelcomeView = function(container, model){
    var container = container;
    var model = model;

    container.load("welcome.html");

    this.show = function(){
        container.style.display = "block";
    }

    this.hide = function(){
        container.style.display = "none";
    }
}

welcome.html:

<button type="button" class="btn btn-default" id="myButton">Create new dinner</button>

js/controllers/welcomeController.js:

var WelcomeController = function(container, model, generalController){
    var container = container;
    var model = model;

    var createButton = container.find("#myButton");

    createButton.click( function() {
        alert("entered");
        generalController.showScreen("DISHSEARCH");
    } );
}

When I click the button, nothing happens. And when I try without jQuery in the controller:

createButton[0].onclick = function(){
    alert("hello");
};

I get the error:

welcomeController.js:7 Uncaught TypeError: Cannot set property 'onclick' of undefined
    at new WelcomeController (welcomeController.js:7)
    at HTMLDocument.<anonymous> (app.js:30)
    at fire (jquery.js:3119)
    at Object.fireWith [as resolveWith] (jquery.js:3231)
    at Function.ready (jquery.js:3443)
    at HTMLDocument.completed (jquery.js:3474)

So there seems to be some problem with finding the button element, but I can't figure it out! What is wrong here?

Sahand
  • 7,980
  • 23
  • 69
  • 137
  • 2
    Please post a [Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) – Ele Mar 07 '18 at 19:26
  • Is that code inside `$(function() { .... });` or loaded after that DOM element? – Alon Eitan Mar 07 '18 at 19:27
  • everything in app.js is inside `$(function(){ ... });` – Sahand Mar 07 '18 at 19:28
  • Nothing you've shown here contains a button with the id myButton. Where is it? Is it part of the DOM before you start trying to `.find()` it? – Daniel Beck Mar 07 '18 at 19:28
  • My bad, it was in welcome.html. I forgot to change the id for the SO post. – Sahand Mar 07 '18 at 19:30
  • 2
    `load` is asynchronous; you're likely doing the `find` before it's finished. – Heretic Monkey Mar 07 '18 at 19:32
  • Maybe try to bind like `container.on('click', "#myButton", function(e) {...})`? – skobaljic Mar 07 '18 at 19:32
  • @MikeMcCaughan, I suspected something like that, any idea how to make it synchronous? – Sahand Mar 07 '18 at 19:33
  • The `welcome` div you posted has a closing tag at the end. Is the button inside or outside of your `welcome` container? – Bee Mar 07 '18 at 19:34
  • @David, I think it's inside, because I'm `load()`ing `welcome.html` into it. – Sahand Mar 07 '18 at 19:36
  • load is asynchronous, so `var createButton = container.find("#myButton");` should not find anything, but why createButton.click didn't throw out one exception `no property=click or function=click for createButton object` – Sphinx Mar 07 '18 at 19:38
  • That's correct, except for one thing: `createButton.click` doesn't throw anything. It's completely silent. However, as I've explained in the question, assigning to `createButton[0].onclick` causes an error. – Sahand Mar 07 '18 at 19:41
  • It seems to me like if this is due to asynchronous loading, I'm going about assigning event listeners the wrong way. Does anyone have a better solution? – Sahand Mar 07 '18 at 19:42
  • @Sahand createButton[0] is undefined, but createButton should not be 'undefined` – Sphinx Mar 07 '18 at 19:43
  • I don't remember saying it was. – Sahand Mar 07 '18 at 19:43
  • "I'm going about assigning event listeners the wrong way." Yes. You need to wait for the `load` to complete before touching the DOM it's creating. The callback function on `load()` will do this but you'll need to instantiate the controller inside WelcomeView, or else have WelcomeView signal WelcomeController when it's ready for you to start digging around in its DOM. – Daniel Beck Mar 07 '18 at 19:46
  • @DanielBeck, thanks. What do you think about the answer I posted? Is it somewhat equivalent to your solution? – Sahand Mar 07 '18 at 19:49
  • Not even vaguely similar, no. That uses a different strategy (of binding a delegated event to the container, which already exists in the DOM, instead of to the button itself.) That should work fine, so long as *all* your controller methods can be modified similarly. – Daniel Beck Mar 07 '18 at 20:01
  • Possible duplicate of [Event binding on dynamically created elements?](https://stackoverflow.com/questions/203198/event-binding-on-dynamically-created-elements) – Heretic Monkey Mar 07 '18 at 22:08

2 Answers2

0

@skobaljic had the right solution. Using .on() solved the issue:

js/controllers/welcomeController.js:

container.on('click', "#myButton", function() {
        alert("Success!");
        generalController.showScreen("DISHSEARCH");
    })
Sahand
  • 7,980
  • 23
  • 69
  • 137
0

with your welcome.html, and welcome div in your index.html, this worked just fine: (the load function has a callback to inform you about loading been completed). as other mentioned; it's an asynchronous task.

$(function() {
    $("#welcome").load("welcome.html", function() {
        $('#myButton',this).on("click",function() {
            alert("clicked");
        });
    });
});

also loading synchronouslyis possible, $.ajax({async:false,... or this vanilla :

var request = new XMLHttpRequest();
request.open('GET', 'welcome.html', false);  // `false` makes the request synchronous
request.send(null);
if (request.status === 200) {
  console.log(request.responseText);
}
nullqube
  • 2,959
  • 19
  • 18