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

Question

Question

Update 5 LFForm Subscribe troubles

asked on March 12, 2024 Show version history

I have a form that relies on a series of lookups to populate most of the form. I recently added a button to allow users to refresh all the lookups. Enter the trouble. Not all the data returns which I know it is present. Entire tables are blank which should not be. So I decided to jump into the javaScript (not jQuery) to try and systematically order the lookup rules. First lookup rule was for just fields, no tables/collections. I have  that working well using subscribe/unsubscribe since using the onlookupDone was looping on itself and growing exponentially. 

The next lookup rule is a table. Big complaint here about subscribe: why can't it point to the field that triggers the lookup? I had to deal with this with my first lookup, but pointing to a specific cell in a specific row in a table does not seem possible using subscribe. I'm good with just the first cell in the first row. I tried (as pointed out in the help doc) to add the index but no luck there.

example: {fieldId: 2, index: 2}

Here is what i have for non-table/collections as a reference:
 

window.changeGU = async function(){
  console.log("lookup trigger");

  var guVal = LFForm.getFieldValues({fieldId: 23}); //'2AD1BEA0-05D4-4AE6-BD38-D7102DC1B286';
  //main incident fields
  var gu1 = function(){
        //subscribe to IncidentID field before lookup trigger
  		LFForm.subscribe("lookupDone",() => { 
    		console.log("lookDone1-1");
            //only here to inform when lookup complete
            //subscribe to School field to see when lookup complete
    		LFForm.subscribe("lookupDone", () =>{   
        		console.log("lookDone1-2");
            },{fieldId: 30});
            //add value back to field to retrigger repopulate
            LFForm.setFieldValues({fieldId: 23}, guVal);
    	},{fieldId: 22});
       	LFForm.unsubscribe("lookupDone", {fieldId: 30});
        //clear value out
    	LFForm.setFieldValues({fieldId: 23}, "");
  	 }; //end gu1
     LFForm.unsubscribe("lookupDone", {fieldId: 22});
  await gu1();

    
};//end window.changeGU


I have tried adding a "[0]" after the fieldId as well. Anyone have a tip? 

 

0 0

Replies

replied on March 12, 2024 Show version history

I have not done any attempts to recreate your form and test with it, so this is just some speculation, not actual confirmation that this is the issue.

You mention using [0] didn't work, how are you using it?  Because LFForm.getFieldValues can return either a single value or an array of values, so using [0] could result in the first value of the array, or the first letter of a string of text, or fail outright, all depending on what was returned from the function.

Also, I'm not entirely certain I completely understand what you are doing with this code, but I can say that a lot of the code isn't necessarily happening in the order you are expecting, particularly the subscribe and then unsubscribe stuff.  The LFForm interface runs its functions asyncronously, so when you call one, it just kind of triggers it and then proceeds with the rest of your code.  If you are making calls to the LFForm interface and need to wait for those actions to complete before proceeding with the next part of your code, then you need to be including the await keyword.  Examples (line 4, 16, and 20 from your code): 

var guVal = await LFForm.getFieldValues({fieldId: 23});
await LFForm.setFieldValues({fieldId: 23}, guVal);
await LFForm.setFieldValues({fieldId: 23}, "");

Without that await keyword, I think you might be unsubscribing from the events before they ever can occur.  Even with the await keyword, it might still not work the way you are thinking, because it just unsubscribes immediately when the code is first running.  You might need to put the unsubscribe calls within the actual functions that run on the subscribed event, so that when the event happens, it triggers the function, does the console log that you have in the function, and then does the unsubscribe.

0 0
replied on March 12, 2024

Hi Matt! Thanks for jumping in. 

You mention using [0] didn't work, how are you using it? 
I am trying to use it in conjunction with subscribe, so not trying to get a value, but rather a cell in the fist row for subscribe to run on. 

Also, I'm not entirely certain I completely understand what you are doing with this code, but I can say that a lot of the code isn't necessarily happening in the order you are expecting, particularly the subscribe and then unsubscribe stuff.
Much of it is trial and error testing, but.... that specific order stops the console log results from compounding and increasing exponentially every time I click the button. I have to use the unsubscribe in that specific order. I could not find any documentation, but by testing determined that this is the sequence you want to use subscribe, or else it doesn't work correctly:
1. subscribe to field
2. Do the thing
3. (optional) unsubscribe
^so subscribe before the action takes place you want to subscribe to. 


If you are making calls to the LFForm interface and need to wait for those actions to complete before proceeding with the next part of your code, then you need to be including the await keyword. 
I saw how you used the async/await in another solution you shared and started there first. I read up on it. Super useful. But, it would not work. When I ran my primary function it did NOT wait for each line with a preceding 'await' to finish before moving to the next. That's why I grouped the lines together into a sub function and put the 'await' on the sub function. This appears to be behaving. 

