Block UI and show message until sforce.connection.remoteFunction() call completes

  • 3
  • Question
  • Updated 2 years ago
  • Answered
My question is along the same lines as this post How to show wait message that is used by actions.

I am trying to accomplish the same using the Action Framework.  See screenshot below:




My issue is that the first Show Message and block UI is never shown (or is happening so fast that it is not perceptible).  The async remote call is executed and falls through to the next action w/o waiting until the promise is invoked.

What are my options?  Any guidance is appreciated.

Best,
Irvin
Photo of Irvin Waldman

Irvin Waldman, Champion

  • 9,006 Points 5k badge 2x thumb

Posted 4 years ago

  • 3
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,056 Points 20k badge 2x thumb
Irvin, what does your Snippet look like? What you'll probably need to do is to re-write your Snippet to make it return a jQuery Deferred's Promise, which you can resolve in the onSuccess of your call to sforce.connection.remoteFunction(), for instance. By having your Snippet return a jQuery Promise, the Action Framework knows that you are doing an asynchronous operation, and waits until the Promise is resolved to carry on with additional steps.

So for instance, you could rewrite your Snippet to look something like this:

// Create a jQuery Deferred
var dfd = $.Deferred();

var url = '/some/remotesite';
var sessionId = skuid.utils.userInfo.sessionId;

sforce.connection.remoteFunction({
   url : url, 
   requestHeaders: {
      "Authorization":"Bearer "+sessionId, 
      "Content-Type":"application/json", 
      "Connection":"Keep-Alive" 
   }, 
   method: "GET", 
   onSuccess : function(response) { 
      dfd.resolve(response); 
   }, 
   onFailure : function(response) { 
      dfd.reject(response);
   } 
});

// Return the Deferred's Promise
return dfd.promise();


Notice how we create a Deferred, then in the onSuccess function we call dfd.resolve() to indicate that our asynchronous operation completed successfully, and in the onFailure function we call dfd.reject() to indicate that something went wrong. The Snippet then returns the Deferred's Promise, telling Skuid to delay further Actions until the Deferred is either resolved --- in which case the Action sequence continues --- or rejected, in which case any On-Error Actions defined for this action will be executed.
Photo of Irvin Waldman

Irvin Waldman, Champion

  • 9,006 Points 5k badge 2x thumb
Hi Zach,

Simply returning the promise did the trick.  

Is this documented anywhere?  If not, this would be a great FAQ.  

As always, thanks for the guidance and timely reply.

Best,
Irvin
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,056 Points 20k badge 2x thumb
Not currently documented anywhere, we definitely need to add an article about this.
Photo of Emily Davis

Emily Davis, Employee

  • 3,502 Points 3k badge 2x thumb
Agreed! This post is super-helpful. It would be great to have some documentation on this.
Photo of Tony Sirna

Tony Sirna

  • 976 Points 500 badge 2x thumb
I am having a similar problem.

Can this Deferred Promise technique be used with sforce.apex.execute?

Or do I have to rewrite my code to use sforce.connection.remoteFunction?

Here's my code snippet:
var params = arguments[0],
	$ = skuid.$;

$.blockUI({
   message: 'Waiting...',
   timeout: 5000
});
var MtgModel = skuid.model.getModel(Items); 
var eventModel = skuid.model.getModel('Event'); 
var eventRow = eventModel.getFirstRow()
var mtgIds = '';
$.each(MtgModel.getRows(),function(i,row){
    if (myIds !== '') myIds = myIds + ',';
    
    myIds = myIds + row.Id;
    
    });
var result = sforce.apex.execute(
    'MyWebservice','MyFunction',{inputString:myIds ,eventId:eventRow.Id}
    );
    
