locked
Using promises in Can_Execute method RRS feed

  • Question

  • Anyone else having trouble using promises in a can_execute method?  It seems like anything async is failing in can_execute.

    Friday, June 26, 2015 8:07 PM

Answers

  • @Hessc, I overcome the issue of data initialization when directly opening other screens by ensuring that the initialization is initiated from the $(document).ready() function inside default.htm before any screen is loaded. That way it is always called, not matter what screen is loaded first.

    In my case the script in default.html looks as follows (lsu.xxx simply refers to one of my utility libraries and the URL can contain a query parameter with a specific screen name as well if required):

    $(document).ready(function () {
    	// can optionally specify startup screen as URL param
    	var screen = lsu.getUrlParameter("screen");
    	msls.promiseOperation(function (operation) {
    		acmeApp.initializeApp(operation);
    	}).then(function () {
    		// choose default screen if not specified
    		if (lsu.stringNullOrEmpty(screen)) {
    			screen = "Dashboard";
    		}
    		msls._run(screen)
    		.then(function succeed() {
    			// success
    		}, function failure(error) {
    			// an error
    		});
    	});
    });

    The AcmeApplication method that is initially called look as follows (yea, I know you don't really have to use the "prototype" way of doing it but this was my first foray into custom javascript libraries):

    var AcmeApplication = (function () {
    	AcmeApplication.prototype.initializeApp = function (operation) {
    		// call webAPI and other stuff...
    		// call complete once done...
    		operation.complete();
    	}
        return AcmeApplication;
    })();
    // -- create main object to use
    var acmeApp = new AcmeApplication();

    So with the above, the AcmeApplication.initializeApp() will *always* be called and finished off before the first screen is loaded.

    Hope this gives you some further ideas.


    Regards, Xander. My Blog

    • Edited by novascape Thursday, July 2, 2015 3:07 AM
    • Marked as answer by Angie Xu Monday, July 6, 2015 5:16 AM
    Thursday, July 2, 2015 3:06 AM

All replies

  • HI Hessc,

    If you have any trouble during use can_execute method, could you post the code snippet? we will help look into it.

    With regards,

    Angie


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Monday, June 29, 2015 8:30 AM
  • Hessc,

    I tried this and it appears promises can be used.  The trick is you must declare a boolean variable outside of the promise, set the var inside the promise completed callback then return that var outside of the promise.  Like this:

    myapp.AddEditOrder.AddOrderDetail_Tap_canExecute = function (screen) {
        // Write code here.
        var result = false;
        myapp.activeDataWorkspace.ApplicationData.getOrders().then(
            function onSuccess(results) {
                result = true;
            },
            function onError(results) {
                result = false;
            }
            );
        return result;
    };

    This appears to be the case because of how msls.js wraps the usercode canExecute function in a promise and it checks to make sure it returns Boolean value.

    HTH,

    Josh


    • Edited by joshbooker Monday, June 29, 2015 2:21 PM
    Monday, June 29, 2015 2:20 PM
  • Josh,

    That makes perfect sense.  I will try it and post back.  My case will require a promise operation that gets data from web api but should (hopefully) work the same way as in your example.  Thanks again!

    Monday, June 29, 2015 2:31 PM
  • Hessc,

    Sorry I spoke too soon.  The above code worked in debug prolly 'cuz I had a breakpoint inside the success callback, but it doesn't work at runtime.  Maybe setTimeout would be a cheap way to workaround, but my code above doesn't work - sorry.

    Josh

    Monday, June 29, 2015 2:52 PM
  • Hmmm.  I wish you could just call the can_execute function as the call back. 

    something_canExecute = function(screen, myVar) {
        var result = false;
        if (myVar) {
            result = true;
        }
        return result;
    };
    
    screen.getSomething().then(function (value) {
       if (value) {
          something_canExecute(screen, value);
       }
    });

    Monday, June 29, 2015 3:27 PM
  • What happens if you take Josh's code and alter it as follows?

    myapp.AddEditOrder.AddOrderDetail_Tap_canExecute = function (screen) {
        // Write code here.
        var result = false;
        myapp.activeDataWorkspace.ApplicationData.getOrders().then(
            function onSuccess(results) {
                result = true;
                return result;
            },
            function onError(results) {
                result = false;
                return result;
            }
            );
        return result;
    };

    Tuesday, June 30, 2015 4:37 AM
  • What happens if you take Josh's code and alter it as follows?

    myapp.AddEditOrder.AddOrderDetail_Tap_canExecute = function (screen) {
        // Write code here.
        var result = false;
        myapp.activeDataWorkspace.ApplicationData.getOrders().then(
            function onSuccess(results) {
                result = true;
                return result;
            },
            function onError(results) {
                result = false;
                return result;
            }
            );
        return result;
    };

    I think you'd still have trouble outside of the debugger / any breakpoints set, because that final 'return result' line could execute before the promise resolves.
    Tuesday, June 30, 2015 6:28 AM
  • Jims correct same problem. I tried a bunch of variations including declaring result var inside promise and checking recursively but while the promise callback was hit still no luck passing result to caller. Looking in msls.js it appears the canExecute method is invoked using .call() and also .apply() so I don't really get why some of my attempts aren't working...probably a var scope thing. Another issue is msls.js assumes a true result so options are a bit limited by that. A third observation is that you can set the canExecute to Boolean instead of function but then need to find a way to set it with dataBind maybe.
    Tuesday, June 30, 2015 11:46 AM
  • It seems if you store the result in a property of screen object, it will stay in scope.  So something like this works:

    myapp.AddEditOrder.AddOrderDetail_Tap_canExecute = function (screen) {
        if (screen.AddOrderDetail_Tap_canExecute_result === undefined) {
            screen.AddOrderDetail_Tap_canExecute_result = myapp.AddEditOrder.AddOrderDetail_Tap_canExecute_compute.call(null, screen);
        } 
        return screen.AddOrderDetail_Tap_canExecute_result;
    };
    
    myapp.AddEditOrder.AddOrderDetail_Tap_canExecute_compute = function (screen) {
        // Write code here.
        screen.AddOrderDetail_Tap_canExecute_result = undefined;
      
        screen.Order.getOrderDetails().then(
                        function onSuccess(results) {
                            // only allow no more than 3 OrderDetails
                            screen.AddOrderDetail_Tap_canExecute_result = (results.array.length != 3);
                        },
                        function onError(results) {
                            screen.AddOrderDetail_Tap_canExecute_result = false;
                        }
        );
        return screen.AddOrderDetail_Tap_canExecute_result;
    };

    The result does indeed return before the promise completes; however, it seems msls.js calls canExecute so often that the result from the first call doesn't really matter - so far as I can tell.

    So as long as the promise callback sets a variable that is in scope for the next canExecute call, it will work.  I found, for a screenMethod (button) the above works quite reliably to enable\disable the button since the result is saved as a property of screen object which is passed to canExecute. 

    I did experience the wrong behavior a couple times so you may need to debug this a little - not sure how it would behave on a long running promise operation(?)

    HTH,

    Josh

    Tuesday, June 30, 2015 8:02 PM
  • Hmmm.  I wish you could just call the can_execute function as the call back. 

    something_canExecute = function(screen, myVar) {
        var result = false;
        if (myVar) {
            result = true;
        }
        return result;
    };
    
    screen.getSomething().then(function (value) {
       if (value) {
          something_canExecute(screen, value);
       }
    });

    canExecute only returns a Boolean and we cannot control when msls.js calls canExecute.  So calling the method from callback wouldn't 'do' anything. 

    However, you should be able to set the canExecute function in the callback. 

    Try something like this:

    myapp.ScreenName.Something_canExecute = function (screen) {
        return false;
    };
    
    screen.getSomething().then(function (value) {
        if (value) {
            myapp.ScreenName.Something_canExecute = function (screen) { return true; };
        } else {
            myapp.ScreenName.Something_canExecute = function (screen) { return false; };
        }
    });

    HTH,

    Josh

    Tuesday, June 30, 2015 8:12 PM
  • Interesting discussion, but just going back to basics, why do we need to return promises from the _canExecute()?  I assume this is due to the fact that certain objects are not available or initialized at the point where _canExecute() is initially called.

    My understanding of the way that _canExecute() works is that it uses standard dependency tracking so if any of the objects or properties inside the _canExecute() changes then the _canExecute() will be evaluated again.

    From that perspective I typically write the _canExecute() methods using the following convention (just pseudo code):

    myapp.ScreenName.Something_canExecute = function (screen) {
        return screen.Order && screen.Order.OrderDetails && screen.Order.OrderDetails.count > 0;
    };

    What am I missing?


    Regards, Xander. My Blog

    Wednesday, July 1, 2015 1:27 AM
  • Hi Xander,

    I think Hessc wants to use a promise to execute an async ajax call to webapi in canExecute method.

    I'm not sure if canExecute uses any dependency tracking but it sure does fire a ton of times (at least twice beforeShown and twice afterClosed for each method on every screen instance).

    Cheers,

    Josh

    Wednesday, July 1, 2015 2:47 AM
  • Hi Josh,

    In that case I would probably just create a screen variable to contain the boolean and return that boolean in the _canExecute() and activate the WebAPI call from the _created() method or elsewhere to set the boolean once the promise returns.

    Off-Topic: you know what is even a bigger issue along the lines of firing too many times is when you have filters with parameterized queries... those queries will all fire initially without parameter values, and then fire once more for each of the parameters as they get values. For queries with many parameters you may find the same query firing numerous times when opening a new screen. The only way I was able to overcome those issue was to code the backend queries to return an empty resultset when the parameter is null... but that only works for parameters that are mandatory. I think this is a bit of a flaw in the way that LS works and if the HTML client supported the concept of switching query AutoExecute off until all the parameters are available then that would have solved that problem. Unless I'm missing something really simple here...


    Regards, Xander. My Blog

    Wednesday, July 1, 2015 2:58 AM
  • Re off topic: Yeah missing optional parameters are a mess. Put one in an OR filter and ALL rows return every time does that sound like intended behavior to you? Not me. See: https://social.msdn.microsoft.com/Forums/vstudio/en-US/01bc581a-c297-4ded-9414-6d3b40c7dda9/multiparameter-query-and-related-table-how-to-accomplish?forum=lightswitch Including Connect bug closed " As "by design"
    Wednesday, July 1, 2015 3:25 AM
  • Thanks Josh and Xander.  I am going to try these suggestions shortly. 

    Josh is correct that my scenario involves an Ajax call to web api.  This is the common task of getting user info including permissions.  My helper library runs the getUser function as soon as the app loads, so for 95% of the time, the user object is populated by the time the user gets to the screen where I need to check the user's permissions in the can_execute method and everything works as intended.  The 5% scenario that is causing my problem is when the user direct links to a detail screen.  The can_execute method on the detail screen executes before the promise containing the Ajax call completes and returns false.  It doesn't call the can_execute function again once the promise is resolved.  I think the idea of assigning the result of the promise to a screen object and referencing the screen object in the can_execute, instead of my custom object, should do the trick.  I will post back with my results.  Thanks to both of you once again!


    • Edited by Hessc Wednesday, July 1, 2015 2:11 PM
    Wednesday, July 1, 2015 2:09 PM
  • @Hessc, I overcome the issue of data initialization when directly opening other screens by ensuring that the initialization is initiated from the $(document).ready() function inside default.htm before any screen is loaded. That way it is always called, not matter what screen is loaded first.

    In my case the script in default.html looks as follows (lsu.xxx simply refers to one of my utility libraries and the URL can contain a query parameter with a specific screen name as well if required):

    $(document).ready(function () {
    	// can optionally specify startup screen as URL param
    	var screen = lsu.getUrlParameter("screen");
    	msls.promiseOperation(function (operation) {
    		acmeApp.initializeApp(operation);
    	}).then(function () {
    		// choose default screen if not specified
    		if (lsu.stringNullOrEmpty(screen)) {
    			screen = "Dashboard";
    		}
    		msls._run(screen)
    		.then(function succeed() {
    			// success
    		}, function failure(error) {
    			// an error
    		});
    	});
    });

    The AcmeApplication method that is initially called look as follows (yea, I know you don't really have to use the "prototype" way of doing it but this was my first foray into custom javascript libraries):

    var AcmeApplication = (function () {
    	AcmeApplication.prototype.initializeApp = function (operation) {
    		// call webAPI and other stuff...
    		// call complete once done...
    		operation.complete();
    	}
        return AcmeApplication;
    })();
    // -- create main object to use
    var acmeApp = new AcmeApplication();

    So with the above, the AcmeApplication.initializeApp() will *always* be called and finished off before the first screen is loaded.

    Hope this gives you some further ideas.


    Regards, Xander. My Blog

    • Edited by novascape Thursday, July 2, 2015 3:07 AM
    • Marked as answer by Angie Xu Monday, July 6, 2015 5:16 AM
    Thursday, July 2, 2015 3:06 AM
  • Xander,

    Thanks for sharing that.  My initialization function works similarly, except that I am returning several helper functions that can be called elsewhere.  So I was getting the user in the screen created method instead of during the initialization, which is probably the problem.  I had a reason for doing it that way, but I see that is probably better to just get the user during init no matter what.  Also, my function is a self executing function that is in a file that is loaded as the last javascript reference in the default.htm.  The function runs no matter what screen the user opens first.  Is that any different or is there any advantage to initializing in the document ready function vs. just referencing the file and letting the function execute when it loads?  Thanks again!

    Thursday, July 2, 2015 5:15 PM
  • The advantage of doing it the way I showed above is that you can be sure that the initialization code is executed *before* the first screen's _created() method is called. Simply referencing the external file with the self executing function does not guarantee that the initialization is completed before the first screen is created. It is good practice to ensure that the initialization is guaranteed before the screen is created as different environments with different internet connection latency and so on will produce varied results when simply relying on auto executing functions.

    Regards, Xander. My Blog

    Thursday, July 2, 2015 7:57 PM