EternallyCurious
EternallyCurious

Reputation: 2415

Running BackboneJs events in Typescript

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

Upvotes: 3

Views: 1570

Answers (2)

Lu Roman
Lu Roman

Reputation: 2260

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

Upvotes: 1

Kevin Peel
Kevin Peel

Reputation: 4090

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();

Upvotes: 2

Related Questions