/*! 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.
Depends on jquery.ui.widget
, jquery.ui.mouse
.
Expects HTML like:
<div></div>@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]
true
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] true
to use the style
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 = $('')[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 true
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 true
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} true
if allowed, false
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 '\n\n' +
'\n';
},
/** Convert the captured lines to an image encoded in a data:
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 data:
URL.
Note that drawing a data:
URL does not reconstruct the internal representation!
lines
being an array of arrays of points
or the text version of the JSON or SVG or a data:
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('