忘れないように記録しとこ

カバの樹

window.printでSafariのバグを回避しながら宛名の書き換えにも対応する

 

はじめに

クライアントより領収書の印刷画面を作って欲しいのと要望がありました。
ある程度作って完成。と思っていたら、クライアントのSafariでバグがあるとのこと、二回目印刷で真っ白なページになってしまうようです。
これだからSafariは・・・(ヘイトスピーチ

 

同じようなことで悩んでいる人の記事を参考にします。

参考
safariでwindow.print()を2回実行すると空白で印刷されてしまう

印刷用の別タブ開いちゃえば良いのね。賢いなあー

 

今回の依頼は、自分で宛名などを書き換えできるようにするので、少し工夫が必要でした。
HTMLでなくPHPでやるのがポイントです。

 

 

ソースコード

CSS

今回のサンプル用にBootstrapをベースに構築します。
@media print  を使う事で印刷モードの時だけCSSを適用させます。

#title {
  padding: 10px;
  text-align: center;
}
#main{
  margin-bottom: 10px;
}
@media print{
  @page { 
    size: A4 landscape;
    margin: 12.7mm 9.7mm;
  }
  .container {
    max-width: 90% !important;
  }
  #signature{
    text-align: right;
  }
  #signature-block{
    display: inline-block;
    text-align: left;
  }
  #signature-logo {
    padding: 10px 10px 10px 0;
    margin-bottom: 10px; 
  }
  .form-control{
    border: 0px !important;
    padding-left: 0px !important;
  }
  .notprint{
    display: none !important;
  }
}

 

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

サンプルはBootstrapを使用しています。

 

PHP

ユーザーが入力した値を印刷モードに反映したいので、PHPを使ってPOST値を受取り input のvalue値を差し替えます。

<div class="container">
  <form method="post">
    <h2 id="title">領収書</h4>
    <div id="main">
      <div class="card">
        <div class="card-body">
          <table class="table table-borderless">
            <tbody>
              <tr>
                <th>発行日</th>
                <td><?php echo date('Y年n月j日') ?></td>
              </tr>
              <tr>
                <th>宛名</th>
                <td>
                  <input type="text" class="form-control" name="name" value="<?php echo isset($_POST['name']) ? htmlspecialchars($_POST['name']) :'領収書 太郎' ?>">
                </td>
              </tr>
              <tr>
                <th>合計</th>
                <td>¥2,000</td>
              </tr>
              <tr>
                <th>但し</th>
                <td>
                  <input type="text" class="form-control" name="item" value="<?php echo isset($_POST['item']) ? htmlspecialchars($_POST['item']) :'チケット' ?>">
                </td>
              </tr>
              <tr>
                <th>注文日</th>
                <td><?php echo date('Y年n月j日') ?></td>
              </tr>
              <tr>
                <th>注文番号</th>
                <td>0123456789</td>
              </tr>
              <tr>
                <th>主催者</th>
                <td>カバノキ</td>
              </tr>
              <tr>
                <th>イベント名</th>
                <td>10周年パーティー</td>
             </tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>
    <div class="notprint d-grid gap-2 col-6 mx-auto mt-4">
      <button class="btn btn-success btn-lg" onclick="doPrint(this.form)" type="button">印刷する</button>
      <button class="btn btn-danger btn-lg mt-3" onclick="window.close();">閉じる</button>
    </div>
  </form> 
</div>

 

Javascript

print() を単体で使うと、safariの不具合で2回目から印刷モードが真っ白になってしまいます。
逆に言うと一回目は上手くいくのだから、別タブを開いてそちらで print() を起動してしまえば良いのです。

ただ別タブを開いただけだと、ユーザーの入力値が初期化されてしまうので、別タブにPOST値を送ります。

function doPrint (myForm)
{
  //POST値を送る別タブ指定
  myForm.target = "print";
  //別タブを起動
  let printWindow = window.open(window.location, myForm.target);
  //別タブをフォーカス
  printWindow.focus();
  //別タブ宛にPOST送信
  myForm.submit();

  //別タブ起動時にスクリプトが動く
  printWindow.onload = function(){
    //印刷モードが起動後に動く
    printWindow.addEventListener('afterprint', function(){
      //起動を遅らせる
      setTimeout(function(){
        printWindow.close()
      }, 100);
    });
    //印刷モードを起動
    printWindow.print();
  }
}

 

さいごに

というわけでSafariのバグに対応するために一日を費やして対応を行いました。
30分で、できますわーとかイキってたのに・・・クソぅ・・・
だいぶ昔からあるバグっぽいので、Appleはとっとと解消してください。

今回は htmlspecialchars を使って、POST値の簡易なエスケープをしていますが、XSSの原因となるのでもう少し丁寧にエスケープ処理をした方が良いと思います。
私はWordPressを使っていたので、esc_attrとか使いました。
おのおのに合わせて処理してください。

今日はこの辺でー

 

  • B!