Capturing Signatures with Signature Pad

Episode #44 by Teacher's Avatar David Kimura

Summary

Using the Javascript Library, Signature Pad, learn how to capture user signatures and store them within your Ruby on Rails application.
rails javascript view 8:14

Resources

Summary

# application.js.erb
//= require signature_pad

# signature.js
function resizeCanvas(canvas) {
    var ratio =  Math.max(window.devicePixelRatio || 1, 1);
    canvas.width = canvas.offsetWidth * ratio;
    canvas.height = canvas.offsetHeight * ratio;
    canvas.getContext("2d").scale(ratio, ratio);
}

$(document).on('turbolinks:load', function() {
  var canvas = document.querySelector("canvas");
  if (canvas){
    canvas.height = canvas.offsetHeight;
    canvas.width = canvas.offsetWidth;
    window.onresize = resizeCanvas(canvas);
    resizeCanvas(canvas);
    signature_pad = new SignaturePad(canvas);
    $('.signature_pad_clear').click(function() { signature_pad.clear() });
    $('.signature_pad_save').click(function(event) { 
      if (signature_pad.isEmpty()){
        alert('You must sign to accept the Terms and Conditions');
        event.preventDefault();
      } else {
        $('.signature_pad_input').val(signature_pad.toDataURL());
      }
    });
  }
});

# application.css
.signature_pad {
  width: 700px;
  height: 400px;
  margin: 0 auto;
}

.signature_pad_body canvas {
  width: 100%;
  height: 100%;
  border: dotted #444;
  background: #CCCCCC;
}

# application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :verify_terms_acceptance 

  private

  def verify_terms_acceptance
    unless user_signed_in? && current_user.document
      redirect_to new_document_path, alert: 'You must first accept terms and conditions before performing any actions.'
    end
  end
end

# documents_controller.rb
class DocumentsController < ApplicationController
  before_action :authenticate_user!
  skip_before_action :verify_terms_acceptance

  def new
    @document = current_user.build_document
  end

  def create
    @document = current_user.build_document(document_params)
    @document.signed_on = DateTime.now
    if @document.save
      redirect_to root_url, notice: 'Terms and Conditions accepted.'
    else
      render :new
    end
  end

  private

    def document_params
      params.require(:document).permit(:signature)
    end
end

On controllers and/or specific actions which should not require the user to accept the Terms and Conditions, make sure that you add

# controller
skip_before_action :verify_terms_acceptance

Adding this override should also apply to the authentication controllers.

# documents/new.html.erb
<%= simple_form_for @document do |f| %>
  <%= f.input_field :signature, as: :hidden, class: 'signature_pad_input' %>
  <div class="signature_pad text-center">
    <div class="signature_pad_body">
      <canvas></canvas>
    </div>
    <div class="signature_pad_footer">
      <div class="text-muted">Sign above</div>
      <button type="button" class="btn btn-default signature_pad_clear">Clear</button>
      <%= f.button :submit, 'Save', class: 'btn btn-success signature_pad_save'  %>
    </div>
  </div>
<% end %>

# visitors/private_content.html.erb
<%= image_tag current_user.document.signature %>