You are viewing limited content. For full access, please sign in.

Discussion

Discussion

Sharing Code: Bypass Required Fields on Form Reject in Layout Designer

posted on March 19 Show version history

I just got this code working, and it's pretty useful, so I wanted to share here for anyone else that may benefit from it.

This code allows a user to reject a form created in the Layout Designer without needing to fill in all required fields.

I was previously doing this by having code populate the submission action into a hidden field and then having Field Rules remove the required validation based on that field value (Link to Example).  But this is a code-only solution (no hidden field and no Field Rules) so it's much easier to add on to new forms.

This was tested in Forms Version 12.0.2509.20409

/*Bypass required fields on form reject by user.
The reqFields array is declared outside the function so that it carries across multiple submission attempts.
If the Reject button is used, the required validation of the fields is removed, even if the submission fails
for another reason.
However, another submission attempt (non-Reject) will reinstate the required validation of those fields
based on what is carried over in the reqFields array from the prior Reject submission attempt.*/
var reqFields = [];
LFForm.onFormSubmission(async function (event) {
  const userAction = event.data.action.value;
  if (userAction == 'Reject') {
    reqFields = LFForm.findFields(f => f.settings.required); 
    for (const e of reqFields) {
      await LFForm.validateFields(e, {"required": false});
    }
  }
  else {
    for (const e of reqFields) {
      await LFForm.validateFields(e, {"required": true});
    }
  }
});

EDIT: Based on feedback from Zac St. Louis, I removed the await keyword from line 11 because the LFForm.findFields function runs synchronously.

EDIT AGAIN: Modified code with more feedback from Zac St. Louis.  Rather than looping through the array, it verifies the array isn't empty and passes the whole array to the LFForm.validateFields function, so that improves functionality. Also addresses some edge cases around the initial Reject submission failing (for a different reason than required fields) and ensuring the required fields are reapplied before any other submissions.

/*Bypass required fields on form reject by user.
The reqFields array is declared outside the function so that it carries across multiple submission attempts.
If the Reject button is used, the required validation of the fields is removed, even if the submission fails
for another reason.
However, another submission attempt (non-Reject) will reinstate the required validation of those fields
based on what is carried over in the reqFields array from the prior Reject submission attempt.*/
let reqFields = [];
LFForm.onFormSubmission(async function (event) {
  const userAction = event.data.action.value;
  if (userAction == 'Reject') {
    reqFields = LFForm.findFields(f => f.settings.required);
    if (reqFields.length > 0) {
      await LFForm.validateFields(reqFields, { "required": false });
    }
  }
  else {
    const stillReq = LFForm.findFields(f => f.settings.required).filter((f) => reqFields.findIndex(rf => rf.fieldId === f.fieldId) === -1);
    const allReq = reqFields.concat(stillReq);
    if (allReq.length > 0) {
      await LFForm.validateFields(allReq, { "required": true });
    }
  }
});

 

1 0
replied on March 19

I guess my question is the opposite, does Laserfiche provide a method to trigger form validation before submission? For example, if we are executing custom code on submit, can we first ensure the form has fully passed validation before that code runs?

1 0
replied on March 20

Not currently, and I'm basing that answer on both the help documentation not having anything addressing that, and also running this Javascript which lets you examine all of the functions on the LFForm object: 

console.log(LFForm);

There isn't any function on the LFForm object currently that has anything to do with validation other than LFForm.validateFields function, which is about adding or removing the validation, not testing the validation.

0 0
replied on March 20

Not yet, but its pretty high on my list. 

1 0
replied on March 19 Show version history

Couple things

1. I'm not sure you ever need the else statement right? Once you submit you're leaving the form so onFormSubmission will either be run by approve or run by reject, never both on one page load.

2. you can actually just pass reqFields directly to validateFields since it accepts an array of fields 

3. findFields runs synchronously so you don't need the await keyword. Doesn't break anything though.

LFForm.onFormSubmission(async function (event) {
  const userAction = event.data.action.value;
  const reqFields = LFForm.findFields(f => f.settings.required);
  if (userAction == 'Reject') {
    await LFForm.validateFields(reqFields, {"required": false});
  }
});

 

0 0
replied on March 20

When I see the email notification that Zac has replied to my post, I'm super excited.  I know it's going to be helpful and educational, so I want to drop everything and dive in immediately.  I didn't do that however, because I was walking into the cinema to see Project Hail Mary on opening night.  It's close though, that's where my brain puts you, almost on par with PHM, you're amazing!  Anyway...

Let's address these in reverse order because #1 has a lot to unpack.

3. Despite having the documentation open as I wrote the code, I didn't actually read that the Output doesn't include a promise, so I should have known I didn't need the await keyword.  I was working from "I'll just await everything!" which is inefficient.  So thank you for helping with that.  Testing without the await keyword shows the same results as with it (of course it does), so I'll keep it out.

