開発情報・ナレッジ

投稿者: ShiningStar株式会社 2024年4月24日 (水)

フォームに電子署名を追加する

この記事ではフォームに電子署名(マウスやスマートフォンのタップで描けるサイン)を、
追加する方法をご紹介いたします。

この様な形でマウスで文字を書く事ができます。


DB設定

電子署名を格納したいDBにフィールドタイプ:ファイルのフィールドを追加してください。
本サンプルにつきましては識別名をsignatureとして設定しています。

設定方法

登録フォームブロックを作成してください。
本サンプルにつきましては識別名をsignatureとして設定しています。
ソース編集を行う為、ソースデザインにて作成を行ってください。
登録フォームブロックが完成したらページを作成し上記で作成した登録フォームブロックを読み込む様にしてください。

登録フォームブロック 入力(Step1)

HTML
  <!--/* signature */-->
  <div class="sp-form-item sp-form-field">
    <div class="sp-form-label">
      電子サイン
    </div>
    <div>
      <canvas id="signature-pad" width="400" height="150" style="border: 1px solid #000;"></canvas>
      <input type="hidden" name="signature" id="signature-base64">  
      <span class="sp-form-noted" th:if="${fields['f02'].help != null}" th:text="${fields['f02'].help}">Help text</span>
      <span class="sp-form-error" th:data-zipcode="|zipCodeError${fields['f02'].name}|" th:text="${errors['f02']?.message}">Error message</span>
    </div>
  </div> 
            
任意の入力欄を出力したい箇所へ、HTMLを貼り付けてください。

登録フォームブロック 確認(Step2)

HTML
 <div class="sp-form-item sp-form-field">
  <div class="sp-form-label" >電子サイン</div>
  <div class="sp-form-data">
    <img th:src="${cp.result.value['signature']}" alt="Signature Image" />
    <input type="hidden" name="signature" id="signature-base64" th:value="${cp.result.value['signature']}" />
  </div> 
            
電子サインの画像データが出力されます。
任意の箇所へ貼り付けてください。

ページ設定

PHP
<?
//変更不可
session_start();
$commonBase = CommonBase::getInstance();

//------------------------------
// 設定値
//------------------------------
define("API_URL", "https://api.spiral-platform.com/v1");
define("API_KEY", "");
define("APP_ROLE", "");
define("DB_ID", ""); //電子署名を格納するDBID
define("APP_ID", ""); //電子署名を格納するDBが属しているアプリID
$registForm = $SPIRAL->getRegistrationForm("signature"); //フォームの識別名
$fileFieldId = "1"; //ファイルフィールドID
$fileFieldName = "signature"; //ファイルフィールド識別名
$fileName = "signatureImg.png"; //ファイル名

//step2での画像表示
$step = $registForm->getStep();
if($step == 2){
    
    $base64 = $SPIRAL->getPostParam("signature");
    $SPIRAL->setTHValue("signature",$base64);

    // 画像データの準備
    $signatureData = explode(',', $base64, 2)[1];
    $decodedData = base64_decode($signatureData);

    //ファイルアップロードトークン発行
    $apiUrlPass = "/apps/". APP_ID. "/dbs/". DB_ID. "/". $fileFieldId. "/files/uploadToken";
    $apiResponse = $commonBase->apiCurlAction("POST", $apiUrlPass);
    $fileUploadToken = $apiResponse["fileUploadToken"];
    //ファイルアップロード
    $apiUrlPass = "/apps/". APP_ID. "/dbs/". DB_ID. "/". $fileFieldId. "/files";

        // リクエストボディ(マルチパート形式)
        $requestBody = "--WebKitFormBoundary7MA4YWxkTrZu0gW\r\n";
        $requestBody .= "Content-Disposition: form-data; name=\"file\"; filename=\"". $fileName. "\"\r\n";
        $requestBody .= "Content-Type: image/png\r\n\r\n";
        $requestBody .= $decodedData. "\r\n";
        $requestBody .= "--WebKitFormBoundary7MA4YWxkTrZu0gW\r\n";
        $requestBody .= "Content-Disposition: form-data; name=\"fileUploadToken\"\r\n\r\n";
        $requestBody .= $fileUploadToken. "\r\n";
        $requestBody .= "--WebKitFormBoundary7MA4YWxkTrZu0gW--";

        $contentType = "Content-Type: multipart/form-data; boundary=WebKitFormBoundary7MA4YWxkTrZu0gW";

    $apiResponse = $commonBase->apiCurlAction("POST", $apiUrlPass, $requestBody, $contentType);
    $_SESSION["filekey"] = $apiResponse["fileKey"];
}

