Your own views

The form component is meant to be framework-agnostic, so how you implement your views really depends on the framework you're using. Banquette comes with a default support for Vue 3open in new window only, so we'll discuss a bit more about this specific type of implementation at the end.

A FormControl on its own can't do much. You have to associate it with a view. To do so, call the setViewModel method, which has the following signature:

setViewModel(viewModel: FormViewModelInterface): FormViewControlInterface;

So it takes a FormViewModelInterface as parameter and returns a FormViewControlInterface.

These interfaces are used so the view and the control can communicate between each other.

  • FormViewModelInterface in the direction control => view
  • FormViewControlInterface in the direction view => control

FormViewModelInterface

This is the type of object the FormControl expects to receive from the view. That's what it will use when it needs to communicate with the view.

/**
 * Bridge between a FormControl and a view in the direction "FormControl -> View".
 */
interface FormViewModelInterface {
    /**
     * Unique id of the view model.
     */
    id: number;

    /**
     * Update the view value with the value from the form control.
     */
    setValue(controlValue: any): void;

    /**
     * Unset the form control assigned with the view model.
     */
    unsetControl(): void;

    /**
     * Try to get the focus on the control.
     */
    focus(): void;

    /**
     * Inverse of `focus`.
     */
    blur(): void;
}

As you can see the interface is very simple. That all the FormControl needs when it comes to the view is:

  • to identify the view model: id field,
  • to update the view value: setValue method,
  • to unbind itself : unsetControl method,
  • to ask for focus : focus method,
  • to ask for blue : blur method.

FormViewControlInterface

This is the type of object returned by the FormControl when you call setViewModel. That what you have to use when you need to communicate with the control.

The interface is a bit more complicated, so click here to show it.
/**
 * Bridge between a FormControl and a view in the direction "View -> FormControl".
 */
export interface FormViewControlInterface {
    /**
     * Unique id of the control.
     */
    readonly id: number;

    /**
     * Unique id of the form tree.
     */
    readonly formId: string;

    /**
     * The absolute path of the component from the root of the form.
     *
     * The path is composed of each level name separated by "/".
     *
     * So the root node has a path of "/".
     * If if has a child named "name", its path will by "/name".
     */
    readonly path: string;

    /**
     * A component is `valid` when the validation has run and no error has been found.
     */
    readonly valid: boolean;

    /**
     * A component is `invalid` when the validation has run and one or more errors has been found.
     */
    readonly invalid: boolean;

    /**
     * A component is `validated` when the validation has run, no matter if errors have been found or not.
     */
    readonly validated: boolean;

    /**
     * Inverse of `validated`.
     */
    readonly notValidated: boolean;

    /**
     * A component is `validating` when its validator is currently running.
     */
    readonly validating: boolean;

    /**
     * Inverse of `validating`.
     */
    readonly notValidating: boolean;

    /**
     * Only `true` when the component has been validated, has not validation running and no error have been found.
     */
    readonly validatedAndValid: boolean;

    /**
     * A component is `busy` when the view model of a component or one of its children is processing something.
     */
    readonly busy: boolean;

    /**
     * Inverse of `busy`.
     */
    readonly notBusy: boolean;

    /**
     * A disabled component is non-interactive and excluded from the validation.
     *
     * This is a "multi origin" flag, meaning it can be set multiple time by different sources.
     * All original sources must remove their flag for the component to become enabled again.
     */
    readonly disabled: boolean;

    /**
     * Inverse of `disabled`.
     */
    readonly enabled: boolean;

    /**
     * A component is `dirty` if the user has changed its value in the UI, no matter its current value.
     */
    readonly dirty: boolean;

    /**
     * Inverse of `dirty`.
     */
    readonly pristine: boolean;

    /**
     * True if the component is marked as `touched`.
     *
     * A component is marked `touched` once the user has triggered a `blur` event on it.
     */
    readonly touched: boolean;

    /**
     * Inverse of `touched`.
     */
    readonly untouched: boolean;

    /**
     * A component is `changed` when its value is different from the initial value.
     */
    readonly changed: boolean;

    /**
     * Inverse of `changed`.
     */
    readonly unchanged: boolean;

    /**
     * A component is `focused` when its the current field on edition.
     */
    readonly focused: boolean;

    /**
     * Inverse of `focused`.
     */
    readonly unfocused: boolean;

    /**
     * The list of errors of the component.
     */
    readonly errors: FormError[];

