active_scaffold_signaturepad 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.textile +61 -0
- data/app/assets/images/active_scaffold_signaturepad/pen.cur +0 -0
- data/app/assets/javascripts/active_scaffold_signaturepad.js.erb +34 -0
- data/app/assets/javascripts/jquery.signaturepad/jquery.signaturepad.js +891 -0
- data/app/assets/javascripts/jquery.signaturepad/json2.min.js +28 -0
- data/app/assets/stylesheets/active_scaffold_signaturepad.scss +171 -0
- data/config/locales/en.yml +3 -0
- data/config/locales/es.yml +3 -0
- data/lib/active_scaffold_signaturepad/engine.rb +4 -0
- data/lib/active_scaffold_signaturepad/version.rb +9 -0
- data/lib/active_scaffold_signaturepad/view_helpers.rb +44 -0
- data/lib/active_scaffold_signaturepad.rb +12 -0
- metadata +101 -0
@@ -0,0 +1,891 @@
|
|
1
|
+
/**
|
2
|
+
* Usage for accepting signatures:
|
3
|
+
* $('.sigPad').signaturePad()
|
4
|
+
*
|
5
|
+
* Usage for displaying previous signatures:
|
6
|
+
* $('.sigPad').signaturePad({displayOnly:true}).regenerate(sig)
|
7
|
+
* or
|
8
|
+
* var api = $('.sigPad').signaturePad({displayOnly:true})
|
9
|
+
* api.regenerate(sig)
|
10
|
+
*/
|
11
|
+
(function ($) {
|
12
|
+
|
13
|
+
function SignaturePad (selector, options) {
|
14
|
+
/**
|
15
|
+
* Reference to the object for use in public methods
|
16
|
+
*
|
17
|
+
* @private
|
18
|
+
*
|
19
|
+
* @type {Object}
|
20
|
+
*/
|
21
|
+
var self = this
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Holds the merged default settings and user passed settings
|
25
|
+
*
|
26
|
+
* @private
|
27
|
+
*
|
28
|
+
* @type {Object}
|
29
|
+
*/
|
30
|
+
, settings = $.extend({}, $.fn.signaturePad.defaults, options)
|
31
|
+
|
32
|
+
/**
|
33
|
+
* The current context, as passed by jQuery, of selected items
|
34
|
+
*
|
35
|
+
* @private
|
36
|
+
*
|
37
|
+
* @type {Object}
|
38
|
+
*/
|
39
|
+
, context = $(selector)
|
40
|
+
|
41
|
+
/**
|
42
|
+
* jQuery reference to the canvas element inside the signature pad
|
43
|
+
*
|
44
|
+
* @private
|
45
|
+
*
|
46
|
+
* @type {Object}
|
47
|
+
*/
|
48
|
+
, canvas = $(settings.canvas, context)
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Dom reference to the canvas element inside the signature pad
|
52
|
+
*
|
53
|
+
* @private
|
54
|
+
*
|
55
|
+
* @type {Object}
|
56
|
+
*/
|
57
|
+
, element = canvas.get(0)
|
58
|
+
|
59
|
+
/**
|
60
|
+
* The drawing context for the signature canvas
|
61
|
+
*
|
62
|
+
* @private
|
63
|
+
*
|
64
|
+
* @type {Object}
|
65
|
+
*/
|
66
|
+
, canvasContext = null
|
67
|
+
|
68
|
+
/**
|
69
|
+
* Holds the previous point of drawing
|
70
|
+
* Disallows drawing over the same location to make lines more delicate
|
71
|
+
*
|
72
|
+
* @private
|
73
|
+
*
|
74
|
+
* @type {Object}
|
75
|
+
*/
|
76
|
+
, previous = {'x': null, 'y': null}
|
77
|
+
|
78
|
+
/**
|
79
|
+
* An array holding all the points and lines to generate the signature
|
80
|
+
* Each item is an object like:
|
81
|
+
* {
|
82
|
+
* mx: moveTo x coordinate
|
83
|
+
* my: moveTo y coordinate
|
84
|
+
* lx: lineTo x coordinate
|
85
|
+
* lx: lineTo y coordinate
|
86
|
+
* }
|
87
|
+
*
|
88
|
+
* @private
|
89
|
+
*
|
90
|
+
* @type {Array}
|
91
|
+
*/
|
92
|
+
, output = []
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Stores a timeout for when the mouse leaves the canvas
|
96
|
+
* If the mouse has left the canvas for a specific amount of time
|
97
|
+
* Stops drawing on the canvas
|
98
|
+
*
|
99
|
+
* @private
|
100
|
+
*
|
101
|
+
* @type {Object}
|
102
|
+
*/
|
103
|
+
, mouseLeaveTimeout = false
|
104
|
+
|
105
|
+
/**
|
106
|
+
* Whether the mouse button is currently pressed down or not
|
107
|
+
*
|
108
|
+
* @private
|
109
|
+
*
|
110
|
+
* @type {Boolean}
|
111
|
+
*/
|
112
|
+
, mouseButtonDown = false
|
113
|
+
|
114
|
+
/**
|
115
|
+
* Whether the browser is a touch event browser or not
|
116
|
+
*
|
117
|
+
* @private
|
118
|
+
*
|
119
|
+
* @type {Boolean}
|
120
|
+
*/
|
121
|
+
, touchable = false
|
122
|
+
|
123
|
+
/**
|
124
|
+
* Whether events have already been bound to the canvas or not
|
125
|
+
*
|
126
|
+
* @private
|
127
|
+
*
|
128
|
+
* @type {Boolean}
|
129
|
+
*/
|
130
|
+
, eventsBound = false
|
131
|
+
|
132
|
+
/**
|
133
|
+
* Remembers the default font-size when typing, and will allow it to be scaled for bigger/smaller names
|
134
|
+
*
|
135
|
+
* @private
|
136
|
+
*
|
137
|
+
* @type {Number}
|
138
|
+
*/
|
139
|
+
, typeItDefaultFontSize = 30
|
140
|
+
|
141
|
+
/**
|
142
|
+
* Remembers the current font-size when typing
|
143
|
+
*
|
144
|
+
* @private
|
145
|
+
*
|
146
|
+
* @type {Number}
|
147
|
+
*/
|
148
|
+
, typeItCurrentFontSize = typeItDefaultFontSize
|
149
|
+
|
150
|
+
/**
|
151
|
+
* Remembers how many characters are in the name field, to help with the scaling feature
|
152
|
+
*
|
153
|
+
* @private
|
154
|
+
*
|
155
|
+
* @type {Number}
|
156
|
+
*/
|
157
|
+
, typeItNumChars = 0
|
158
|
+
|
159
|
+
|
160
|
+
/**
|
161
|
+
* Clears the mouseLeaveTimeout
|
162
|
+
* Resets some other variables that may be active
|
163
|
+
*
|
164
|
+
* @private
|
165
|
+
*/
|
166
|
+
function clearMouseLeaveTimeout () {
|
167
|
+
clearTimeout(mouseLeaveTimeout)
|
168
|
+
mouseLeaveTimeout = false
|
169
|
+
mouseButtonDown = false
|
170
|
+
}
|
171
|
+
|
172
|
+
/**
|
173
|
+
* Draws a line on canvas using the mouse position
|
174
|
+
* Checks previous position to not draw over top of previous drawing
|
175
|
+
* (makes the line really thick and poorly anti-aliased)
|
176
|
+
*
|
177
|
+
* @private
|
178
|
+
*
|
179
|
+
* @param {Object} e The event object
|
180
|
+
* @param {Number} newYOffset A pixel value for drawing the newY, used for drawing a single dot on click
|
181
|
+
*/
|
182
|
+
function drawLine (e, newYOffset) {
|
183
|
+
var offset, newX, newY
|
184
|
+
|
185
|
+
e.preventDefault()
|
186
|
+
|
187
|
+
offset = $(e.target).offset()
|
188
|
+
|
189
|
+
clearTimeout(mouseLeaveTimeout)
|
190
|
+
mouseLeaveTimeout = false
|
191
|
+
|
192
|
+
if (typeof e.targetTouches !== 'undefined') {
|
193
|
+
newX = Math.floor(e.targetTouches[0].pageX - offset.left)
|
194
|
+
newY = Math.floor(e.targetTouches[0].pageY - offset.top)
|
195
|
+
} else {
|
196
|
+
newX = Math.floor(e.pageX - offset.left)
|
197
|
+
newY = Math.floor(e.pageY - offset.top)
|
198
|
+
}
|
199
|
+
|
200
|
+
if (previous.x === newX && previous.y === newY)
|
201
|
+
return true
|
202
|
+
|
203
|
+
if (previous.x === null)
|
204
|
+
previous.x = newX
|
205
|
+
|
206
|
+
if (previous.y === null)
|
207
|
+
previous.y = newY
|
208
|
+
|
209
|
+
if (newYOffset)
|
210
|
+
newY += newYOffset
|
211
|
+
|
212
|
+
canvasContext.beginPath()
|
213
|
+
canvasContext.moveTo(previous.x, previous.y)
|
214
|
+
canvasContext.lineTo(newX, newY)
|
215
|
+
canvasContext.lineCap = settings.penCap
|
216
|
+
canvasContext.stroke()
|
217
|
+
canvasContext.closePath()
|
218
|
+
|
219
|
+
output.push({
|
220
|
+
'lx' : newX
|
221
|
+
, 'ly' : newY
|
222
|
+
, 'mx' : previous.x
|
223
|
+
, 'my' : previous.y
|
224
|
+
})
|
225
|
+
|
226
|
+
previous.x = newX
|
227
|
+
previous.y = newY
|
228
|
+
|
229
|
+
if (settings.onDraw && typeof settings.onDraw === 'function')
|
230
|
+
settings.onDraw.apply(self)
|
231
|
+
}
|
232
|
+
|
233
|
+
/**
|
234
|
+
* Callback wrapper for executing stopDrawing without the event
|
235
|
+
* Put up here so that it can be removed at a later time
|
236
|
+
*
|
237
|
+
* @private
|
238
|
+
*/
|
239
|
+
function stopDrawingWrapper () {
|
240
|
+
stopDrawing()
|
241
|
+
}
|
242
|
+
|
243
|
+
/**
|
244
|
+
* Callback registered to mouse/touch events of the canvas
|
245
|
+
* Stops the drawing abilities
|
246
|
+
*
|
247
|
+
* @private
|
248
|
+
*
|
249
|
+
* @param {Object} e The event object
|
250
|
+
*/
|
251
|
+
function stopDrawing (e) {
|
252
|
+
if (!!e) {
|
253
|
+
drawLine(e, 1)
|
254
|
+
} else {
|
255
|
+
if (touchable) {
|
256
|
+
canvas.each(function () {
|
257
|
+
this.removeEventListener('touchmove', drawLine)
|
258
|
+
// this.removeEventListener('MSPointerMove', drawLine)
|
259
|
+
})
|
260
|
+
} else {
|
261
|
+
canvas.unbind('mousemove.signaturepad')
|
262
|
+
}
|
263
|
+
|
264
|
+
if (output.length > 0 && settings.onDrawEnd && typeof settings.onDrawEnd === 'function')
|
265
|
+
settings.onDrawEnd.apply(self)
|
266
|
+
}
|
267
|
+
|
268
|
+
previous.x = null
|
269
|
+
previous.y = null
|
270
|
+
|
271
|
+
if (settings.output && output.length > 0)
|
272
|
+
$(settings.output, context).val(JSON.stringify(output))
|
273
|
+
}
|
274
|
+
|
275
|
+
/**
|
276
|
+
* Draws the signature line
|
277
|
+
*
|
278
|
+
* @private
|
279
|
+
*/
|
280
|
+
function drawSigLine () {
|
281
|
+
if (!settings.lineWidth)
|
282
|
+
return false
|
283
|
+
|
284
|
+
canvasContext.beginPath()
|
285
|
+
canvasContext.lineWidth = settings.lineWidth
|
286
|
+
canvasContext.strokeStyle = settings.lineColour
|
287
|
+
canvasContext.moveTo(settings.lineMargin, settings.lineTop)
|
288
|
+
canvasContext.lineTo(element.width - settings.lineMargin, settings.lineTop)
|
289
|
+
canvasContext.stroke()
|
290
|
+
canvasContext.closePath()
|
291
|
+
}
|
292
|
+
|
293
|
+
/**
|
294
|
+
* Clears all drawings off the canvas and redraws the signature line
|
295
|
+
*
|
296
|
+
* @private
|
297
|
+
*/
|
298
|
+
function clearCanvas () {
|
299
|
+
canvasContext.clearRect(0, 0, element.width, element.height)
|
300
|
+
canvasContext.fillStyle = settings.bgColour
|
301
|
+
canvasContext.fillRect(0, 0, element.width, element.height)
|
302
|
+
|
303
|
+
if (!settings.displayOnly)
|
304
|
+
drawSigLine()
|
305
|
+
|
306
|
+
canvasContext.lineWidth = settings.penWidth
|
307
|
+
canvasContext.strokeStyle = settings.penColour
|
308
|
+
|
309
|
+
$(settings.output, context).val('')
|
310
|
+
output = []
|
311
|
+
|
312
|
+
stopDrawing()
|
313
|
+
}
|
314
|
+
|
315
|
+
/**
|
316
|
+
* Callback registered to mouse/touch events of the canvas
|
317
|
+
* Draws a line at the mouse cursor location, starting a new line if necessary
|
318
|
+
*
|
319
|
+
* @private
|
320
|
+
*
|
321
|
+
* @param {Object} e The event object
|
322
|
+
* @param {Object} o The object context registered to the event; canvas
|
323
|
+
*/
|
324
|
+
function onMouseMove(e, o) {
|
325
|
+
if (previous.x == null) {
|
326
|
+
drawLine(e, 1)
|
327
|
+
} else {
|
328
|
+
drawLine(e, o)
|
329
|
+
}
|
330
|
+
}
|
331
|
+
|
332
|
+
/**
|
333
|
+
* Callback registered to mouse/touch events of canvas
|
334
|
+
* Triggers the drawLine function
|
335
|
+
*
|
336
|
+
* @private
|
337
|
+
*
|
338
|
+
* @param {Object} e The event object
|
339
|
+
* @param {Object} touchObject The object context registered to the event; canvas
|
340
|
+
*/
|
341
|
+
function startDrawing (e, touchObject) {
|
342
|
+
if (touchable) {
|
343
|
+
touchObject.addEventListener('touchmove', onMouseMove, false)
|
344
|
+
// touchObject.addEventListener('MSPointerMove', onMouseMove, false)
|
345
|
+
} else {
|
346
|
+
canvas.bind('mousemove.signaturepad', onMouseMove)
|
347
|
+
}
|
348
|
+
|
349
|
+
// Draws a single point on initial mouse down, for people with periods in their name
|
350
|
+
drawLine(e, 1)
|
351
|
+
}
|
352
|
+
|
353
|
+
/**
|
354
|
+
* Removes all the mouse events from the canvas
|
355
|
+
*
|
356
|
+
* @private
|
357
|
+
*/
|
358
|
+
function disableCanvas () {
|
359
|
+
eventsBound = false
|
360
|
+
|
361
|
+
canvas.each(function () {
|
362
|
+
if (this.removeEventListener) {
|
363
|
+
this.removeEventListener('touchend', stopDrawingWrapper)
|
364
|
+
this.removeEventListener('touchcancel', stopDrawingWrapper)
|
365
|
+
this.removeEventListener('touchmove', drawLine)
|
366
|
+
// this.removeEventListener('MSPointerUp', stopDrawingWrapper)
|
367
|
+
// this.removeEventListener('MSPointerCancel', stopDrawingWrapper)
|
368
|
+
// this.removeEventListener('MSPointerMove', drawLine)
|
369
|
+
}
|
370
|
+
|
371
|
+
if (this.ontouchstart)
|
372
|
+
this.ontouchstart = null;
|
373
|
+
})
|
374
|
+
|
375
|
+
$(document).unbind('mouseup.signaturepad')
|
376
|
+
canvas.unbind('mousedown.signaturepad')
|
377
|
+
canvas.unbind('mousemove.signaturepad')
|
378
|
+
canvas.unbind('mouseleave.signaturepad')
|
379
|
+
|
380
|
+
$(settings.clear, context).unbind('click.signaturepad')
|
381
|
+
}
|
382
|
+
|
383
|
+
/**
|
384
|
+
* Lazy touch event detection
|
385
|
+
* Uses the first press on the canvas to detect either touch or mouse reliably
|
386
|
+
* Will then bind other events as needed
|
387
|
+
*
|
388
|
+
* @private
|
389
|
+
*
|
390
|
+
* @param {Object} e The event object
|
391
|
+
*/
|
392
|
+
function initDrawEvents (e) {
|
393
|
+
if (eventsBound)
|
394
|
+
return false
|
395
|
+
|
396
|
+
eventsBound = true
|
397
|
+
|
398
|
+
// Closes open keyboards to free up space
|
399
|
+
$('input').blur();
|
400
|
+
|
401
|
+
if (typeof e.targetTouches !== 'undefined')
|
402
|
+
touchable = true
|
403
|
+
|
404
|
+
if (touchable) {
|
405
|
+
canvas.each(function () {
|
406
|
+
this.addEventListener('touchend', stopDrawingWrapper, false)
|
407
|
+
this.addEventListener('touchcancel', stopDrawingWrapper, false)
|
408
|
+
// this.addEventListener('MSPointerUp', stopDrawingWrapper, false)
|
409
|
+
// this.addEventListener('MSPointerCancel', stopDrawingWrapper, false)
|
410
|
+
})
|
411
|
+
|
412
|
+
canvas.unbind('mousedown.signaturepad')
|
413
|
+
} else {
|
414
|
+
$(document).bind('mouseup.signaturepad', function () {
|
415
|
+
if (mouseButtonDown) {
|
416
|
+
stopDrawing()
|
417
|
+
clearMouseLeaveTimeout()
|
418
|
+
}
|
419
|
+
})
|
420
|
+
canvas.bind('mouseleave.signaturepad', function (e) {
|
421
|
+
if (mouseButtonDown) stopDrawing(e)
|
422
|
+
|
423
|
+
if (mouseButtonDown && !mouseLeaveTimeout) {
|
424
|
+
mouseLeaveTimeout = setTimeout(function () {
|
425
|
+
stopDrawing()
|
426
|
+
clearMouseLeaveTimeout()
|
427
|
+
}, 500)
|
428
|
+
}
|
429
|
+
})
|
430
|
+
|
431
|
+
canvas.each(function () {
|
432
|
+
this.ontouchstart = null
|
433
|
+
})
|
434
|
+
}
|
435
|
+
}
|
436
|
+
|
437
|
+
/**
|
438
|
+
* Triggers the abilities to draw on the canvas
|
439
|
+
* Sets up mouse/touch events, hides and shows descriptions and sets current classes
|
440
|
+
*
|
441
|
+
* @private
|
442
|
+
*/
|
443
|
+
function drawIt () {
|
444
|
+
$(settings.typed, context).hide()
|
445
|
+
clearCanvas()
|
446
|
+
|
447
|
+
canvas.each(function () {
|
448
|
+
this.ontouchstart = function (e) {
|
449
|
+
e.preventDefault()
|
450
|
+
mouseButtonDown = true
|
451
|
+
initDrawEvents(e)
|
452
|
+
startDrawing(e, this)
|
453
|
+
}
|
454
|
+
})
|
455
|
+
|
456
|
+
canvas.bind('mousedown.signaturepad', function (e) {
|
457
|
+
e.preventDefault()
|
458
|
+
|
459
|
+
// Only allow left mouse clicks to trigger signature drawing
|
460
|
+
if (e.which > 1) return false
|
461
|
+
|
462
|
+
mouseButtonDown = true
|
463
|
+
initDrawEvents(e)
|
464
|
+
startDrawing(e)
|
465
|
+
})
|
466
|
+
|
467
|
+
$(settings.clear, context).bind('click.signaturepad', function (e) { e.preventDefault(); clearCanvas() })
|
468
|
+
|
469
|
+
$(settings.typeIt, context).bind('click.signaturepad', function (e) { e.preventDefault(); typeIt() })
|
470
|
+
$(settings.drawIt, context).unbind('click.signaturepad')
|
471
|
+
$(settings.drawIt, context).bind('click.signaturepad', function (e) { e.preventDefault() })
|
472
|
+
|
473
|
+
$(settings.typeIt, context).removeClass(settings.currentClass)
|
474
|
+
$(settings.drawIt, context).addClass(settings.currentClass)
|
475
|
+
$(settings.sig, context).addClass(settings.currentClass)
|
476
|
+
|
477
|
+
$(settings.typeItDesc, context).hide()
|
478
|
+
$(settings.drawItDesc, context).show()
|
479
|
+
$(settings.clear, context).show()
|
480
|
+
}
|
481
|
+
|
482
|
+
/**
|
483
|
+
* Triggers the abilities to type in the input for generating a signature
|
484
|
+
* Sets up mouse events, hides and shows descriptions and sets current classes
|
485
|
+
*
|
486
|
+
* @private
|
487
|
+
*/
|
488
|
+
function typeIt () {
|
489
|
+
clearCanvas()
|
490
|
+
disableCanvas()
|
491
|
+
$(settings.typed, context).show()
|
492
|
+
|
493
|
+
$(settings.drawIt, context).bind('click.signaturepad', function (e) { e.preventDefault(); drawIt() })
|
494
|
+
$(settings.typeIt, context).unbind('click.signaturepad')
|
495
|
+
$(settings.typeIt, context).bind('click.signaturepad', function (e) { e.preventDefault() })
|
496
|
+
|
497
|
+
$(settings.output, context).val('')
|
498
|
+
|
499
|
+
$(settings.drawIt, context).removeClass(settings.currentClass)
|
500
|
+
$(settings.typeIt, context).addClass(settings.currentClass)
|
501
|
+
$(settings.sig, context).removeClass(settings.currentClass)
|
502
|
+
|
503
|
+
$(settings.drawItDesc, context).hide()
|
504
|
+
$(settings.clear, context).hide()
|
505
|
+
$(settings.typeItDesc, context).show()
|
506
|
+
|
507
|
+
typeItCurrentFontSize = typeItDefaultFontSize = $(settings.typed, context).css('font-size').replace(/px/, '')
|
508
|
+
}
|
509
|
+
|
510
|
+
/**
|
511
|
+
* Callback registered on key up and blur events for input field
|
512
|
+
* Writes the text fields value as Html into an element
|
513
|
+
*
|
514
|
+
* @private
|
515
|
+
*
|
516
|
+
* @param {String} val The value of the input field
|
517
|
+
*/
|
518
|
+
function type (val) {
|
519
|
+
var typed = $(settings.typed, context)
|
520
|
+
, cleanedVal = $.trim(val.replace(/>/g, '>').replace(/</g, '<'))
|
521
|
+
, oldLength = typeItNumChars
|
522
|
+
, edgeOffset = typeItCurrentFontSize * 0.5
|
523
|
+
|
524
|
+
typeItNumChars = cleanedVal.length
|
525
|
+
typed.html(cleanedVal)
|
526
|
+
|
527
|
+
if (!cleanedVal) {
|
528
|
+
typed.css('font-size', typeItDefaultFontSize + 'px')
|
529
|
+
return
|
530
|
+
}
|
531
|
+
|
532
|
+
if (typeItNumChars > oldLength && typed.outerWidth() > element.width) {
|
533
|
+
while (typed.outerWidth() > element.width) {
|
534
|
+
typeItCurrentFontSize--
|
535
|
+
typed.css('font-size', typeItCurrentFontSize + 'px')
|
536
|
+
}
|
537
|
+
}
|
538
|
+
|
539
|
+
if (typeItNumChars < oldLength && typed.outerWidth() + edgeOffset < element.width && typeItCurrentFontSize < typeItDefaultFontSize) {
|
540
|
+
while (typed.outerWidth() + edgeOffset < element.width && typeItCurrentFontSize < typeItDefaultFontSize) {
|
541
|
+
typeItCurrentFontSize++
|
542
|
+
typed.css('font-size', typeItCurrentFontSize + 'px')
|
543
|
+
}
|
544
|
+
}
|
545
|
+
}
|
546
|
+
|
547
|
+
/**
|
548
|
+
* Default onBeforeValidate function to clear errors
|
549
|
+
*
|
550
|
+
* @private
|
551
|
+
*
|
552
|
+
* @param {Object} context current context object
|
553
|
+
* @param {Object} settings provided settings
|
554
|
+
*/
|
555
|
+
function onBeforeValidate (context, settings) {
|
556
|
+
$('p.' + settings.errorClass, context).remove()
|
557
|
+
context.removeClass(settings.errorClass)
|
558
|
+
$('input, label', context).removeClass(settings.errorClass)
|
559
|
+
}
|
560
|
+
|
561
|
+
/**
|
562
|
+
* Default onFormError function to show errors
|
563
|
+
*
|
564
|
+
* @private
|
565
|
+
*
|
566
|
+
* @param {Object} errors object contains validation errors (e.g. nameInvalid=true)
|
567
|
+
* @param {Object} context current context object
|
568
|
+
* @param {Object} settings provided settings
|
569
|
+
*/
|
570
|
+
function onFormError (errors, context, settings) {
|
571
|
+
if (errors.nameInvalid) {
|
572
|
+
context.prepend(['<p class="', settings.errorClass, '">', settings.errorMessage, '</p>'].join(''))
|
573
|
+
$(settings.name, context).focus()
|
574
|
+
$(settings.name, context).addClass(settings.errorClass)
|
575
|
+
$('label[for=' + $(settings.name).attr('id') + ']', context).addClass(settings.errorClass)
|
576
|
+
}
|
577
|
+
|
578
|
+
if (errors.drawInvalid)
|
579
|
+
context.prepend(['<p class="', settings.errorClass, '">', settings.errorMessageDraw, '</p>'].join(''))
|
580
|
+
}
|
581
|
+
|
582
|
+
/**
|
583
|
+
* Validates the form to confirm a name was typed in the field
|
584
|
+
* If drawOnly also confirms that the user drew a signature
|
585
|
+
*
|
586
|
+
* @private
|
587
|
+
*
|
588
|
+
* @return {Boolean}
|
589
|
+
*/
|
590
|
+
function validateForm () {
|
591
|
+
var valid = true
|
592
|
+
, errors = {drawInvalid: false, nameInvalid: false}
|
593
|
+
, onBeforeArguments = [context, settings]
|
594
|
+
, onErrorArguments = [errors, context, settings]
|
595
|
+
|
596
|
+
if (settings.onBeforeValidate && typeof settings.onBeforeValidate === 'function') {
|
597
|
+
settings.onBeforeValidate.apply(self,onBeforeArguments)
|
598
|
+
} else {
|
599
|
+
onBeforeValidate.apply(self, onBeforeArguments)
|
600
|
+
}
|
601
|
+
|
602
|
+
if (settings.drawOnly && output.length < 1) {
|
603
|
+
errors.drawInvalid = true
|
604
|
+
valid = false
|
605
|
+
}
|
606
|
+
|
607
|
+
if ($(settings.name, context).val() === '') {
|
608
|
+
errors.nameInvalid = true
|
609
|
+
valid = false
|
610
|
+
}
|
611
|
+
|
612
|
+
if (settings.onFormError && typeof settings.onFormError === 'function') {
|
613
|
+
settings.onFormError.apply(self,onErrorArguments)
|
614
|
+
} else {
|
615
|
+
onFormError.apply(self, onErrorArguments)
|
616
|
+
}
|
617
|
+
|
618
|
+
return valid
|
619
|
+
}
|
620
|
+
|
621
|
+
/**
|
622
|
+
* Redraws the signature on a specific canvas
|
623
|
+
*
|
624
|
+
* @private
|
625
|
+
*
|
626
|
+
* @param {Array} paths the signature JSON
|
627
|
+
* @param {Object} context the canvas context to draw on
|
628
|
+
* @param {Boolean} saveOutput whether to write the path to the output array or not
|
629
|
+
*/
|
630
|
+
function drawSignature (paths, context, saveOutput) {
|
631
|
+
for(var i in paths) {
|
632
|
+
if (typeof paths[i] === 'object') {
|
633
|
+
context.beginPath()
|
634
|
+
context.moveTo(paths[i].mx, paths[i].my)
|
635
|
+
context.lineTo(paths[i].lx, paths[i].ly)
|
636
|
+
context.lineCap = settings.penCap
|
637
|
+
context.stroke()
|
638
|
+
context.closePath()
|
639
|
+
|
640
|
+
if (saveOutput) {
|
641
|
+
output.push({
|
642
|
+
'lx' : paths[i].lx
|
643
|
+
, 'ly' : paths[i].ly
|
644
|
+
, 'mx' : paths[i].mx
|
645
|
+
, 'my' : paths[i].my
|
646
|
+
})
|
647
|
+
}
|
648
|
+
}
|
649
|
+
}
|
650
|
+
}
|
651
|
+
|
652
|
+
/**
|
653
|
+
* Initialisation function, called immediately after all declarations
|
654
|
+
* Technically public, but only should be used internally
|
655
|
+
*
|
656
|
+
* @private
|
657
|
+
*/
|
658
|
+
function init () {
|
659
|
+
// Fixes the jQuery.fn.offset() function for Mobile Safari Browsers i.e. iPod Touch, iPad and iPhone
|
660
|
+
// https://gist.github.com/661844
|
661
|
+
// http://bugs.jquery.com/ticket/6446
|
662
|
+
if (parseFloat(((/CPU.+OS ([0-9_]{3}).*AppleWebkit.*Mobile/i.exec(navigator.userAgent)) || [0,'4_2'])[1].replace('_','.')) < 4.1) {
|
663
|
+
$.fn.Oldoffset = $.fn.offset;
|
664
|
+
$.fn.offset = function () {
|
665
|
+
var result = $(this).Oldoffset()
|
666
|
+
result.top -= window.scrollY
|
667
|
+
result.left -= window.scrollX
|
668
|
+
|
669
|
+
return result
|
670
|
+
}
|
671
|
+
}
|
672
|
+
|
673
|
+
// Disable selection on the typed div and canvas
|
674
|
+
$(settings.typed, context).bind('selectstart.signaturepad', function (e) { return $(e.target).is(':input') })
|
675
|
+
canvas.bind('selectstart.signaturepad', function (e) { return $(e.target).is(':input') })
|
676
|
+
|
677
|
+
if (!element.getContext && FlashCanvas)
|
678
|
+
FlashCanvas.initElement(element)
|
679
|
+
|
680
|
+
if (element.getContext) {
|
681
|
+
canvasContext = element.getContext('2d')
|
682
|
+
|
683
|
+
$(settings.sig, context).show()
|
684
|
+
|
685
|
+
if (!settings.displayOnly) {
|
686
|
+
if (!settings.drawOnly) {
|
687
|
+
$(settings.name, context).bind('keyup.signaturepad', function () {
|
688
|
+
type($(this).val())
|
689
|
+
})
|
690
|
+
|
691
|
+
$(settings.name, context).bind('blur.signaturepad', function () {
|
692
|
+
type($(this).val())
|
693
|
+
})
|
694
|
+
|
695
|
+
$(settings.drawIt, context).bind('click.signaturepad', function (e) {
|
696
|
+
e.preventDefault()
|
697
|
+
drawIt()
|
698
|
+
})
|
699
|
+
}
|
700
|
+
|
701
|
+
if (settings.drawOnly || settings.defaultAction === 'drawIt') {
|
702
|
+
drawIt()
|
703
|
+
} else {
|
704
|
+
typeIt()
|
705
|
+
}
|
706
|
+
|
707
|
+
if (settings.validateFields) {
|
708
|
+
if ($(selector).is('form')) {
|
709
|
+
$(selector).bind('submit.signaturepad', function () { return validateForm() })
|
710
|
+
} else {
|
711
|
+
$(selector).parents('form').bind('submit.signaturepad', function () { return validateForm() })
|
712
|
+
}
|
713
|
+
}
|
714
|
+
|
715
|
+
$(settings.sigNav, context).show()
|
716
|
+
}
|
717
|
+
}
|
718
|
+
}
|
719
|
+
|
720
|
+
$.extend(self, {
|
721
|
+
/**
|
722
|
+
* A property to store the current version of Signature Pad
|
723
|
+
*/
|
724
|
+
signaturePad : '{{version}}'
|
725
|
+
|
726
|
+
/**
|
727
|
+
* Initializes SignaturePad
|
728
|
+
*/
|
729
|
+
, init : function () { init() }
|
730
|
+
|
731
|
+
/**
|
732
|
+
* Allows options to be updated after initialization
|
733
|
+
*
|
734
|
+
* @param {Object} options An object containing the options to be changed
|
735
|
+
*/
|
736
|
+
, updateOptions : function (options) {
|
737
|
+
$.extend(settings, options)
|
738
|
+
}
|
739
|
+
|
740
|
+
/**
|
741
|
+
* Regenerates a signature on the canvas using an array of objects
|
742
|
+
* Follows same format as object property
|
743
|
+
* @see var object
|
744
|
+
*
|
745
|
+
* @param {Array} paths An array of the lines and points
|
746
|
+
*/
|
747
|
+
, regenerate : function (paths) {
|
748
|
+
self.clearCanvas()
|
749
|
+
$(settings.typed, context).hide()
|
750
|
+
|
751
|
+
if (typeof paths === 'string')
|
752
|
+
paths = JSON.parse(paths)
|
753
|
+
|
754
|
+
drawSignature(paths, canvasContext, true)
|
755
|
+
|
756
|
+
if (settings.output && $(settings.output, context).length > 0)
|
757
|
+
$(settings.output, context).val(JSON.stringify(output))
|
758
|
+
}
|
759
|
+
|
760
|
+
/**
|
761
|
+
* Clears the canvas
|
762
|
+
* Redraws the background colour and the signature line
|
763
|
+
*/
|
764
|
+
, clearCanvas : function () { clearCanvas() }
|
765
|
+
|
766
|
+
/**
|
767
|
+
* Returns the signature as a Js array
|
768
|
+
*
|
769
|
+
* @return {Array}
|
770
|
+
*/
|
771
|
+
, getSignature : function () { return output }
|
772
|
+
|
773
|
+
/**
|
774
|
+
* Returns the signature as a Json string
|
775
|
+
*
|
776
|
+
* @return {String}
|
777
|
+
*/
|
778
|
+
, getSignatureString : function () { return JSON.stringify(output) }
|
779
|
+
|
780
|
+
/**
|
781
|
+
* Returns the signature as an image
|
782
|
+
* Re-draws the signature in a shadow canvas to create a clean version
|
783
|
+
*
|
784
|
+
* @return {String}
|
785
|
+
*/
|
786
|
+
, getSignatureImage : function () {
|
787
|
+
var tmpCanvas = document.createElement('canvas')
|
788
|
+
, tmpContext = null
|
789
|
+
, data = null
|
790
|
+
|
791
|
+
tmpCanvas.style.position = 'absolute'
|
792
|
+
tmpCanvas.style.top = '-999em'
|
793
|
+
tmpCanvas.width = element.width
|
794
|
+
tmpCanvas.height = element.height
|
795
|
+
document.body.appendChild(tmpCanvas)
|
796
|
+
|
797
|
+
if (!tmpCanvas.getContext && FlashCanvas)
|
798
|
+
FlashCanvas.initElement(tmpCanvas)
|
799
|
+
|
800
|
+
tmpContext = tmpCanvas.getContext('2d')
|
801
|
+
|
802
|
+
tmpContext.fillStyle = settings.bgColour
|
803
|
+
tmpContext.fillRect(0, 0, element.width, element.height)
|
804
|
+
tmpContext.lineWidth = settings.penWidth
|
805
|
+
tmpContext.strokeStyle = settings.penColour
|
806
|
+
|
807
|
+
drawSignature(output, tmpContext)
|
808
|
+
data = tmpCanvas.toDataURL.apply(tmpCanvas, arguments)
|
809
|
+
|
810
|
+
document.body.removeChild(tmpCanvas)
|
811
|
+
tmpCanvas = null
|
812
|
+
|
813
|
+
return data
|
814
|
+
}
|
815
|
+
|
816
|
+
/**
|
817
|
+
* The form validation function
|
818
|
+
* Validates that the signature has been filled in properly
|
819
|
+
* Allows it to be hooked into another validation function and called at a different time
|
820
|
+
*
|
821
|
+
* @return {Boolean}
|
822
|
+
*/
|
823
|
+
, validateForm : function () { return validateForm() }
|
824
|
+
})
|
825
|
+
}
|
826
|
+
|
827
|
+
/**
|
828
|
+
* Create the plugin
|
829
|
+
* Returns an Api which can be used to call specific methods
|
830
|
+
*
|
831
|
+
* @param {Object} options The options array
|
832
|
+
*
|
833
|
+
* @return {Object} The Api for controlling the instance
|
834
|
+
*/
|
835
|
+
$.fn.signaturePad = function (options) {
|
836
|
+
var api = null
|
837
|
+
|
838
|
+
this.each(function () {
|
839
|
+
if (!$.data(this, 'plugin-signaturePad')) {
|
840
|
+
api = new SignaturePad(this, options)
|
841
|
+
api.init()
|
842
|
+
$.data(this, 'plugin-signaturePad', api)
|
843
|
+
} else {
|
844
|
+
api = $.data(this, 'plugin-signaturePad')
|
845
|
+
api.updateOptions(options)
|
846
|
+
}
|
847
|
+
})
|
848
|
+
|
849
|
+
return api
|
850
|
+
}
|
851
|
+
|
852
|
+
/**
|
853
|
+
* Expose the defaults so they can be overwritten for multiple instances
|
854
|
+
*
|
855
|
+
* @type {Object}
|
856
|
+
*/
|
857
|
+
$.fn.signaturePad.defaults = {
|
858
|
+
defaultAction : 'typeIt' // What action should be highlighted first: typeIt or drawIt
|
859
|
+
, displayOnly : false // Initialize canvas for signature display only; ignore buttons and inputs
|
860
|
+
, drawOnly : false // Whether the to allow a typed signature or not
|
861
|
+
, canvas : 'canvas' // Selector for selecting the canvas element
|
862
|
+
, sig : '.sig' // Parts of the signature form that require Javascript (hidden by default)
|
863
|
+
, sigNav : '.sigNav' // The TypeIt/DrawIt navigation (hidden by default)
|
864
|
+
, bgColour : '#ffffff' // The colour fill for the background of the canvas; or transparent
|
865
|
+
, penColour : '#145394' // Colour of the drawing ink
|
866
|
+
, penWidth : 2 // Thickness of the pen
|
867
|
+
, penCap : 'round' // Determines how the end points of each line are drawn (values: 'butt', 'round', 'square')
|
868
|
+
, lineColour : '#ccc' // Colour of the signature line
|
869
|
+
, lineWidth : 2 // Thickness of the signature line
|
870
|
+
, lineMargin : 5 // Margin on right and left of signature line
|
871
|
+
, lineTop : 35 // Distance to draw the line from the top
|
872
|
+
, name : '.name' // The input field for typing a name
|
873
|
+
, typed : '.typed' // The Html element to accept the printed name
|
874
|
+
, clear : '.clearButton' // Button for clearing the canvas
|
875
|
+
, typeIt : '.typeIt a' // Button to trigger name typing actions (current by default)
|
876
|
+
, drawIt : '.drawIt a' // Button to trigger name drawing actions
|
877
|
+
, typeItDesc : '.typeItDesc' // The description for TypeIt actions
|
878
|
+
, drawItDesc : '.drawItDesc' // The description for DrawIt actions (hidden by default)
|
879
|
+
, output : '.output' // The hidden input field for remembering line coordinates
|
880
|
+
, currentClass : 'current' // The class used to mark items as being currently active
|
881
|
+
, validateFields : true // Whether the name, draw fields should be validated
|
882
|
+
, errorClass : 'error' // The class applied to the new error Html element
|
883
|
+
, errorMessage : 'Please enter your name' // The error message displayed on invalid submission
|
884
|
+
, errorMessageDraw : 'Please sign the document' // The error message displayed when drawOnly and no signature is drawn
|
885
|
+
, onBeforeValidate : null // Pass a callback to be used instead of the built-in function
|
886
|
+
, onFormError : null // Pass a callback to be used instead of the built-in function
|
887
|
+
, onDraw : null // Pass a callback to be used to capture the drawing process
|
888
|
+
, onDrawEnd : null // Pass a callback to be exectued after the drawing process
|
889
|
+
}
|
890
|
+
|
891
|
+
}(jQuery));
|