How to flush all events before running a query?

I am saving changes to components to a temp var object using onBlur.

I have a save button that then sends the temp var to the database.

However, if you change a component and then click the Save button, the query tied to the button tends to fire before the component's onBlur fires. This means the changed value does not get saved to the Temp var in time for it to be sent to the DB.

If there a way to ensure all other events and triggers have finished before continuing?

Adding a setTimeout in my js query does seem to have the right effect:

// Let other actions on this thread finish
await new Promise(resolve => setTimeout(resolve, 2000))
// Now we can upload the data to the DB
await qryUpload.trigger()

But this seems like a fragile and non-deterministic solution. Any better ones?

Hey Bradly!
What I do for these is that I make a JS query where I put the multiple queries I want executed together (as a promise all), and then I make a resource call on success in this zone of the query editor;

@jonathanbredo, Thanks, that is indeed one of my most used patterns.

But not applicable in this case unfortunately. I have onBlur handlers on a bunch of components. They all go the the same js handler which determines which value to update based on its triggeredById.

If the user changes a value and then clicks the save button, the button handler fires and then the onBlur fires resulting in the most recent change getting lost.

When the button is clicked I could check if any of the components are dirty, save to the temp var and then save to the DB, but that is a bunch of extra work that just waiting for the onBlur to fire first would avoid.

Hey @bradlymathews!

Sorry about the late reply here, this is an interesting angle on the ability to set the order of event handlers.

Would you mind sharing some of the code for how you have your blur and click handlers configured? I've tried reproducing the issue with a simple blur event handler, tempstate, and click event handler but it seems to be running in the expected order thus far:

I am firing the onBlur from multiple components in a ListView, here is one recrod:

Everyone of those has an onBlur event that calls this jsQuery which checks which component called it, verifies that it is dirty and then saves the new value to the underlying data source which is a Temp Var.

// jsSaveRTCaptionField
// No judgement on my code repetition please, still in development!
if (triggeredById === 'selRTCaptionTimeZone') {
  if (listView1.data[i].selRTCaptionTimeZone != captionJobs.value[i].time_zone) {
    captionJobs.setIn([i, 'time_zone'], listView1.data[i].selRTCaptionTimeZone)
  }  
  return
}

if (triggeredById === 'txtRTCaptionPublicJobNote') {
  if (listView1.data[i].txtRTCaptionPublicJobNote != captionJobs.value[i].note_public) {
    captionJobs.setIn([i, 'note_public'], listView1.data[i].txtRTCaptionPublicJobNote)
  }  
  return
}

if (triggeredById === 'txtRTCaptionPrivateJobNote') {
  if (listView1.data[i].txtRTCaptionPrivateJobNote != captionJobs.value[i].note_private) {
    captionJobs.setIn([i, 'note_private'], listView1.data[i].txtRTCaptionPrivateJobNote)
  }  
  return
}

if (triggeredById === 'txtRTCaptionTaskCaptionNote') {
  if (listView1.data[i].txtRTCaptionTaskCaptionNote != captionJobs.value[i].caption_task_note) {
    captionJobs.setIn([i, 'caption_task_note'], listView1.data[i].txtRTCaptionTaskCaptionNote)
  }  
  return
}

if (triggeredById === 'txtRTCaptionMonitorTaskNote') {
  if (listView1.data[i].txtRTCaptionMonitorTaskNote != captionJobs.value[i].monitor_task_note) {
    captionJobs.setIn([i, 'monitor_task_note'], listView1.data[i].txtRTCaptionMonitorTaskNote)
  }  
  return
}

if (triggeredById === 'dtRTCaptionJobDate') {
  if (listView1.data[i].dtRTCaptionJobDate != captionJobs.value[i].job_date) {
    captionJobs.setIn([i, 'job_date'], listView1.data[i].dtRTCaptionJobDate)
  }  
  return
}
//debugger
if (triggeredById === 'tmRTCaptionStartTime') {
  if (listView1.data[i].tmRTCaptionStartTime != captionJobs.value[i].start_time) {
    captionJobs.setIn([i, 'start_time'], listView1.data[i].tmRTCaptionStartTime)
  }  
  return
}

