JavaScript can be used to add a mask to an input field in a Dynaform. A mask is a pattern of characters, such as a phone number like (999) 999-9999 or a product code like aa-99a9, that ensures that the user only inputs characters which conform to the pattern. Unlike the validate property, which checks whether the entered text conforms to the pattern after the focus leaves the field, masks work while the user is typing and are better at guiding the user to enter the proper input in the field.

Adding the Masked Input Code

Josh Bush's Masked Input code can be used to add masks to Dynaform fields. This code has an MIT license, which allows it to incorporated into ProcessMaker without problems.

The Masked Input code can be incorporated into a Dynaform by pasting the following code into its JavaScript code:

/*
    jQuery Masked Input Plugin
    Copyright (c) 2007 - 2015 Josh Bush (digitalbush.com)
    Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)
    Version: 1.4.1
*/

!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){var b,c=navigator.userAgent,d=/iphone/i.test(c),e=/chrome/i.test(c),f=/android/i.test(c);a.mask={definitions:{9:"[0-9]",a:"[A-Za-z]","*":"[A-Za-z0-9]"},autoclear:!0,dataName:"rawMaskFn",placeholder:"_"},a.fn.extend({caret:function(a,b){var c;if(0!==this.length&&!this.is(":hidden"))return"number"==typeof a?(b="number"==typeof b?b:a,this.each(function(){this.setSelectionRange?this.setSelectionRange(a,b):this.createTextRange&&(c=this.createTextRange(),c.collapse(!0),c.moveEnd("character",b),c.moveStart("character",a),c.select())})):(this[0].setSelectionRange?(a=this[0].selectionStart,b=this[0].selectionEnd):document.selection&&document.selection.createRange&&(c=document.selection.createRange(),a=0-c.duplicate().moveStart("character",-1e5),b=a+c.text.length),{begin:a,end:b})},unmask:function(){return this.trigger("unmask")},mask:function(c,g){var h,i,j,k,l,m,n,o;if(!c&&this.length>0){h=a(this[0]);var p=h.data(a.mask.dataName);return p?p():void 0}return g=a.extend({autoclear:a.mask.autoclear,placeholder:a.mask.placeholder,completed:null},g),i=a.mask.definitions,j=[],k=n=c.length,l=null,a.each(c.split(""),function(a,b){"?"==b?(n--,k=a):i[b]?(j.push(new RegExp(i[b])),null===l&&(l=j.length-1),k>a&&(m=j.length-1)):j.push(null)}),this.trigger("unmask").each(function(){function h(){if(g.completed){for(var a=l;m>=a;a++)if(j[a]&&C[a]===p(a))return;g.completed.call(B)}}function p(a){return g.placeholder.charAt(a<g.placeholder.length?a:0)}function q(a){for(;++a<n&&!j[a];);return a}function r(a){for(;--a>=0&&!j[a];);return a}function s(a,b){var c,d;if(!(0>a)){for(c=a,d=q(b);n>c;c++)if(j[c]){if(!(n>d&&j[c].test(C[d])))break;C[c]=C[d],C[d]=p(d),d=q(d)}z(),B.caret(Math.max(l,a))}}function t(a){var b,c,d,e;for(b=a,c=p(a);n>b;b++)if(j[b]){if(d=q(b),e=C[b],C[b]=c,!(n>d&&j[d].test(e)))break;c=e}}function u(){var a=B.val(),b=B.caret();if(o&&o.length&&o.length>a.length){for(A(!0);b.begin>0&&!j[b.begin-1];)b.begin--;if(0===b.begin)for(;b.begin<l&&!j[b.begin];)b.begin++;B.caret(b.begin,b.begin)}else{for(A(!0);b.begin<n&&!j[b.begin];)b.begin++;B.caret(b.begin,b.begin)}h()}function v(){A(),B.val()!=E&&B.change()}function w(a){if(!B.prop("readonly")){var b,c,e,f=a.which||a.keyCode;o=B.val(),8===f||46===f||d&&127===f?(b=B.caret(),c=b.begin,e=b.end,e-c===0&&(c=46!==f?r(c):e=q(c-1),e=46===f?q(e):e),y(c,e),s(c,e-1),a.preventDefault()):13===f?v.call(this,a):27===f&&(B.val(E),B.caret(0,A()),a.preventDefault())}}function x(b){if(!B.prop("readonly")){var c,d,e,g=b.which||b.keyCode,i=B.caret();if(!(b.ctrlKey||b.altKey||b.metaKey||32>g)&&g&&13!==g){if(i.end-i.begin!==0&&(y(i.begin,i.end),s(i.begin,i.end-1)),c=q(i.begin-1),n>c&&(d=String.fromCharCode(g),j[c].test(d))){if(t(c),C[c]=d,z(),e=q(c),f){var k=function(){a.proxy(a.fn.caret,B,e)()};setTimeout(k,0)}else B.caret(e);i.begin<=m&&h()}b.preventDefault()}}}function y(a,b){var c;for(c=a;b>c&&n>c;c++)j[c]&&(C[c]=p(c))}function z(){B.val(C.join(""))}function A(a){var b,c,d,e=B.val(),f=-1;for(b=0,d=0;n>b;b++)if(j[b]){for(C[b]=p(b);d++<e.length;)if(c=e.charAt(d-1),j[b].test(c)){C[b]=c,f=b;break}if(d>e.length){y(b+1,n);break}}else C[b]===e.charAt(d)&&d++,k>b&&(f=b);return a?z():k>f+1?g.autoclear||C.join("")===D?(B.val()&&B.val(""),y(0,n)):z():(z(),B.val(B.val().substring(0,f+1))),k?b:l}var B=a(this),C=a.map(c.split(""),function(a,b){return"?"!=a?i[a]?p(b):a:void 0}),D=C.join(""),E=B.val();B.data(a.mask.dataName,function(){return a.map(C,function(a,b){return j[b]&&a!=p(b)?a:null}).join("")}),B.one("unmask",function(){B.off(".mask").removeData(a.mask.dataName)}).on("focus.mask",function(){if(!B.prop("readonly")){clearTimeout(b);var a;E=B.val(),a=A(),b=setTimeout(function(){B.get(0)===document.activeElement&&(z(),a==c.replace("?","").length?B.caret(0,a):B.caret(a))},10)}}).on("blur.mask",v).on("keydown.mask",w).on("keypress.mask",x).on("input.mask paste.mask",function(){B.prop("readonly")||setTimeout(function(){var a=A(!0);B.caret(a),h()},0)}),e&&f&&B.off("input.mask").on("input.mask",u),A()})}})});

