3 * @link http://www.open-emr.org
4 * @author Jerry Padgett <sjpadgett@gmail.com>
5 * @copyright Copyright (c) 2016-2019 Jerry Padgett <sjpadgett@gmail.com>
6 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
9 function getSignature(othis) {
10 let libUrl, signer, signerType = "";
11 let isLink = $(othis).attr('src').indexOf('signhere');
13 if ($(othis).attr('src') != signhere && isLink == -1) {
14 $(othis).attr('src', signhere);
18 if (webRoot !== undefined && webRoot !== null) {
19 libUrl = webRoot + '/portal/';
24 if ($(othis).attr('type') == 'admin-signature') {
26 signerType = "admin-signature";
29 signerType = "patient-signature";
38 let url = libUrl + "sign/lib/show-signature.php";
41 body: JSON.stringify(params),
43 'Accept': 'application/json, text/plain, */*',
44 'Content-Type': 'application/json'
46 }).then(signature => signature.json())
48 placeImg(signature, othis)
49 }).catch(error => alert(error));
52 function placeImg(responseData, el) {
53 if (responseData == "error") {
54 $(el).attr('src', "");
55 alert('Error Patient and or User Id missing');
58 else if (responseData == "insert error") {
59 $(el).attr('src', "");
60 alert('Error adding signature');
63 else if (responseData == "waiting" && $(el).attr('type') == 'patient-signature') {
64 $(el).attr('src', "");
65 alert('Signature not on file. Please sign');
66 $("#isAdmin").attr('checked', false);
67 $("#openSignModal").modal("show");
70 else if (responseData == "waiting" && $(el).attr('type') == 'admin-signature') {
71 $(el).attr('src', "");
72 alert('Signature not on file. Please sign');
73 $("#isAdmin").attr('checked', true);
74 $("#openSignModal").modal("show");
78 i.onload = function () {
79 $(el).attr('src', i.src); // display image
81 i.src = isDataURL(responseData) ? responseData : 'data:image/png;base64,' + responseData; // load image
84 function signDoc(signImage) {
85 let libUrl, signer, signerType = "";
89 if (webRoot !== undefined && webRoot !== null)
90 libUrl = webRoot + '/portal/';
94 if ($("#isAdmin").is(':checked') == false) {
97 signerType = "patient-signature";
101 signerType = "admin-signature";
111 let url = libUrl + "sign/lib/save-signature.php";
114 body: JSON.stringify(data),
116 'Content-Type': 'application/json',
117 'Connection': 'close'
119 }).then(response => response.text())
121 $("#loading").toggle(),
122 $("#openSignModal").modal("hide")
123 ).catch(error => alert(error));
125 $("#loading").toggle();
128 function isDataURL(dataUrl) {
129 return !!dataUrl.match(isDataURL.regex);
131 isDataURL.regex = /^\s*data:([a-z]+\/[a-z]+(;[a-z\-]+\=[a-z\-]+)?)?(;base64)?,[a-z0-9\!\$\&\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i;
135 let url = top.webroot_url ? top.webroot_url : webRoot;
136 url += "/portal/sign/assets/signer_modal.tpl.php?isAdmin=" + encodeURIComponent(isAdmin);
138 .then(jsonTemplate => jsonTemplate.json())
139 .then(jsonTemplate => {
140 $("body").append(jsonTemplate);
145 .catch((error) => alert(error));
148 function initSignerApi() {
150 const canvasOptions = {
155 velocityFilterWeight: .2,
156 penColor: 'rgb(0, 0, 255)',
158 var wrapper = document.getElementById("openSignModal");
159 var placeSignature = wrapper.querySelector("[data-action=place]");
160 var showSignature = wrapper.querySelector("[data-action=show]");
161 var clearButton = wrapper.querySelector("[data-action=clear]");
162 var saveSignatureButton = wrapper.querySelector("[data-action=save-png]");
163 var canvas = wrapper.querySelector("canvas");
167 $("#openSignModal").on('show.bs.modal', function (e) {
168 let triggeredBy = $(e.relatedTarget);
169 let type = triggeredBy.prop('type');
170 if (type === "admin-signature") {
171 $("#isAdmin").prop('checked', true);
172 placeSignature.setAttribute("type", type);
175 $(this).data('bs.modal').options.backdrop = 'static';
180 'padding-right': '0px'
182 $('body').bind('selectstart', function () {
185 $(this).modal('handleUpdate');
186 }).on('shown.bs.modal', function (e) {
187 signaturePad = new SignaturePad(canvas, canvasOptions);
189 }).on('hide.bs.modal', function () {
191 'overflow': 'inherit'
193 $('body').unbind('selectstart');
196 clearButton.addEventListener("click", function (event) {
197 signaturePad.clear();
200 saveSignatureButton.addEventListener("click", function (event) {
201 if (signaturePad.isEmpty()) {
202 alert("Please provide a signature first.");
204 let dataURL = signaturePad.toDataURL();
205 signDoc(encodeURIComponent(dataURL));
209 placeSignature.addEventListener("click", function (event) {
210 let thisElement = $(this);
211 getSignature(thisElement);
214 showSignature.addEventListener("click", function (event) {
215 let thisElement = $(this);
216 let showElement = document.getElementById('signatureModal');
217 getSignature(showElement);
220 function resizeCanvas() {
221 let ratio = Math.max(window.devicePixelRatio || 1, 1);
222 canvas.width = canvas.offsetWidth * ratio;
223 canvas.height = canvas.offsetHeight * ratio;
224 canvas.getContext("2d").scale(ratio, ratio);
227 function download(dataURL, filename) {
228 let blob = dataURLToBlob(dataURL);
229 let url = window.URL.createObjectURL(blob);
230 let a = document.createElement("a");
231 a.style = "display: none";
233 a.download = filename;
235 document.body.appendChild(a);
237 window.URL.revokeObjectURL(url);
240 function dataURLToBlob(dataURL) {
241 // Code taken from https://github.com/ebidel/filer.js
242 let parts = dataURL.split(';base64,');
243 let contentType = parts[0].split(":")[1];
244 let raw = window.atob(parts[1]);
245 let rawLength = raw.length;
246 let uInt8Array = new Uint8Array(rawLength);
248 for (var i = 0; i < rawLength; ++i) {
249 uInt8Array[i] = raw.charCodeAt(i);
252 return new Blob([uInt8Array], {type: contentType});
255 function drawSignatureLine() {
256 let context = canvas.getContext('2d');
257 context.lineWidth = .4;
258 context.strokeStyle = '#333';
260 context.moveTo(0, 200);
261 context.lineTo(900, 200);
265 // resize event and initial resize
266 window.onresize = resizeCanvas;
271 const signhere = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFQAAABUCAYAAAAcaxDBAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAphJREFUeJzt2j1oE2Ecx/FvMdW+biK+1EVxEnESm0Xcxero6Gqto+iqKHQTcXUQHBTqUKiL0KUWFAQp6KQdRKPopA2+VjHn8L/jee54ckm0zeXq7wOBp/dcycOXa3N5EhAREREREREREREREREREZH1sqnoBfSIHcBpoAp8AOqFrqaktgH9wBEsYBQ/vgHHC1xXKe0EXgDzwGcs5BLwOB6vAhOFra5kkpiR95gHBoEKMIOLeqygNZaGH/M98DMe38NiQjrqJ2C0+8ssBz/ma2APcBIXdYZ01Fp8/Gi3F1oGoZiJUNQq9icfAfu7utIS2I6LWSMdM+FHfYB71Z/r0hpLZRgXtI5dfSEncFEjYCH+XcmYBBq4UHVgvMm5E6RfqPTGJ8OPeQNYJD/qGe/8a11aY2lM4q7KJM4I8BAX9bB3vmLmCMVMjGD/HyNgBYuqmDnyYiaGcVfqFxSzqbO0jgl2X/rSO1cxA/4mZg141OL8/1K7MXeRjrkXGFj31ZXMv8SUjCk6j/kGxQzqJOYyiplLMddQFRdzCehrcl42ZmiXSYD7tL5/HEMx23IQi/Qb2+wIRVXMDtzFQt2Jf75IOqofM7szLxn7sCuzARzwjvtRk512xWzDTSzWbOb4AG73SDHbtBu3k34oPjYKXMC+OpPEfIVituU6FqwBnAMuAx9JX5VT9MD78mb3cJ0YBLZkjv0CvsbjIWBzZn4V+B6PK9imry/5fwiwFQs2FHjuZWAauB0/54ZwhfR9YYS9GiduBeanvfnxwPyKN38pMP8MOEUPfohWaX1K4ca88RPgKvZ5eVTMcvKtRdCFwLHn3ngOeJuZX/TG77BIvh/e+Dy2U/QU+1KXiIiIiIiIiIiIiIiIiIiIiIhsfH8AekL6s5feEc0AAAAASUVORK5CYII=";