This is my controller
public function postSignature(Request $request) {
$signature = new Signature;
$data_uri = $request->signature;
$encoded_image = explode(",", $data_uri)[1];
$decoded_image = base64_decode($encoded_image);
$signature->signature = $encoded_image;
$signature->save();
}
Whats your actual form look like from your blade file?
You should have something like this:
<div>
<div class="digital-signature">
<div class="form-group">
<canvas id="signature-pad" class="signature-pad" width="7500" height="400"></canvas>
</div>
</div>
</div>
<div class="form-group">
<input type="hidden" id="signature_data" name="signature_data" class="form-control" value="">
</div>
This is my form
<form action="{{ route('signature.save') }}" method="POST">
@csrf
<div id="signature-pad" class="signature-pad">
<div class="signature-pad--body">
<canvas id="signature" name="signature"></canvas>
</div>
<div class="signature-pad--footer">
<div class="description">Sign above</div>
<div class="signature-pad--actions">
<div>
<button type="button" class="button clear" data-action="clear" id="clear">Clear</button>
</div>
<div>
<button type="submit" class="button save" data-action="save" id="save">Save</button>
<input type="button" class="btn btn-default btn-sm" id="saveSign" value="Add Signature">
</div>
</div>
</div>
</div>
</form>
Then you should have something along these lines in a JS file
$( function() {
var signaturePad = new SignaturePad(document.getElementById("signature-pad"), {
backgroundColor: 'rgba(255, 255, 255, 0)',
penColor: 'rgb(0, 0, 0)'
});
var saveButton = document.querySelector('.btn-save');
var clearButton = document.querySelector('[data-action=clear]');
saveButton.addEventListener('click', function (e) {
document.querySelector('[name=signature_data]').value = signaturePad.toDataURL('image/png', 100);
});
clearButton.addEventListener('click', function () {
signaturePad.clear();
});
var form = $('#sign_form');
$(saveButton).click(function() {
$.ajax({
url: form.attr( 'action' ),
data: form.serialize(),
type: 'POST',
success: function(response, ui) {
swal({
title: "Signature Saved",
text: "Your signature has now been stored.",
icon: "success",
});
window.setTimeout(function(){window.location.reload()}, 3000);
},
error: function(response) {
console.log('Error!');
}
});
});
});
@theunforgiven Cant handle my signature pad as well :( It doesnt pass to the controller. Could you help me out ?
BLADE:
<div class="form-row mb-3">
<div class="wrapper mb-3">
<canvas id="signature-pad" class="signature-pad" width=400 height=200 required name="signature"></canvas>
</div>
</div>
// other form input data between these lines
<div class="col-sm-10">
<button type="submit" id="submit" class="btn btn-primary">Apply</button>
</div>
JS:
<script>
var canvas = document.getElementById('signature-pad');
// Adjust canvas coordinate space taking into account pixel ratio,
// to make it look crisp on mobile devices.
// This also causes canvas to be cleared.
function resizeCanvas() {
// When zoomed out to less than 100%, for some very strange reason,
// some browsers report devicePixelRatio as less than 1
// and only part of the canvas is cleared then.
var ratio = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
canvas.getContext("2d").scale(ratio, ratio);
}
window.onresize = resizeCanvas;
resizeCanvas();
var signaturePad = new SignaturePad(canvas, {
backgroundColor: 'rgb(255, 255, 255)' // necessary for saving image as JPEG; can be removed is only saving as PNG or SVG
});
var data = signaturePad.toDataURL('image/png');
signaturePad.toDataURL();
CONTROLLER:
$data_uri = "data:image/png;base64,iVBORw0K...";
$encoded_image = explode(",", $data_uri)[1];
$decoded_image = base64_decode($encoded_image);
Storage::put('signature.png', $decoded_image, 'public');
@ROMAND - Surely your $data_uri = "data:image/png;base64,iVBORw0K..."; should be $data_uri = $request->signature; to get that from the request rather than pass it as a string?
@theunforgiven I tried that but then I get an error: ErrorException (E_NOTICE) Undefined offset: 1 for the following line:
$encoded_image = explode(",", $data_uri)[1];
@ROMAND - What does your view look like?
@theunforgiven Its a quite long form, will try to limit to the necessary:
<form id="form" class="mt-5" method="POST" action="/submit" enctype="multipart/form-data">
{{ csrf_field() }}
// several inputs between this lines
<div class="form-row mb-3">
<div class="wrapper mb-3">
<canvas id="signature-pad" class="signature-pad" width=400 height=200 required name="signature">
</canvas>
</div>
</div>
// several inputs between this lines
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" data-action="save" id="save" class="btn btn-primary">Apply</button>
</div>
</div>
</form>
It should pass the data by clicking on submit button, without additional save button below the canvas
@theunforgiven Could you understand the problem?
Sorry without seeing the JS and view file in full etc I can't really help
@theunforgiven , here is the complete view + layout blade view:
@extends('layout')
@section('title')
studygermany
@endsection
@section('content')
<!-- Jumbotron -->
<div class="jumbotron text-center mb-5">
<!-- Title -->
<h2 class="card-title h2">Welcome to studygermany!</h2>
<img src="assets/logo_studygermany.png" style="max-width: 250px">
<!-- Grid row -->
<div class="row d-flex justify-content-center">
<!-- Grid column -->
<div class="col-xl-7 pb-2 pt-3">
<p class="card-text">Apply for your Public German Health Insurance here! We support you with all necessary steps for the entry and stay in Germany including incoming insurance, public health insurance as well as own bank account in Germany, step-by-step! Furthermore, you have the opportunity to get additional exclusive bonuses.</p>
</div>
<!-- Grid column -->
</div>
<!-- Grid row -->
<hr class="my-2">
<div>
<a href="#form" class="btn btn-info" role="button">Application</a>
</div>
</div>
<!-- JUMBOTRON END -->
<!-- LISTS BEGIN -->
<div class="row">
<div class="col-sm-12 col-md-6 mb-5">
<ul class="list-group">
<li class="bg-primary list-group-item active">YOUR BENEFITS</li>
<li class="list-group-item">Safety - You're covered from the semester begin</li>
<li class="list-group-item">Visa - Adds visa weightage</li>
<li class="list-group-item">Valid all over Europe</li>
<li class="list-group-item">Stressfree arrival in Germany</li>
</ul>
</div>
<div class="col-sm-12 col-md-6 mb-5">
<ol class="list-group">
<li class="bg-primary list-group-item active">HOW TO REGISTER?</li>
<li class="list-group-item">1. Fill the form below</li>
<li class="list-group-item">2. Get an Email confirmation</li>
<li class="list-group-item">3. Receive your Health Insurance Certificate in 24 Hours *</li>
<li class="list-group-item">4. Done!</li>
</ol>
</div>
</div>
<div class="container">
<!-- Partnership Information -->
<div class="row">
<div class="col-xs-12 col-sm-3 mt-4 mb-4">
<img src="assets/tk_logo_eps.png" class="rounded mx-auto d-block card-img-top" alt="tk_logo">
</div>
<div class="col-xs-12 col-sm-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">Partnership</h5>
<p class="card-text">We are official partner of TK, the largest health insurance fund in Germany. TK offers excellent benefits, reasonable contributions and great service. TK, named Germany’s best health insurance fund by Focus Money business magazine</p>
<a href="assets/Flyer-TK-Highlights%20englisch.pdf" target="_blank" class="card-link">More information</a>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-3 mt-4 mb-4">
<img src="assets/focus-money-logo.bmp" class="mx-auto d-block card-img-top" alt="focus_money_logo">
</div>
</div>
</div>
<!-- FORM BEGIN PERSONAL INFORMATION-->
<form id="form" class="mt-5" method="POST" action="/submit" enctype="multipart/form-data">
{{ csrf_field() }}
<h3>Personal Information</h3>
<div class="form-row mb-4">
<div class="col-sm-12 col-md-2 mb-2">
<label for="formGroupExampleInput">Gender*</label>
<select class="form-control" required name="gender">
<option value="">- Select-</option>
<option value="Male">Male</option>
<option value="Female">Female</option>
</select>
</div>
<div class="col-sm-12 col-md-4 mb-2">
<label for="formGroupExampleInput">First name*</label>
<input type="text" class="form-control" placeholder="First name" required name="firstname">
</div>
<div class="col-sm-12 col-md-6 mb-2">
<label for="formGroupExampleInput">Last name*</label>
<input type="text" class="form-control" placeholder="Last name" required name="lastname">
</div>
</div>
<!-- SECOND FORM ROW -->
<div class="form-row mb-4">
<div class="col-sm-12 col-md-6 mb-2">
<label for="formGroupExampleInput">Birthday*</label>
<input type="text" class="form-control" id="datepicker3" placeholder="e.g. 13.12.98" data-date-format='dd.mm.yy' required name="birthday">
</div>
<div class="col-sm-12 col-md-6 mb-2">
<label for="formGroupExampleInput">Place/City of Birth*</label>
<input type="text" class="form-control" placeholder="Place/City of Birth" required name="place_of_birth">
</div>
</div>
<div class="form-row mb-4">
<div class="col-sm-12 col-md-6 mb-2">
<label for="formGroupExampleInput">Phone Number*</label>
<input type="tel" class="form-control" placeholder="e.g. 007 495 123 4567" required name="phone_number">
<small id="important" class="form-text text-muted" style="color: blue!important">IMPORTANT! Reachable number required for contacting<br> and sending your insurance within 24 hours! </small>
</div>
<div class="col-sm-12 col-md-6 mb-2">
<label for="formGroupExampleInput">Email Address*</label>
<input type="email" class="form-control" placeholder="Email Address" required name="email">
</div>
</div>
<!-- ADMISSION DETAILS -->
<h3>Admission Details</h3>
<div class="form-row mb-4">
<div class="col">
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="degree" id="inlineRadio1" value="Bachelor" required>
<label class="form-check-label" for="inlineRadio1">Bachelor</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="degree" id="inlineRadio2" value="Masters">
<label class="form-check-label" for="inlineRadio2">Masters</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="degree" id="inlineRadio2" value="PhD candidate">
<label class="form-check-label" for="inlineRadio2">PhD candidate</label>
</div>
</div>
</div>
<!-- UNIVERSITY AND SEMESTER BEGIN -->
<div class="form-row mb-4">
<div class="col-sm-12 col-md-6 mb-2">
<label for="formGroupExampleInput">University*</label>
<input type="text" class="form-control" placeholder="University" required name="university">
</div>
<div class="col-sm-12 col-md-6 mb-2">
<label for="formGroupExampleInput">Semester Begin* (As on admit letter!)</label>
<select class="form-control" required name="semester_begin">
<option value="">Please select</option>
<option value="01/02/2019">01/02/2019</option>
<option value="01/03/2019">01/03/2019</option>
<option value="01/04/2019">01/04/2019</option>
<option value="01/05/2019">01/05/2019</option>
<option value="01/06/2019">01/06/2019</option>
<option value="01/07/2019">01/07/2019</option>
<option value="01/08/2019">01/08/2019</option>
<option value="01/09/2019">01/09/2019</option>
<option value="01/10/2019">01/10/2019</option>
<option value="01/11/2019">01/11/2019</option>
<option value="01/12/2019">01/12/2019</option>
<option value="01/01/2020">01/01/2020</option>
<option value="01/02/2020">01/02/2020</option>
<option value="01/03/2020">01/03/2020</option>
<option value="01/04/2020">01/04/2020</option>
<option value="01/05/2020">01/05/2020</option>
<option value="01/06/2020">01/06/2020</option>
<option value="01/07/2020">01/07/2020</option>
<option value="01/08/2020">01/08/2020</option>
<option value="01/09/2020">01/09/2020</option>
<option value="01/10/2020">01/10/2020</option>
<option value="01/11/2020">01/11/2020</option>
<option value="01/12/2020">01/12/2020</option>
</select>
</div>
</div>
<!-- PROGRAM AND GRADUATION DATE -->
<div class="form-row mb-4">
<div class="col-sm-12 col-md-6 mb-2">
<label for="formGroupExampleInput">Name of the Program/Course*</label>
<input type="text" class="form-control" placeholder="Program name including Masters or Bachelors" required name="name_of_program">
</div>
<div class="col-sm-12 col-md-6 mb-2">
<label for="formGroupExampleInput">Expected Graduation Date*</label>
<input type="text" class="form-control" id="datepicker" placeholder="Expected Graduation Date" data-date-format='dd.mm.yy' required name="expected_graduation_date">
</div>
</div>
<!-- BANK DETAILS BEGIN -->
<h3>Bank Details</h3>
<h5>(Leave blank if you don't have the information yet)</h5>
<div class="form-row mb-4">
<div class="col-sm-12">
<label for="formGroupExampleInput">Bank Name</label>
<input type="text" class="form-control" placeholder="Bank Name" name="bank_name">
</div>
</div>
<div class="form-row mb-4">
<div class="col-sm-12 col-md-4 mb-2">
<label for="formGroupExampleInput">IBAN</label>
<input type="text" class="form-control" placeholder="DE __ ________ __________" name="bank_iban">
</div>
<div class="col-sm-12 col-md-2 mb-2">
<h5 class="text-center">OR</h5>
</div>
<div class="col-sm-12 col-md-3 mb-2">
<label for="formGroupExampleInput">Account Number</label>
<input type="text" class="form-control" placeholder="Account Number" name="bank_acount_number">
</div>
<div class="col-sm-12 col-md-3 mb-2">
<label for="formGroupExampleInput">Branch Code</label>
<input type="text" class="form-control" placeholder="Branch Code" name="bank_branch_code">
</div>
</div>
<!-- DOCUMENTS BEGIN -->
<h3>Documents</h3>
<div class="form-row mb-4">
<div class="col-sm-12 col-md-4">
<div class="form-group">
<label for="exampleFormControlFile1">Admit letter</label>
<input type="file" class="form-control-file" id="exampleFormControlFile1" name="admit_letter" accept=".png, .jpg, .jpeg, .pdf">
</div>
</div>
<div class="col-sm-12 col-md-4 mb-2">
<div class="form-group">
<label for="exampleFormControlFile1">Passport (First Page)</label>
<input type="file" class="form-control-file" id="exampleFormControlFile1" name="passport_first_page" accept=".png, .jpg, .jpeg, .pdf">
</div>
</div>
<div class="col-sm-12 col-md-4 mb-2">
<div class="form-group">
<label for="exampleFormControlFile1">Passport (Last Page)</label>
<input type="file" class="form-control-file" id="exampleFormControlFile1" name="passport_last_page" accept=".png, .jpg, .jpeg, .pdf">
</div>
</div>
</div>
<!-- TRAVEL DETAILS BEGIN -->
<h3>Travel Details</h3>
<div class="form-row mb-4">
<div class="col-sm-12 col-md-3 mb-2">
<label for="formGroupExampleInput">Travel Date*</label>
<input type="text" class="form-control" id="datepicker1" placeholder="Travel Date" data-date-format='dd.mm.yy' required name="travel_date">
<small id="important" class="form-text text-muted" style="color: blue!important">(Intended travel date if not sure)</small>
</div>
<div class="col-sm-12 col-md-6 mb-2">
<label for="formGroupExampleInput">Nominee Name*</label>
<input type="text" class="form-control" placeholder="Nominee Name" required name="nominee_name">
<small id="important" class="form-text text-muted" style="color: blue!important">(EITHER Mother or Father's NAME)</small>
</div>
<div class="col-sm-12 col-md-3 mb-2">
<label for="formGroupExampleInput">Nominee Date Of Birth*</label>
<input type="text" class="form-control" id="datepicker2" placeholder="Nominee Date Of Birth" data-date-format='dd.mm.yy' required name="nominee_date_of_birth">
<small id="important" class="form-text text-muted" style="color: blue!important">(EITHER Mother or Father's Date of Birth)</small>
</div>
</div>
<div class="form-row mb-4 mb-2">
<div class="col-sm-12">
<label for="formGroupExampleInput">Comments</label>
<input type="text" class="form-control" placeholder="Comments" name="comments">
</div>
</div>
<div class="form-row mb-3">
<div class="wrapper mb-3">
<canvas id="signature-pad" class="signature-pad" width=400 height=200 required name="signature"></canvas>
</div>
</div>
<div class="form-row mb-3">
<button id="save-png">Save as PNG</button>
<button id="save-jpeg">Save as JPEG</button>
<button id="save-svg">Save as SVG</button>
<button id="undo">Undo</button>
<button id="clear">Clear</button>
</div>
<div class="form-row mb-4">
<div class="col-sm-10 mb-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="gridCheck1" required name="sepa_mandate_confirmed" value="1">
<label class="form-check-label" for="gridCheck1">
I agree with the issuance of a SEPA direct debit mandate for chosen Insurance Company.*
</label>
</div>
</div>
<div class="col-sm-10 mb-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="gridCheck1" required name="terms_c_confirmed" value="1">
<label class="form-check-label" for="gridCheck1">
I have read and understood the terms and conditions, policy * (It is mandatory that you must sign first and then check this checkbox)
</label>
</div>
</div>
<div class="col-sm-10 mb-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="gridCheck1" name="private_thi_confirmed" value="1">
<label class="form-check-label" for="gridCheck1">
I also would like to apply for Private Travel Health Insurance to cover the VISA requirements for German Student Visa. (If yes, write your travel date in the comment section above)
</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" data-action="save" id="save" class="btn btn-primary">Apply</button>
</div>
</div>
</form>
@endsection
LAYOUT BLADE VIEW WITH JS:
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="./css/bootstrap.min.css">
<link rel="stylesheet" href="./css/bootstrap-datepicker.css">
<title>@yield('title', 'studygermany')</title>
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.1.0/cookieconsent.min.css" />
<script src="//cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.1.0/cookieconsent.min.js"></script>
<script>
window.addEventListener("load", function(){
window.cookieconsent.initialise({
"palette": {
"popup": {
"background": "#edeff5",
"text": "#838391"
},
"button": {
"background": "#4b81e8"
}
},
"theme": "classic",
"content": {
"href": "www.studygermany.com/datenschutz"
}
})});
</script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/signature_pad.min.js"></script>
<style>
.wrapper {
position: relative;
width: 400px;
height: 200px;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.signature-pad {
position: absolute;
left: 0;
top: 0;
width:400px;
height:200px;
background-color: white;
border: 2px dotted;
}
</style>
</head>
<body>
<div class="container">
<!-- NAV BEGIN -->
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-3">
<a class="navbar-brand" href="#">
<img src="assets/logo_studygermany.png" width="200" alt="">
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarTogglerDemo01">
<ul class="navbar-nav ml-auto">
<li class="nav-item active">
<a class="nav-link" href="/">Home<span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/initial_information">Initial Information</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/privacy_policy">Privacy policy</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/imprint">Imprint</a>
</li>
</ul>
</div>
</nav>
<!-- NAV END -->
@yield('content')
<!-- Footer -->
<footer class="page-footer font-small blue pt-4">
<!-- Copyright -->
<div class="footer-copyright text-center py-3">© 2019 Copyright:
<a href="https://www.studygermany.de"> studygermany.de</a>
</div>
<!-- Copyright -->
</footer>
<!-- Footer -->
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/bootstrap-datepicker.js"></script>
<script>
$(function(){
$('#datepicker').datepicker();
});
$(function(){
$('#datepicker1').datepicker();
});
$(function(){
$('#datepicker2').datepicker();
});
$(function(){
$('#datepicker3').datepicker();
});
</script>
<script>
var canvas = document.getElementById('signature-pad');
// Adjust canvas coordinate space taking into account pixel ratio,
// to make it look crisp on mobile devices.
// This also causes canvas to be cleared.
function resizeCanvas() {
// When zoomed out to less than 100%, for some very strange reason,
// some browsers report devicePixelRatio as less than 1
// and only part of the canvas is cleared then.
var ratio = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
canvas.getContext("2d").scale(ratio, ratio);
}
window.onresize = resizeCanvas;
resizeCanvas();
var signaturePad = new SignaturePad(canvas, {
backgroundColor: 'rgb(255, 255, 255)' // necessary for saving image as JPEG; can be removed is only saving as PNG or SVG
});
var data = signaturePad.toDataURL('image/png');
signaturePad.toDataURL();
/*document.getElementById('save-png').addEventListener('click', function () {
if (signaturePad.isEmpty()) {
return alert("Please save the signature first.");
}
var data = signaturePad.toDataURL('image/png');
console.log(data);
window.open(data);
});
document.getElementById('save-jpeg').addEventListener('click', function () {
if (signaturePad.isEmpty()) {
return alert("Please save a signature first.");
}
signaturePad.fromDataURL("data:image/jpeg;base64,signature");
});
document.getElementById('save-svg').addEventListener('click', function () {
if (signaturePad.isEmpty()) {
return alert("Please provide a signature first.");
}
var data = signaturePad.toDataURL('image/svg+xml');
console.log(data);
console.log(atob(data.split(',')[1]));
window.open(data);
});*/
document.getElementById('clear').addEventListener('click', function () {
signaturePad.clear();
});
document.getElementById('undo').addEventListener('click', function () {
var data = signaturePad.toData();
if (data) {
data.pop(); // remove the last dot or line
signaturePad.fromData(data);
}
});
</script>
</body>
</html>
Not related but I saw:
$(function(){
$('#datepicker').datepicker();
});
$(function(){
$('#datepicker1').datepicker();
});
$(function(){
$('#datepicker2').datepicker();
});
$(function(){
$('#datepicker3').datepicker();
});
That you could reduce to just $('.datepicker').datepicker(); change from ID to a class, then you aren't duplicating on the same code.
As for the signature pad, from what I can see quickly, looks ok to me and did you follow the docs through on the github repo?
Please or to participate in this conversation.