Azure/Cloud Forms System for AX

In my previous short post, I created a simple doodle control for AX, and mentioned that the actual objective was to build a signature pad using the Extensible Control Framework. In this post I’ll extend that a little by adding the ability to auto-complete a PDF form using data from AX and then give it to the user to sign using their pointing device of choice.

For the AX developers out there this post will unfortunately not include source code as I am working on a commercial product, but hopefully the ideas I share here will help you in your own projects at some stage.

I am not very familiar with AX Document Management, but what I have seen in the latest version of AX 7 RTW is that attachments are supported, but what I am really looking for is something that also gives me the following features:

  • PDF support with fillable fields
  • Signatures, without requiring digital certificates
  • Offsite-storage for once the volume of documents reaches critical mass
  • Archiving offsite for say, 7 years (payslips, tax certificates)
  • Forms distribution to users (mail merge)
  • Access to archived forms OUTSIDE of AX (like payslips, when an employee has been terminated)
  • Annotations
  • Library with form templates

In a previous project I built a forms management system supporting all of the above items, and as I am learning X++ I thought it would be a good learning project to convert as much of that as possible into AX. The platform, called FormsBox, is still in BETA so please don’t sign up. However, one item I did add early on was a REST API to allow retrieval, editing and downloading of forms via that API. So I will utilize the same platform now to try and achieve a similar experience in AX. FormsBox runs in Azure and was built using ASP.NET (C#).

Let’s have a quick tour of FormsBox (www.formsbox.com)

 

FBA

First off, when signing in, users can see their home folder with subfolders and all their uploaded forms. Forms can be uploaded as images, Word, Excel or PDF. They are all automatically converted to PDF during the upload process. You can also email a form to the site, and a backend service will collect the email, extract any attachment, and stick it into your home folder automatically. Handy for storing email attachments. Forms are stored as Azure Blobs and encrypted.

 

FBBFormsBox also features a public library of forms (currently just under 500) which any user can copy into their home folder for use, completion and sharing/distribution. Looking at the Library view, notice the GUID which lists the form code. We’ll need this code for the API, and we will use code F03B7658-9CE1-4921-A5D7-FEFF02FFC733 which is the code for the Check, Stop Payment form.

 

FBC

We can preview the form in FormsBox, and note it contains fillable fields. Each field has a field name, in this case FirstNameLastName, AccountNumber, Payee, Amount etc.

FBD

One item of concern in AX is PDF support, without requiring the addition of anything additional to core AX. Like it or not, PDF is still the de-facto choice for creating and completing forms. So we have a couple of issues to deal with here:

  • Display a PDF in AX, without any plugins
  • Complete that PDF using data from AX
  • Allow a user to sign the PDF, correct/undo a bad signature
  • Save, Download or return that form (I’m putting that into the to-do list for another post)

We’ve already got a basic signature pad going, as per my previous blog post. I’ve added some extra JavaScript to allow the resetting of the canvas object to undo a bad signature.

So the bigger challenge now is to display a PDF in AX.

There are a couple of ways we can do that. First, we can call an external service for rendering the PDF, and host that in an IFRAME. Unfortunately, that will display a pop-up for the user asking whether they want to navigate away from the current page. Bad choice. Also, unless that site uses HTTPS, a security warning will also be displayed. Not the type of quality we want from what will end up being a commercial product costing money.

As we only want to display the PDF, what we can do instead is convert the PDF to an image, and send the image back to AX to embed into an IMG tag inside of our Extensible Control HTML.

Thus, what we want to achieve is to call the FormsBox API, pass our AppKey, AX data fields, and the form code, and have it complete the form for us, and send it back as a PNG image so the user can sign on it within AX. This is depicted graphically below.

Drawing1

For that I used a combination of tools including GhostScript and iTextSharp for conversion and data-stamping into the fillable fields. I have to also resize the end result to ensure it properly fits in the AX form. There are a number of improvements I need to make, but so far so good.

Let’s take a look at the PDF being display in AX, as shown below. As we can see we called the FormsBox API and passed the data from AX to the API, which returned the completed form as a resized PNG image which we embed into an IMG tag.

 

FB0

The next screen shows the signing process in action using our signature pad. We’ve also added a “New” button, to allow the user to reset the image if they botched their signature. As it takes a few seconds to load the image via the API, we also added a “Loading…” text into the canvas, which is erased once the image arrives back.

 

FB1

Finally, we can zoom in a bit to view the completed, signed document.

 

FB2

Still some things to improve, but we have a workable solution using the kitchen sink of toolsets (Azure, ASP.NET, HTML, PDF, CSS, JavaScript, C#, X++, AX 7).

With that, we have the beginnings of a viable commercial product or at the very least a usable plugin for other AX modules.

 

Advertisements

Doodle/Notes Control for AX7

As I’m in the process of learning what can and can’t be done in AX7, I’ve started porting a cloud-based forms system over to AX. One item I require is the ability to allow users to digitally sign documents using a pen or mouse, which is then “stamped” into the PDF document they are signing.

This requires some interesting JavaScript usage within an AX7 Extensible Control and I’ve learned quite a lot about how the Extensible Control Framework hangs together while doing so. In building the signature control I thought I’d give something back to the AX community and created a control that will no doubt be used extensively during those exciting project implementation meetings – a doodle control. Feel free to embed this into your own module or hide it as an Easter Egg if you want.

The basic container form code is shown below, and does nothing but instantiate our control:

[Form]
public class DoodleForm extends FormRun
{
    DoodleControl doodle;

    public void init()
    {
        super();
        doodle = this.design().addControlEx(classStr(DoodleControl),"DoodleControl1");
    }
}

 

The control itself is very simple as well, as shown below.

[FormControlAttribute("DoodleControl","resources/html/DoodleControl", classstr(FormBuildControl))]
class DoodleControl extends FormTemplateControl
{
    public void new(FormBuildControl _build, FormRun _formRun)
    {
        super(_build,_formRun);
        this.setTemplateId("DoodleControl");
        this.setResourceBundleName("resources/html/DoodleControl");
    }
}

 

Finally, the HTML code which is stored as a resource contains the JavaScript where all the fun happens. As AX7 uses HTML5 we can use the canvas object for our drawing. I’ve embedded the CSS styling into the button, and instead of sticking the JavaScript into another resource file just embedded it at the end of the page WHICH WORKS FINE. The only trick I learned is in order for it all to work, all the HTML and JavaScript needs to be wrapped in the main control DIV element.

<div id="DoodleControl">
    <div>
        <canvas id="canvas" width="600" height="400" style="border: 1px solid #ccc;"></canvas>
    </div>
    <p>
        <button id="clearDoodle" type="button" style="background-color:#7892c2;
	border:1px solid #4e6096;
	display:inline-block;
	cursor:pointer;
	color:#ffffff;
	font-family:Arial;
	font-size:19px;
	padding:4px 15px;
	text-decoration:none;
	text-shadow:0px 1px 0px #283966;">New</button>&nbsp;
    </p>

    <script type="text/javascript">
		$(document).ready(function () {

    		var canvasWidth = 600;                           
    		var canvasHeight = 400;                           
    		var canvas = document.getElementById('canvas');
    		var context = canvas.getContext("2d");           
    		var clickX = new Array();
    		var clickY = new Array();
    		var clickDrag = new Array();
    		var paint;

    		canvas.addEventListener("mousedown", mouseDown, false);
            	canvas.addEventListener("mousemove", mouseXY, false);
            	document.body.addEventListener("mouseup", mouseUp, false);
            	canvas.addEventListener("touchstart", mouseDown, false);
            	canvas.addEventListener("touchmove", mouseXY, true);
            	canvas.addEventListener("touchend", mouseUp, false);
            	document.body.addEventListener("touchcancel", mouseUp, false);

    		function draw() {
        		context.clearRect(0, 0, canvas.width, canvas.height); 
        		context.strokeStyle = "#000000";  
        		context.lineJoin = "miter";       
        		context.lineWidth = 1;            

        		for (var i = 0; i < clickX.length; i++) {
            		context.beginPath();                               
            		if (clickDrag[i] && i) {
                		context.moveTo(clickX[i - 1], clickY[i - 1]); 
            		} else {
                		context.moveTo(clickX[i] - 1, clickY[i]);     
            		}
            		context.lineTo(clickX[i], clickY[i]);              
            		context.stroke();                                  
            		context.closePath();                               
        		}
    		}

    		$('#clearDoodle').click(
    			function clearSig() {
        			clickX = new Array();
        			clickY = new Array();
        			clickDrag = new Array();
        			context.clearRect(0, 0, canvas.width, canvas.height);
		});

		function addClick(x, y, dragging) {
        		clickX.push(x);
        		clickY.push(y);
        		clickDrag.push(dragging);
    		}

		function mouseXY(e) {
		   if (paint) {
            		addClick(e.pageX - this.offsetLeft, e.pageY - (this.offsetTop * 3), true);
            		draw();
        	   }
		}

		function mouseUp() {
			paint = false;
		}

		function mouseDown(e)
		{
        		paint = true;
        		addClick(e.pageX - this.offsetLeft, e.pageY - (this.offsetTop * 3));
        		draw();
		}
		});
    </script>
</div>

 

Finally, as shown in the screen below, we can doodle away and click the New button to clear the canvas.

Doodle

Enjoy!