Hiding and showing fields

Explicit field omission

The setFieldOmission method

This is a method of the changesetWebform class.

Sets a fields omitted property to true. It receives a single argument, the fieldId of the field to update.

Setting omitted to true on a fields has several implications:

  • the related changeset property will not be validated when the validateFields method is run on submit, or in an action.
  • the fields HTML element will be removed from the DOM entirely.
  • the related data property will not be included in the data which is sent with the submit action.
  • if the field's resetWhenOmitted property is true (Which is the default) the field will be reset. This means that any unsaved chnages to the field's changeset property will be rolled back using changeset.rollback() and the field will be unvalidated.

See the below example of using includeField and omitField.

import React from 'react';
import ChangesetWebform from 'react-changeset-webforms/src/components/ChangesetWebform.jsx';

const formSchema = {
  formSettings: {
    formName: 'omittingFields1',
    hideSubmitButton: true,
  },
  fields: [
    {
      fieldId: 'mealRequired',
      fieldLabel: 'Would you like to order a meal?',
      fieldType: 'radioButtonGroup',
      validatesOn: ['$inherited', 'insertWithValue'],
      validationRules: [
        {
          validationMethod: 'validatePresence',
          arguments: {
            presence: true,
            description: 'Would you like to order a meal',
          },
        },
      ],
      options: ['Yes', 'No'],
    },
    {
      fieldId: 'mealOption',
      fieldType: 'radioButtonGroup',
      fieldLabel: 'Please select a meal option',
      omitted: true,
      validatesOn: ['$inherited', 'insertWithValue'],
      validationRules: [
        {
          validationMethod: 'validatePresence',
          arguments: { presence: true, description: 'Meal option' },
        },
      ],
      options: ['Beef', 'Chicken', 'Vegetarian', 'Vegan'],
    },
  ],
};

export default function HiddenFieldsExampleOne() {
  const [changesetIsValid, setChangesetIsValid] = React.useState(false);

  async function onFieldValueChange(formField, changesetWebform) {
    if (formField.fieldId === 'mealRequired') {
      if (formField.fieldValue === 'Yes') {
        changesetWebform.setFieldOmission('mealOption', false);
      } else {
        changesetWebform.setFieldOmission('mealOption', true);
      }
    }
    setChangesetIsValid(!changesetWebform.hasValidationErrors && !changesetWebform.hasUnvalidatedFields);
  }

  return (
    <div data-test-id="omitted-fields-example-one">
      <ChangesetWebform
        formSchema={formSchema}
        onFieldValueChange={onFieldValueChange}
      />
      {changesetIsValid && (
        <button
          data-test-id="next-button"
          className="btn btn-outline-primary"
          type="button"
        >
          Next
        </button>
      )}
    </div>
  );
}

The setOmission method

This is a method of the formField class.

The effect is exactly the same as with using the setFieldOmission method of a changesetWebform class instance above. This method simply offers an alternative way to achieve the same thing.

import React from 'react';
import ChangesetWebform from 'react-changeset-webforms/src/components/ChangesetWebform.jsx';

const formSchema = {
  formSettings: {
    formName: 'omittingFields5',
    hideSubmitButton: true,
  },
  fields: [
    {
      fieldId: 'mealRequired',
      fieldLabel: 'Would you like to order a meal?',
      fieldType: 'radioButtonGroup',
      validatesOn: ['$inherited', 'insertWithValue'],
      validationRules: [
        {
          validationMethod: 'validatePresence',
          arguments: {
            presence: true,
            description: 'Would you like to order a meal',
          },
        },
      ],
      options: ['Yes', 'No'],
    },
    {
      fieldId: 'mealOption',
      fieldType: 'radioButtonGroup',
      fieldLabel: 'Please select a meal option',
      omitted: true,
      validatesOn: ['$inherited', 'insertWithValue'],
      validationRules: [
        {
          validationMethod: 'validatePresence',
          arguments: { presence: true, description: 'Meal option' },
        },
      ],
      options: ['Beef', 'Chicken', 'Vegetarian', 'Vegan'],
    },
  ],
};

export default function HiddenFieldsExampleFive() {
  const [changesetIsValid, setChangesetIsValid] = React.useState(false);

  async function onFieldValueChange(formField, changesetWebform) {
    const mealOptionField = changesetWebform.fields.find((field) => field.fieldId === 'mealOption');
    if (formField.fieldId === 'mealRequired') {
      if (formField.fieldValue === 'Yes') {
        mealOptionField.setOmission(false);
      } else {
        mealOptionField.setOmission(true);
      }
    }
    setChangesetIsValid(!changesetWebform.hasValidationErrors && !changesetWebform.hasUnvalidatedFields);
  }

  return (
    <div data-test-id="omitted-fields-example-five">
      <ChangesetWebform
        formSchema={formSchema}
        onFieldValueChange={onFieldValueChange}
      />
      {changesetIsValid && (
        <button
          data-test-id="next-button"
          className="btn btn-outline-primary"
          type="button"
        >
          Next
        </button>
      )}
    </div>
  );
}

