Laravelで業務システム

Laravel関連の記事掲載してます

dompdfを使ってみる2

前回、dompdf用のHTML吐き出しのVBAを作って、その動きをお話ししました。

しかし、前回、最後に記載した通り、結構めんどくさい問題点があります。

それが、以下の問題点です。

f:id:pgslit:20211024144313p:plain

上記が結構きつくて、例えば、プロジェクトのメンバーに使ってもらうシチュエーションで、仕様書に問題点を記載していても、けっこう気づきにくい問題だと思います。

1番目の問題は、Excelでは表現できる行が、TABLEタグ(TD,TH)のrowspanでは表現できないという問題です。

特に、今回の件があるまで気にしてなかったのですが、よく考えれば確かににできないよなーと、改めて認識。

2番目の問題は、結合しているセルの中に罫線が入っているとそれを認識してしまうということです。

基本的に、セル単位で読み取っているので、こういう問題もおきるわけですが、それにしても、結合したセルの中の罫線って残っているんですね。こういったことないと、気づかないです。

というわけで、上記の改善を時間あるときにでも対策考えたいと思います。まあ、セル結合しないという究極選択もあるかもしれませんが、これはこれできつい。

では、今日はここまで。

dompdfを使ってみる1

業務システムと言えば帳票出力機能は欠かせないものですよね。今回はLaravelで扱いやすいdompdfとよばれる、pdf形式で帳票出力できるライブラリを使ってみたいと思います。

インストールについては、ググっていただければたくさんでてきますのでそちらを参照ください。

dompdfの便利なところはhtmlを読み取って、それをpdfにしてくれるというところです。 つまりはLaravelのbladeファイルとの相性がよく、コーディングもしやすいわけです。 線を一本一本引いたりする必要もないし、タグ操作でいろいろとできるわけですね。

さて、ここからが本題なのですが、htmlを作るのって簡単かといわれれば、座標指定して線引いたりするのに比べればはるかに楽です。ただ、面倒なことに変わりないですよね。

ここで、レガシープログラマーの小生が考えることはExcelでレイアウト作ってVBAでタグを吐き出せればいいんじゃね?という結論に至るわけです。

とはいえ、VBはずいぶん利用してないので、とりあえず、いいサンプルが転がっていないかググることに。 探した限りは、単純な、TABLEタグ吐き出しはあるけど、セル結合や、線の色やフォントサイズとかまで加味しているのはほとんどない感じ。

というわけで、html吐き出しのVBAアドインを自作してみることにした。

EXCELのすべての機能をhtmlに吐き出すのはさすがに限界があるので、以下の情報をhtml出力できるようにする。もっとも、出力してもdompdfやhtmlで対応してないものもありますしね。

  1. フォントサイズ
  2. フォントカラー
  3. 線種
  4. 線幅
  5. 線色
  6. 背景色
  7. セルの連結

実際、作ったアドインの動きを以下に記載します。

1 作成した帳票の右下をクリック(範囲指定していると考えてください。)

f:id:pgslit:20211023203741j:plain

2 アドインメニューの"HTML出力ツール"ボタンをクリック

f:id:pgslit:20211023203952j:plain

3 表示されたウィンドウの"出力"ボタンをクリック

f:id:pgslit:20211023204101j:plain

4 すると、表示用ウィンドウに、以下画像の通り、HTMLが出力されます。尚、装飾はほぼCSSで行うようにしており、かつ対象の帳票にない、不要なCSSは吐き出さないようにしました。CSSでなく、タグに直接属性指定したり、不要なCSSがあるとPDFの出力が遅くなったりするので。

f:id:pgslit:20211023204222j:plain

5 上記のHTMLをBladeファイルに張り付けて、実際にdompdfで出力したイメージが以下の通りです。いい感じですが、見積番号のところ等が、ラップしているのでここは、直接タグに"nowrap"をつけます。

f:id:pgslit:20211023204837j:plain

6 以下が、"nowrap"をつけたものです。どうですか!いい感じですね。VBAExcelの幅の割合を算出して、"width" 属性を吐き出すという手もありますが、dompdfが旨く幅の調整してくれるので、かえってレイアウトが崩れる可能性もあるので、後で微調整するのが無難かもしれません。

f:id:pgslit:20211023205154j:plain

7 尚、以下のように、画像を出力したいところは、画像作って、所定フォルダに保存し、そのまま”img”タグを書けばいいわけです。Laravelとdompdfのタッグが生んだ機能ですね。同様に、変数とかもあらかじめ埋め込んでおけば、かなり楽になりますよね。

f:id:pgslit:20211023205630j:plain

