Capture signature using HTML5 canvas - Use for iPad, iPhone, Android Tablets and Phones

With the help of HTML5 canvas its very easy to draw stuff such as lines, arcs, rectangle etc. Capturing a signature on a mobile devices such as iPads, Android Tablets and smart Phones has so many uses in our day to to day business applications. It can completely automate so many business processes such as proof of delivery, any type of form application, may be in future we can sign credit card transactions that are done online.

So here is simple application with complete source that captures a signature and then lets you save it on the server. The client side programming is done with the help of HTML5 canvas, ExtJs 4 JavaScript framework. On the server side its Java Servlet. You can replace them with any technology of your linking such as plain JavaScript or jQuery and php for server programming. You can also use this as a starting point for your own paint application.


Capture signature using HTML5 canvas, ExtJs 4 and Java Servlet
Capture signature using HTML5 canvas, ExtJs 4 and Java Servlet
Capture signature using HTML5 canvas, ExtJs 4 and Java Servlet

Please Note: I used the computer mouse to draw that Text on my browser but if you use the finger on your touch screen the text will be much more smooth.

The application runs on any browser that supports the CANVAS element introduced in the HTML5. Also you can run the application on your iPad, iPhone, Android OS based smart phones and tablets. It should run on Windows and Blackberry smart phones and tablets as the application is not OS dependent. The backbone of this application is the HTML5 canvas element and the following events ...
  • Browser Mouse events
    • mousedown
      • Fires when the user depresses the mouse button
    • mousemove
      • Fires when the user moves the mouse.
    • mouseup
      • Fires when the user releases the mouse button.
  • Browser Touch events
    • touchstart 
      • Happens every time a finger is placed on the screen
    • touchmove 
      • Happens as a finger already placed on the screen is moved across the screen
    • touchend 
      • Happens every time a finger is removed from the screen

Step 1: Create the HTML file - index.html

<!DOCTYPE html>
<html>
<head>
    <title>Signature Capture Example using HTML5 canvas</title>
    <link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css">
   
    <script type="text/javascript" src="extjs/ext-all-debug.js"></script>
    <script type="text/javascript" src="app.js"></script>
   
</head>
<body>
</body>
</html>

Step 2: Create the ExtJs application JavaScript file - app.js

This has two buttons, one for saving the signature and the other one just to clear the signature in case you messed up. In addition to that we have the signature capture panel.
Ext.Loader.setConfig({ 
    enabled: true 
});

Ext.application({
   
    name: 'MyApp',
   
    appFolder: 'app',
   
    controllers: [
                  'Signature'
              ],
   
      launch: function() {
          Ext.create('Ext.container.Viewport', {
             margin: 10, 
             defaults: {
                margin: 10,
             }, 
               items: [{
                      xtype: 'label',
                      html: '<b>Capture signature using using HTML5 canvas</b>'
                  },
                  
                  {
                      xtype: 'signatureCapture',
                  },
                  {
                    xtype: 'button',
                    text: 'Save Signature',
                    id: 'save',
                    disabled: true,
                  },
                  {
                    xtype: 'button',
                    text: 'Clear Signature',
                    id: 'clear'
                }]
          });
      }
   
});

Step 3: Signature Capture Panel - SignatureCapture.js

SignatureCapture panel has one child panel that contains the canvas element that we need to draw our signature.

Ext.define('MyApp.view.SignatureCapture', {
    extend: 'Ext.Container',
    alias : 'widget.signatureCapture',
   
    layout: {
        type: 'vbox',
        align: 'stretch'
    },
    width: 600,
    height: 300,
    border: 1,
    style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
   
    items: [
        {
            xtype: 'panel',
            id: 'signature',
            html: '<canvas id="signaturePanel" width="600" height="300">no canvas support</canvas>'
        }]
  
});

Step 4: Application controller file - Signature.js