Dynamic field omission

It may not be convenient to use action handlers to forcibly show and hide fields in this scenario, so your field schema can define general conditions under which it should be shown or omitted. This is done using the omitted property.

The omitted property

In order to enable dynamic field omission, the omitted property of a form field must be an object with three required properties.

  • returns
    • Boolean. The value to set field omission to if the where property evaluates as true. Note that if the where property evaluates as false, the field omission will be set to the opposite of the returns value.
  • where
    • either anyConditionsTrue or allConditionsTrue. If allConditionsTrue, then where will evaluate to true if every condition in the conditions array evaluates to true. If anyConditionsTrue then where will evaluate to true if at least one condition in the conditions array evaluates to true.
  • conditions
    • an array of objects each specifiying a fieldId and valueEquals. The condition evaluates to true if the current value of the related field matches that of valueEquals.
fieldId: 'chooseSeat',omitted: {returns: false,where: 'allConditionsTrue',conditions: [ {fieldId: 'isMember',valueEquals: 'Yes', }, {fieldId: 'hasTicket'valueEquals: 'Yes'} ], },

Let's consider the omitted property in relation to the snippet above.

First, returns is false, and where is allConditionsTrue.

  • This means that if every item in the conditions array evaluates to true, the field with a fieldId of chooseSeat will not be omitted.
    • This only occurs if:
      • the field with a fieldId of isMember has a value of Yes, and
      • the field with a fieldId of hasTicket has a value of Yes.
  • This also means that if any item in the conditions array evaluates to false the field with a fieldId of chooseSeat will be omitted.
    • This occurs if:
      • the field with a fieldId of isMember does not have a value of Yes, or
      • the field with a fieldId of hasTicket does not have a value of Yes.

The live example below shows the mealOption field being omitted or not, based on the value of the mealRequired field.

import React from 'react';
import ChangesetWebform from 'react-changeset-webforms/src/components/ChangesetWebform.jsx';

const formSchema = {
  formSettings: {
    formName: 'omittingFields2',
    hideSubmitButton: true,
  },
  fields: [
    {
      fieldId: 'mealRequired',
      fieldLabel: 'Would you like to order a meal?',
      fieldType: 'radioButtonGroup',
      validatesOn: ['$inherited', 'insertWithValue'],
      validationRules: [
        {
          validationMethod: 'validatePresence',
          arguments: {
            presence: true,
            description: 'Would you like to order a meal',
          },
        },
      ],
      options: ['Yes', 'No'],
    },
    {
      fieldId: 'mealOption',
      fieldType: 'radioButtonGroup',
      fieldLabel: 'Please select a meal option',
      validatesOn: ['$inherited', 'insertWithValue'],
      validationRules: [
        {
          validationMethod: 'validatePresence',
          arguments: { presence: true, description: 'Meal option' },
        },
      ],
      options: ['Beef', 'Chicken', 'Vegetarian', 'Vegan'],
      omitted: {
        returns: false,
        where: 'anyConditionsTrue',
        conditions: [
          {
            fieldId: 'mealRequired',
            valueEquals: 'Yes',
          },
        ],
      },
    },
  ],
};

export default function HiddenFieldsExampleTwo() {
  const [changesetIsValid, setChangesetIsValid] = React.useState(false);

  async function onFieldValueChange(_formField, changesetWebform) {
    setChangesetIsValid(!changesetWebform.hasValidationErrors && !changesetWebform.hasUnvalidatedFields);
  }

  return (
    <div data-test-id="omitted-fields-example-two">
      <ChangesetWebform
        formSchema={formSchema}
        onFieldValueChange={onFieldValueChange}
      />
      {changesetIsValid && (
        <button
          data-test-id="next-button"
          className="btn btn-outline-primary"
          type="button"
        >
          Next
        </button>
      )}
    </div>
  );
}

Extending the dynamic field omission API

By default, objects in the conditions array can only include valueEquals along with fieldId as properties.

If you need extend this API to include comparisions other than exact string match, you can do so by passing a hash of additional methods to the ChangesetWebform component as the @dynamicIncludeExcludeConditions property.

Each method receives value and condition as arguments, and should return a truthy value.

  • value is the current value of the field with the fieldId specified it the condition.
  • condition is the relevant condition specified.

Now, any conditions in then dynamicOmission property of your field schema can use the names of any of these methods as a key, with the value to compare to.

In the example below, we add and invoke the valueDoesNotEqual method.

