Skip to content

Creating custom FormApplications

Johannes Loher edited this page Mar 16, 2022 · 3 revisions

The FormApplication class is a very handy tool to create Applications that manage specific objects and many modules and systems will have custom FormApplications.

The type definitions for FormApplication are set in a way so that by default, when you extend the FormApplication, you will get the correct types for all of the methods in the base implementation. In particular, this means that by default, the managed object is of type {} and the type returned by getData is:

{
    object: {};
    options: FormApplicationOptions;
    title: string;
}

Obviously, that is not very helpful (it doesn't really make sense to manage an empty object) and often times, you will want to return something completely different in getData. For that reason, it is possible to specify both the type of the managed object as well as the type returned by getData as generic parameters. Additionally, it is also possible to specify the type of the options (in case you have additional custom options in your FormApplication) and for technical reasons, this is actually the first generic parameter.

Specifying a custom type for options

In order to use a custom type for the options, simply provide the custom type as first generic parameter to FormApplication when extending it. Any such custom type must extend FormApplicationOptions.

Example

interface MyCustomOptions extends FormApplicationOptions {
    someCustomOption?: number;
}

class MyFormApplication extends FormApplication<MyCustomOptions> {
    someFunction() {
        // can access this.options.someCustomOption here
    }

    protected async _updateObject(event: Event, formData?: object): Promise<void> {
        // properly update the managed object
    }
}

const myFormApplication = new MyFormApplication({}, { someCustomOption: 42 });

Specifying a custom type for the return type of getData

In order to use a custom type for the return type of getData, pass it as the second generic parameter to FormApplication when extending it. It can be any type that extends object. You can extend FormApplication.Data (to which you need to provide the type of the managed object and the type of the options as generic parameters) if you want but it is completely optional and you can also just roll with your own thing.

In your getData function, you must then return something of the specified type or a promise that resolves to something of the specified type (allowing you to make getData async if you want).

Example

interface MyData {
    foo: string;
    bar: number;
}

class MyFormApplication extends FormApplication<FormApplicationOptions, MyData> {
    getData(): MyData {
        return {
            foo: "some string",
            bar: 42,
        };
    }

    protected async _updateObject(event: Event, formData?: object): Promise<void> {
        // properly update the managed object
    }
}

Specifying a custom type for the object managed by the FormApplication

In order to use a custom type for the managed object, you have two options:

  • Use a type that extends FormApplication.Data as second parameter to the FormApplication when extending it and pass the type of the managed object as second parameter to FormApplication.Data (remember, the first one needs to be the type of the options). When doing this, the type of the managed object will correctly be inferred.
  • Simply pass the type of the managed object as third generic parameter to FormApplication when extending it. In both cases, you can use any type you want. The first variant has the benefit that you don't have to specify that many generic parameters if you want to use the structure given by FormApplication.Data anyways. It has the downside that need to return something that matches FormApplication.Data in your getData method, which often is not what you want. So most of the time, using the second variant is preferable (which is why we only provide an example for that variant).

Example

interface MyManagedObject {
    baz: number;
}

class MyFormApplication extends FormApplication<FormApplicationOptions, any, MyManagedObject> {
    protected async _updateObject(event: Event, formData?: object): Promise<void> {
        // properly update the managed object
    }
}

const myFormApplication = new MyFormApplication({ baz: 42 });

Example combining all of the above

interface MyCustomOptions extends FormApplicationOptions {
    someCustomOption?: boolean;
}

interface MyData {
    foo: string;
    bar: number;
}

interface MyManagedObject {
    baz: number;
}

class MyFormApplication extends FormApplication<MyCustomOptions, MyData, MyManagedObject> {
    async getData(): Promise<MyData> {
        return {
            foo: this.options.someCustomOption ? "someCustomOption is set" : "someCustomOption is not set",
            bar: this.object.baz,
        };
    }

    protected async _updateObject(event: Event, formData?: object): Promise<void> {
        // properly update the managed object
    }
}

const myFormApplication = new MyFormApplication({ baz: 42 }, { someCustomOption: true });