jsonforms icon indicating copy to clipboard operation
jsonforms copied to clipboard

[renderers] Possibility to choose whether `onChange` is triggered at each typed character or on focus loss for text-based inputs

Open vhemery opened this issue 3 years ago • 7 comments

Is your feature request related to a problem? Please describe.

Currently (at least with Material UI renderer and I think also with vanilla React renderer), the onChange method is called each time a character is typed in the text-based input (with eventual debounced introduced by the user). For some use cases, it would be preferable to trigger the onChange method only when the concerned text-based input looses the focus. (regardless of eventual debounce)

Of course, both scenarii are valid, and it's really a matter of personal preference.

Describe the solution you'd like

It would be nice to upgrade all text-based renderers to have the two possibilities:

  • trigger onChange when a character is typed in the input.
  • trigger onChange when the input looses focus and its content has actually changed.

Users should be able to choose between the two behaviors by configuring a simple option in the JsonForms configuration.

Describe alternatives you've considered

As a user, it should already be possible to achieve this by providing custom renderers and overriding the behavior in each used text-based renderer. But it would be so much cleaner if the change was centralized and inherited by supporting renderers. Plus, as this looks like a recurring user need, we should probably consider this evolution intead of having diverging user renderers.

Framework

React, Angular, Vue 3, Vue 2

RendererSet

Material, Vanilla

Additional context

See also discussion https://github.com/eclipse-emfcloud/emfcloud/discussions/152

vhemery avatar Mar 28 '22 07:03 vhemery

Hi @vhemery, thanks for the suggestion!

That's interesting because in the past we had many complaints whenever we implemented a "focus-out/blur" approach and this is the first request to actually support this behavior. Of course when we had this "focus-out/blur" approach, we did not update the internal data state at all (including no validation execution), leading to inconsistent UI states, e.g. an input still shows an "is required" error although data is already entered but the input was not yet focused out of.

Switching between these two modes is relatively simple by just binding to a different change mechanism in the input. This can easily be supported via a UI Schema option and should not be much effort. However if you like internal data updates (and therefore also validation runs) for every key input but a change event only on "focus-out/blur" then things become more complicated. We probably need another call back then to be injected into the renderers, but I did not yet spend much time thinking about this.

Would you like to contribute something like this to JSON Forms?

sdirix avatar Mar 28 '22 10:03 sdirix

About the validation, the point was to not perform the backend validation (which may cost time) until the value has been fully entered, but still enable some basic frontend validation. In our application, we have disabled AJV for the moment due to issues related to #1491. So I'm not sure exactly how and when AJV validation comes into the game.

The ideal contribution would indeed support frontend light validation at each character, and backend submission only when requested by the user. So, like you suggest, we may have to distinguish the two events in the end.

At first, the described alternative was being considered. But we may also try and contribute it instead. So I can make no promise of contribution for the moment.

vhemery avatar Mar 28 '22 12:03 vhemery

Just for the record: Ajv validation is invoked at https://github.com/eclipsesource/jsonforms/blob/d65d7eb8a5050c6de7a4e086e01237bd2488f64c/packages/core/src/util/renderer.ts#L971 on the data from JsonForms state. The state data is updated by https://github.com/eclipsesource/jsonforms/blob/d65d7eb8a5050c6de7a4e086e01237bd2488f64c/packages/react/src/Control.ts#L54 The user's onChange method is invoked by https://github.com/eclipsesource/jsonforms/blob/d65d7eb8a5050c6de7a4e086e01237bd2488f64c/packages/react/src/JsonFormsContext.tsx#L188 at each re-rendering (hence when the state changes).

So it may be hard to distinguish the two events without messing up with JsonFormsContext.

vhemery avatar Mar 28 '22 14:03 vhemery

OK, I am not as familiar with the code as you both are. But to explain my problem: in my BPMN Modelling scenario the properties for a node can be very complex and they need to be validated in the backend at the end of the day in some way. For example, if a script function entered into a ScriptTask or a conditional sequence flow is valid or not, can not be validated by the Input Renderer. So currently I am sending the data on each key stroke to the server and back which makes not much sense. On the other hand I understand that for a simple 'required' input field or an e-mail input it is nice if this is validated and shown directly during typing. But for my BPMN elements this will never be a usecase. So adding a debounce (from 'lodash') to the onChange function - as @vhemery mentioned first in https://github.com/eclipse-emfcloud/emfcloud/discussions/152 seems to be sufficient. And it would be nice if I could activate such a behaviour via an option.

rsoika avatar Mar 28 '22 20:03 rsoika

So adding a debounce (from 'lodash') to the onChange function - as @vhemery mentioned first in eclipse-emfcloud/emfcloud#152 seems to be sufficient. And it would be nice if I could activate such a behaviour via an option.

You don't need this feature request for this. You just pass the debounced function to JsonForms... Here is an illustrated exemple. In your React component invoking JsonForms, create the function once (hence not in the render method) :

    /** The debounced version of jsonformsOnChange method, allowing to not invoke it too often */
    private debouncedJsonformsOnChange: DebouncedFunc<(state: Pick<JsonFormsCore, 'data' | 'errors'>) => void>;

    /**
     * Create a new React component
     * @param props component properties
     */
    constructor(props: Properties.Props) {
        super(props);
        [...]
        this.debouncedJsonformsOnChange =
            // add a debounce delay to subscription to let user type
            debounce(
                (state: Pick<JsonFormsCore, 'data' | 'errors'>) => this.jsonformsOnChange(state),
                500, { trailing: true }
            );
    }

    /**
     * React to the json forms change.
     * This function must not be called outside of a debounce, to make sure the update job is not executed too often on quick keyboard typing.
     * @param state the updated json forms state
     */
    private jsonformsOnChange(state: Pick<JsonFormsCore, 'data' | 'errors'>): void {
        [...]
    }

then on each re-render, pass it to the JsonForms element : <JsonForms [...] onChange={this.debouncedJsonformsOnChange} />

The goal of the feature request is not to delay the submission, but to send it immediately and only once when the input looses focus (ideally while still enabling frontend immediate validation on typing). If discussion on the debounced solution is needed, please continue in [eclipse-emfcloud/emfcloud#152]

vhemery avatar Mar 29 '22 05:03 vhemery