Examples (line 4, 16, and 20 from your code): 
'Await' can only go on a primary function and not sub functions....I tried that out first :)

0 0
replied on March 12, 2024 Show version history

Not to make this experiment any more complicated than it needs to be haha, but I encourage you to do some quick reading on promises and async/await because as mentioned, they are critical to your success when ordering and executing asynchronous code in a stable pattern. For example, line 23 in the OP is awaiting a function that does not return a promise so it is not actually awaiting anything. This probably doesn't matter because nothing is really happening afterwards that relies on this function being complete.


The subscribe functions are inherently asynchronous using the callback format. The actual subscription happens synchronously, but the callback in the subscribe call is called when the  lookup being subscribed to is called. The callback itself runs synchronously and any async code inside needs to be handled similarly, without complicating this post there are ways to connect the dots here but it doesn't look like anything you are trying to do requires it. The unsubscribe call on line 30 is most likely not doing anything because it is run immediately and the lookup is not actually subscribed to when this function is called.

I dont recommend nesting subscribe calls, it seems like you think it is doing something it likely isn't. Because of the async nature of the subscribe callbacks, you are better off subscribing at the top level and handling any subscription callback asynchronicity with block scoped variables. 

Again, I'm not entirely sure what you intend the execution order of your JS to do, but hopefully this code snippet helps illustrate the point I'm trying to make. I am not sure what the Clearing the field out line is supposed to do but in this example it is the 3rd operation to be executed in the gu1 function block. All other lines execute asynchronously.  It is worthwhile to inspect the event object passed into the subscribe callback as it may contain some of the data you need.

window.changeGU = async function () {
  console.log('lookup trigger');

  var guVal = LFForm.getFieldValues({ fieldId: 23 }); //'2AD1BEA0-05D4-4AE6-BD38-D7102DC1B286';
  //main incident fields
  var gu1 = async function () {
    //subscribe to School field to see when lookup complete
    const schoolFieldLookupComplete = new Promise((resolve, reject) => {
      LFForm.subscribe(
        'lookupDone',
        () => {
          console.log('lookDone1-2');
          LFForm.unsubscribe('lookupDone', { fieldId: 30 });
          resolve();
        },
        { fieldId: 30 }
      );
    });
    //subscribe to IncidentID field before lookup trigger
    LFForm.subscribe(
      'lookupDone',
      async () => {
        console.log('lookDone1-1');
        //only here to inform when lookup complete
        // wait for school field lookup to complete
        await schoolFieldLookupComplete;
        //add value back to field to retrigger repopulate
        await LFForm.setFieldValues({ fieldId: 23 }, guVal);
      },
      { fieldId: 22 }
    );

    //clear value out
    await LFForm.setFieldValues({ fieldId: 23 }, '');
  }; //end gu1
  LFForm.unsubscribe('lookupDone', { fieldId: 22 });
  await gu1();
}; //end window.changeGU

 

3 0
replied on March 12, 2024 Show version history

I think this might be a good one to try to get some feedback from @████████- he knows the LFForm functions inside and out (literally, since he knows the actual code of those functions) - and should be able to provide some best practice advice on how to set this code up so that it functions properly.

EDIT TO ADD: He literally posted that like 2 seconds before me.  It was magnificent timing for sure.

3 0
replied on March 12, 2024 Show version history

Thanks guys for hopping on this thread. Knowing all the jQuery is not helping knowing the javaScript lol. Sorry about that. 

I will consume all this info here and will likely have a follow up question, but wanted to put this out there in case you have another method:

I am not sure what the Clearing the field out line is supposed to do but in this example it is the 3rd operation to be executed in the gu1 function block. All other lines execute asynchronously. 
This is the only way I have seen out there to retrigger a lookup. So I save the value as a variable, clear it out and then bring it back. Do you have a better method to retrigger? in jQuery it was ".change". 

Thanks!

0 0
replied on March 12, 2024

Ya the best way to retrigger a lookup would be to clear the trigger field and repopulate it with the required data something like the following. Note that setFieldValues must be awaited in order to properly let the form engine handle its state management. Perhaps looking at your original jquery code would help us determine what you need and provide the conversion to the new syntax.

const main = async () => {
  const originalValue = LFForm.getFieldValues({ fieldId: 23 });
  await LFForm.setFieldValues({ fieldId: 23 }, '');
  await LFForm.setFieldValues({ fieldId: 23 }, originalValue);
}
main();

 

