You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

243 lines
7.8 KiB

  1. (function($) { // Hide scope, no $ conflict
  2. var signatureOverrides = {
  3. // Global defaults for signature
  4. options: {
  5. background: '#ffffff', // Colour of the background
  6. color: '#000000', // Colour of the signature
  7. thickness: 2, // Thickness of the lines
  8. guideline: false, // Add a guide line or not?
  9. guidelineColor: '#a0a0a0', // Guide line colour
  10. guidelineOffset: 50, // Guide line offset from the bottom
  11. guidelineIndent: 10, // Guide line indent from the edges
  12. notAvailable: 'Your browser doesn\'t support signing', // Error message when no canvas
  13. syncField: null, // Selector for synchronised text field
  14. change: null // Callback when signature changed
  15. },
  16. /* Initialise a new signature area. */
  17. _create: function() {
  18. this.element.addClass(this.widgetFullName || this.widgetBaseClass);
  19. try {
  20. this.canvas = $('<canvas width="' + 170 + '" height="' +
  21. 50 + '">' + '' + '</canvas>')[0];
  22. this.element.prepend(this.canvas);
  23. this.element.find('img').remove();
  24. this.ctx = this.canvas.getContext('2d');
  25. }
  26. catch (e) {
  27. $(this.canvas).remove();
  28. this.resize = true;
  29. this.canvas = document.createElement('canvas');
  30. this.canvas.setAttribute('width', this.element.width());
  31. this.canvas.setAttribute('height', this.element.height());
  32. this.canvas.innerHTML = this.options.notAvailable;
  33. this.element.append(this.canvas);
  34. if (G_vmlCanvasManager) { // Requires excanvas.js
  35. G_vmlCanvasManager.initElement(this.canvas);
  36. }
  37. this.ctx = this.canvas.getContext('2d');
  38. }
  39. this._refresh(true);
  40. this._mouseInit();
  41. },
  42. /* Refresh the appearance of the signature area.
  43. @param init (boolean, internal) true if initialising */
  44. _refresh: function(init) {
  45. if (this.resize) {
  46. var parent = $(this.canvas);
  47. $('div', this.canvas).css({width: parent.width(), height: parent.height()});
  48. }
  49. this.ctx.fillStyle = this.options.background;
  50. this.ctx.strokeStyle = this.options.color;
  51. this.ctx.lineWidth = this.options.thickness;
  52. this.ctx.lineCap = 'round';
  53. this.ctx.lineJoin = 'round';
  54. this.clear(init);
  55. },
  56. /* Clear the signature area.
  57. @param init (boolean, internal) true if initialising */
  58. clear: function(init) {
  59. this.ctx.fillRect(0, 0, this.element.width(), this.element.height());
  60. if (this.options.guideline) {
  61. this.ctx.save();
  62. this.ctx.strokeStyle = this.options.guidelineColor;
  63. this.ctx.lineWidth = 1;
  64. this.ctx.beginPath();
  65. this.ctx.moveTo(this.options.guidelineIndent,
  66. this.element.height() - this.options.guidelineOffset);
  67. this.ctx.lineTo(this.element.width() - this.options.guidelineIndent,
  68. this.element.height() - this.options.guidelineOffset);
  69. this.ctx.stroke();
  70. this.ctx.restore();
  71. }
  72. this.lines = [];
  73. if (!init) {
  74. this._changed();
  75. }
  76. },
  77. /* Synchronise changes and trigger change event.
  78. @param event (Event) the triggering event */
  79. _changed: function(event) {
  80. if (this.options.syncField) {
  81. $(this.options.syncField).val(this.toJSON());
  82. }
  83. this._trigger('change', event, {});
  84. },
  85. /* Custom options handling.
  86. @param options (object) the new option values */
  87. _setOptions: function(options) {
  88. if (this._superApply) {
  89. this._superApply(arguments); // Base widget handling
  90. }
  91. else {
  92. $.Widget.prototype._setOptions.apply(this, arguments); // Base widget handling
  93. }
  94. this._refresh();
  95. },
  96. /* Determine if dragging can start.
  97. @param event (Event) the triggering mouse event
  98. @return (boolean) true if allowed, false if not */
  99. _mouseCapture: function(event) {
  100. return !this.options.disabled;
  101. },
  102. /* Start a new line.
  103. @param event (Event) the triggering mouse event */
  104. _mouseStart: function(event) {
  105. this.offset = this.element.offset();
  106. this.offset.left -= document.documentElement.scrollLeft || document.body.scrollLeft;
  107. this.offset.top -= document.documentElement.scrollTop || document.body.scrollTop;
  108. this.lastPoint = [this._round(event.clientX - this.offset.left),
  109. this._round(event.clientY - this.offset.top)];
  110. this.curLine = [this.lastPoint];
  111. this.lines.push(this.curLine);
  112. },
  113. /* Track the mouse.
  114. @param event (Event) the triggering mouse event */
  115. _mouseDrag: function(event) {
  116. var point = [this._round(event.clientX - this.offset.left),
  117. this._round(event.clientY - this.offset.top)];
  118. this.curLine.push(point);
  119. this.ctx.beginPath();
  120. this.ctx.moveTo(this.lastPoint[0], this.lastPoint[1]);
  121. this.ctx.lineTo(point[0], point[1]);
  122. this.ctx.stroke();
  123. this.lastPoint = point;
  124. },
  125. /* End a line.
  126. @param event (Event) the triggering mouse event */
  127. _mouseStop: function(event) {
  128. this.lastPoint = null;
  129. this.curLine = null;
  130. this._changed(event);
  131. },
  132. /* Round to two decimal points.
  133. @param value (number) the value to round
  134. @return (number) the rounded value */
  135. _round: function(value) {
  136. return Math.round(value * 100) / 100;
  137. },
  138. /* Convert the captured lines to JSON text.
  139. @return (string) the JSON text version of the lines */
  140. toJSON: function() {
  141. return '{"lines":[' + $.map(this.lines, function(line) {
  142. return '[' + $.map(line, function(point) {
  143. return '[' + point + ']';
  144. }) + ']';
  145. }) + ']}';
  146. },
  147. /* Convert the captured lines to SVG text.
  148. @return (string) the SVG text version of the lines */
  149. toSVG: function() {
  150. return '<?xml version="1.0"?>\n<!DOCTYPE svg PUBLIC ' +
  151. '"-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
  152. '<svg xmlns="http://www.w3.org/2000/svg" width="15cm" height="15cm">\n' +
  153. ' <g fill="' + this.options.background + '">\n' +
  154. ' <rect x="0" y="0" width="' + this.canvas.width +
  155. '" height="' + this.canvas.height + '"/>\n' +
  156. ' <g fill="none" stroke="' + this.options.color + '" stroke-width="' +
  157. this.options.thickness + '">\n'+
  158. $.map(this.lines, function(line) {
  159. return ' <polyline points="' +
  160. $.map(line, function(point) { return point + ''; }).join(' ') + '"/>\n';
  161. }).join('') +
  162. ' </g>\n </g>\n</svg>\n';
  163. },
  164. /* Draw a signature from its JSON description.
  165. @param sigJSON (object) object with attribute lines
  166. being an array of arrays of points or
  167. (string) text version of the JSON */
  168. draw: function(sigJSON) {
  169. this.clear(true);
  170. if (typeof sigJSON === 'string') {
  171. sigJSON = $.parseJSON(sigJSON);
  172. }
  173. this.lines = sigJSON.lines || [];
  174. var ctx = this.ctx;
  175. $.each(this.lines, function() {
  176. ctx.beginPath();
  177. $.each(this, function(i) {
  178. ctx[i === 0 ? 'moveTo' : 'lineTo'](this[0], this[1]);
  179. });
  180. ctx.stroke();
  181. });
  182. this._changed();
  183. },
  184. /* Determine whether or not any drawing has occurred.
  185. @return (boolean) true if not signed, false if signed */
  186. isEmpty: function() {
  187. return this.lines.length === 0;
  188. },
  189. /* Remove the signature functionality. */
  190. _destroy: function() {
  191. this.element.removeClass(this.widgetFullName || this.widgetBaseClass);
  192. $(this.canvas).remove();
  193. this.canvas = this.ctx = this.lines = null;
  194. this._mouseDestroy();
  195. }
  196. };
  197. if (!$.Widget.prototype._destroy) {
  198. $.extend(signatureOverrides, {
  199. /* Remove the signature functionality. */
  200. destroy: function() {
  201. this._destroy();
  202. $.Widget.prototype.destroy.call(this); // Base widget handling
  203. }
  204. });
  205. }
  206. if($.Widget.prototype._getCreateOptions === $.noop) {
  207. $.extend(signatureOverrides, {
  208. /* Restore the metadata functionality. */
  209. _getCreateOptions: function() {
  210. return $.metadata && $.metadata.get(this.element[0])[this.widgetName];
  211. }
  212. });
  213. }
  214. /* Signature capture and display.
  215. Depends on jquery.ui.widget, jquery.ui.mouse. */
  216. $.widget('kbw.signature', $.ui.mouse, signatureOverrides);
  217. // Make some things more accessible
  218. $.kbw.signature.options = $.kbw.signature.prototype.options;
  219. })(jQuery);