Adding Electronic Signatures to a Rails 8 App

Integrating an electronic signature feature can be a great addition when building applications that require document signing. In this tutorial, we will walk through how to add a signature pad to a Rails 8 app using Tailwind CSS, PostgreSQL, and Importmap. We'll leverage the signature_pad JavaScript library to enable users to draw their signatures directly within a form. Step 1: Install Signature Pad Rails 8 with Importmap allows us to pin JavaScript libraries easily. Start by running the following command to install signature_pad: bin/importmap pin signature_pad This command will pin signature_pad and update the importmap.rb file with: pin "signature_pad" # @5.0.4 Step 2: Create a Stimulus Controller Next, we will create a Stimulus controller to handle the signature pad interactions. Run the following command: bin/rails generate stimulus signature_pad Now, open the generated signature_pad_controller.js file and replace its contents with: import { Controller } from "@hotwired/stimulus"; import SignaturePad from "signature_pad"; export default class extends Controller { static targets = ["canvas", "input"]; connect() { this.signaturePad = new SignaturePad( this.canvasTarget, { dotSize: 3, minWidth: 1, maxWidth: 3 } ); console.log("Please add signature before saving."); } disconnect() { this.signaturePad.off(); } clear() { this.signaturePad.clear(); } submit(event) { event.preventDefault(); this.canvasTarget.toBlob((blob) => { const signatureFile = new File([blob], "signature.png", { type: "image/png" }); const dataTransfer = new DataTransfer(); dataTransfer.items.add(signatureFile); this.inputTarget.files = dataTransfer.files; event.target.submit(); }); } } Explanation of the Stimulus Controller static targets = ["canvas", "input"]: Defines the elements our controller interacts with. connect(): Initializes signature_pad on the canvas element. disconnect(): Cleans up the signature pad instance when the controller is removed. clear(): Clears the signature from the canvas when the clear button is clicked. submit(event): Prevents the default form submission. Converts the signature drawn on the canvas to a blob. Creates a File object and adds it to a DataTransfer instance. Assigns the file to the hidden input field so it gets submitted with the form. Step 3: Set Up the Model To start, let’s assume we have a Consent model. We will now add an attached signature: class Consent

Mar 29, 2025 - 03:40
 0
Adding Electronic Signatures to a Rails 8 App

Integrating an electronic signature feature can be a great addition when building applications that require document signing. In this tutorial, we will walk through how to add a signature pad to a Rails 8 app using Tailwind CSS, PostgreSQL, and Importmap. We'll leverage the signature_pad JavaScript library to enable users to draw their signatures directly within a form.

Step 1: Install Signature Pad

Rails 8 with Importmap allows us to pin JavaScript libraries easily. Start by running the following command to install signature_pad:

bin/importmap pin signature_pad

This command will pin signature_pad and update the importmap.rb file with:

pin "signature_pad" # @5.0.4

Step 2: Create a Stimulus Controller

Next, we will create a Stimulus controller to handle the signature pad interactions. Run the following command:

bin/rails generate stimulus signature_pad

Now, open the generated signature_pad_controller.js file and replace its contents with:

import { Controller } from "@hotwired/stimulus";
import SignaturePad from "signature_pad";

export default class extends Controller {
    static targets = ["canvas", "input"];

    connect() {
      this.signaturePad = new SignaturePad(
        this.canvasTarget,
        { dotSize: 3, minWidth: 1, maxWidth: 3 }
      );
      console.log("Please add signature before saving.");
    }

    disconnect() {
      this.signaturePad.off();
    }

    clear() {
      this.signaturePad.clear();
    }

    submit(event) {
      event.preventDefault();

      this.canvasTarget.toBlob((blob) => {
        const signatureFile = new File([blob], "signature.png", { type: "image/png" });
        const dataTransfer = new DataTransfer();

        dataTransfer.items.add(signatureFile);
        this.inputTarget.files = dataTransfer.files;

        event.target.submit();
      });
    }
}

Explanation of the Stimulus Controller

  • static targets = ["canvas", "input"]: Defines the elements our controller interacts with.
  • connect(): Initializes signature_pad on the canvas element.
  • disconnect(): Cleans up the signature pad instance when the controller is removed.
  • clear(): Clears the signature from the canvas when the clear button is clicked.
  • submit(event):
    • Prevents the default form submission.
    • Converts the signature drawn on the canvas to a blob.
    • Creates a File object and adds it to a DataTransfer instance.
    • Assigns the file to the hidden input field so it gets submitted with the form.

Step 3: Set Up the Model

To start, let’s assume we have a Consent model. We will now add an attached signature:

class Consent < ApplicationRecord
  belongs_to :employee

  has_one_attached :signature #Add this line

  validates :first_name, :last_name, :phone_number, :email, :date_signed, presence: true
  validates :accepted, inclusion: { in: [ true ], message: "must be accepted" }
  validates :advisor_authorization, :consent_persistence, :information_confirmation, inclusion: { in: [ true ], message: "must be checked" }
end

Step 4: Update the Form

Now, let's update the consent form to include the signature pad:

<%= form_for @consent, url: wizard_path, method: :put, class: "space-y-6 mb-12", data: { controller: "signature-pad", action: "submit->signature-pad#submit" } do |form| %>

  
<%= form.label :signature, class: "block text-sm font-normal text-slate-700 mt-8 mb-2" do %> Client Signature class="text-purple-500">* <% end %> <% if consent.persisted? && consent.signature.attached? %> <%= image_tag consent.signature, class: "max-w-[300px] rounded border border-gray-300 mb-2" %> <% end %> <%= form.file_field :signature, data: { signature_pad_target: "input" }, accept: "image/png", hidden: true %> <%= tag.canvas width: 950, height: 150, data: { signature_pad_target: "canvas" }, class: "rounded-lg border border-gray-300 mb-2 hover:bg-gray-50 cursor-pointer" %> <%= tag.div class: "flex justify-between" do %> <%= tag.button "Clear", type: "button", data: { action: "signature-pad#clear" }, class: "bg-gray-200 hover:bg-gray-300 text-gray-500 px-2 py-1 rounded" %> <% end %>
class="w-1/2 mt-2"> <%= form.label :date_signed, class: "block text-sm font-normal text-slate-700" do %> Date signed class="text-purple-500">* <% end %> class="mt-1"> <%= form.date_field :date_signed, class: "block w-full rounded-lg border-slate-200 shadow-sm focus:border-purple-500 focus:ring-purple-500 sm:text-sm" %> <% if consent.errors[:date_signed].any? %> class="mt-2 text-sm text-red-600"><%= consent.errors[:date_signed].join(", ") %> <% end %>
class="flex justify-between mt-16"> <%= link_to "Go back", previous_wizard_path, class: "px-4 py-2 text-sm font-medium text-gray-700 border border-gray-300 rounded-md hover:bg-gray-100" %> <%= form.submit "Next step", class: "px-6 py-2 text-sm font-medium text-white bg-purple-500 rounded-md hover:bg-purple-600 focus:outline-none focus:ring focus:ring-purple-300 cursor-pointer" %>