Same field, different Edit Widget

I have a collection which has several string fields and I would like to know if there is a way for me to be able to have two different Edit Widgets of the field on the same collection based on a specific check?

I would like to be able to display either a simple Text input or a Rich text editor, based on another field I have in that collection.

Is this possible?

Context

  • Agent technology: NodeJS
  • Agent (forest package) name & version: @forestadmin/agent 1.35.16, @forestadmin/datasource-mongoose 1.7.3
  • Database type: Mongo DB

Hello @Mihai_Chiciu1,

This kind of behaviour would be available within a collection edit action, using dynamic forms.
Let me know if this is something that would fit your use case and I can provide you with an example how to do it.

@Nicolas.M

Hi @Nicolas.M ,

Thank you for you quick answer. I wasn’t aware that the default Edit action can be customized. Would you mind sharing how can I do that?

I will give an example of the data I have in the Mongo Collection.

The collection is called Locale and looks like this:

{
   _id: ObjectId;
   field: string;
   en: string;
   de: string;
   it: string;
   fr: string;
}

What I want to achieve is when the field includes the string richTextLocale, I want to display the field in the Edit as a Rich Text Editor. Otherwise, I want to display it as a Text Input.

It would help a lot if you could show me an example on how to do this.

Thank you!

What I meant by edit action is to create a new Action that will handle field edition (your users will have to call this action instead of clicking Edit. You can remove the “native” Edit button by removing the rights to Edit, so your users will need to use the Action instead)

Here is a sample below that should work for your use case (for editing field and en of the locale collection). Please let me know if this is ok :pray:

    .customizeCollection('locale', locales =>
      locales.addAction('Edit', {
        scope: 'Single',
        async form(context) {
          // field: String,
          // en: String,
          const record = await context.getRecord(['field', 'en']);

   
           return [
            {
              id: 'field',
              label: 'field',
              type: 'String',
              widget: record.field.includes('richTextLocale') ? 'RichText' : 'TextInput',
              defaultValue: record.field,
            },
            {
              id: 'en',
              label: 'en',
              type: 'String',
              widget: 'TextInput',
              defaultValue: record.en,
            },
          ];
        },
        async execute(context, resultBuilder) {
          await context.collection.update(context.filter, {
            field: context.formValues.field,
            en: context.formValues.en,
          });

          return resultBuilder.success('ok');
        },
      }),
    );

Thank you very much @Nicolas.M!

I suppose the same thing can be done for creating a new entry in the Locale collection?

Later Edit:

I tried using you solution and unfortunately it seems like the solution doesn’t work. I tried with your solution and the solution from the documentation at Example 7, but in the code I am not allow to provide a function for the Form. It only let’s me provide an Array.

I will paste the error as well:

Type '(context: any) => Promise<{ id: string; label: string; type: string; widget: string; defaultValue: any; }[]>' is not assignable to type 'DynamicField<ActionContextSingle<Schema, "locales">>[] | DynamicField<ActionContext<Schema, "locales">>[]'.ts(2322)

actions.d.ts(11, 5): The expected type comes from property 'form' which is declared here on type 'ActionDefinition<Schema, "locales">'

Hello @Mihai_Chiciu1,

I suppose the same thing can be done for creating a new entry in the Locale collection?

→ Yes indeed. It will be an Action with scope: ‘Global’ then

Sorry, I should have checked your agent version. Indeed, it does not support functions in the form attribute.

You will need to upgrade your agent to this version at least: @forestadmin/agent - npm (and update your deps as well)

It should be fairly easy since there are no breaking changes.

Please let me know if this is an option for you.

Thanks @Nicolas.M !

Yes, that works for me and I managed to have a functional version. But I have one more question for this.

How can I make the widget dynamic? If I change I manually change the field in the form, the Widget doesn’t update until I save and close the window. Is there a way to update the widget it immediately after the condition record.field.includes('richTextLocale') changes?

Right,
So we are at the right at the limit of what can be achieved with action forms :slight_smile:
I did find a way, but the typing does not currently support it, even though the code works. :frowning:
I guess that changing widgets on the fly wasn’t a forecast use case.
If you are ok with the ts-ignore, (or are working in js), then this is a way you can do it:

      locales.addAction('Edit', {
        scope: 'Single',
        form: [
          {
            id: 'field',
            label: 'field',
            type: 'String',
            defaultValue: async context => {
              const record = await context.getRecord(['field', 'en']);

              return record.field;
            },
          },
          {
            id: 'en',
            label: 'en',
            type: 'String',
            // @ts-ignore
            widget: context => {
              return context.formValues.field.includes('richtext') ? 'RichText' : 'TextInput';
            },
            // @ts-ignore
            value: async context => {
              const record = await context.getRecord(['field', 'en']);

              return `${record.en}`; // to prevent wrapping with divs
            },
            // @ts-ignore
            defaultValue: async context => {
              const record = await context.getRecord(['field', 'en']);

              return record.en;
            },
          },
        ],
        async execute(context, resultBuilder) {
          await context.collection.update(context.filter, {
            field: context.formValues.field,
            en: context.formValues.en,
          });

          return resultBuilder.success('ok');
        },
      }),

I’m making a note to check (and eventually fix) why is the typing not working with this example