8 ちなみに出力イメージは以下になります。注意が必要なのは、"<"や">"といった、文字をそのまま出力したい場合もあるかと思うので、一応、出力画面の"実態参照を利用する"をチェックすることで、文字変換できるようにはしました。

f:id:pgslit:20211023210054j:plain

といった感じでいいようにも見えますが、実はわりと意識してExcelで帳票レイアウト作る必要があります。 もちろん、吐き出せない内容があるのは止む無しですが、残念ながら、意識しないと、帳票のレイアウトがくずれる、問題点あります。(これじゃー公開できない)

この問題は次回お話ししようと思います。今回はここまで。

新規登録画面に登録済データを呼び出しセットする機能をつけてみる6

コンポーネントクラスも作成できましたので、いよいよ本命のコンポーネントの中身を作っていきますが、その前にルート設定とデータ呼び出しのコントローラの関数を作成します。

1. ルート設定


以下のようにデータ呼び出しのルート設定をweb.phpに記述します。

 Route::get('/customer/index/{name}', [App\Http\Controllers\MstCustomerController::class, 'getCustomersBySearchName'])->name('mst_customer_by_search_name');

2. MstCustomerController.phpに以下を記述します。


public function getCustomersBySearchName($CustomerName)
{
      $customers = Customer::where('status', 1)->where('customer_nm','like',"%$CustomerName%")->orderBy('id', 'asc')->get();
      return response()->json($customers);
}

顧客名を検索キーにデータを取得しajaxで取得できるようにjsonでパックしてます。

3. コンポーネントのコーディングを行う


いよいよラストです。とりあえず、コードをまずは掲載します。

"modal-list.blade.php"を以下のようにコーディングします。

{{-- Modal画面 --}}
<div class="modal fade" id="ListModal{{ $modalnm }}" tabindex="-1" aria-labelledby="ListModalLabel{{ $modalnm }}" aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">検索</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            {{-- 検索ボックス、ボタン配置 --}}
            <div class="modal-header input-group">                
                <input name="search_nm{{ $modalnm }}" type="text" class="form-control" id="search_nm{{ $modalnm }}" placeholder="入力してください。"><br/>
                <button type="button" class="btn btn-primary" id="searchplay{{ $modalnm }}">検索</button>
            </div>
            <div class="modal-body">
                <table class="table table-sm table-bordered table-striped" id="searchtable{{ $modalnm }}">
                    {{-- Tableヘッダ生成 --}}
                    <thead>
                        <tr>
                        @foreach ($tableheader as $item)
                            <th class="bg-light" scope="col">{{ $item }}</th>
                        @endforeach
                        </tr>
                    </thead>
                    {{-- ここにAjaxで取得したデータをセット --}}
                    <tbody>
                    </tbody>
                </table>
                {{-- ローディングアニメーション用のタグを配置 --}}
                <div class="loading hide"><div class="circle"></div></div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-dismiss="modal">閉じる</button>
            </div>
        </div>
    </div>
</div>
<script type="module">
    $('#searchplay{{ $modalnm }}').on('click', function () {
        $('#searchtable{{ $modalnm }} tbody').empty(); //もともとある要素を空にする
        let searchName = $('#search_nm{{ $modalnm }}').val(); //検索ワードを取得        
        //検索ワードが入力されていない場合は何もしない
        if (!searchName) {
            return false;
        }

        $.ajax({
            type: 'GET', //GETで送信
            url: '{{ $modalurl }}' + searchName,//データを取得するURLを指定
            dataType: 'json', //json形式で受け取る
            beforeSend: function () {
                $('.loading').removeClass('hide');
            } //ローディングアニメーションを表示
    
        }).done(function (data) { //ajaxが成功したときの処理
            $('.loading').addClass('hide');//ローディングアニメーションを消す
            let html = '';
            $.each(data, function (index, value) { //dataの中身からvalueを取り出す

                //取得したデータのTableを作成
                html = html +  `
                            <tr>
                                @foreach ($inputname as $item)
                                    @if (trim($truncatesize[$loop->iteration - 1]) == "")
                                        <td class="col-{{ $celsize[$loop->iteration - 1] }}">${value.{{ $item }}}</td>
                                    @else
                                        <td class="col-{{ $celsize[$loop->iteration - 1] }}"><div class="text-truncate" style="max-width: {{ $truncatesize[$loop->iteration - 1] }}px;">${value.{{ $item }}}</div></td>
                                    @endif
                                @endforeach
                            </tr>
                                `
            })
            $('#searchtable{{ $modalnm }} tbody').append(html); //できあがったTableをセット
            // 検索結果がなかったときの処理
            if (data.length === 0) {
                $('#searchtable{{ $modalnm }} tbody').append('<td colspan="{{ count($tableheader) }}">該当データが存在しません</td>'); 
            }
        }).fail(function () {
            //ajax通信がエラーのときコンソールログを吐き出し
            console.log('Error.');
        })
    });
    //選択されたデータをテキストボックスにセットする
    //動的に生成したタグについては以下のように記述しないとイベントが発生しない
    $(document).on("click", "#searchtable{{ $modalnm }} td", function(){

        let row = $(this).closest('tr').index();
        //選択したテーブル行のデータを読み取りテキストボックスにセットする
        @foreach ($outputname as $item)
            @if (trim($item) == "")
            @else
                $("#{{ $item }}").val($("#searchtable{{ $modalnm }}").children("tbody").children("tr").eq(row).children().eq({{ $loop->iteration - 1 }}).text());
            @endif        
        @endforeach
    
        $('#ListModal{{ $modalnm }}').modal('hide');
    });
