Print

Zoho Creator: Prevent Endless Loops On User Input of a Field

What?
This article serves as a best practice and reminder to myself on how to stop endless loops from happening in Creator and crashing the application. This is more for complex Creator forms which have workflows triggered from many deltas/changes.

Why?
Some clients have referred to this as the "spinning circle of death": they load up a Creator application or change a field and there will be a spinning icon indicating that the field has triggered an automation/workflow but it keeps spinning and doesn't stop. The app page will ultimately timeout and when reloading the form it simply displays a blank page. Their only workaround is wait about 30 seconds or more for the loop to automatically cut out and then they can use the interface again only to find it may crash again.

Zoho Creator: Prevent Endless Loops On User Input of a Field

In the background and from a developers understanding: field1 is told to update field2 when field1 is changed. When field2 is changed, it is told to update field1. This causes a recursive or endless loop where each field triggers an workflow/automation, even if the value has not changed, to modify another field and vice-versa. This can get more complex when there are a lot of fields in the mix.

How?
I'm using this article to go through various methods I tried to prevent this from happening:

First - Off: The one workflow to rule them all
In the first instance, we don't want large amounts of code to be copied and pasted for each field, especially if they're doing the same thing for example displaying a preview in a notes field. We would rather only have to update the code for 1 workflow rather than 20 workflows every time we want to tweak the code. There are also cases we don't want to use a standalone function as we want the code to read from the input fields dynamically and in real-time. To this end, I create a field called "Audit_Change" and then I add to it our workflow with the main bit of code. For any other field that needs to update this preview, I tell it to update the "Audit_Change" field with something like:
copyraw
input.Audit_Change = "Title: " + input.Title;
  1.  input.Audit_Change = "Title: " + input.Title; 


Method #1: (do not use)
Previously I have tried adding a field called "DateTime form was loaded" which would have the currenttime when the form was loaded. Then I would tell Audit_Change to only trigger the workflow if it had waited about 5 seconds till after this datetime to allow auto-updates to happen. This method is fine for 1 or 2 fields and if your workflows only trigger when the form first loads (especially on Edit as this populates each field and triggers their workflows) but cumbersome when there are a lot of fields.
copyraw
if(zoho.currenttime > input.DateTime_form_was_loaded.addSeconds(10))
{
	// do my stuff here
}
  1.  if(zoho.currenttime > input.DateTime_form_was_loaded.addSeconds(10)) 
  2.  { 
  3.      // do my stuff here 
  4.  } 

Method #2: Boolean checkbox to switch on/off workflow
Another method I tried was adding a checkbox/decision box (boolean) to the form called "Update_Preview". Onload or on the audit change, my code would look something like the following:
copyraw
// temporarily disable auto-update preview
input.Update_Preview=false;
//
// do stuff here ... eg getting data fields, generating a title and setting the title:
input.Title = "Hello World";
//
// output
input.Note_Preview = input.Title;
//
// re-enable auto-update preview
input.Update_Preview=true;
  1.  // temporarily disable auto-update preview 
  2.  input.Update_Preview=false
  3.  // 
  4.  // do stuff here ... eg getting data fields, generating a title and setting the title: 
  5.  input.Title = "Hello World"
  6.  // 
  7.  // output 
  8.  input.Note_Preview = input.Title; 
  9.  // 
  10.  // re-enable auto-update preview 
  11.  input.Update_Preview=true
To any other fields I would add the code:
copyraw
if(input.Update_Preview)
{
	input.Audit_Change = "Title: " + zoho.encryption.md5(input.Title);
}
  1.  if(input.Update_Preview) 
  2.  { 
  3.      input.Audit_Change = "Title: " + zoho.encryption.md5(input.Title)
  4.  } 
This still causes endless loops in certain cases because OnEdit, when we reach field 1 and audit change is invoked, the tickbox becomes false then true at the end of that workflow, but then field2 gets populated and runs the audit change (false to true). In any way that you do this, OnEdit will populate the form with the record data and trigger each field in the displayed order.