if (triggeredById === 'tmRTCaptionEndTime') {
  if (listView1.data[i].tmRTCaptionEndTime != captionJobs.value[i].end_time) {
    captionJobs.setIn([i, 'end_time'], listView1.data[i].tmRTCaptionEndTime)
  }  
  return
}

if (triggeredById === 'curRTCaptionClientPrice') {
  if (listView1.data[i].curRTCaptionClientPrice != captionJobs.value[i].client_price) {
    captionJobs.setIn([i, 'client_price'], listView1.data[i].curRTCaptionClientPrice)
  }  
  return
}

if (triggeredById === 'curRTCaptionICCost') {
  if (listView1.data[i].curRTCaptionICCost != captionJobs.value[i].ic_cost) {
    captionJobs.setIn([i, 'ic_cost'], listView1.data[i].curRTCaptionICCost)
  }  
  return
}

if (triggeredById === 'txtRTCaptionLocation') {
  if (listView1.data[i].txtRTCaptionLocation != captionJobs.value[i].location) {
    captionJobs.setIn([i, 'location'], listView1.data[i].txtRTCaptionLocation)
  }  
  return
}


if (triggeredById === 'txtRTCaptionJobName') {
  if (listView1.data[i].txtRTCaptionJobName != captionJobs.value[i].job_name) {
    captionJobs.setIn([i, 'job_name'], listView1.data[i].txtRTCaptionJobName)
  }  
  if (listView1.data[i].txtRTCaptionSTName === null || listView1.data[i].txtRTCaptionSTName === '') {
    debugger
    let v = listView1.data[i].txtRTCaptionJobName
    captionJobs.setIn([i, 'st_job_name'], v.indexOf(' ') === -1 ? v : v.substring(0, v.indexOf(' ')))
  }
    
  return
}

if (triggeredById === 'txtRTCaptionSTName') {
  if (listView1.data[i].txtRTCaptionSTName != captionJobs.value[i].st_job_name) {
    captionJobs.setIn([i, 'st_job_name'], listView1.data[i].txtRTCaptionSTName)
  }  
  return
}

When the user has edited say, the Job Name field and goes to click the Save button, I run this to do the save:

await new Promise(resolve => setTimeout(resolve, 1000))
await qryUpload.trigger()

That timeout lets the onBlur finish.

But I just had an idea. What if I await each setIn()?

As a note, this is a very large app with 150+ components. which may be contributing to delays?

:thinking: I'll see if I can repro with that. Thanks for calling out that the app is large, I can check to see if that might be impacting things.

A couple of thoughts just in case any are worth exploring:

  1. Have you tried using form components for this so that you can reference form[i].data instead of the tempstate values?
  2. What particular issues do you run into when referencing, for instance, selRTCaptionTimeZone[i].value directly?
  3. Is the save button outside of the listview?
  4. Where else is the temp state used?
  1. Hmmm.... The more I think about using forms the better I like the idea. I would still need the temp state to store the contents of the form, (or would I?), but if the form's Initial data could be tied to each temp state array item rather then each individual component's Default value it would make adding/removing components a bit easier. And maybe I could replace the temp states array item with the forms .data property it would make that script a fair bit shorter. Or maybe I don't need the temp state, just need a way to instantiate the forms with Initial data. I am rubber ducking via my keyboard, aka, rambling. But you got me thinking!

  2. I did not try that here, A while back I noticed that I could get to the Listview's children through it's .data property and was playing around with that and it worked.

  3. Yes. FYI, the Save sends the JSON off to a SQL Server stored procedure where multiple records in multiple tables are created and/or updated from its content.

  4. It is populated through another process on the page, but otherwise only used as the data source for the Listview.