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 which 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 a DynaForm's 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()})}})});

Below this code define 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 the code will load more slowly 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 in order to define the mask. $("#id").getControl().mask("mask-parameters", {placeholder: "placeholder", completed: function})

Parameters:

  • id: Unique ID of the field.
  • "mask-parameters": The template for 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 in the input of the field.
      Ex: $("id").getControl().mask("Good evening, aa?aaaaaaaaaaaaaa")
  • { placeholder: "placeholder", completed: function }: An optional object to define properties of the mask.
    • By default, characters to be entered by the user are displayed as "_" (an underscore) when the field has 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 which is 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, which are used 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");

Numbers with thousands separators

Normandes Junior provides a good jQuery mask for numbers with two decimal digits and integers which 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 in 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: '.'});