//完了ステップでのデータ送信
if($registForm->isCompletedStep()){
    $record = $SPIRAL->getRecordValue(); 
    $recordId = $record['item']['_id']; 
    
    // 更新するデータを指定
    $UpdateData = array(
    $fileFieldName  => array($_SESSION["filekey"])
    );
    $resultRecordUpdate = $commonBase->apiCurlAction("PATCH", "/apps/". APP_ID. "/dbs/". DB_ID. "/records/". $recordId, $UpdateData);
}


//------------------------------
// 共通モジュール
//------------------------------
class CommonBase {
    /**
     * シングルトンインスタンス
     * @var UserManager
     */
    protected static $singleton;

    public function __construct() {
        if (self::$singleton) {
            throw new Exception('must be singleton');
        }
        self::$singleton = $this;
    }
    /**
     * シングルトンインスタンスを返す
     * @return UserManager
     */
    public static function getInstance() {
        if (!self::$singleton) {
            return new CommonBase();
        } else {
            return self::$singleton;
        }
    }
    /**
     * V2用 API送信ロジック
     * @return Result
     */
    function apiCurlAction($method, $addUrlPass, $data = null, $multiPart = null, $jsonDecode = null) {
        $header = array(
            "Authorization:Bearer ". API_KEY,
            "X-Spiral-Api-Version: 1.1",
        );
        if($multiPart) {
            $header = array_merge($header, array($multiPart));
        } else {
            $header = array_merge($header, array("Content-Type:application/json"));
        }
        if(APP_ROLE){
			$header = array_merge($header, array("X-Spiral-App-Role: ".APP_ROLE));
		}
        // curl
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_URL, API_URL. $addUrlPass);
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        if ($method == "POST") {
            if ($multiPart) {
                curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
            } else {
                curl_setopt($curl, CURLOPT_POSTFIELDS , json_encode($data));
            }
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        }
        if ($method == "PATCH") {
            curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        }
        if ($method == "DELETE") {
            curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        }
        $response = curl_exec($curl);
        if (curl_errno($curl)) echo curl_error($curl);
        curl_close($curl);
        if($jsonDecode){
			return $response;
		}else{
            return json_decode($response, true);
		}
    }
}
?> 
            
JavaScript
document.addEventListener("DOMContentLoaded", function() {
    var canvas = document.getElementById('signature-pad');
    var ctx = canvas.getContext('2d');
    var drawing = false;

    function getTouchPos(canvasDom, touchEvent) {
        var rect = canvasDom.getBoundingClientRect();
        return {
            x: touchEvent.touches[0].clientX - rect.left,
            y: touchEvent.touches[0].clientY - rect.top
        };
    }

    function startDrawing(e) {
        drawing = true;
        // タッチイベントの場合は座標を調整
        if (e.touches) {
            e.preventDefault(); // スクロールを防ぎます
            var touch = getTouchPos(canvas, e);
            draw(touch.x, touch.y);
        } else {
            draw(e.offsetX, e.offsetY);
        }
    }

    function draw(x, y) {
        if (!drawing) return;
        ctx.lineWidth = 2;
        ctx.lineCap = 'round';
        ctx.lineTo(x, y);
        ctx.stroke();
        ctx.beginPath();
        ctx.moveTo(x, y);
    }

    function stopDrawing() {
        drawing = false;
        ctx.beginPath();
        document.getElementById('signature-base64').value = canvas.toDataURL();
    }

    function clearCanvas() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
    }

    canvas.addEventListener('mousedown', startDrawing);
    canvas.addEventListener('mousemove', (e) => {
        if (drawing) {
            draw(e.offsetX, e.offsetY);
        }
    });
    canvas.addEventListener('mouseup', stopDrawing);
    canvas.addEventListener('mouseout', stopDrawing);

    // タッチイベントリスナーを追加
    canvas.addEventListener('touchstart', startDrawing);
    canvas.addEventListener('touchmove', (e) => {
        var touch = getTouchPos(canvas, e);
        draw(touch.x, touch.y);
    });
    canvas.addEventListener('touchend', stopDrawing);

    document.getElementById('clear').addEventListener('click', clearCanvas);
}); 
            
ページ設定を開きそれぞれのタブへ貼り付けてください。
貼り付けた後PHPにつきましては設定値欄をお使いの環境の物へ変更してください。

解説

入力ページにてJavaScriptを用いて電子署名を書いてその画像をbase64形式に変換しPOSTを行います。
POSTされたbase64の画像データをデコードしバイナリに変換を行います。
フォーム上にて登録されたレコードに対して、バイナリ化した画像ファイルを SPIRAL API で更新する処理になっています。

解決しない場合はこちら コンテンツに関しての
要望はこちら