Method #3: Use an eTag
I use to use an eTag for a database-driven system I created to store the record details of properties. It was a quicker way of detecting a change on the record rather than comparing all the fields, compare only 1 value which is a hash or MD5 of all the fields of the record that I want to monitor. An eTag for me is simply an MD5 or hash of a bunch of data. If your MD5 value is different, then it means there was a change. If not then it is unlikely that any change was made. Instead, I need to add an additional field called "eTag_Title", check if the new MD5 is different, if it is then I proceed with the automations otherwise I stop it.
copyraw
// get given eTag
v_eTag = zoho.encryption.md5(input.Title.trim());
//
// now compare to stored eTag
if(v_eTag != input.eTag_Title)
{
	input.eTag_Title= v_eTag;
	input.Audit_Change = "Title: " + zoho.encryption.md5(input.Title);
}
  1.  // get given eTag 
  2.  v_eTag = zoho.encryption.md5(input.Title.trim())
  3.  // 
  4.  // now compare to stored eTag 
  5.  if(v_eTag != input.eTag_Title) 
  6.  { 
  7.      input.eTag_Title= v_eTag; 
  8.      input.Audit_Change = "Title: " + zoho.encryption.md5(input.Title)
  9.  } 

Now this stops the loop because it won't make a change to the triggering field if there was no difference on the eTag value.

Monitoring multiple fields
Additionally, you might ask if you need an eTag field for each field that you want to monitor changes. The answer is no, you only need one. You can have 1 eTag which combines all input fields into 1 MD5 or hash. Do you need to add a comparing if statement to all fields? Well no again, just compare on the workflow of the audit change. Something along the lines of:

  1. Add an Audit Change field: This will contain the code triggered when one of the fields is changed
  2. Add an eTag field: Will hold a 32 hexadecimal character string / a hash of the fields to monitor
  3. Add the if statement to compare etags on your main workflow.
  4. Other fields needing to trigger the main workflow will simply have something like:
    copyraw
    input.Audit_Change = "Title: " + input.Title;
    1.  input.Audit_Change = "Title: " + input.Title; 

The code on user input for Audit_Change field would look something like:
copyraw
// get given eTag
l_FieldsToMonitor = List();
l_FieldsToMonitor.add(input.Title);
l_FieldsToMonitor.add(input.Contact);
l_FieldsToMonitor.add(input.Account);
l_FieldsToMonitor.add(input.Opportunity);
v_eTag = zoho.encryption.md5(l_FieldsToMonitor.toString());
//
// now compare to stored eTag
if(v_eTag != input.eTag)
{
	input.eTag = v_eTag;
	// do the main code that takes into consideration all the fields triggering a change
	// ...
}
  1.  // get given eTag 
  2.  l_FieldsToMonitor = List()
  3.  l_FieldsToMonitor.add(input.Title)
  4.  l_FieldsToMonitor.add(input.Contact)
  5.  l_FieldsToMonitor.add(input.Account)
  6.  l_FieldsToMonitor.add(input.Opportunity)
  7.  v_eTag = zoho.encryption.md5(l_FieldsToMonitor.toString())
  8.  // 
  9.  // now compare to stored eTag 
  10.  if(v_eTag != input.eTag) 
  11.  { 
  12.      input.eTag = v_eTag; 
  13.      // do the main code that takes into consideration all the fields triggering a change 
  14.      // ... 
  15.  } 

An extra layer:
To go one step further, and only if you notice the form going slowly when it first loads (or doesn't load at all), consider adding a boolean box as per Method 2
  1. Add a decision box field called "Loaded Once"
  2. At the end of your "OnLoad" workflow (after any value assignment to "Audit Change"), enter
    copyraw
    input.Loaded_Once=true;
    1.  input.Loaded_Once=true
  3. On all the triggering fields add the if statement
    copyraw
    if(input.Loaded_Once)
    {
    	input.Audit_Change = zoho.currenttime;
    }
    1.  if(input.Loaded_Once) 
    2.  { 
    3.      input.Audit_Change = zoho.currenttime
    4.  } 

Would love to hear suggestions as to how else people do this but this has been proving to be a more stable solution for myself and my clients.

Category: Zoho :: Article: 769