Extensibility for the Microsoft Dynamics NAV Tablet Client


Get in touch

With Microsoft Dynamics NAV 2015, you will be able to run your Microsoft Dynamics NAV application on tablets. The touch interface on these devices opens for a few new cool scenarios. One of the obvious usage of touch is to allow users to write directly on the tablet, for example to sign documents.

In this blog post, I will walk you through how to develop a client control add-in with JavaScript that you will be able to add to any Microsoft Dynamics NAV page. This add-in shows a box in which the user can write with a tablet pen or just with his finger. It also demonstrates how to save the image into a Microsoft Dynamics NAV table as a BLOB.

If you are not familiar with JavaScript client add-ins or if you just need a refresher, take a look at this walkthrough for your classic ‘Hello World’ example.

I am referring to this add-in as the ‘Signature Add-in’ and to the graphical data as ‘the signature’, but it could really be any type of hand-drawn graphics.

So, let’s get started.

Creating the C# class library

In Visual Studio, create a new C# class library project and add a reference to the Microsoft.Dynamics.Framework.UI.Extensibility.dll assembly. You will find this assembly in a directory similar to C:\Program Files (x86)\Microsoft Dynamics NAV\80\RoleTailored Client.

If you are already familiar with Microsoft Dynamics NAV HTML/JavaScript add-ins, you know that the purpose of this class library is merely to specify the interface and make the C/AL compiler happy. It does not contain any actual executing code.

On the server side, besides the usual AddInReady event, we will need two more events; one to write the signature data: the SaveSignature and one to read the signature from the Microsoft Dynamics NAV table to trigger an update on the page; the UpdateSignature.

On the client side, that is in the JavaScript code, we also need a method to actually draw the graphics and we also want to be able to clear the content.

To specify this API, create a single public interface looking like this:

 

namespace SignatureAddIn

{

    using Microsoft.Dynamics.Framework.UI.Extensibility;

 

    /// <summary>

    /// Interface definition for the signature add-in.

    /// </summary>

    [ControlAddInExport(“SignatureControl”)]

    public interface ISignatureAddIn

    {

        [ApplicationVisible]

        event ApplicationEventHandler AddInReady;

 

        [ApplicationVisible]

        event ApplicationEventHandler UpdateSignature;

       

        [ApplicationVisible]

        event SaveSignatureEventHandler SaveSignature;

 

        [ApplicationVisible]

        void ClearSignature();

 

        [ApplicationVisible]

        void PutSignature(string signatureData);

    }

 

    public delegate void SaveSignatureEventHandler(string signatureData);

}

Notice that the SaveSignatureEventHandler delegate takes a string parameter, which will contain the actual serialized data representing the image.

Build your assembly to make sure you did not forget a semi-colon somewhere.

Next, you will need to sign your assembly, obtain its public key token and copy it to the client add-ins folder. To do that, follow the steps as described in the walkthrough.

 

Creating the manifest file

In the manifest of an add-in, which is just regular XML file, we specify the resources that the control will use. The client side code consists of one single JavaScript file signature.js and use a single CSS file to style the HTML. We will also add a call to an initialization method in our script. The manifest is a good place to do that as the framework ensures that it gets called only when the browser is ready.

That makes our manifest look like this:

<?xml version=”1.0″ encoding=”utf-8″ ?>

<Manifest>

  <Resources>

    <Script>signature.js</Script>

    <StyleSheet>signature.css</StyleSheet>

  </Resources>

  <ScriptUrls>

  </ScriptUrls>

  <Script>

      <![CDATA[

          init();

      ]]>

  </Script>

 

  <RequestedHeight>200</RequestedHeight>

  <RequestedWidth>700</RequestedWidth>

  <VerticalStretch>false</VerticalStretch>

  <HorizontalStretch>false</HorizontalStretch>

</Manifest>

 

Creating the CSS file

No big deal here, just create a file named signature.css (the name needs to match the one in the manifest) with the following content:

 

.signatureArea {

    width: 300px;

}

 

.signatureCanvas {

    border: solid;

    border-width: 1px;

    border-color: #777777;  

    background-color: #fff;

    width: 100%;

}

 

.signatureButton {

  width: 100px;

  height: 40px;

  color: white;

  background-color: #666666;

  font-size: 12pt;

  outline: 0;

  border-color: white;

}

Feel free to play with the styles, this will only affect your add-in and will not affect the Microsoft Dynamics NAV pages whatsoever.

The interesting part

