3
votes

I'm writing a Backbone program in Typescript, in which I am not able to initialize any events. Here's a test class that I've created to fix the problem. The function start() is not being called when the div is clicked on.

class TestView extends Backbone.View{

    events = {
        "click #testDiv" : "start"
    }

    start(){
        console.log("Clicked");
    }

    constructor(options?){
        super(options);
    }

    render(){
        $root.html(getNewDiv("testDiv"));
        $("#testDiv").css("height", 100).css("width", 100).css("background-color", "green");
        console.log("Rendered");
        return this;
    }
}

function getNewDiv(id:string) {
    return "<div id = \"" + id + "\"></div>"
}

new TestView().render();

Here's the console output:

Rendered

Here's the backbone typescript definition that I'm using:

https://github.com/borisyankov/DefinitelyTyped/blob/master/backbone/backbone.d.ts


Here's the CDN location for backboneJS

Minified : http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.0/backbone-min.js

Non-Minified : http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.0/backbone.js

I'm concerned if my syntax is right or this has something to do with the Typescript definition of Backbone.

UPDATE

The answer shown by Kevin Peel below is throwing an error, because in the Typescript definition file, "events" is defined as a function ( eg. events()). If I just create an "events" function, I get the error - "Uncaught TypeError: Object [object Object] has no method 'off'". If I use the getter method (eg. get events(){}), then nothing really happens on actually firing the event.

Using delegate Events

I tried to use the delegateEvents() function in Backbone.View which creates an error:

constructor(options:any, question:Question, div:JQuery) {
        this.delegateEvents({
           "click" : "clicked"
        });
    }

Error:

Uncaught TypeError: Object [object Object] has no method 'off' backbone.js:1082
h.extend.undelegateEvents backbone.js:1082
h.extend.delegateEvents backbone.js:1059
2
@JqueryLearner I'm not sure what you mean. This code is written in Typescript. Are you familiar with Typescript? - EternallyCurious
@JqueryLearner Try this: documentcloud.github.io/backbone/docs/backbone.html. Please don't use the commenting section for comments unrelated to the stated problem. Thanks. - EternallyCurious
Does #testDiv exist when you instantiate your view? I'm not sure what getNewDiv is or does... - Kevin Peel
@KevinPeel Thanks for pointing that out. getNewDiv generates new div html with the specified id. Please see updated code. - EternallyCurious
Is $root the same as $el? because in order for the click event to be fired the #testDiv must be a descendent of the views el - Jack

2 Answers

2
votes

The problem is with where TypeScript defines the events. When you define events like this:

class TestView extends Backbone.View {
    events = {
        "click #testDiv": "start"
    }
    // ...
}

What TypeScript does is attaches events as a property after initializing the instance. So when Backbone is initializing the view, events haven't yet been attached, so Backbone isn't able to bind them.

Unfortunately, there doesn't seem to be a nice way to get TypeScript to assign events to the prototype, so you need to hack around it in some way.

The absolute easiest way is by setting the events on the prototype yourself:

class TestView extends Backbone.View {
    // Get rid of the events declaration in here
    // ...
}

TestView.prototype.events = {
    "click #testDiv": "start"
}

A second possible way to get around the issue is using the getter functionality of TypeScript as suggested in this StackOverflow answer.

Finally, a third way would be to bind your events each time you render. This is another easy way to get around the TypeScript problem, but it might not be the best solution if the view gets rendered many times.

class TestView extends Backbone.View{
    events = {
        "click #testDiv" : "start"
    }

    // ...

    render(){
        $root.html(getNewDiv("testDiv"));
        // Here is where you'll bind the events using Backbone's delegateEvents
        this.delegateEvents();
        $("#testDiv").css("height", 100).css("width", 100).css("background-color", "green");
        console.log("Rendered");
        return this;
    }
}

// ...

new TestView().render();
1
votes

I know is an old question but there is now another possibility, using es7 decorators, similar to what angular2 does:

 @component({
   events: {
     "click #testDiv" : "start"
   }
 })
 class TestView extends Backbone.View{
   start(){
     console.log("Clicked");
   }
   render(){
     // ...
   }
 }

Decorator file

export function component(definition: any) {
  return function (constructor: any) {
    Object.assign(constructor.prototype, definition)
  }
}

This way, all properties defined in the component decorator will be attached to the class prototype and will be available at instantiation time.


Another option besides using a class/constructor decorator could be to use a method decorator on the preinitialize method if you wish to have all the code inside the class braces like this:

 class TestView extends Backbone.View {
   @component({
     events: {
       "click #testDiv" : "start"
     }
   })
   preinitialize(){}

   start(){
     console.log("Clicked");
   }
   render(){
     // ...
   }
 }

Decorator file

export function component(definition: any) {
  return function (target: Object, methodName: string, descriptor: TypedPropertyDescriptor<Function>) {
    Object.assign(target, definition)
  }
}