3 0
replied on March 12, 2024

This is really the core of what I am trying to do here. If I am doing lookups on multiple lookup ID's, would you separate these out into different functions? Or make one big one since they are all awaiting. I have tried it both ways and can reproduce on command the lookup failing. That is what lead me down the rabbit hole of 'proving' when one lookup successfully completes THEN running the next. 

0 0
replied on March 12, 2024 Show version history

ah, ok I think I'm seeing the root of your issue. Sounds like you need to retrigger some lookups that may or may not be cascading. I'm wondering if you need to wait, I think you have two options to test

  1. reset all trigger fields with correct values and hope they dont get overwritten by cascading lookup rules. You can give this code a try, update the array to have any of the field ids that you know trigger the lookups in question
    const main = async () => {
      const triggerFieldIds = []; // Add field IDs here e.g., 22, 23, 30
      const resetLookupRules = async () => {
        const originalValues = triggerFieldIds.map((fieldId) =>
          LFForm.getFieldValues({ fieldId })
        );
        const resetValuePromise = triggerFieldIds.map((fieldId, index) =>
          LFForm.setFieldValues({ fieldId }, '')
        );
        const repopulateValuePromise = triggerFieldIds.map((fieldId, index) =>
          LFForm.setFieldValues({ fieldId }, originalValues[index])
        );
        await Promise.all(resetValuePromise);
        await Promise.all(repopulateValuePromise);
      };
      await resetLookupRules();
    };
    void main();
    
  2. You probably have cascading lookup rules that will overwrite any changes, they should use the current value as the default if it exists, but it also may not especially with tables and row counts etc. If you can give me a list of how many lookup rules you have in the order they cascade as well as each lookup rules trigger field ids I can probably code something that works out for you. Something in the following format would be nice (can use bullets if its easier to reason out)
    [
      {
        "1": {
          "triggerFieldIds": [
            23
          ],
          "cascadeRule": {
            "2": {
              "triggerFieldIds": [
                30
              ],
              "cascadeRule": null
            }
          }
        }
      }
    ]

     

2 0
replied on March 12, 2024 Show version history

Thanks so much! These are brilliant. I wanted to tell you about one more layer which could drive which direction to take. 

In troubleshooting, I duplicated the field that triggers lookups. So, it's 6 fields with identical data in them. So, would it be better to isolate each lookup to a separate field (even thought they are identical), or point all lookups to the same field? I just want to go down the path that makes it the most reliable.

The problem is not that the same fields are overlapping in any way. In other words each lookup is an isolated data set getting returned on an isolated trigger field id. They just don't show up on the form for some reason :)

Question about #1 above: Don't we need to set the value to "" and then setFieldValues after it clears the values out?

And on #2, here are the values you asked for:

