Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Define a custom jQuery UI widget in TypeScript

We're currently looking at translating our JavaScript project to TypeScript. Our application relies heavily on custom developed jQuery UI widgets.

In our current code base, we're using a deep copy mechanism to inherit from widget definitions allowing us, for example, to declare a generic TableWidget as well as an OrdersTableWidget which defines more specific functions.

Therefore, I'd like to define my widget definitions as TypeScript classes and then bind an instance of these classes to jQuery.

For example

class MyWidget {
    options: WidgetOptions;
    _init(){
        // general initialization
    }
}

class MySecondWidget extends MyWidget {
    _init(){
        super._init();
        // specific initialization
    }
}

And then

$.widget("MyNameSpace.MyWidget", new MyWidget());
$.widget("MyNameSpace.MySeWidget", new MyWidget());

Furthermore, I'd like to denote my custom widgets as implementations of jQuery UI's Widget definition

class MyWidget implements Widget {
    options: WidgetOptions;
    _init(){
        // general initialization
    }
}

so I'm able to use the following syntax in TypeScript:

$(selector).MyWidget(options);

I know I have to work with the definition file (from DefinitelyTyped), however I have not yet found a reliable source explaining me how I should write custom jQuery UI Widgets in TypeScript. Has anyone got experience with this?

Any help greatly appreciated, as always!

like image 954
thomaux Avatar asked Jan 29 '13 08:01

thomaux


2 Answers

I'm not sure you can write a class that implements the Widget interface, due to the lack of overloaded constructors. You could create a variable that is typed by the Widget interface.

A standard jQuery plugin would be represent in almost pure JavaScript and wouldn't use modules or classes as it ends up being wrapped up as part of jQuery, which itself isn't a module or class.

Here is an empty plugin called plugin that looks like any standard jQuery plugin, but you can see it takes advantage of the TypeScript type system and extends the JQuery interface to allow it to be called.

/// <reference path="jquery.d.ts" />

interface JQuery {
    plugin(): JQuery;
    plugin(settings: Object): JQuery;
}

(function ($) {

    function DoSomething(someParamater: string) : void {

    }

    $.fn.plugin = function (settings) {

        var config = {
            settingA: "Example",
            settingB: 5
        };

        if (settings) {
            $.extend(config, settings);
        }

        return this.each(function () {

        });
    };

})(jQuery);

This would be called in the normal way.

$('#id').plugin();

So really, my answer is - you can't really do what you want because you are adding to the declared interfaces for jQuery rather than exposing them as modules. You could wrap the usage in a module, like an adaptor that abstracts the jQuery aspect away from the use in your TypeScript, or you can call your classes from inside the plugin, but the plugin or widget doesn't really fit into a module or class.

like image 112
Fenton Avatar answered Nov 13 '22 17:11

Fenton


It might help to have a base class in typescript from which other widget classes may derive. Its only purpose is to provide the base class semantic so you can access the base class'es members without having to resort to weak typing.

The trick is to remove all the members at runtime (in the constructor) -- otherwise you run into problems with the inheritance provided by the widget factory. For example, the option method would override the widget's original method which is not desired: we just want to be able to call it (in a statically typed way).

class WidgetBase {

    public element:JQuery;

    constructor() {
        // remove all members, they are only needed at compile time.
        var myPrototype =  (<Function>WidgetBase).prototype;
        $.each(myPrototype, (propertyName, value)=>{
           delete myPrototype[propertyName];
        });
    }

    /**
     * Calles the base implementation of a method when called from a derived method.
     * @private
     */
    public _super(arg1?:any, arg2?:any, arg3?:any, arg4?:any) {

    }

    /**
     * @private
     */
    public _superApply(arguments) {

    }

    /**
     * Gets or sets the value of the widget option associated with the specified optionName.
     */
    public option(optionName:string, value?:any):any {

    }

    // ... further methods from http://api.jqueryui.com/jQuery.widget/
}

Then you can implement your own widget like this:

class SmartWidget extends WidgetBase {

    constructor(){
        super();
    }

    public _create() {
        var mySmartOption = this.option('smart'); // compiles because of base class
        this.beSmart(mySmartOption);
    }

    public _setOption(key:string, value:any) {
        if (key === 'smart') {
           this.beSmart(value);
        }

        this._super(key, value); // compiles because of base class
    }

    private beSmart(smartOne:any){
        // ...
    }

}

// register
jQuery.widget("myLib.smartWidget", new SmartWidget());

// assuming you are using https://github.com/borisyankov/DefinitelyTyped
declare interface JQuery{
    smartWidget();
    smartWidget(options:any);
    smartWidget(methodName:string, param1?:any, param2?:any, param3?:any, param4?:any);
}

And finally, you can use your widget:

$(".selector").smartWidget({smart:"you"});
like image 26
Benjamin Avatar answered Nov 13 '22 16:11

Benjamin