This code defines the masks for the fields, as shown below.

Another way to incorporate this code into a Dynaform is to add the following URL to the form's external libs property:
    https://raw.githubusercontent.com/digitalBush/jquery.maskedinput/1.4.1/dist/jquery.maskedinput.min.js

The problem is that the code will load more slowly when downloading from an external site, so it may be a good idea to store the file in the public_html directory on the ProcessMaker server:

    {INSTALL-DIRECTORY}/workflow/public_html/jquery.maskedinput.min.js

For example on a Linux/UNIX server:
    /opt/processmaker/workflow/public_html/jquery.maskedinput.min.js

The file can then be accessed at the web address:
    http://{DOMAIN-OR-IP-ADDRESS}/jquery.maskedinput.min.js

For example:
    http://example.com/jquery.maskedinput.min.js

Defining the Mask

Then, add JavaScript code to the Dynaform to call the mask() method for the input field to define the mask.

$("#id").getControl().mask("mask-parameters", {placeholder: "placeholder", completed: function})

Parameters:

  • id: Unique ID of the field.
  • mask-parameters: The template of the mask, where:
    • 9 represents any number (0-9).
      Ex: A US phone number:
      $("#id").getControl().mask("1(999) 999-9999")
    • a represents any letter (A-Z,a-z).
    • * represents any letter or number (A-Z,a-z,0-9).
    • Any characters after ? are optional.
      Ex: An optional extension to a phone number:
      $("#id").getControl().mask("(999) 999-9999? x9999")
    • Any other characters are automatically inserted into the field.
      Ex: $("id").getControl().mask("Good evening, aa?aaaaaaaaaaaaaa")
  • { placeholder: "placeholder", completed: function }: An optional object that defines the mask's properties.
    • By default, characters to be entered by the user are displayed as "_" (an underscore) when the field has the focus. To use different character(s), define the placeholder.
      Ex: Use a space instead of an underscore:
      $("#id").getControl().mask("IM-99a9", {placeholder: " "})
    • completed: Defines a custom function that will be executed after the focus leaves the field.
      Ex: Display a message if the value starts with "C" in the "productCode" field.
      $("#productCode").getControl().mask("a***", {completed: function() {
         if (this.val().substr(0,1) == "C") {
            alert("Remember to only use Acme Co. product codes");
         }
      }});