</script>

これで、コンポーネント化は完了です。

4. まとめ


実際にコンポーネント化がポイントなので、コンポーネントの中身自体はあまり重要ではないですが、別で解説していこうと思います。

新規登録画面に登録済データを呼び出しセットする機能をつけてみる5

前回は作成するコンポーネントの記述について掲載しました。今回は、コンポーネントクラスを作成していきたいと思います。

1. コンポーネントクラスの作成


コンポーネントクラスのテンプレートはすでにArtisanで作成しているので、ModalList.phpの中身を以下のように書き換えます。

public $modalnm;
public $modalurl;
public $tableheader;
public $inputname;
public $outputname;
public $celsize;
public $truncatesize;

/**
 * Create a new component instance.
 *
 * @return void
 */
public function __construct($modalnm,$modalurl,$tableheader,$inputname,$outputname,$celsize,$truncatesize)
{
    //
    $this->modalnm = $modalnm;
    $this->modalurl = $modalurl;
    $this->tableheader = $tableheader;
    $this->inputname = $inputname;
    $this->outputname = $outputname;
    $this->celsize = $celsize;
    $this->truncatesize = $truncatesize;
}

render関数については変更してません。

引数をコントラクタから受け取って、パブリック変数に代入しているだけです。こんな感じでパブリック変数に代入すると、render関数でレンダリングするコンポーネントのBladeファイルに変数を渡すことができます。

なるほどって感じです。

2. まとめ


今回は単純に変数を渡すことしかしてないですが、ほかにもコンポーネントのクラスでいろいろとできるようです。コンポーネントの強みはこのクラスにあるといってもいいかもしれないです。

次回はいよいよコンポーネントの中身のコーディングを行いたいと思います。

新規登録画面に登録済データを呼び出しセットする機能をつけてみる4

Laravelのコンポーネットの使い方はわかりました。では実際にどういう構文のコンポーネントにするか、先に記載します。

1. コンポーネントの使用方法


コンポーネントの中身は別で説明しますが、先にコンポーネントの使用方法についてみてもらうほうがイメージしやすいかと思うので、先にこれについて説明しようと思います。

コンポーネントの記述方法は以下の通り

@php
    $tableheader = array("顧客ID","顧客名","住所","TEL","FAX");
    $inputname = array("customer_id","customer_nm","customer_addr","customer_tel","customer_fax");
    $outputname = array("","customer_nm","customer_addr","customer_tel","customer_fax");
    $celsize = array("1","2","3","2","2");
    $truncatesize = array("","150","200","","");
@endphp
<x-modal-list modalnm="m1" modalurl="/customer/index/" :tableheader="$tableheader" :inputname="$inputname" :outputname="$outputname" :celsize="$celsize" :truncatesize="$truncatesize"/>

コンポーネントのいいところは、ファンクションのように使えるところですね。 ◎変数や式を受け取る場合は属性の頭に":"をつけないといけないようです。

2. 引数の説明


  • modalnm : コンポーネント
  • modalurl : データ取得先URL
  • tableheader : テーブルのヘッダ名(配列で指定)
  • inputname : データ取得後の変数名(配列で指定)
  • outputname : 値をセットしたいテキストボックスの名前。セットしない場合は空白指定。(配列で指定)
  • celsize : セルサイズ Bootstrapのcol-[x]の[x]の部分数字(配列で指定)
  • truncatesize : 長い文字列の場合、どのくらいの幅できるか指定(配列で指定)

3. まとめ


ざっと記載しましたが、わかりましたかね。 再利用できるように引数が多くなりましたが、なかなか使い勝手はよさそうです。 次回は、コンポーネントの中身を作っていきたいと思います。