Now that you are done through this mouthful of the title,
you may recognize that it’s the method you invoke when you want to run a control
add-in trigger in AL from JavaScript.
There is nothing new about this method itself, it’s just
that most people aren’t aware of full power of this method, and they are using
this method in a very inefficient way. In this blog, I want to show you what
this method can do for you that you may have not been aware of. And I hope I
get you to change your habits.
The syntax is this:
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(name, args[, skipIfBusy[, callback]]);
It takes at minimum two arguments, and this is what most
people invoke it. Obviously, name is the name of the event as declared
in the controladdin object that you
also must implement as a trigger inside the usercontrol
in the page that uses it. Also, args is the array of arguments you want
to pass to AL.
Imagine this is how you declare your event:
event SayHello(FirstName: Text; LastName: Text);
Then from JavaScript you would invoke it like this:
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod("SayHello", ["John", "Doe"]);
So far, very simple, obvious, and easy. But here we get to the biggest mistake most people do when invoking the Microsoft.Dynamics.NAV.InvokeExtensibilityMethod method. They invoke it directly. The reason why it’s a mistake is because most often you’ll want to synchronize the invocations between JavaScript and AL as much as you can, and this method – as anything in JavaScript that invokes stuff outside JavaScript – is asynchronous. If you have this:
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod("SayHello", ["John", "Doe"]);
alert("You see me immediately");
… you will see the “You see me immediately” message before
AL even gets a chance to start executing.
Yes, you can take advantage of more arguments here to make
it behave differently. So, let’s take a look at the remaining two arguments.
The skipIfBusy argument tells the control add-in
JavaScript runtime to not even invoke your event in AL if the NST session is
currently busy doing something else. If you omit it, the skipIfBusy parameter
defaults to false so it means your AL event will be raised, and if AL is
already busy, it will be raised as soon as AL stops being busy.
The callback argument, though, is where cool stuff
happens. This arguments is of function type (you can imply as much from its
name), and it is invoked as soon as AL has finished doing whatever you just
made it busy with. So, if you want some JavaScript code to happen after the SayHello
event completes its work, you can do it like this:
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(
"SayHello",
["John", "Doe"],
false,
function() {
alert("You see me after SayHello finished running in AL");
});
However, that’s not really the most beautiful way of writing
JavaScript. That’s how you would write it in late 1990’s, we are now nearly a
quarter century ahead. Let’s write some at least tiny little bit less outdated
JavaScript, and let’s introduce Promises.
Promises are objects which allow you to synchronize asynchronous calls in a
syntactically less offensive way than callbacks.
Let’s take a look at why promises are superior to callbacks.
Imagine you want to structure your code nicely, and you don’t
want to just call your extensibility methods out of a blue, so you decide to
wrap your call into a function, like this:
function sayHello(first, last, callback) {
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(
"SayHello",
[first, last],
false,
callback);
}
// Invoking the function
sayHello("John", "Doe", function() {
alert("You see me after SayHello finished running in AL");
});
The syntax of the sayHello invocation is not really that easy to follow. However, we could translate the entire example to promises:
function sayHello(first, last) {
return new Promise(resolve =>
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(
"SayHello",
[first, last],
false,
resolve));
}
// Invoking the function
sayHello("John", "Doe")
.then(() => alert("You see me after SayHello finished running in AL"));
… and suddenly it becomes more readable. (Okay, a part of it
being more readable is that I used arrow
functions, but that’s because they are both supported at the same language level
of JavaScript, and if your browser supports Promises, it will support arrow
functions too, and if it doesn’t support Promises, it won’t support arrow
functions either).
Apart from this readability benefit, there is another, far
bigger benefit of wrapping your
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod invocations into promise-returning
wrapper functions: it’s the fact that all promises are awaitable in
JavaScript.
In newer versions of JavaScript (EcmaScript 2017 and newer)
there is a concept of async
functions. Async functions perform some asynchronous work, and you can await
on them to make your code look and behave as if it were synchronous.
For example, if you have a function declared as this:
async function somethingAsync() {
// Do some asynchronous work
}
… then you can invoke it like this:
await somethingAsync();
alert("This won’t execute before somethingAsync completes its async work");
Cool thing about async/await is that it’s nothing more than syntactic sugar for Promises.
Every async function implicitly returns a Promise, and you can invoke it either
with await syntax, or with .then() syntax. Cosenquently, if a function
explicitly returns a Promise, you can await
on it as if it were declared as async.
In short, in our earlier example, we could easily do this:
function sayHello(first, last) {
return new Promise(resolve =>
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(
"SayHello",
[first, last],
false,
resolve));
}
// Invoking the function
await sayHello("John", "Doe");
alert("You see me after SayHello finished running in AL");
… and it would have exactly the same meaning as the earlier
example, except that this time it’s far more readable.
At this stage, our sayHello
function is a handy asynchronous wrapper around Microsoft.Dynamics.NAV.InvokeExtensibilityMethod
method invocation, but we can do better than that. Instead of having to write
wrappers for every single event declared in your controladdin
object, you could write something like this:
function getALEventHandler(eventName, skipIfBusy) {
return (…args) => new Promise(resolve =>
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(
eventName,
args,
skipIfBusy,
resolve));
}
When you have that, you can use it like this:
// Obtain a reference to an asynchronous event invocation wrapper
var sayHello = getALEventHandler("SayHello", false);
// … and then use it as an asynchronous function
await sayHello("John", "Doe");
alert("You see me after SayHello finished running in AL");
Cool, isn’t it? You now not only never have to write that
wordy Microsoft.Dynamics.NAV.InvokeExtensibilityMethod
ever again (and risk making typos), you also have it fully synchronizable using
the await syntax. But we can get even
cooler – way cooler – than that. Hold my beer.
You know already that event invocations in AL are void,
or that they cannot ever return a value. Your JavaScript cannot invoke AL and
have AL return a value to it, that’s just not how AL/JavaScript integration
works. At the heart it’s because it’s all asynchronous, but at the end of it,
it’s just because Microsoft never cared enough to make it fully synchronized through
an abstraction layer that could make it possible. Now that we’ve got it to an awaitable
stage, let’s take it to another level by allowing AL to actually return values
to your JavaScript wrappers.
Imagine that you declare this event in your controlladdin:
event GetCustomer(No: Code[10]);
You pass a customer number to it, and you want it to return a JSON object containing your customer record information by its primary key. Ideally, you’ll want to invoke it like this:
var cust = await getCustomer("10000");
Of course, that won’t work, because your GetCustomer trigger in AL – once you implement it in a page – cannot return values. You’d have to have a method declared in your controladdin object and then implement that method in the global scope in JavaScript, where you can pass the result of this operation, something you’d declare like this:
procedure GetCustomerResult(Cust: JsonObject);
However, implementing it as a separate function in your
JavaScript would require some acrobatics to allow you to retain your await getCustomer() syntax. But this is
only true if you take the traditional approach of implementing methods as
global-scope-level functions in one of your scripts. In JavaScript, you can
implement methods on the fly, so let’s do it.
Let’s start with the statement that the GetCustomerResult
function should be available in JavaScript only during the invocation of GetResult
event in AL, and invoking it outside of such invocation would be a bug, and
should not be allowed. When you do it like this, then you can write your code
in such a way that you create this function in JavaScript just before you
invoke the AL event, and you delete this function immediately when AL returns
the result, something like this:
function getALEventHandler(eventName, skipIfBusy) {
return (...args) => new Promise(resolve => {
var result;
var eventResult = `${eventName}Result`;
window[eventResult] = alresult => {
result = alresult;
delete window[eventResult];
};
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(
eventName,
args,
skipIfBusy,
() => resolve(result));
});
}
You can then do something like this:
// Obtain a reference to an asynchronous event invocation wrapper
var getCustomer = getALEventHandler("GetCustomer", false);
// … and then use it as an asynchronous function
var cust = await getCustomer("10000");
alert(<code>Your customer record is ${JSON.stringify(cust)});
How cool is this?
There is an even further level of awesomeness you can add to
your event invocations, and it has to do with the skipIfBusy argument,
but that’s a topic for a future blog post, I think you have enough to chew on
for now. And I know that at this stage, invoking Microsoft.Dynamics.NAV.InvokeExtensibilityMethod
directly, instead of through a pattern such as this, seems very stone age.