All of what has been described so far is boilerplate stuff, which you will have to do for any Microsoft Dynamics NAV HTML client add-in. We are now getting to the interesting piece, which is the JavaScript code.

Create a file named signature.js. Again here, the name has to match the one you declared in the manifest.

Let’s start with the implementation of the interface contract that we previously defined in the C# class library:

var signature;

 

function init() {

 

    signature = new ns.SignatureControl();

    signature.init();

    RaiseAddInReady();

}

 

 

// Event will be fired when the control add-in is ready for communication through its API.

function RaiseAddInReady() {

    Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(‘AddInReady’);

}

 

// Event raised when the update signature has been called.

function RaiseUpdateSignature() {

    Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(‘UpdateSignature’);

}

 

// Event raised when the save signature has been called.

function RaiseSaveSignature(signatureData) {

    Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(‘SaveSignature’, [signatureData]);

}

 

 

function PutSignature(signatureData) {

    signature.updateSignature(signatureData);

}

 

function ClearSignature() {

    signature.clearSignature();

}

 

As you can see the SignatureControl object in the ns namespace is doing all the work, so let’s take a closer look at it.

(function (ns) {

 

    ns.SignatureControl = function () {

        var canvas,

            ctx;

 

        function init() {

            createControlElements();

            wireButtonEvents();

            wireTouchEvents();

            ctx = canvas.getContext(“2d”);

        }

 

     …

Here we declare the SignatureControl class in the ns namespace and the init()method. The createControlElements() creates the various HTML elements that the control is made of.

       function createControlElements() {

            var signatureArea = document.createElement(“div”),

                canvasDiv = document.createElement(“div”),

                buttonsContainer = document.createElement(“div”),

                buttonClear = document.createElement(“button”),

                buttonAccept = document.createElement(“button”),

                buttonDraw = document.createElement(“button”);

 

            canvas = document.createElement(“canvas”),

            canvas.id = “signatureCanvas”;

            canvas.clientWidth = “100%”;

            canvas.clientHeight = “100%”;

            canvas.className = “signatureCanvas”;

 

            buttonClear.id = “btnClear”;

            buttonClear.textContent = “Clear”;

            buttonClear.className = “signatureButton”;

 

            buttonAccept.id = “btnAccept”;

            buttonAccept.textContent = “Accept”;

            buttonAccept.className = “signatureButton”;

 

            buttonDraw.id = “btnDraw”;

            buttonDraw.textContent = “Draw”;

            buttonDraw.className = “signatureButton”;

 

            canvasDiv.appendChild(canvas);

            buttonsContainer.appendChild(buttonDraw);

            buttonsContainer.appendChild(buttonAccept);

            buttonsContainer.appendChild(buttonClear);

 

            signatureArea.className = “signatureArea”;

            signatureArea.appendChild(canvasDiv);

            signatureArea.appendChild(buttonsContainer);

 

            document.getElementById(“controlAddIn”).appendChild(signatureArea);

        }

Besides plain old divs and buttons, the canvas is where we will actually be able to draw. Canvas has been supported in most browsers for a while and you can read more about it here.

The control has three buttons. One to accept the signature, which will save it to the database, one to clear the field and one to redraw the signature from the database, mostly for test purposes, as you would probably not need it in most real-life scenarios. Let’s wire these buttons so do something useful:

function wireButtonEvents() {

    var btnClear = document.getElementById(“btnClear”),

        btnAccept = document.getElementById(“btnAccept”),

        btnDraw = document.getElementById(“btnDraw”);

 

    btnClear.addEventListener(“click”, function () {

        ctx.clearRect(0, 0, canvas.width, canvas.height);

    }, false);

 

    btnAccept.addEventListener(“click”, function () {

        var signatureImage = getSignatureImage();

        ctx.clearRect(0, 0, canvas.width, canvas.height);

        RaiseSaveSignature(signatureImage);

    }, false);

 

    btnDraw.addEventListener(“click”, function () {

        RaiseUpdateSignature();

    }, false);

}

Notice that we use the drawing context ctx, that we obtained during initialization to clear the content of the canvas. We will see what the getSignatureImage() exactly does to obtain the data in a sec but before that let’s wire the touch events.

The touch events

In order to be able draw, we want to react to touch events. In this example, we also hook up mouse events, which is convenient if you want to test your add-in on a non-touch device with an old-fashioned mouse.

function wireTouchEvents() {

    canvas.addEventListener(“mousedown”, pointerDown, false);

    canvas.addEventListener(“touchstart”, pointerDown, false);

    canvas.addEventListener(“mouseup”, pointerUp, false);

    canvas.addEventListener(“touchend”, pointerUp, false);

}

As you can see, touchstart is the equivalent of a mousedown, while a touchend is the counterpart of a mouseup.

Once we have detected a touchstart, the trick is to start listening to touchmove and draw in the canvas to the current position of the ‘touching’. Once we get a touchend, we will then stop the listening and the drawing:

function pointerDown(evt) {

    ctx.beginPath();

    ctx.moveTo(evt.offsetX, evt.offsetY);

    canvas.addEventListener(“mousemove”, paint, false);

    canvas.addEventListener(“touchmove”, paint, false);

}

 

function pointerUp(evt) {

    canvas.removeEventListener(“mousemove”, paint);

    canvas.removeEventListener(“touchmove”, paint);

    paint(evt);

}

 

function paint(evt) {

    ctx.lineTo(evt.offsetX, evt.offsetY);

    ctx.stroke();

}

Canvas image data

We want to be able to serialize and de-serialize the image data from the canvas, so we can send it back and forth to the server in a string. The HTML canvas has built-in functionalities to do that through the context:

function updateSignature(signatureData) {

    var img = new Image();

    img.src = signatureData;

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    ctx.drawImage(img, 0, 0);

}

 

function getSignatureImage() {

    return canvas.toDataURL();

}

 

function clearSignature() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);

}

 

return {

    init: init,

    updateSignature : updateSignature,

    getSignatureImage: getSignatureImage,

    clearSignature: clearSignature

};

 

The toDataURL() method converts the image into a (rather long) URL encoded string containing all the pixels. To convert it back, we only need to create an image and set its src property to this URL encoded string and pass this image to the method drawImage on the canvas context. This is pretty convenient as it allows us to use a simple string rather than more complex data structure such as arrays.

We are now done with the JavaScript part and the entire file looks like this:

var signature;

 

function init() {

    signature = new ns.SignatureControl();

    signature.init();

    RaiseAddInReady();

}

 

// Event will be fired when the control add-in is ready for communication through its API.

function RaiseAddInReady() {

    Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(‘AddInReady’);

}

 

// Event raised when the update signature has been called.

function RaiseUpdateSignature() {

    Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(‘UpdateSignature’);

}

 

// Event raised when the save signature has been called.

function RaiseSaveSignature(signatureData) {

    Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(‘SaveSignature’, [signatureData]);

}

 

 

function PutSignature(signatureData) {

    signature.updateSignature(signatureData);

}

 

function ClearSignature() {

    signature.clearSignature();

}

 

(function (ns) {

 

    ns.SignatureControl = function () {

        var canvas,

            ctx;

 

        function init() {

            createControlElements();

            wireButtonEvents();

            wireTouchEvents();

            ctx = canvas.getContext(“2d”);

        }

 

        function createControlElements() {

            var signatureArea = document.createElement(“div”),

                canvasDiv = document.createElement(“div”),

                buttonsContainer = document.createElement(“div”),

                buttonClear = document.createElement(“button”),

                buttonAccept = document.createElement(“button”),

                buttonDraw = document.createElement(“button”);

 

            canvas = document.createElement(“canvas”),

            canvas.id = “signatureCanvas”;

            canvas.clientWidth = “100%”;

            canvas.clientHeight = “100%”;

            canvas.className = “signatureCanvas”;

 

            buttonClear.id = “btnClear”;

            buttonClear.textContent = “Clear”;

            buttonClear.className = “signatureButton”;

 

            buttonAccept.id = “btnAccept”;

            buttonAccept.textContent = “Accept”;

            buttonAccept.className = “signatureButton”;

 

            buttonDraw.id = “btnDraw”;

            buttonDraw.textContent = “Draw”;

            buttonDraw.className = “signatureButton”;

 

            canvasDiv.appendChild(canvas);

            buttonsContainer.appendChild(buttonDraw);

            buttonsContainer.appendChild(buttonAccept);

            buttonsContainer.appendChild(buttonClear);

 

            signatureArea.className = “signatureArea”;

            signatureArea.appendChild(canvasDiv);

            signatureArea.appendChild(buttonsContainer);

 

            document.getElementById(“controlAddIn”).appendChild(signatureArea);

        }

 

        function wireTouchEvents() {

            canvas.addEventListener(“mousedown”, pointerDown, false);

            canvas.addEventListener(“touchstart”, pointerDown, false);

            canvas.addEventListener(“mouseup”, pointerUp, false);

            canvas.addEventListener(“touchend”, pointerUp, false);

        }

 

 

        function pointerDown(evt) {

            ctx.beginPath();

            ctx.moveTo(evt.offsetX, evt.offsetY);

            canvas.addEventListener(“mousemove”, paint, false);

            canvas.addEventListener(“touchmove”, paint, false);

        }

 

        function pointerUp(evt) {

            canvas.removeEventListener(“mousemove”, paint);

            canvas.removeEventListener(“touchmove”, paint);

            paint(evt);

        }

 

        function paint(evt) {

            ctx.lineTo(evt.offsetX, evt.offsetY);

            ctx.stroke();

        }

 

        function wireButtonEvents() {

            var btnClear = document.getElementById(“btnClear”),

                btnAccept = document.getElementById(“btnAccept”),

                btnDraw = document.getElementById(“btnDraw”);

 

            btnClear.addEventListener(“click”, function () {

                ctx.clearRect(0, 0, canvas.width, canvas.height);

            }, false);

 

            btnAccept.addEventListener(“click”, function () {

                var signatureImage = getSignatureImage();

                ctx.clearRect(0, 0, canvas.width, canvas.height);

                RaiseSaveSignature(signatureImage);

            }, false);

 

            btnDraw.addEventListener(“click”, function () {

                RaiseUpdateSignature();

            }, false);

        }

 

        function updateSignature(signatureData) {

            var img = new Image();

            img.src = signatureData;

            ctx.clearRect(0, 0, canvas.width, canvas.height);

            ctx.drawImage(img, 0, 0);

        }

 

        function getSignatureImage() {

            return canvas.toDataURL();

        }

 

        function clearSignature() {

            ctx.clearRect(0, 0, canvas.width, canvas.height);

        }

 

        return {

            init: init,

            updateSignature : updateSignature,

            getSignatureImage: getSignatureImage,

            clearSignature: clearSignature

        };

    };

})(this.ns = this.ns || {});

Packaging your add-in

Now that we have all the parts of the component, we need to zip it together and import it in Microsoft Dynamics NAV. This is again as you would do for any other add-in.

Create a zip file with the following structure:

 

Put the manifest at the root, the JavaScript file in the script folder and the CSS file in the Stylesheet folder.

Open any of the Microsoft Dynamics NAV clients (Windows, Web or Tablet) and go to the Control Add-ins page. Create a new entry named SignatureControl and enter the public key token that you saved earlier. Import the zip file.

 

The C/SIDE side of things

Now that our add-in is sitting comfortably within the confines of the Microsoft Dynamics NAV database, we need to add it to page. But before that, we want a place to save the signature image data. In this fabricated example, I will add the signature to the Sales Invoice card page from the Mini app (1304) which is based on the Sales Header table.

  1. In Object Designer, open the Sales Header table and add BLOB field called ‘SignatureImage’.
  2. Add the actual control page by opening page 1304 and add the control into a separate group.

  3.  

By now you should be able to fire up this page and see how our control looks like. To do that open the client of your choice in the mini app. Navigate to the Sales Invoices and open the Sales Invoice card page.

You should see the signature control. Try to draw in with the mouse or with your finger if you are on a touch enabled device.

Even the clear button works already and allows you to delete your doodles.

The last part that we are missing is to save and retrieve the pixels to the Microsoft Dynamics NAV database. To do that we need to write a bit of C/AL code.

The C/AL code

If you recall how we defined the add-in interface, we have three triggers to take care of: AddInReady, UpdateSignature and SignatureSaved.

Nothing surprising here. The really interesting methods are SaveSignature and GetDataUriFromImage.

This is where the conversion from between the URL encoded image string and a Microsoft Dynamics NAV BLOB occurs.

The most convenient way to do this is to use the power of .NET for regular expressions matching and memory streams.

So, let’s create a SaveSignature method and add the following .NET type variables to the locals:

The URL encoded representation of the image contains some goo around the actual pixel information. With .NET regular expressions, we strip the header by matching it and preserving the rest.

What is left is a base 64 encoded string, which we can convert to a byte array using the .net Convert utility class. We then pass it to the memory stream and save it to the Microsoft Dynamics NAV table as a BLOB.

Obtaining the encoded URI is obviously the reverse operation. This is somewhat simpler; after reading the BLOB, we just need to re-add the header.

Finally, we want to update the drawing, when we navigate the records:

That’s it!

Now you should be able to save the graphics and when you close and re-open the page or navigate through the Sales Invoices, the picture gets updated accordingly.

Even though the most obvious usage scenarios are on the tablet, this add-in works on all three clients (Windows, Web and Tablet). 

NOTE: To copy the code samples, see Extensibility for the Microsoft Dynamics NAV Tablet Client on MSDN.

Comments (19)

  1. Pallea says:

    Thankyou so much – It's an awesome example!

    /Palle

  2. Kasper says:

    I don't get the new methods after using the guide. I am using the singnature.zip file downloaded via the examples.

    Is it because I need to re-sign the assembly and use another Public key or?

  3. Vincent Nicolas says:

    @Kasper

    Have you copied the C# assembly to C:Program Files (x86)Microsoft Dynamics NAV80RoleTailored ClientAdd-ins (or similar)?

    This is necessary for the NAV Development environement to pick up those methods.

  4. Wackyice says:

    I have tried it and tested it in RTC and Webclient is ok but in tablet app in ipad is not working I cannot sign the box it's like it is not getting focus on ipad. Any idea? Thank you.

  5. Gilian says:

    I also have it running in the RTC and webclient, but no luck on the tablet client on an ipad. Tablet client on Windows works perfect.

  6. Vincent Nicolas MSFT says:

    There is definitly an issue on the iPad when running in the app. I have a fix for it which I will publish soon.

  7. Vincent Nicolas MSFT says:

    I got a some reports of the sample not working properly with touch on Windows.

    There is a bit of CSS and event trickery required to make it work on all platforms with both touch and mouse.

    I have updated the sample and the post on MSDN with these fixes here: go.microsoft.com/fwlink.

    To make a long story short, you need to add a touch-action:none CSS directive on the canvas to prevent IE to translate the touch events to a scoll event for the whole page. Additionaly, if you want both touch and click to work in IE, you need the best is to use 'pointerUp', 'pointerDown' and 'pointerMove' JavaScript events. Those are from the Microsoft unified event model.

  8. Wackyice says:

    Hi Vincent,

    Already downloaded and tested the new jscript that you revised.

    Confirmed already working on different platforms, you have save me a lot in developing a customize ios app for doc signature for one of our client.

    Great work!

    Thank you… =)

  9. Mohana says:

    What is the Public key for thid DLL? or do we have to recreated dll?

  10. Ben Denger says:

    Hey, i already made this Addin by myself.

    there is one Problem:

    when i draw a signature on the ipad, the page scrols up or down while moving the finger on the addin.

    Is there a way to stop that?

  11. Aleksander says:

    Please can you help me,

    I have one problem with my Addin on tablet client. (It's only one button).

    When I run page in browser with simple url https://myserver/myinstance/WebClient/tablet.aspx?page=50000 everything is Ok.

    Problem is when I run this page from another page i got error "Unable to set property 'Selected' of undefined or null reference".

    Addin controls loaded normally but DN.BrowserLogicalControl.updateProperty throw to exception.

    Do you have any idea how to fix this?

    Thx an regards!

    1. Taddeo says:

      I have the same issue… did you fix it? and how???? Please help!!

  12. Kais says:

    Thank you for this example!
    I am trying to implement it on NAV 2016 and I am encountering an error, I hope you could help me solve it.
    Every time I open the page that has the signature AddIn I get this error “Method ‘Page90005.PageName58a58aUpdateSignature(number of arguments: 0), with matching argument types.’ not found.”
    The same error pops up when I click on saveSignature or updateSignature.

    I added the assembly to :\Program Files (x86)\Microsoft Dynamics NAV\90\RoleTailored Client\Add-ins\ and I don’t know what I did wrong.
    Any help is much appreciated.

    1. Kais says:

      Never mind, the issue was the field that references the Control AddIn in NAV had a space in its name and that blocked everything.

      1. YR says:

        Hello, which build of 2016 you were implementing it on? I’m putting it onto 9.00.45480 and everything runs ok but does not display nor update anything.

        1. mohana_cse06 says:

          Its not working in latest builds of NAV2016.
          did anyone find a solution?

          1. Pedgaard says:

            I can get it to show the drawing area and buttons on NAV 2017, but I get errors like Kais mentions.
            Unable to access the functions using currpage.SignatureControls.

          2. Pedgaard says:

            Can get it to display without any errors, also the events are auto created. But absolutely nothing happens if I try to edit the field, it does not load the image if already present. So i guess it’s broken for nav 2017?

          3. Pedgaard says:

            Got it to work after playing for half a day :=)
            if anyone has same issue, here is my solution http://forum.mibuso.com/discussion/67903/signature-box-nav-2015-6-2017
            (Using on NAV 2017)