formio / formio.js

JavaScript powered Forms with JSON Form Builder
https://formio.github.io/formio.js
MIT License
1.9k stars 1.07k forks source link

[HOW-TO] Programmatically set custom error message to a field #2399

Closed frabanca closed 2 years ago

frabanca commented 4 years ago

Hi! I've just discovered this awesome framework and I'm trying to integrate it in a test environment in localhost. Everything looks fine to me but I don't undestand how to handle server-side errors. Let's suppose user submit the form but the server found a duplicate email address. My server will respond with a response body like this:

{
  status: false,
  message: 'Invalid data, please check and try again',
  errors: [
    {
      key: 'email',
      message: 'This email is already associated to another user',
    }
  ]
}

So my question is: How can I programmatically set custom error messages to each field of the form starting from fields' key?

EXAMPLE FIDDLE

Here's my code: HTML:

  <!-- OTHER HTML... --->
  <div id="form-container"></div>
  <!-- OTHER HTML, JS FILES AND JS DEPENDENCIES... --->
  <script src="formio-test.js"></script>

Javascript:

window.onload = function() {
  initForm('form-container');
}

  /**
   * Init form in provided container
   *
   * @param {string} containerId The ID of the form-container used in the HTML
   *
   * @returns {undefined}
   */
  async initForm(containerId) {
    const formData = {
      display: "form",
      components: [
        {
          label: "Columns",
          columns: [
            {
              components: [
                {
                  label: "Nome",
                  placeholder: "Inserisci il nome",
                  showCharCount: true,
                  spellcheck: true,
                  tableView: true,
                  validate: {
                    required: true,
                    minLength: 2,
                    maxLength: 30,
                    unique: false,
                    multiple: false
                  },
                  key: "firstName",
                  type: "textfield",
                  input: true,
                  hideOnChildrenHidden: false
                },
                {
                  label: "Email",
                  placeholder: "Inserisci l'email",
                  spellcheck: true,
                  tableView: true,
                  validate: {
                    required: true,
                    minLength: 5,
                    maxLength: 320,
                    unique: false,
                    multiple: false
                  },
                  key: "email",
                  type: "email",
                  input: true,
                  hideOnChildrenHidden: false
                }
              ],
              width: 6,
              offset: 0,
              push: 0,
              pull: 0
            },
            {
              components: [
                {
                  label: "Cognome",
                  placeholder: "Inserisci il cognome",
                  showCharCount: true,
                  spellcheck: true,
                  tableView: true,
                  validate: {
                    required: true,
                    minLength: 2,
                    maxLength: 30,
                    unique: false,
                    multiple: false
                  },
                  key: "lastName",
                  attributes: {
                    boooohAtr: "bohhhval"
                  },
                  type: "textfield",
                  input: true,
                  hideOnChildrenHidden: false
                },
                {
                  label: "Role",
                  widget: "choicesjs",
                  placeholder: "Seleziona un ruolo",
                  tableView: true,
                  data: {
                    values: [
                      {
                        value: "admin",
                        label: "Admin"
                      },
                      {
                        label: "Utente",
                        value: "user"
                      }
                    ]
                  },
                  selectThreshold: 0.3,
                  validate: {
                    required: true,
                    unique: false,
                    multiple: false
                  },
                  key: "role",
                  type: "select",
                  indexeddb: {
                    filter: {}
                  },
                  input: true,
                  defaultValue: "user",
                  hideOnChildrenHidden: false
                }
              ],
              width: 6,
              offset: 0,
              push: 0,
              pull: 0
            }
          ],
          tableView: false,
          key: "columns",
          type: "columns",
          input: false
        },
        {
          label: "Invia",
          showValidations: false,
          rightIcon: "fa fa-chevron-right",
          tableView: false,
          key: "submit",
          type: "button",
          input: true,
          validate: {
            unique: false,
            multiple: false
          }
        }
      ]
    };

    const formSettings = {
      readOnly: false,
      noAlerts: false,
      language: "it",
      i18n: {
        it: formioTranslationIt
      }
    };

    // Init form
    this.form = await Formio.createForm(
      document.getElementById(containerId),
      formData,
      formSettings
    );

    // Prevent the submission from going to the form.io server.
    this.form.nosubmit = true;

    // Set default data
    this.form.submission = {
      data: {
        firstName: "Joe",
        lastName: "Smith",
        email: "info@loremipsum.com"
      }
    };

    // Handle submit
    this.form.on("submit", submission => {
      this.onFormSubmit(submission);
    });
  }

  /**
   * Handle form submit
   *
   * @param {any} submission Submission data emitted from the form
   *
   * @returns {undefined}
   */
  async onFormSubmit(submission) {
    try {
      // Reset previous custom errors
      this.form.customErrors = [];

      // Call back-end API
      const response = await fetch("http://localhost:8080/api/v1/users", {
        method: "POST",
        body: JSON.stringify(submission.data),
        mode: "cors",
        headers: {
          "Content-Type": "application/json"
        }
      });

      // Stop button loading and parse result
      this.form.emit("submitDone", submission);

      // Handle failiture
      if (!res.status) {
        // I NEED TO ADD ERROR MESSAGES:
        // 1) A general error message to the form
        // 2) A specific error message to each field that has encountered an error in the back-end validation

        return;
      }

      // Handle success
      this.form.setAlert("success", res.message);
      this.form.showAlert();
    } catch (error) {
      console.error("ERROR: ", error);
      // todo 
    }
  }