[
  {
    "1": {
      "triggerFieldIds": [
        23
      ],
      "cascadeRule": {
        "2": {
          "triggerFieldIds": [
            118
          ],
	  "cascadeRule": {
            "3": {
              "triggerFieldIds": [
            	119
              ],
	      "cascadeRule": {
            	"4": {
              	  "triggerFieldIds": [
            	    120
              	  ],
		  "cascadeRule": {
            	    "5": {
              	      "triggerFieldIds": [
            	        121
              	      ],
		      "cascadeRule": {
            		"6": {
              	  	  "triggerFieldIds": [
            	    	    122
              	  	  ],					
              	          "cascadeRule": null
	        }
	      } 	
	    }
	  }	
        }
      }
    }
  }
]

^sorry that didn't copy in from notepad very clean...

So in theory, all the above lookups could point to one trigger id. Let me know if that makes a difference with reliability. 

Thanks again, seriously.

0 0
replied on March 14, 2024

Ya you are right, I forgot to reset the field with "" before setting the original value. I updated the OP and attached the new code. I'm trying to get a quick reproduction in a form to test option 2's code. While I do that were you able to attempt option 1?
 

const main = async () => {
  const triggerFieldIds = []; // Add field IDs here e.g., 22, 23, 30
  const resetLookupRules = async () => {
    const originalValues = triggerFieldIds.map((fieldId) =>
      LFForm.getFieldValues({ fieldId })
    );
    const resetValuePromise = triggerFieldIds.map((fieldId, index) =>
      LFForm.setFieldValues({ fieldId }, '')
    );
    const repopulateValuePromise = triggerFieldIds.map((fieldId, index) =>
      LFForm.setFieldValues({ fieldId }, originalValues[index])
    );
    await Promise.all(resetValuePromise);
    await Promise.all(repopulateValuePromise);
  };
  await resetLookupRules();
};
void main();

 

0 0
replied on March 14, 2024

I got sideswiped by fires to put out yesterday ha ha. I hope to test your code out here in a few hours. Thanks again!

0 0
replied on March 14, 2024

@████████, ok I tested it out. Same problem where the lookups are getting tangled up and not consistently returning all the data. One click it returns everything, another it will omit one table, another click it omits a different table, etc..
 

0 0
replied on March 18, 2024

Sorry for the delay, have been busy preparing for empower. Noticed your lookup chain is simpler than that JSON required, so I wrote this simpler version. The console.logs should happen sequentially if they dont let me know (cannot test)

 

const main = async () => {
  const triggerFieldIds = [23, 118, 119, 120, 121, 122];
  const lookupIds = [1, 2, 3, 4, 5, 6];
  const originalValues = triggerFieldIds.map((fieldId) =>
    LFForm.getFieldValues({ fieldId })
  );
  await Promise.all(
    triggerFieldIds.map((fieldId) =>
      LFForm.setFieldValues({ fieldId, value: '' })
    )
  );
  console.log(`Values of ${JSON.stringify(triggerFieldIds)} set to ''`);
  for (let i = 0; i < lookupIds.length; i++) {
    const lookupId = lookupIds[i];
    const fieldId = triggerFieldIds[i];
    const value = originalValues[i];
    const handlerName = `lookup ${lookupId} Done`;
    const lookupPromise = new Promise((resolve) => {
      LFForm.onLookupDone(
        () => {
          LFForm.unsubscribe('lookupDone', handlerName);
          return resolve();
        },
        { lookupId, handlerName }
      );
    });
    console.log(`Setting ${fieldId} to ${value}`);
    await LFForm.setFieldValues({ fieldId, value });
    console.log(`Waiting for lookup ${lookupId} to be done`);
    await lookupPromise;
  }
};
void main();

 

1 0
replied on March 19, 2024

Actually the logic for that code may be incorrect, definitely try it out. Otherwise this might solve the problem. I think you would need to wait for the next lookup to complete before setting the field. Really hard to test without the form haha. If this is still an issue, I can email you and get the actual process in question and work through it.

const main = async () => {
  const triggerFieldIds = [23, 118, 119, 120, 121, 122];
  const lookupIds = [1, 2, 3, 4, 5, 6];
  const originalValues = triggerFieldIds.map((fieldId) =>
    LFForm.getFieldValues({ fieldId })
  );
  await Promise.all(
    triggerFieldIds.map((fieldId) =>
      LFForm.setFieldValues({ fieldId, value: '' })
    )
  );
  console.log(`Values of ${JSON.stringify(triggerFieldIds)} set to ''`);
  const lookupPromiseFunc = (lookupId) =>
    new Promise((resolve) => {
      const handlerName = `lookup ${lookupId} Done`;
      LFForm.onLookupDone(
        () => {
          console.log(handlerName);
          LFForm.unsubscribe('lookupDone', handlerName);
          return resolve();
        },
        { lookupId, handlerName: handlerName }
      );
    });
  let nextLookup;
  for (let i = 0; i < lookupIds.length; i++) {
    const lookupId = lookupIds[i];
    const fieldId = triggerFieldIds[i];
    const value = originalValues[i];
    const lookupPromise = nextLookup ? nextLookup : lookupPromiseFunc(lookupId);
    const nextLookupId = lookupMap[i + 1];
    nextLookup = nextLookupId
      ? lookupPromiseFunc(nextLookupId)
      : Promise.resolve();
    if (i === 0) {
      // on the first run we want to set the field and then wait for the lookup
      console.log(`Setting ${fieldId} to ${value}`);
      await LFForm.setFieldValues({ fieldId, value });
      console.log(`Waiting for lookup ${lookupId} to be done`);
      await lookupPromise;
    } else {
      // on subsequent runs we want to wait for the previous lookup to be done before setting the field
      console.log(`Waiting for lookup ${lookupId} to be done`);
      await lookupPromise;
      console.log(`Setting ${fieldId} to ${value}`);
      await LFForm.setFieldValues({ fieldId, value });
    }
  }
  await nextLookup;
};
void main();

 

0 0
replied on March 22, 2024 Show version history

I spent an hour or so tinkering around with both of those blocks of code. Thanks again for whipping them up. I can get it this far but then it isn't putting the value back in:

So I hardcoded the fieldId and value and then it was getting stuck on 'lookupPromiseFunc' (lookupPromise in upper code). 


I made a copy of the process and deleted everything but this one form. If you could email me, I'll send that over. You've been flying blind on this thing and it's real, real close :) Thanks again!

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

Sign in to reply to this post.