import React from 'react';
import ChangesetWebform from 'react-changeset-webforms/src/components/ChangesetWebform.jsx';

const dynamicIncludeExcludeConditions = {
  valueDoesNotEqual: (value, condition) => value !== condition.valueDoesNotEqual,
};

const formSchema = {
  formSettings: {
    formName: 'omittingFields3',
    hideSubmitButton: true,
  },
  fields: [
    {
      fieldId: 'mealRequired',
      fieldLabel: 'Would you like to order a meal?',
      fieldType: 'radioButtonGroup',
      validatesOn: ['$inherited', 'insertWithValue'],
      validationRules: [
        {
          validationMethod: 'validatePresence',
          arguments: {
            presence: true,
            description: 'Would you like to order a meal',
          },
        },
      ],
      options: ['Yes', 'No'],
    },
    {
      fieldId: 'mealOption',
      fieldType: 'radioButtonGroup',
      fieldLabel: 'Please select a meal option',
      validatesOn: ['$inherited', 'insertWithValue'],
      validationRules: [
        {
          validationMethod: 'validatePresence',
          arguments: { presence: true, description: 'Meal option' },
        },
      ],
      options: ['Beef', 'Chicken', 'Vegetarian', 'Vegan'],
      omitted: {
        returns: false,
        where: 'anyConditionsTrue',
        conditions: [
          {
            fieldId: 'mealRequired',
            valueDoesNotEqual: 'No',
          },
        ],
      },
    },
  ],
};

export default function HiddenFieldsExampleThree() {
  const [changesetIsValid, setChangesetIsValid] = React.useState(false);

  async function onFieldValueChange(_formField, changesetWebform) {
    setChangesetIsValid(!changesetWebform.hasValidationErrors && !changesetWebform.hasUnvalidatedFields);
  }

  return (
    <div data-test-id="omitted-fields-example-three">
      <ChangesetWebform
        formSchema={formSchema}
        onFieldValueChange={onFieldValueChange}
        dynamicIncludeExcludeConditions={dynamicIncludeExcludeConditions}
      />
      {changesetIsValid && (
        <button
          data-test-id="next-button"
          className="btn btn-outline-primary"
          type="button"
        >
          Next
        </button>
      )}
    </div>
  );
}

Nested dynamic omission rules

Conditions can also be nested.

Any item in the conditions array of a ruleset can itself be a ruleset.

The example below shows howe the second condition on the anyConditionsTrue ruleset is itself an allConditionsTrue ruleset.

Free drink for members and orders including 3 mains and 3 side dishes!
import ChangesetWebform from 'react-changeset-webforms/src/components/ChangesetWebform.jsx';

const formSchema = {
  formSettings: {
    formName: 'omittingFields4',
    hideSubmitButton: true,
  },
  fields: [
    {
      fieldId: 'isMember',
      fieldLabel: 'Are you a member?',
      fieldType: 'radioButtonGroup',
      validatesOn: ['$inherited', 'insertWithValue'],
      validationRules: [
        {
          validationMethod: 'validatePresence',
          arguments: {
            presence: true,
            description: 'Are you a member',
          },
        },
      ],
      options: ['Yes', 'No'],
    },
    {
      fieldId: 'mains',
      fieldType: 'input',
      inputType: 'number',
      min: 1,
      max: 3,
      fieldLabel: 'How many main meals would you like to order?',
      validationRules: [
        {
          validationMethod: 'validateFormat',
          arguments: { type: 'number' },
        },
      ],
    },
    {
      fieldId: 'sides',
      fieldType: 'input',
      inputType: 'number',
      min: 1,
      max: 3,
      fieldLabel: 'How many side dishes would you like to order?',
      validationRules: [
        {
          validationMethod: 'validateFormat',
          arguments: { type: 'number' },
        },
      ],
    },
    {
      fieldId: 'freeDrink',
      fieldType: 'radioButtonGroup',
      fieldLabel: `You've qualified for a free drink!  You can select one of the following:`,
      options: ['Orange juice', 'Water', 'Chocolate milk'],
      omitted: {
        returns: false,
        where: 'anyConditionsTrue',
        conditions: [
          {
            fieldId: 'isMember',
            valueEquals: 'Yes',
          },
          {
            returns: true,
            where: 'allConditionsTrue',
            conditions: [
              {
                fieldId: 'mains',
                valueEquals: '3',
              },
              {
                fieldId: 'sides',
                valueEquals: '3',
              },
            ],
          },
        ],
      },
    },
  ],
};

export default function HiddenFieldsExampleFour() {
  return (
    <div data-test-id="omitted-fields-example-four">
      Free drink for members and orders including 3 mains and 3 side dishes!
      <ChangesetWebform formSchema={formSchema} />
    </div>
  );
}