It is also possible to define new wildcard characters or redefine existing ones to use in a mask. For example, the following code defines a new wildcard ~, which represents either + (plus sign) or - (minus sign). Then, it uses that wildcard in a mask in the "amount" field:

$.mask.definitions['~'] = '[+-]';
$("#amount").getControl().mask("~9.99");

The mask() function can also be used with text fields inside grids. For example, the following code applies the mask "99-H99" to a grid field with the ID "itemCode", inside a grid whose ID is "orderList":

var gridId = "itemCode"; //set to ID of grid
var fieldId = "amount"; //set to ID of text field inside grid

//set the mask in all existing rows when the Dynaform loads:
var nRows = $("#"+gridId).getNumberRows()
for (var i = 1; i <= nRows; i++) {
    $("[id='form["+gridId+"]["+i+"]["+fieldId+"]']").mask("99-H99");
}

//set the mask in newly added rows:
$("#"+gridId).onAddRow(function(aNewRow, oGrid, rowIndex) {
    $("[id='form["+gridId+"]["+rowIndex+"]["+fieldId+"]']").mask("99-H99");
});

Numbers with Thousands Separators

Normandes Junior provides a good jQuery mask for numbers with two decimal digits and integers that need to automatically insert a thousands separator.

To use this mask in a Dynaform, first go to https://github.com/normandesjr/jquery-mask-number and download the file jquery.masknumber.js.

Then, save the file in the following location on your ProcessMaker server:
    {INSTALL-DIRECTORY}/workflow/public_html/jquery.masknumber.js

The file can then be accessed at the web address:
    http://{DOMAIN-OR-IP-ADDRESS}/jquery.masknumber.js
For example:
    http://example.com/jquery.masknumber.js

Place this web address in the Dynaform's external libs property.

Then, add JavaScript code to the Dynaform to configure controls to use the imported number mask library.

For an English-style decimal number such as 1.99, 12,288.07 or 182,837,627,897.54, which uses a , (comma) as the thousands separator and a . (dot) as the decimal point, use the code:
    $("#id").getControl().maskNumber()

For example, for a textbox whose ID is "invoiceAmount":
    $("#invoiceAmount").getControl().maskNumber();

For an English-style integer such as 13, 12,288 or 182,837,627,897, which uses a , (comma) as the thousands separator, use the code:
    $("#id").getControl().maskNumber({integer: true})

For example, for a textbox whose ID is "invoiceAmount":
    $("#invoiceAmount").getControl().maskNumber({integer: true});

For an European-style number such as 1,99, 12.288,07 or 182.837.627.897,54, which uses a . (dot) as the thousands separator and a , (comma) as the decimal point, use the code:
    $("#id").getControl().maskNumber({decimal: ',', thousands: '.'})

For example, for a textbox whose ID is "invoiceAmount":
    $("#invoiceAmount").getControl().maskNumber({decimal: ',', thousands: '.'});

For an European-style integer such as 13, 12.288 or 182.837.627.897, which uses a . (dot) as the thousands separator, use the code:
    $("#id").getControl().maskNumber({integer: true, thousands: '.'})

For example, for a textbox whose ID is "invoiceAmount":
    $("#invoiceAmount").getControl().maskNumber({integer: true, thousands: '.'});

The maskNumber() function can also be used with text fields inside grids. For example, the following code applies a European style number mask to a grid field with the ID "amount" inside a grid whose ID is "orderList":

var gridId = "orderList"; //set to ID of grid
var fieldId = "amount"; //set to ID of text field inside grid

//set the thousands separator in all existing rows when the Dynaform loads:
var nRows = $("#"+gridId).getNumberRows()
for (var i = 1; i <= nRows; i++) {
    $("[id='form["+gridId+"]["+i+"]["+fieldId+"]']").maskNumber({decimal: ',', thousands: '.'});
}

//set the thousands separator in newly added rows:
$("#"+gridId).onAddRow(function(aNewRow, oGrid, rowIndex) {
    $("[id='form["+gridId+"]["+rowIndex+"]["+fieldId+"]']").maskNumber({decimal: ',', thousands: '.'});
});