    /**
     * The original value of the control.
     */
    readonly defaultValue: any;

    /**
     * The current value of the control.
     */
    readonly value: any;

    /**
     * A reference on the view model instance that holds the focus.
     */
    readonly focusedViewModel:  FormViewModelInterface|null;

    /**
     * Update the value of the control with the value of the view.
     */
    setValue(value: any): void;

    /**
     * Set the default value of this control.
     *
     * Calling this method will also set the field back an "unchanged" state.
     * Further reset of the control will set this value back into the "real" value of the control.
     */
    setDefaultValue(value: any): void;

    /**
     * Change the `disabled` state of the control to `true`.
     */
    markAsDisabled(): void;

    /**
     * Change the `focused` state of the control to `false`.
     */
    markAsEnabled(): void;

    /**
     * Change the `focused` state of the control to `true`.
     */
    markAsFocused(): void;

    /**
     * Change the `focused` state of the control to `false`.
     */
    markAsBlurred(): void;

    /**
     * Change the `busy` state of the control to `true`.
     */
    markAsBusy(): void;

    /**
     * Change the `busy` state of the control to `false`.
     */
    markAsNotBusy(): void;

    /**
     * Unset the view assigned with the form control.
     */
    unsetViewModel(viewModel: FormViewModelInterface): void;

    /**
     * Reset the control. It has the following effects:
     *
     *   - Set the value to the "default value",
     *   - Unmark the following states: `BasicState.Changed`, `BasicState.Touched`, `BasicState.Dirty`, `BasicState.Validated`,
     *   - Blur the control if focused,
     *   - Clear validation errors.
     *
     * Resetting the control does not impact the following states: `ContextualizedState.Disabled`, `BasicState.Busy`, `BasicState.Validating`, `BasicState.Concrete`.
     */
    reset(): void;

    /**
     * Get all extra data.
     */
    getExtras(): Record<string, any>;

    /**
     * Replace all extra data.
     */
    setExtras(extras: Record<string, any>): void;

    /**
     * Get a single extra value.
     */
    getExtra<T = any>(name: string, defaultValue?: any): T;

    /**
     * Set a single extra value.
     */
    setExtra(name: string, value: any): void;

    /**
     * Register a callback that will be called before the value of the control changes.
     *
     * @return A method to call to unsubscribe.
     */
    onBeforeValueChange(callback: (event: BeforeValueChangeFormEvent) => void, priority?: number): UnsubscribeFunction;

    /**
     * Register a callback that will be called when the value of the component changes.
     *
     * @return A method to call to unsubscribe.
     */
    onValueChanged(callback: (event: ValueChangedFormEvent) => void, priority?: number): UnsubscribeFunction;

    /**
     * Register a callback that will be called when the value of a flag changes.
     *
     * @return A method to call to unsubscribe.
     */
    onStateChanged(callback: (event: StateChangedFormEvent) => void, priority?: number): UnsubscribeFunction;

    /**
     * Register a callback that will be called when an error is added of removed from the component.
     *
     * @return A method to call to unsubscribe.
     */
    onErrorsChanged(callback: (event: ErrorsChangedFormEvent) => void, priority?: number, selfOnly?: boolean): UnsubscribeFunction;
}

Through this object, you can do anything you need to the control (update its value, its states, subscribe to events, etc.).

WARNING

It's important that you use the object returned by setViewModel to communicate with the FormControl, and not the FormControl itself.

Internally the control uses this object to know that the calls come from the view model, and slightly adapts its internal behaviors in consequence.

The rest is up to you, the development of the UI components is a whole other subject, out of the scope of this documentation.

Extend Vue components

If you want to create new form components with Vue 3, it's much simpler because Banquette comes with a lot of built-in logic specific to VueJS.

First of all, instead of dealing with the interfaces we discussed above, you can inherit from AbstractVueFormComponent that does everything for you:

import { Component } from "@banquette/vue-typescript";
import { AbstractVueFormComponent } from "@banquette/vue-ui/form/abstract-vue-form.component";
import { TextViewDataInterface } from "./text-view-data.interface";
import { TextViewModel } from "./text.view-model";

@Component('my-input-component')
export default class MyInputComponent extends AbstractVueFormComponent<TextViewDataInterface, TextViewModel> {
    
    protected setupViewModel(): TextViewModel {
        return new TextViewModel(this.proxy, this.base);
    }
}