SAP / ui5-typescript

Tooling to enable TypeScript support in SAPUI5/OpenUI5 projects
https://sap.github.io/ui5-typescript
Apache License 2.0
200 stars 28 forks source link

Enhancement of the Event by using generics #402

Closed Revest117 closed 1 year ago

Revest117 commented 1 year ago

This may be related to #356

The event class generally feels like a black box, so many type assertions are needed to even use the event class. The parameters defined in the MetadataOptions.Event definition are currently wasted and only used for documentation, as they are not even mentioned in the gen.d.ts.

In the gen.d.ts the possibility to define the 'data' is given with the 'attach'-functions, but why do I have to define again which data the event expects? Sure you could specify arbitrary data, but that would defeat the purpose of defining the parameters in the MetadataOptions.Event.

attachSomeEvent<CustomDataType extends object>(data: CustomDataType, fn: (event: Event, data: CustomDataType) => void, listener?: object): this;

I have tried to the best of my TypeScript knowledge to create an example class for Event how I would use generics.

The example does not represent the actual implementation of sap.ui.base.Event, but is meant to represent its outer appearance.

/* this does not conflicts with 'no-unnecessary-generics' */
class Event<Source, Parameters extends Record<string, unknown>> extends BaseObject {

    constructor(id: string, source: Source, parameters: Parameters) {
        /* ... */
    }

    getId(): string {
        /* ... */
    }

    getParameters(): Parameters {
        /* ... */
    }

    /* I do not know if there is a better way to ensure that 'T' is in 'Parameters' therefor this fuction conflicts with 'no-unnecessary-generics' */
    /* getParameter<T>(name: string): T */
    getParameter(name: string): unknown {
        /* ... */
    }

    getSource(): Source {
        /* ... */
    }

    preventDefault(): void {
        /* ... */
    }
}

/* Example for the 'liveChange' event in Input */

/* Generated with the MetadataOptions.Event */
type InputLiveChangeParameters = {
    value: string
    escPressed: boolean
    previousValue: string
}
class Input extends InputBase {
    static readonly metadata: MetadataOptions = {
        events: {
            liveChange: {
                parameters: {
                    value: "string",
                    escPressed: "boolean",
                    previousValue: "string"
                }
                /* ... */
            }
            /* ... */
        }
        /* ... */
    }

    /* ... */
    fireLiveChange(parameters?: InputLiveChangeParameters) {
        /* ... */
    }

    attachLiveChange(data?: InputLiveChangeParameters, fn: (event: Event<Input, InputLiveChangeParameters>, data: InputLiveChangeParameters) => void, listener?: object): this attachLiveChange(fn: (event: Event<Input, InputLiveChangeParameters>) => void, listener?: object): this {
        /* ... */
    }
    /* ... */
}

const InputLiveChangeEvent = new Event<Input, InputLiveChangeParameters>("inputLiveChangeEvent", new Input(), {
    value: "a",
    escPressed: true,
    previousValue: "b"
});

/* no need to use 'as' as the type is given in the constructor */
InputLiveChangeEvent.getParameters();
InputLiveChangeEvent.getSource();

/* if the generic <T> would exists the type of 'value' would be 'boolean' */
/* InputLiveChangeEvent.getParameter<InputLiveChangeParameters["value"]>("value") */
InputLiveChangeEvent.getParameter("value") as InputLiveChangeParameters["value"];

/* if the generic <T> would exists the type of 'value' would be 'boolean' */
/* InputLiveChangeEvent.getParameter<InputLiveChangeParameters["escPressed"]>("escPressed") */
InputLiveChangeEvent.getParameter("escPressed") as InputLiveChangeParameters["escPressed"];

/* if the generic <T> would exists the type of 'value' would be 'boolean' */
/* InputLiveChangeEvent.getParameter<InputLiveChangeParameters["previousValue"]>("previousValue") */
InputLiveChangeEvent.getParameter("previousValue") as InputLiveChangeParameters["previousValue"];