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.

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