Define a custom jQuery UI widget in TypeScript
Asked Answered
A

2

19

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!

Annalisaannalise answered 29/1, 2013 at 8:37 Comment(1)
I like your approach. It seem to work for me :)Selfhelp
H
14

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.

Hydraulic answered 29/1, 2013 at 9:51 Comment(2)
I was afraid this would be the answer. Good point on the interface, though, definitely going to use that! I'll accept in a few days, maybe other opinions rise, but I guess I'll have to face the cruel truth ;)Annalisaannalise
Sorry to be the bearer of bad news!Hydraulic
S
4

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"});
Selfhelp answered 6/6, 2013 at 16:22 Comment(3)
AFAIK the jQuery WidgetFactory does not support inheritance. I'm using a base class though and there's no problem whatsoever using TypeScript inheritance together with the WidgetFactory. Thanks for your answer though!Annalisaannalise
Hi Anzeo, you can use t he jQuery UI WidgetFactory to inherit from other widgets. But that's another point. The issue I solved by removing the members in the constructor is, that having a method like option() in your base class means that this method will be called instead of the widget's 'native' option method. In fact, the typescript base class just serves as a typed representation of the base class magic the WidgetFactory is doing at runtime -- hence no real overrides are wanted here.Selfhelp
I just noticed the factory indeed supports the option to pass in a base definition to derive your new widgets of. Looked over this option earlierAnnalisaannalise

© 2022 - 2024 — McMap. All rights reserved.