This is the heart of the application where we get a reference to our canvas element and its 2d context. It takes care of saving the signature on the server with the help of an AJAX request. The image is sent to the server as a Base64 encoded string. To learn more about HTML5 canvas click on the link below ...
HTML5 canvas tutorial for beginners - getContext() toDataURL() toBlob()
Ext.define('MyApp.controller.Signature', {
    extend : 'Ext.app.Controller',
   
    //define the views
    views : ['SignatureCapture'],
    //define refs
    refs: [{
        ref: 'mySignature', 
        selector: 'panel[id="signature"]'
    }], 
   
    init : function() {
        this.control({
           
            'signatureCapture' : {
                afterrender : this.onPanelRendered
            },
            'viewport button[id=clear]' : {
                click : this.onClearSignature   
            }
            ,
            'viewport button[id=save]' : {
                click : this.onSaveSignature   
            }
        });
    },

    onClearSignature: function(button) {
        console.log('Clear Signature button clicked!');
        signPad.width = signPad.width;
        signPadContext.lineWidth = 3;
        saveButton.disable();
    },
   
    onSaveSignature: function() {
        console.log('Save Signature button clicked!');
        //Returns the content of the current canvas as an image that you can 
        //use as a source for another canvas or an HTML element 
        var data = signPad.toDataURL();
        var signatureData = data.replace(/^data:image\/(png|jpg);base64,/, "");
       
        //create an AJAX request
        Ext.Ajax.request({
            url : 'SaveSignature',
            method:'POST', 
            params : {
                'signatureData' : signatureData
            },
            scope : this,
            //method to call when the request is successful
            success : this.onSaveSuccess,
            //method to call when the request is a failure
            failure : this.onSaveFailure
        }); 
    },
     
    onPanelRendered: function(panel) {
        console.log('Signature Panel rendered, get ready to Sign!');
       
        var view = panel.up('viewport');
        saveButton = view.down('button[id=save]');
       
        //get the signature capture panel
        signPad = Ext.getDom("signaturePanel");
        if (signPad && signPad.getContext) {
            signPadContext = signPad.getContext('2d');
        }

        if (!signPad || !signPadContext) {
            alert('Error creating signature pad.');
            return;
        }
       
        signPad.width = this.getMySignature().getWidth();
        signPad.height = this.getMySignature().getHeight();
       
        //Mouse events
        signPad.addEventListener('mousedown', this.eventSignPad, false);
        signPad.addEventListener('mousemove', this.eventSignPad, false);
        signPad.addEventListener('mouseup', this.eventSignPad, false);
       
        //Touch screen events
        signPad.addEventListener('touchstart', this.eventTouchPad, false);
        signPad.addEventListener('touchmove', this.eventTouchPad, false);
        signPad.addEventListener('touchend', this.eventTouchPad, false);
       
        sign = new this.signCap();
        signPadContext.lineWidth = 3;
    },
   
    signCap: function()  {
        var sign = this;
        this.draw = false;
        this.start = false;
       
        this.mousedown = function(event) {
            signPadContext.beginPath();
            signPadContext.arc(event._x, event._y,1,0*Math.PI,2*Math.PI);
            signPadContext.fill();
            signPadContext.stroke();
            signPadContext.moveTo(event._x, event._y);
            sign.draw = true;
            saveButton.enable();
        };

        this.mousemove = function(event) {
            if (sign.draw) {
                signPadContext.lineTo(event._x, event._y);
                signPadContext.stroke();
            }
        };

        this.mouseup = function(event) {
            if (sign.draw) {
                sign.mousemove(event);
                sign.draw = false;
            }
        };
       
        this.touchstart = function(event) {
            signPadContext.beginPath();
            signPadContext.arc(event._x, event._y,1,0*Math.PI,2*Math.PI);
            signPadContext.fill();
            signPadContext.stroke();
            signPadContext.moveTo(event._x, event._y);
            sign.start = true;
            saveButton.enable();
        };

        this.touchmove = function(event) {
            event.preventDefault(); 
            if (sign.start) {
                signPadContext.lineTo(event._x, event._y);
                signPadContext.stroke();
            }
        };

        this.touchend = function(event) {
            if (sign.start) {
                sign.touchmove(event);
                sign.start = false;
            }
        };
         
    },

    eventSignPad: function(event) {
        if (event.offsetX || event.offsetX == 0) {
            event._x = event.offsetX;
            event._y = event.offsetY;
        } else if (event.layerX || event.layerX == 0) {
            event._x = event.layerX;
            event._y = event.layerY;
        }
       
        var func = sign[event.type];
        if (func) {
            func(event);
        }
  
    },
   
    eventTouchPad: function(event) {
       
        var mySign = Ext.get("signature");
        //in the case of a mouse there can only be one point of click
        //but when using a touch screen you can touch at multiple places
        //at the same time. Here we are only concerned about the first
        //touch event. Next we get the canvas element's left and Top offsets 
        //and deduct them from the current coordinates to get the position
        //relative to the canvas 0,0 (x,y) reference.
        event._x = event.targetTouches[0].pageX - mySign.getX();
        event._y = event.targetTouches[0].pageY - mySign.getY();
       
        var func = sign[event.type];
        if (func) {
            func(event);
        }
       
    },
   
    onSaveFailure : function(err) {
        //Alert the user about communication error
        Ext.MessageBox.alert('Error occured during saving the signature', 'Please try again!');
    },

    onSaveSuccess : function(response, opts) {
        //Received response from the server
        response = Ext.decode(response.responseText);
        Ext.MessageBox.alert('Save Successful', 'File name is: ' + response.fileName);
    },
   
           
});


Capture signature using HTML5 canvas, ExtJs 4 and Java Servlet

Step 5: Java Servlet for server side programming - SaveSignature.java

The Java Servlet creates an unique file name using current date and time. It uses the Base64 decoder to create a PNG image file from the image string.
package com.as400samplecode;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gson.JsonObject;

public class SaveSignature extends HttpServlet {
   
    private static final long serialVersionUID = 1L;
    private static final String DESTINATION_DIR_PATH ="/signatureFolder";
    private File destinationDir, filePath;
   
    public SaveSignature() {
        super();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // nothing here
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       
        //PrintWriter to send the JSON response back
        PrintWriter out = response.getWriter();

        //set content type and header attributes
        response.setContentType("text/html");
        response.setHeader("Cache-control", "no-cache, no-store");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Expires", "-1");
       
        JsonObject myObj = new JsonObject();
       
        String signatureData = request.getParameter("signatureData");
        String realPath = getServletContext().getRealPath(DESTINATION_DIR_PATH);
        String fileName = "Signature_" + getTodaysDate() + "_" + getCurrentTime() + ".png";
       
        destinationDir = new File(realPath);
        if(!destinationDir.isDirectory()) {
            myObj.addProperty("success", false);
            myObj.addProperty("message", "Signature destination folder not found!");
        }
        else {
            filePath = new File(realPath, fileName);
            Base64.decodeToFile(signatureData, filePath.getPath());
            myObj.addProperty("success", true);
            myObj.addProperty("fileName", fileName);
        }
        out.println(myObj.toString());
        out.close();
       
    }
   
    private String getTodaysDate() { 

        DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
        String todaysDate = dateFormat.format(System.currentTimeMillis());
        return todaysDate;

    }

    private String getCurrentTime() {

        DateFormat dateFormat = new SimpleDateFormat("kkmmss");
        String currentTime = dateFormat.format(System.currentTimeMillis());
        return currentTime;

    }

}

No comments:

Post a Comment

NO JUNK, Please try to keep this clean and related to the topic at hand.
Comments are for users to ask questions, collaborate or improve on existing.