422 lines
14 KiB
JavaScript
422 lines
14 KiB
JavaScript
![]() |
/*! http://keith-wood.name/signature.html
|
||
|
Signature plugin for jQuery UI v1.2.1.
|
||
|
Requires excanvas.js in IE.
|
||
|
Written by Keith Wood (wood.keith{at}optusnet.com.au) April 2012.
|
||
|
Available under the MIT (http://keith-wood.name/licence.html) license.
|
||
|
Please attribute the author if you use it. */
|
||
|
|
||
|
/* globals G_vmlCanvasManager */
|
||
|
|
||
|
(function($) { // Hide scope, no $ conflict
|
||
|
'use strict';
|
||
|
|
||
|
/** Signature capture and display.
|
||
|
<p>Depends on <code>jquery.ui.widget</code>, <code>jquery.ui.mouse</code>.</p>
|
||
|
<p>Expects HTML like:</p>
|
||
|
<pre><div></div></pre>
|
||
|
@namespace Signature
|
||
|
@augments $.Widget
|
||
|
@example $(selector).signature()
|
||
|
$(selector).signature({color: 'blue', guideline: true}) */
|
||
|
var signatureOverrides = {
|
||
|
|
||
|
/** Be notified when a signature changes.
|
||
|
@callback SignatureChange
|
||
|
@global
|
||
|
@this Signature
|
||
|
@example change: function() {
|
||
|
console.log('Signature changed');
|
||
|
} */
|
||
|
|
||
|
/** Global defaults for signature.
|
||
|
@memberof Signature
|
||
|
@property {number} [distance=0] The minimum distance to start a drag.
|
||
|
@property {string} [background='#fff'] The background colour.
|
||
|
@property {string} [color='#000'] The colour of the signature.
|
||
|
@property {number} [thickness=2] The thickness of the lines.
|
||
|
@property {boolean} [guideline=false] <code>true</code> to add a guideline.
|
||
|
@property {string} [guidelineColor='#a0a0a0'] The guideline colour.
|
||
|
@property {number} [guidelineOffset=50] The guideline offset (pixels) from the bottom.
|
||
|
@property {number} [guidelineIndex=10] The guideline indent (pixels) from the edges.
|
||
|
@property {string} [notAvailable='Your browser doesn\'t support signing']
|
||
|
The error message to show when no canvas is available.
|
||
|
@property {number} [scale=1] A scaling factor for rendering the signature (only applies to redraws).
|
||
|
@property {string|Element|jQuery} [syncField=null] The selector, DOM element, or jQuery object
|
||
|
for a field to automatically synchronise with a text version of the signature.
|
||
|
@property {string} [syncFormat='JSON'] The output representation: 'JSON', 'SVG', 'PNG', 'JPEG'.
|
||
|
@property {boolean} [svgStyles=false] <code>true</code> to use the <code>style</code> attribute in SVG.
|
||
|
@property {SignatureChange} [change=null] A callback triggered when the signature changes.
|
||
|
@example $.extend($.kbw.signature.options, {guideline: true}) */
|
||
|
options: {
|
||
|
distance: 0,
|
||
|
background: '#fff',
|
||
|
color: '#000',
|
||
|
thickness: 2,
|
||
|
guideline: false,
|
||
|
guidelineColor: '#a0a0a0',
|
||
|
guidelineOffset: 50,
|
||
|
guidelineIndent: 10,
|
||
|
notAvailable: 'Your browser doesn\'t support signing',
|
||
|
scale: 1,
|
||
|
syncField: null,
|
||
|
syncFormat: 'JSON',
|
||
|
svgStyles: false,
|
||
|
change: null
|
||
|
},
|
||
|
|
||
|
/** Initialise a new signature area.
|
||
|
@memberof Signature
|
||
|
@private */
|
||
|
_create: function() {
|
||
|
this.element.addClass(this.widgetFullName || this.widgetBaseClass);
|
||
|
try {
|
||
|
this.canvas = $('<canvas width="' + this.element.width() + '" height="' +
|
||
|
this.element.height() + '">' + this.options.notAvailable + '</canvas>')[0];
|
||
|
this.element.append(this.canvas);
|
||
|
}
|
||
|
catch (e) {
|
||
|
$(this.canvas).remove();
|
||
|
this.resize = true;
|
||
|
this.canvas = document.createElement('canvas');
|
||
|
this.canvas.setAttribute('width', this.element.width());
|
||
|
this.canvas.setAttribute('height', this.element.height());
|
||
|
this.canvas.innerHTML = this.options.notAvailable;
|
||
|
this.element.append(this.canvas);
|
||
|
/* jshint -W106 */
|
||
|
if (G_vmlCanvasManager) { // Requires excanvas.js
|
||
|
G_vmlCanvasManager.initElement(this.canvas);
|
||
|
}
|
||
|
/* jshint +W106 */
|
||
|
}
|
||
|
this.ctx = this.canvas.getContext('2d');
|
||
|
this._refresh(true);
|
||
|
this._mouseInit();
|
||
|
},
|
||
|
|
||
|
/** Refresh the appearance of the signature area.
|
||
|
@memberof Signature
|
||
|
@private
|
||
|
@param {boolean} init <code>true</code> if initialising. */
|
||
|
_refresh: function(init) {
|
||
|
if (this.resize) {
|
||
|
var parent = $(this.canvas);
|
||
|
$('div', this.canvas).css({width: parent.width(), height: parent.height()});
|
||
|
}
|
||
|
this.ctx.fillStyle = this.options.background;
|
||
|
this.ctx.strokeStyle = this.options.color;
|
||
|
this.ctx.lineWidth = this.options.thickness;
|
||
|
this.ctx.lineCap = 'round';
|
||
|
this.ctx.lineJoin = 'round';
|
||
|
this.clear(init);
|
||
|
},
|
||
|
|
||
|
/** Clear the signature area.
|
||
|
@memberof Signature
|
||
|
@param {boolean} init <code>true</code> if initialising - internal use only.
|
||
|
@example $(selector).signature('clear') */
|
||
|
clear: function(init) {
|
||
|
if (this.options.disabled) {
|
||
|
return;
|
||
|
}
|
||
|
this.ctx.clearRect(0, 0, this.element.width(), this.element.height());
|
||
|
this.ctx.fillRect(0, 0, this.element.width(), this.element.height());
|
||
|
if (this.options.guideline) {
|
||
|
this.ctx.save();
|
||
|
this.ctx.strokeStyle = this.options.guidelineColor;
|
||
|
this.ctx.lineWidth = 1;
|
||
|
this.ctx.beginPath();
|
||
|
this.ctx.moveTo(this.options.guidelineIndent,
|
||
|
this.element.height() - this.options.guidelineOffset);
|
||
|
this.ctx.lineTo(this.element.width() - this.options.guidelineIndent,
|
||
|
this.element.height() - this.options.guidelineOffset);
|
||
|
this.ctx.stroke();
|
||
|
this.ctx.restore();
|
||
|
}
|
||
|
this.lines = [];
|
||
|
if (!init) {
|
||
|
this._changed();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/** Synchronise changes and trigger a change event.
|
||
|
@memberof Signature
|
||
|
@private
|
||
|
@param {Event} event The triggering event. */
|
||
|
_changed: function(event) {
|
||
|
if (this.options.syncField) {
|
||
|
var output = '';
|
||
|
switch (this.options.syncFormat) {
|
||
|
case 'PNG':
|
||
|
output = this.toDataURL();
|
||
|
break;
|
||
|
case 'JPEG':
|
||
|
output = this.toDataURL('image/jpeg');
|
||
|
break;
|
||
|
case 'SVG':
|
||
|
output = this.toSVG();
|
||
|
break;
|
||
|
default:
|
||
|
output = this.toJSON();
|
||
|
}
|
||
|
$(this.options.syncField).val(output);
|
||
|
}
|
||
|
this._trigger('change', event, {});
|
||
|
},
|
||
|
|
||
|
/** Refresh the signature when options change.
|
||
|
@memberof Signature
|
||
|
@private
|
||
|
@param {object} options The new option values. */
|
||
|
_setOptions: function(/* options */) {
|
||
|
if (this._superApply) {
|
||
|
this._superApply(arguments); // Base widget handling
|
||
|
}
|
||
|
else {
|
||
|
$.Widget.prototype._setOptions.apply(this, arguments); // Base widget handling
|
||
|
}
|
||
|
var count = 0;
|
||
|
var onlyDisable = true;
|
||
|
for (var name in arguments[0]) {
|
||
|
if (arguments[0].hasOwnProperty(name)) {
|
||
|
count++;
|
||
|
onlyDisable = onlyDisable && name === 'disabled';
|
||
|
}
|
||
|
}
|
||
|
if (count > 1 || !onlyDisable) {
|
||
|
this._refresh();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/** Determine if dragging can start.
|
||
|
@memberof Signature
|
||
|
@private
|
||
|
@param {Event} event The triggering mouse event.
|
||
|
@return {boolean} <code>true</code> if allowed, <code>false</code> if not */
|
||
|
_mouseCapture: function(/* event */) {
|
||
|
return !this.options.disabled;
|
||
|
},
|
||
|
|
||
|
/** Start a new line.
|
||
|
@memberof Signature
|
||
|
@private
|
||
|
@param {Event} event The triggering mouse event. */
|
||
|
_mouseStart: function(event) {
|
||
|
this.offset = this.element.offset();
|
||
|
this.offset.left -= document.documentElement.scrollLeft || document.body.scrollLeft;
|
||
|
this.offset.top -= document.documentElement.scrollTop || document.body.scrollTop;
|
||
|
this.lastPoint = [this._round(event.clientX - this.offset.left),
|
||
|
this._round(event.clientY - this.offset.top)];
|
||
|
this.curLine = [this.lastPoint];
|
||
|
this.lines.push(this.curLine);
|
||
|
},
|
||
|
|
||
|
/** Track the mouse.
|
||
|
@memberof Signature
|
||
|
@private
|
||
|
@param {Event} event The triggering mouse event. */
|
||
|
_mouseDrag: function(event) {
|
||
|
var point = [this._round(event.clientX - this.offset.left),
|
||
|
this._round(event.clientY - this.offset.top)];
|
||
|
this.curLine.push(point);
|
||
|
this.ctx.beginPath();
|
||
|
this.ctx.moveTo(this.lastPoint[0], this.lastPoint[1]);
|
||
|
this.ctx.lineTo(point[0], point[1]);
|
||
|
this.ctx.stroke();
|
||
|
this.lastPoint = point;
|
||
|
},
|
||
|
|
||
|
/** End a line.
|
||
|
@memberof Signature
|
||
|
@private
|
||
|
@param {Event} event The triggering mouse event. */
|
||
|
_mouseStop: function(event) {
|
||
|
if (this.curLine.length === 1) {
|
||
|
event.clientY += this.options.thickness;
|
||
|
this._mouseDrag(event);
|
||
|
}
|
||
|
this.lastPoint = null;
|
||
|
this.curLine = null;
|
||
|
this._changed(event);
|
||
|
},
|
||
|
|
||
|
/** Round to two decimal points.
|
||
|
@memberof Signature
|
||
|
@private
|
||
|
@param {number} value The value to round.
|
||
|
@return {number} The rounded value. */
|
||
|
_round: function(value) {
|
||
|
return Math.round(value * 100) / 100;
|
||
|
},
|
||
|
|
||
|
/** Convert the captured lines to JSON text.
|
||
|
@memberof Signature
|
||
|
@return {string} The JSON text version of the lines.
|
||
|
@example var json = $(selector).signature('toJSON') */
|
||
|
toJSON: function() {
|
||
|
return '{"lines":[' + $.map(this.lines, function(line) {
|
||
|
return '[' + $.map(line, function(point) {
|
||
|
return '[' + point + ']';
|
||
|
}) + ']';
|
||
|
}) + ']}';
|
||
|
},
|
||
|
|
||
|
/** Convert the captured lines to SVG text.
|
||
|
@memberof Signature
|
||
|
@return {string} The SVG text version of the lines.
|
||
|
@example var svg = $(selector).signature('toSVG') */
|
||
|
toSVG: function() {
|
||
|
var attrs1 = (this.options.svgStyles ? 'style="fill: ' + this.options.background + ';"' :
|
||
|
'fill="' + this.options.background + '"');
|
||
|
var attrs2 = (this.options.svgStyles ?
|
||
|
'style="fill: none; stroke: ' + this.options.color + '; stroke-width: ' + this.options.thickness + ';"' :
|
||
|
'fill="none" stroke="' + this.options.color + '" stroke-width="' + this.options.thickness + '"');
|
||
|
return '<?xml version="1.0"?>\n<!DOCTYPE svg PUBLIC ' +
|
||
|
'"-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
|
||
|
'<svg xmlns="http://www.w3.org/2000/svg" width="15cm" height="15cm">\n' +
|
||
|
' <g ' + attrs1 + '>\n' +
|
||
|
' <rect x="0" y="0" width="' + this.canvas.width + '" height="' + this.canvas.height + '"/>\n' +
|
||
|
' <g ' + attrs2 + '>\n'+
|
||
|
$.map(this.lines, function(line) {
|
||
|
return ' <polyline points="' +
|
||
|
$.map(line, function(point) { return point + ''; }).join(' ') + '"/>\n';
|
||
|
}).join('') +
|
||
|
' </g>\n </g>\n</svg>\n';
|
||
|
},
|
||
|
|
||
|
/** Convert the captured lines to an image encoded in a <code>data:</code> URL.
|
||
|
@memberof Signature
|
||
|
@param {string} [type='image/png'] The MIME type of the image.
|
||
|
@param {number} [quality=0.92] The image quality, between 0 and 1.
|
||
|
@return {string} The signature as a data: URL image.
|
||
|
@example var data = $(selector).signature('toDataURL', 'image/jpeg') */
|
||
|
toDataURL: function(type, quality) {
|
||
|
return this.canvas.toDataURL(type, quality);
|
||
|
},
|
||
|
|
||
|
/** Draw a signature from its JSON or SVG description or <code>data:</code> URL.
|
||
|
<p>Note that drawing a <code>data:</code> URL does not reconstruct the internal representation!</p>
|
||
|
@memberof Signature
|
||
|
@param {object|string} sig An object with attribute <code>lines</code> being an array of arrays of points
|
||
|
or the text version of the JSON or SVG or a <code>data:</code> URL containing an image.
|
||
|
@example $(selector).signature('draw', sigAsJSON) */
|
||
|
draw: function(sig) {
|
||
|
if (this.options.disabled) {
|
||
|
return;
|
||
|
}
|
||
|
this.clear(true);
|
||
|
if (typeof sig === 'string' && sig.indexOf('data:') === 0) { // Data URL
|
||
|
this._drawDataURL(sig, this.options.scale);
|
||
|
} else if (typeof sig === 'string' && sig.indexOf('<svg') > -1) { // SVG
|
||
|
this._drawSVG(sig, this.options.scale);
|
||
|
} else {
|
||
|
this._drawJSON(sig, this.options.scale);
|
||
|
}
|
||
|
this._changed();
|
||
|
},
|
||
|
|
||
|
/** Draw a signature from its JSON description.
|
||
|
@memberof Signature
|
||
|
@private
|
||
|
@param {object|string} sig An object with attribute <code>lines</code> being an array of arrays of points
|
||
|
or the text version of the JSON.
|
||
|
@param {number} scale A scaling factor. */
|
||
|
_drawJSON: function(sig, scale) {
|
||
|
if (typeof sig === 'string') {
|
||
|
sig = $.parseJSON(sig);
|
||
|
}
|
||
|
this.lines = sig.lines || [];
|
||
|
var ctx = this.ctx;
|
||
|
$.each(this.lines, function() {
|
||
|
ctx.beginPath();
|
||
|
$.each(this, function(i) {
|
||
|
ctx[i === 0 ? 'moveTo' : 'lineTo'](this[0] * scale, this[1] * scale);
|
||
|
});
|
||
|
ctx.stroke();
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/** Draw a signature from its SVG description.
|
||
|
@memberof Signature
|
||
|
@private
|
||
|
@param {string} sig The text version of the SVG.
|
||
|
@param {number} scale A scaling factor. */
|
||
|
_drawSVG: function(sig, scale) {
|
||
|
var lines = this.lines = [];
|
||
|
$(sig).find('polyline').each(function() {
|
||
|
var line = [];
|
||
|
$.each($(this).attr('points').split(' '), function(i, point) {
|
||
|
var xy = point.split(',');
|
||
|
line.push([parseFloat(xy[0]), parseFloat(xy[1])]);
|
||
|
});
|
||
|
lines.push(line);
|
||
|
});
|
||
|
var ctx = this.ctx;
|
||
|
$.each(this.lines, function() {
|
||
|
ctx.beginPath();
|
||
|
$.each(this, function(i) {
|
||
|
ctx[i === 0 ? 'moveTo' : 'lineTo'](this[0] * scale, this[1] * scale);
|
||
|
});
|
||
|
ctx.stroke();
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/** Draw a signature from its <code>data:</code> URL.
|
||
|
<p>Note that this does not reconstruct the internal representation!</p>
|
||
|
@memberof Signature
|
||
|
@private
|
||
|
@param {string} sig The <code>data:</code> URL containing an image.
|
||
|
@param {number} scale A scaling factor. */
|
||
|
_drawDataURL: function(sig, scale) {
|
||
|
var image = new Image();
|
||
|
var context = this.ctx;
|
||
|
image.onload = function() {
|
||
|
context.drawImage(this, 0, 0, image.width * scale, image.height * scale);
|
||
|
};
|
||
|
image.src = sig;
|
||
|
},
|
||
|
|
||
|
/** Determine whether or not any drawing has occurred.
|
||
|
@memberof Signature
|
||
|
@return {boolean} <code>true</code> if not signed, <code>false</code> if signed.
|
||
|
@example if ($(selector).signature('isEmpty')) ... */
|
||
|
isEmpty: function() {
|
||
|
return this.lines.length === 0;
|
||
|
},
|
||
|
|
||
|
/** Remove the signature functionality.
|
||
|
@memberof Signature
|
||
|
@private */
|
||
|
_destroy: function() {
|
||
|
this.element.removeClass(this.widgetFullName || this.widgetBaseClass);
|
||
|
$(this.canvas).remove();
|
||
|
this.canvas = this.ctx = this.lines = null;
|
||
|
this._mouseDestroy();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if (!$.Widget.prototype._destroy) {
|
||
|
$.extend(signatureOverrides, {
|
||
|
/* Remove the signature functionality. */
|
||
|
destroy: function() {
|
||
|
this._destroy();
|
||
|
$.Widget.prototype.destroy.call(this); // Base widget handling
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if ($.Widget.prototype._getCreateOptions === $.noop) {
|
||
|
$.extend(signatureOverrides, {
|
||
|
/* Restore the metadata functionality. */
|
||
|
_getCreateOptions: function() {
|
||
|
return $.metadata && $.metadata.get(this.element[0])[this.widgetName];
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
$.widget('kbw.signature', $.ui.mouse, signatureOverrides);
|
||
|
|
||
|
// Make some things more accessible
|
||
|
$.kbw.signature.options = $.kbw.signature.prototype.options;
|
||
|
|
||
|
})(jQuery);
|