2. Once again, I'm guilty of not reading the documentation fully despite having it open in front of me.  It does say clearly that it'll take an array.  So thank you for helping with that too.  There is a possible problem here, and I need to explain my concerns with #1 first.

1. The reason that I included the else statement, and that I populated the reqFields array within the "if action is reject" block, was to deal with the possibility of the Reject submission failing for a different reason (such as pattern mismatching).

Let's say there are missing required fields, and the user tries to Reject.  The required validation on the fields is removed.  But let's say the Reject submission fails due to a Pattern Mismatch on a field.  They fix the Pattern Mismatch, but now instead of Reject, they hit Submit.  The Submit should have stopped them due to the missing required fields, but they are not required anymore (we removed the required validation on the initial Reject attempt).

So we need to make sure the validation is removed prior to the Reject submission, but reinstate them if that submission fails for some other reason.

I'm certainly open to suggestions here.

What we really need is for the form to acknowledge that the submission attempt failed and reinstate all the required automatically fields at that time, but I don't think there is a way to do that currently with the LFForm functions.  So we need some other way to reinstate those before any other submission attempts occur.

This is where I think my original solution of populating the submission action into a hidden field and using that hidden field to trigger Field Rules that remove the required validation (this post) is more solid - because it self corrects on later submission attempts.  The reason I was looking for a code-only solution is because the Field Rules solution can be a pain to set-up if you have a lot of required fields.  A code-only solution is super easy to add to new forms.

A couple other ideas I had, but rejected:

  1. In addition to removing the required validation on Reject, I could still populate the submission action into a hidden field and use that hidden field to trigger Field Rules.  In this case, the Field Rules disable every submission button other than Reject.  So once you try to Reject the first time, you can only Reject on any additional attempts, everything else is disabled.  But, as I said, I want to try to find a code only solution.
  2. I thought that after removing the required fields, I could set a timeout function to re-enable the requirements after 5 seconds or something.  If the submission succeeds, the timeout never runs because the browser has left the page, but if the submission fails, it's still there and triggers the timeout to reinstate those required fields.  I was worried that a large form or a slow connection could result in the timeout triggering while the submission was still processing and causing issues.

 

So that's how I landed on the "if action is reject" block (with the reqFields array populated in this block) and the else block for any other actions.  If the very first attempt to submit is the Reject button, the reqFields array is populated prior to removing the required validation on those fields, but reqFields isn't repopulated upon a different submission on the second attempt, so we still have those values to reinstate the required validation before running Submit or Approve or whatever.  Therefore, absent a better solution, I want to keep the if and else blocks with reqFields being populated within the "if action is reject" block.

This is where there is a protential problem with #2.  If I am only populating reqFields when hitting Reject, and the first submission attempt is not Reject, then when I try to pass the empty reqFields array to the LFForm.validateFields function, there is a syntax error and the whole thing locks up.  It cannot identify the field id from the empty array.  This doesn't happen with the loop I was using, because the loop runs zero times with the empty array and it never tries to call the LFForm.validateFields.  So although the theory is sound that passing the array to the function is more efficient than looping through the values and calling the function a bunch of times, it doesn't achieve the desired result in this case.

So...  Based on all of that, and a bunch of additional tests, I'm sticking with the majority of my original code - only removing the await keyword from the LFForm.findFields function.

Thank you so much for taking the time to respond to my post, and providing your amazing insight and guidance.  Even though I didn't take all your suggestions, I have a much better understanding of all of the components involved, and I really appreciate your help!

0 0
replied on March 20

Those are fair points, and you bring up a (probably hyper edge case) other issue. If reject does fail because of some validation, there could be some other field rule making something else required between submission attempts.

I also added an array length check. Didn't realize that errors in LFForm and I broke my golden rule of using try/catches 

 

let reqFields = [];
LFForm.onFormSubmission(async function (event) {
  const userAction = event.data.action.value;
  if (userAction == 'Reject') {
    reqFields = LFForm.findFields(f => f.settings.required);
    if (reqFields.length > 0) {
      await LFForm.validateFields(reqFields, { "required": false });
    }
  }
  else {
    const stillReq = LFForm.findFields(f => f.settings.required).filter((f) => reqFields.findIndex(rf => rf.fieldId === f.fieldId) === -1);
    const allReq = reqFields.concat(stillReq);
    if (allReq.length > 0) {
      await LFForm.validateFields(allReq, { "required": true });
    }
  }
});

 

0 0
replied on March 20

Nice!  I'll take this code as the official.

I tested on my work, including that edge case, because I had actually wondered about that kind of situation.

Thank you so much for your help!

0 0
You are not allowed to follow up in this post.

Sign in to reply to this post.