$.unblockUI();
$.blockUI({
   message: 'Done
' + result, timeout: 5000 });
Suggestions on how to adapt this to properly block the UI are appreciated.

Thanks,
Tony
Photo of Tony Sirna

Tony Sirna

  • 976 Points 500 badge 2x thumb
It's a pretty simple Action Framework



I tried blocking and unblocking the UI in the action framework rather than in the snippet but that also did not work properly:



In this case the first Show message does not appear until after the Snippet has run. Then the first message appears, then when the second message appears immediately after.

If I comment out the "sforce.apex.execute" call it works as you would expect with the first message showing immediately upon clicking the navigation item.

I could send you a video or give you access to my org if you would like.
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,056 Points 20k badge 2x thumb
Can you try removing the Query 2 Models action and see if that makes the initial Block UI take effect?
Photo of Tony Sirna

Tony Sirna

  • 976 Points 500 badge 2x thumb
When I remove the Query 2 Models it goes:

1. click
2. wait a second or so while snippet runs
3. second block UI message appears

First block UI message never appears.
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,056 Points 20k badge 2x thumb
Wow, this is quite confusing. My recommendation at this point would be to follow the typical debugging path of removing code until you get it to work, then code back in until it stops. So I'd start by just having the code to run your first blockUI message, comment out everything after that. Once you (hopefully) get this to show as expected, start adding in additional code till the first blockUI message stops showing, then we'll at least have a better idea of which piece of code is the culprit, or if it's even related to this code at all.
Photo of Tony Sirna

Tony Sirna

  • 976 Points 500 badge 2x thumb
I finally got it to work. The secret was putting my remote call in the "onBlock" parameter to the blockUI call. That way the remote call does not happen until after the onBlock has truly blocked the UI. I have no idea why this is necessary, but it seems to work.

This was key: http://malsup.com/jquery/block/#options
var params = arguments[0],
$ = skuid.$;

$.blockUI({
   message: 'Before Remote Message',
   onBlock:function(){
       //do stuff
        var result = sforce.apex.execute(
            'MyWebservice','MyFunction',{inputString:ids}
            );
            
        skuid.model.updateData([Model1,Model2,Model3],function(){
           $.blockUI({
           message: 'After Remote Message' + result,
           timeout: 2000
        });
        });
   }
});
(Edited)
Photo of Jack Sanford

Jack Sanford, Champion

  • 8,322 Points 5k badge 2x thumb
I'm having trouble getting this snippet to work with the deferred promise. I've got the same thing set up on other snippets working fine. What's wrong here?

var params = arguments[0],
	$ = skuid.$;
var dfd = $.Deferred();

// define and save model to search through for criteria
var criteriaModel = skuid.model.getModel('Collateral');
criteriaModel.save({callback: function(result){

// if model saves, proceed
	if (result.totalsuccess) {
	    console.log(criteriaModel+'save successful');


var rows = criteriaModel.getRows();

// set the model we are querying and define all conditions
var templates = skuid.model.getModel('ConditionTemplateSearch');
var condition = templates.getConditionByName('CollateralCategories__c');
var leased = templates.getConditionByName('CollateralLeased__c');
var owned = templates.getConditionByName('CollateralOwned__c');

// set the destination model to push rows into
var allTemplates = skuid.model.getModel('ConditionTemplates');

// for each selected row of the criteria model
$.each(rows, function(){
	var currentId = this.Id;
	
	// set conditions on the template search model
	var row = this;
	var isLeased = criteriaModel.getFieldValue(this, 'Leased__c');
	console.log(isLeased);
	if (isLeased) {
	    templates.activateCondition(leased);
	    templates.deactivateCondition(owned);
	}
	else if (isLeased === false) {
	    templates.activateCondition(owned);
	    templates.deactivateCondition(leased);

	}
	var category = criteriaModel.getFieldValue(row, 'Category__c');
    templates.setCondition(condition,category);
	
	// query the model and add any rows found to the allTemplates model
	templates.updateData( function(){
	    var newRows = templates.getRows();
	    allTemplates.adoptRows (newRows);
	    }
	    );
});
 dfd.resolve();	

// if save fails, do nothing
	} else {
		console.log(result.insertResults);
		dfd.reject();
		
	}
	
}
    
});
return dfd.promise();
Photo of Jack Sanford

Jack Sanford, Champion

  • 8,322 Points 5k badge 2x thumb
I'm trying to run several snippets in a row, synchronously, and in this snippet the dfd is never recognized, the ui is never blocked, even if it Just have in my action framework, show message and block ui, this snippet, and unblock ui. 
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
I find the jquery $.when() more reliable than the .save(callback) method.

Have you tried
$.when(model.save())
    .done(function(){
       //do your work here
       dfd.resolve();
     })
     .fail(funcion(){
       dfd.reject();
     });

return dfd.promise();


Also, you might want to try
var dfd = new $.Deferred();
Photo of Jack Sanford

Jack Sanford, Champion

  • 8,322 Points 5k badge 2x thumb
adding new worked, and then it didn't. When it worked, it delayed the running of the second snippet until the first resolved, but never showed the block ui message. 

then it just stopped working. I've stopped trying to save, just trying to get the snippet to trigger a pause in the action framework until complete. can you help??

This is not pausing the sequence:

var $ = skuid.$;
var dfd = new $.Deferred();
// define and save model to search through for criteria
var criteriaModel = skuid.model.getModel('Property');

var rows = criteriaModel.getRows();

// set the model we are querying and define all conditions
var templates = skuid.model.getModel('ConditionTemplateSearch');
//var condition = templates.getConditionByName('CollateralCategories__c');
var leased = templates.getConditionByName('RealPropertyLeased__c');
var owned = templates.getConditionByName('RealProperty__c');

// set the destination model to push rows into
var allTemplates = skuid.model.getModel('ConditionTemplates');

// for each selected row of the criteria model
$.each(rows, function(){
	var currentId = this.Id;
	
	// set conditions on the template search model
	var row = this;
	var isLeased = criteriaModel.getFieldValue(this, 'Leased__c');
	console.log(isLeased);
	if (isLeased) {
	    templates.activateCondition(leased);
	    templates.deactivateCondition(owned);
	}
	else if (isLeased === false) {
	    templates.activateCondition(owned);
	    templates.deactivateCondition(leased);

	}
//	var category = criteriaModel.getFieldValue(row, 'Category__c');
//    templates.setCondition(condition,category);
	
	// query the model and add any rows found to the allTemplates model
	templates.updateData( function(){
	    var newRows = templates.getRows();
	    allTemplates.adoptRows (newRows);
	    }
	    );
}); 

 dfd.resolve();
return dfd.promise();