An easy way determine if a form is dirty (user has changed something)

This has been asked my previosly by myself: A way to tell if form fields in the app are dirty? - #8 by Funkdaddy and I remember this coming up in other places.

Well I just figure out a way to do this easily.

  1. All of the components you want to check for dirty are in a form component. A simple container won't do.
  2. Assign the form a default value that is the object of the data the user may edit. This is most commonly selected row in a table so it would be simply: {{table.selectedRow}}. I also use an object passed to a module that holds the editing functionality.
  3. You need to make sure the Form data key property has the same object property names as your Initial data. If you used Generate Form to add your components, this is is done for you.
  4. Make this transformer:
// trIsFormDirty
const keys = Object.keys({{form1.data}})
return !keys.every(key => {
  if ({{form1.initialData}}.hasOwnProperty(key)) {
    return (({{form1.data}}[key] || null) === ({{form1.initialData}}[key] || null))
  } else {
    return true
  }
})

You can double check if it is all set up correctly by going to Debug Tools and expanding .data and .intitalData on your form to make sure the field names all match. You do not need to have all of your fields in the form, just the ones you want to edit.

Now you just need to check trIsFormDirty.value to see if the user has change anything!

Yo can add/remove components form the form without changing anything else in the app making maintenance much easier then previous techniques.

Let me explain the function in case you are interested:

const keys = Object.keys({{form1.data}})
This gets all of the Form data key values we set up that are also the same names as your initialData object properties.

return !keys.every(key => {
Now we loop through all of them. every will return true if all of its lambda functions return true. If any one of the me returns false, it will stop and return a false. We return the not (!) of its value as we asking if dirty, not if clean.

if ({{form1.initialData}}.hasOwnProperty(key)) {
This is a safety check for an apparent bug (Form.data property has an extra item that does not belong) that allows random values in the form's .data array.

return (({{form1.data}}[key] || null) === ({{form1.initialData}}[key] || null))
This checks to see if the form1.data value is the same as its intitialData. A component's value will often be an empty string or a 0 if the intitalData's value was null. This accounts for that so 0 === null and "" === null'.

  } else {
    return true
  }

This is the bottom half the the safety check, we must return true if there is a rogue value or else .every assume it is false and fails the entire thing.

2 Likes

If your form has Time Components you need to make a slight change to the code.

Why? When changing a time field its value will add .000 to the seconds. If your input in 09:45:00 and you change your time to 8:45, it will be 08:45:00.000. The form will be dirty. Now change it back to 9:45 and the value will be 09:45:00.000 which !== 09:45:00 and the form will still be dirty.

We need to strip out the .000. on Time fields. But how do you know the component it is comparing to is a time component? I don't know. All of my time fields have 'time' in their name so checking for that in the field name works for me. This may not work for you. You could check for any value containing .000 and trip it out if needed. You would have to error check for non-string fields, but that could work

New code that works for me:

const keys = Object.keys({{frmTaskDetails.data}})
return !keys.every(key => {
  if ({{frmTaskDetails.initialData}}.hasOwnProperty(key)) {
    if (key.toLowerCase().includes("time")) {
      return (({{frmTaskDetails.data}}[key].split('.')[0] || null) === ({{frmTaskDetails.initialData}}[key] || null))
    } else {
      return (({{frmTaskDetails.data}}[key] || null) === ({{frmTaskDetails.initialData}}[key] || null))  
    }
  } else {
    return true
  }
})
1 Like

Great post. Extremely helpful. I especially appreciate the detailed explanation. Taught me a lot.

Retool is a pain. Here's another short solution:

For a single form:

{{!.every(.keys(form1[0].data), key => _.isEqual(form1[0].data[key], form1[0].initialData[key])) }}

For forms in repeated group items:

{{ .some(form1, item => !.every(_.keys(item.data), key => _.isEqual(item.data[key], item.initialData[key]))) }}

1 Like

Thanks @bradlymathews, that is amazingly helpful and an elegant solution.

Now to have @tess include it as functionality in the Retool form component.

2 Likes