randallknutson commented 4 years ago

You can add a customValidation hook into the form that will allow you to send the data to a custom server url for validation first. You can then return a string/error object/array of errors that will then be displayed on the form. See:

https://github.com/formio/formio.js/blob/master/src/Webform.js#L1329-L1344

frabanca commented 4 years ago

Hi @randallknutson , thank you very much for your reply!

I'm new to formio.js and I apologise for my question by... how can I do that? Can you make an example of a customValidation hook?

In the example I provided above, where (and how) should I add that hook?

frabanca commented 4 years ago

Hi @randallknutson , I just found the path.

For everyone else reading this, I solved adding the following code to the options param (the third one used to create the form instance)

    // ...set var containerId and var formData

    const formSettings = {
      readOnly: false,
      noAlerts: false,
      hooks: {
        customValidation: (submission, next) => {
          // Call back-end APIs and handle response
        }
      }
    };

    // Init form
    this.form = await Formio.createForm(
      document.getElementById(containerId),
      formData,
      formSettings
    );

Thanks for your help :)

SalTor commented 3 years ago

Starting at what version is this hook "customValidation" available? And is it available to Wizard-based forms?

travist commented 3 years ago

this hook has been around for a while and should also be available for any version released within the past year in 4.x branch. And yes it also applies to wizards.

Alexa-Green commented 2 years ago

Has anyone gotten this working with @formio/react? See sandbox: https://codesandbox.io/s/practical-chaum-rwgxqj?file=/src/index.js

https://github.com/formio/react/issues/440

matt-whiteley commented 2 years ago

I am adding this here as this issue appears on Google when searching for how to do this.

It appears to be an undocumented 'feature', but if you return a response in the following format then the formio.js framework displays the errors natively without writing a custom callback:

{
  "name": "ValidationError",
  "details": [
     {
       "message": "My custom message about this field",
       "type": "custom",
       "path": ["input1"],
       "level": "error"
     },
     {
       "message": "My custom message about another field",
       "type": "custom",
       "path": ["input2"],
       "level": "error"
     }
  ]
}

details is an array with an object per input that is invalid. path must be an array for some reason, but it takes the key of the input, and this is what puts the error in the right place in the form. Using this you can display a custom message for as many inputs as needed.

Screenshot 2022-03-29 at 13 38 55
Demacri commented 1 month ago

I am adding this here as this issue appears on Google when searching for how to do this.

It appears to be an undocumented 'feature', but if you return a response in the following format then the formio.js framework displays the errors natively without writing a custom callback:

{
  "name": "ValidationError",
  "details": [
     {
       "message": "My custom message about this field",
       "type": "custom",
       "path": ["input1"],
       "level": "error"
     },
     {
       "message": "My custom message about another field",
       "type": "custom",
       "path": ["input2"],
       "level": "error"
     }
  ]
}

details is an array with an object per input that is invalid. path must be an array for some reason, but it takes the key of the input, and this is what puts the error in the right place in the form. Using this you can display a custom message for as many inputs as needed.

Screenshot 2022-03-29 at 13 38 55

Came here in 2024, really helped, thank you. Just want to add a small note: If you need to show errors programmatically (whenever/wherever you want), you can just invoke:

formioInstance.showErrors([
     {
       "message": "My custom message about this field",
       "type": "custom",
       "path": ["input1"],
       "level": "error"
     },
     {
       "message": "My custom message about another field",
       "type": "custom",
       "path": ["input2"],
       "level": "error"
     }
  ]
, true);

where formioInstance is the result of createForm:


const formioInstance = await Formio.createForm(<yourElement>, <yourSchema>, <yourOptions>);