0.1.0 • Published 9 months ago

@orne/jqueryui-ts-widgets v0.1.0

Weekly downloads
-
License
LGPL-3.0
Repository
github
Last release
9 months ago

Typescript JQueryUI types and utilities

Provides Typescript types for jQuery UI modules and widgets.

Also provides a decorator for creating new jQuery UI as vanilla TS classes.

Preliminary warning

This package is the result of some unavoidable requirements of a private project.

jQuery UI seems pretty dead and lacks (and have lack for years) support for modern JS development tools including (but not limited to) source maps of minimized files or ESM/CommonJS modules support, witch prevent usage of tools like Vite.

It provided a valuable UI framework for years. Nowadays has been superseeded by more modern tools and approaches.

My opinion right now (2025-01) is this:

Get rid of jQuery UI and/or jQuery and adopt modern tools and frameworks when Web API is not enought.

jQuery UI Typescript types

Add development dependency with @orne/jqueryui-ts-widgets to have typed imports of jQuery UI packages and modules.

jQuery UI widgets as classes

To define new jQuery UI widgets as classes Typescript experimental support for decorators must be enabled:

{
    "compilerOptions": {
      ...
      "experimentalDecorators": true,
      ...
    }
}

Once decorators are enabled jQuery UI widgets can be defined using Typescript classes:

import 'jquery-ui/ui/widget';
import { JQueryWidget } from '@orne/jqueryui-ts-widgets';

/**
 * Interface of widget styling classes.
 */
export type Classes = object & {
    // Extra widget styling classes
    "myns-mywidget-fancyclass"?: string | null;
}
/**
 * Interface of widget options.
 *
 * @typeParam C The widget classes.
 */
export interface Options<C extends Classes = Classes>
extends JQueryUI.Widget.Options<C> {
    // Extra widget options
    myTypedOption: boolean;
}
/**
 * Interface of widget API.
 *
 * @typeParam This The JQuery instance.
 */
export type API<This extends JQuery<any>> =
    JQueryUI.Widget.API<This, MyWidget> &
    JQueryUI.Widget.API.Methods<This, MyWidget> &
    {
      /**
       * Public widget method.
       */
      (methodName: 'myMethod'): This,
    };
/**
 * The widget class.
 * Note the `@JQueryWidget` decorator with namespace and widget name.
 *
 * @typeParam O The widget options.
 */
@JQueryWidget({ namespace: 'myNs', name: "myWidget" })
export class MyWidget<O extends Options<any> = Options>
extends Widget<O> {
    public constructor(options?: Partial<O>, element?: JQuery<any>) {
      super(options, element);
    }
    protected _init(): void {
      super._init();
    }
    /**
     * Public widget method.
     */
    public myMethod(): void {
    }
}
declare global {
  interface JQuery<TElement = HTMLElement> {
    myWidget: API<this>;
  }
  interface JQueryStatic {
    myNs: {
      myWidget: typeof MyWidget;
    };
  }
}

Widget API declaration can be ommited and extracted from widget type declaration, using JQueryUI.Widget.API.From. However, this shortcut losts public method's documentation.

import 'jquery-ui/ui/widget';
import { JQueryWidget } from '@orne/jqueryui-ts-widgets';

/**
 * Interface of widget styling classes.
 */
export type Classes = object & {
    // Extra widget styling classes
    "myns-mywidget-fancyclass"?: string | null;
}
/**
 * Interface of widget options.
 *
 * @typeParam C The widget classes.
 */
export interface Options<C extends Classes = Classes>
extends JQueryUI.Widget.Options<C> {
    // Extra widget options
    myTypedOption: boolean;
}
/**
 * The widget class.
 * Note the `@JQueryWidget` decorator with namespace and widget name.
 *
 * @typeParam O The widget options.
 */
@JQueryWidget({ namespace: 'myNs', name: "myWidget" })
export class MyWidget<O extends Options<any> = Options>
extends Widget<O> {
    public constructor(options?: Partial<O>, element?: JQuery<any>) {
      super(options, element);
    }
    protected _init(): void {
      super._init();
    }
    /**
     * Public widget method.
     */
    public myMethod(): void {
    }
}
declare global {
  interface JQuery<TElement = HTMLElement> {
    myWidget: JQueryUI.Widget.API.From<this, MyWidget>;
  }
  interface JQueryStatic {
    myNs: {
      myWidget: typeof MyWidget;
    };
  }
}

Events

To declare events triggered by widgets define the event type specifying the full event name and add it to the JQueryUI.Widget.Event.Mappings interface. This will automatically add signatures to JQuery.on() and JQuery.one() for the defined event type:

export type MyEvent<TElement = HTMLElement> = JQueryUI.Widget.Event<"myFancyEvent", TElement>;
declare namespace JQueryUI {
    namespace Widget {
        namespace Event {
            interface Mappings<TElement = HTMLElement> {
                'myFancyEvent': MyEvent<TElement>;
            }
        }
    }
}

JQuery UI widgets allow triggering events with data associated to the event. To provide data type information to event consumers add to the JQueryUI.Widget.Event.Mappings interface a definition including event type and event data type:

export type MyEvent<TElement = HTMLElement> = JQueryUI.Widget.Event<"myFancyEvent", TElement>;
export interface MyEventData {
  ...
  test: boolean;
}
interface MyEventDef<TElement = HTMLElement> {
  event: MyEvent<TElement>,
  ui: MyEventData
}
declare namespace JQueryUI {
    namespace Widget {
        namespace Event {
            interface Mappings<TElement = HTMLElement> {
                'myFancyEvent': MyEventDef<TElement>;
            }
        }
    }
}

// Allows typed access to event data:
$('#my-elem').on('myFancyEvent', (event, data) => {
  // Typed data
  data.test == true
});

Existing widgets ambient types

To add typescript types for existing JQuery UI widgets add ambient widget namespace declaration and extend JQueryStatic interface with namespace:

/// <reference types="jquery" />
declare namespace AmbientWidgets {
  interface WidgetsNamespace {
  }
}
declare global {
  interface JQueryStatic {
    ambientNs: WidgetsNamespace;
  }
}

For each widget in the namespace add ambient module declaration for widget module, defining widget classes, options, widget API and, if required, events and extending JQuery interface as previously explained:

/// <reference types="jquery" />
/// <reference types="jquery-ui" />
/// <reference types="./namespace" />
export class AmbientWidget<O extends AmbientWidget.Options<any> = AmbientWidget.Options>
extends jQuery.Widget<O> {
  public someMethod(): void;
}
export namespace AmbientWidget {
  type Classes = object & {
    // Extra widget styling classes
    "myns-mywidget-fancyclass"?: string | null;
  }
  interface Options<C extends Classes = Classes>
  extends JQueryUI.Widget.Options<C> {
      // Extra widget options
      myTypedOption: boolean;
  }
}
declare namespace AmbientWidgets {
  interface WidgetsNamespace {
    ambientWidget: typeof AmbientWidget;
  }
}
interface JQuery<TElement = HTMLElement> {
  ambientwidget: JQueryUI.Widget.API.From<this, AmbientWidget>;
}
declare module 'fancylib/my-ambient-widget' {
  import 'jquery';
  import 'jquery-ui';
  // If module exports widget
  export default jQuery.ambientNs.ambientWidget;
}