Laravel + Vueで追加&削除できる複数入力ボックスをつくる

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、このところLaravelがメインの話題が続いているので、今回はこのブログのもうひとつの大きなテーマ「Vue.js」について取り上げてみたいと思います。

というのも、以前から「ブログ記事のネタ帳」には書いてたんですが、他にも記事にしたいことがあって後回しになっていました。

それは・・・・・

追加&削除できる複数入力ボックス

です。

例えば、ユーザーに趣味を入力してもらう必要があったとします。

もちろん趣味がひとつだけの場合は、入力ボックスはひとつだけでいいかもしれませんが、たくさんある場合は入力ボックスがひとつでは足りなくなってしまいます。

そんな場合に「追加&削除できる入力ボックス」をつくっておくと便利です。(つまり、自由に入力項目を追加&削除できる訳ですね)

ということで、今回はこの機能をLaravel + Vueで実装してみたいと思います。

ぜひ皆さんのご参考になれたら嬉しいです。

「首にかけるクーラーで、
夏対策しました。
(ちょっとうるさいけど…😂)」

開発環境: Vue. 2.6

やりたいこと

以下の画像のように入力項目を自由に追加したり、削除したりできるような機能です。

そして、「送信ボタン」をクリックするとAjaxでデータ送信できるようにし、さらに以下3つの「あると嬉しいな!」機能も追加してみます。

  • 追加できる入力項目の最大件数を指定する(残り件数も表示)
  • 入力ボックスが追加されたら自動でフォーカスする
  • ショートカットキーで入力項目を追加できるようにする

では、一緒にやってみましょう!

ルートをつくる

まずはルートですが、今回のメインはVueなので省略形で書きます。
ただ、実際の開発ではきちんとコントローラーをつくることをおすすめします。

/routes/web.php

Route::get('multiple_inputs', function(){

    return view('multiple_inputs');

});

ビューをつくる

続いて、HTMLVue.jsのコードを書いていくビューです。(vueとビューでややこしいですが、こちらは「View」です😂)

なお、ここが今回のメインになりますので、以下のように番号をつけてひとつずつ紹介していきます。

/resources/views/multiple_inputs.blade.php

<html>
<body>
<div id="app">

    <!-- 入力ボックスを表示する場所 ① -->

    <!-- 入力ボックスを追加するボタン ② -->

    <!-- 入力されたデータを送信するボタン ③ -->

    <!-- 確認用 -->
    <hr>
    <label>textsの中身</label>
    <div v-text="texts"></div>

</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
<script>

    new Vue({
        el: '#app',
        data: {
            texts: [], // 複数入力のデータ(配列)
        },
        methods: {

            // ボタンをクリックしたときのイベント ①〜③
            
        }
    });

</script>
</body>
</html>

なお、textsの中に入力された内容が配列で入ってくることになります。

では、ひとつずつ見ていきましょう!

入力ボックスを表示する場所 ・・・ ①

ではまず、複数入力に対応したテキストボックスを表示します。
基本コードの①番に以下のコードを追加してください。

<!-- 入力ボックスを表示する場所 ① -->

<div v-for="(text,index) in texts">

    <!-- 各入力ボックス -->
    <input type="text" v-model="texts[index]">

    <!-- 入力ボックスの削除ボタン -->
    <button type="button" @click="removeInput(index)">削除</button>

</div>

ここでは、textsという変数をv-forでひとつずつ取り出し、それをテキストボックスの値としてv-modelに指定しています。

※ ここで注意が必要なのがv-modelの指定方法を以下のようにしてしまうと、うまくいかない点です。

<!-- ⚠ これはうまくいきません! -->
<input type="text" v-model="text">

この形だと入力自体はできますが、textsに変更内容が伝わらなくなってしまいます。

そして、テキストボックスの下にはその項目を削除するボタンを配置し、クリックされたらremoveInput()を実行するようにします。

removeInput()の中身はこのようになります。

new Vue({

    // 省略

    methods: {
        // ボタンをクリックしたときのイベント ③
        removeInput(index) {

            this.texts.splice(index, 1); // 👈 該当するデータを削除

        }
    }
});

ここでは、クリックされたボタンのインデックス番号(0, 1, 2, 3 …の通し番号)を受取り、その番号を元にしてtextsから1件データを削除することになります。

例:インデックス番号「1」を削除する場合

['テスト文字列 - 1', 'テスト文字列 - 2', 'テスト文字列 - 3']

↓↓↓

['テスト文字列 - 1', 'テスト文字列 - 3']

入力ボックスを追加するボタン ・・・ ②

先ほどの項目では「削除ボタン」をつくったので、次は「追加ボタン」をつくっていきましょう。

基本コードの②番に以下のコードを追加してください。

<!-- 入力ボックスを追加するボタン ② -->
<button type="button" @click="addInput">追加する</button>

そして、ここでもクリックされたときに実行されるイベントaddInput()を追加します。

addInput()の中身は次のようになります。

new Vue({

    // 省略

    methods: {
        // ボタンをクリックしたときのイベント ③
        addInput() {

            this.texts.push(''); // 配列に1つ空データを追加する

        },

        // 省略

    }
});

Vueではこのようにデータを追加するだけで、①番のv-forが再実行され、自動的に入力項目の数が変更になります。

なお、この時点ですでに「増減する」入力項目は完成しています😊

入力されたデータを送信するボタン ・・・ ③

では、入力された配列データをAjaxで送信するボタンをつくりましょう。

<!-- 入力されたデータを送信するボタン ③ -->
<br><br>
<button type="button" @click="onSubmit">送信する</button>

ここでは、ボタンがクリックされたらonSubmit()というメソッドが実行されるようにします。

メソッドの中身はこうなります。

new Vue({

    // 省略

    methods: {

        // 省略

        onSubmit() {

            const url = '/multiple_inputs';
            const params = {
                texts: this.texts
            };
            axios.post(url, params)
                .then(response => {

                    // 成功した時

                })
                .catch(error => {

                    // 失敗した時

                });

        }
    }
});

中身としてはaxiosのパラメータにtextsを入れてPOST送信しているだけです。

そのため、例えば次のような入力をして送信をすると、

このように配列としてデータ送信されることになります。

では、ここまではVueばかりでしたが、せっかくですので送信されたデータをLaravelで受け取る方法もご紹介します。例えば、Itemモデルでデータを保存する場合です。

※ なお、繰り返しになりますが今回は直接ルートにコードを書きます。実際の開発ではコントローラーを使ってください👍

/routes/web.php

use Illuminate\Http\Request;

Route::post('multiple_inputs', function(Request $request){

    foreach($request->texts as $text) {

        $item = new \App\Item();
        $item->text = $text; // ここが入力された値
        $item->save();

    }

});

ユーザービリティを向上させる

ここまでで基本的な機能を実装しましたが、よりユーザーが使いやすい機能をここから追加してみましょう。

追加できる入力項目の最大件数を指定する

例えば、「入力項目は最大5件まで」という制限です。

では、まずは最大件数を保持する変数を追加しましょう。

new Vue({
    el: '#app',
    data: {

        // 省略

        maxTextCount: 5 // 👈 追加
    },

そして、computedに「現在の入力項目が最大件数に達してるかどうか??」をチェックするisTextMaxを追加します。(つまり、すでに最大件数に達していればtrueになります)

new Vue({

    // 省略

    computed: {
        isTextMax() {

            return (this.texts.length >= this.maxTextCount);

        }
    }
});

では、このisTextMaxを使って「追加ボタン」の表示/非表示を切り替えるようにしてみましょう。

<!-- 入力ボックスを追加するボタン ② -->
<button type="button" @click="addInput" v-if="!isTextMax">追加する</button>

これで(今回の場合ですと)入力項目が5件になると「追加ボタン」は非表示になります。

さらに、「追加できる残りの件数」を表示するとユーザーは分かりやすいかもしれませんので、この機能も追加してみましょう。

<!-- 入力ボックスを追加するボタン ② -->
<button type="button" @click="addInput" v-if="!isTextMax">
    追加する
    (残り<span v-text="remainingTextCount"></span>件)
</button>

remainingTextCount()computedに追加します。

new Vue({

    // 省略

    computed: {

        // 省略

        remainingTextCount() {

            return this.maxTextCount - this.texts.length; // 追加できる残り件数

        }
    }
});

この機能を実装するとこうなります。

なお、念のためaddInput()にも以下のコードを追加しておくことをおすすめします。

new Vue({

    // 省略

    methods: {
        // ボタンをクリックしたときのイベント ①〜③
        addInput() {

            if(this.isTextMax) { // 最大件数に達している場合は何もしない

                return;

            }

            // 省略

        },

// 以下省略

入力ボックスが追加されたら自動でフォーカスする

もし「追加ボタン」をクリックして入力項目が追加されたとき、その追加されたばかりの入力ボックスにフォーカスされたら(選択状態になったら)すぐに入力がしやすいですよね。

そのため、この「自動フォーカス機能」もつけてみましょう。

まず、①番のコードにref="texts"を追加して参照ができるようにします。

<!-- 入力ボックスを表示する場所 ① -->
<div v-for="(text,index) in texts">

    <!-- 各入力ボックス -->
    <input ref="texts" type="text" v-model="texts[index]">
    
    // 省略

</div>

もしかすると「ループの中だからtext-${index}とかにするべきじゃ・・・??」と思われたかもしれませんが、Vueでは「v-for」ループの中では「ref」を配列として扱ってくれるので問題ありません。

そのため、実際にフォーカスするコードは以下のようになります。

new Vue({

    // 省略

    methods: {
        // ボタンをクリックしたときのイベント ①〜③
        addInput() {

            this.texts.push(''); // 配列に1つ空データを追加する

            // 👇 追加された入力ボックスにフォーカスする
            Vue.nextTick(() => {

                const maxIndex = this.texts.length - 1;
                this.$refs['texts'][maxIndex].focus();

            });

        },

// 以下省略

先ほども書いたようにthis.$refs['texts']は配列として格納されるので、this.$refs['texts'][maxIndex]という形でinputタグにアクセスすることができます。

なお、Vue.nextTick()は「Vueでの表示変更が完了した後に実行しますよ👍」というコードですが、これはthis.$refsに新しいデータが反映されるのが表示変更された後だからです。

これで、入力項目が追加されると自動的に選択状態になり、すぐ入力ができるようになります。

ショートカットキーで入力項目を追加できるようにする

実際にサイトを運用していると操作に慣れてきて「いちいちマウスとキーボードの移動をするのはメンドウだよ・・・😫」となったことはないでしょうか。

そんな場合を想定して「ショートカット機能」を追加してみましょう。

例えば、入力ボックスが選択されているときにShift + Enterキーをクリックすると自動で新しい入力項目が追加になる機能です。

この機能を実装するには、<input>タグに@keypress.shift.enterを追加するだけでOKです。

<!-- 入力ボックスを表示する場所 ① -->
<div v-for="(text,index) in texts">

    <!-- 各入力ボックス -->
    <input ref="texts"
           type="text"
           v-model="texts[index]"
           @keypress.shift.enter="addInput">

    // 省略

</div>

こうすることでいちいち「追加ボタン」をクリックしなくても入力項目を追加することができて便利ですよ😊👍(さらに、Enterキーだけでは何も動かないことを確かめてみてください)

テストしてみる

今回はテーマ的に体験していただく方がいいと思いましたので、テストページを用意しました。ぜひ実際に動かしてみてくださいね。

サンプルページ

教材ソースコードをダウンロードする

今回実際に開発したソースコード一式を以下からダウンロードすることができます。

ぜひ学習のお役に立ててください。

Laravel + Vueで増減できる複数入力を実装する

ちなみに

条件によってはv-for内で配列を部分削除をするとうまくいかない場合があります。(例: 消していないものが消えたり、消えるべきものが消えない)

これはkeyを設定していないために起こる現象です。
詳しくは、Vue.js の v-for で気をつけておくべきことをご覧ください。

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで、今回はLaravel + Vueで複数入力に対応させてみました。(・・・といってもほぼVueだけの話題になってしまいましたね😂)

今回の内容でぜひ体験していただきたかったのは、「Vueを使えば変数の中身を変更するだけで、リアルタイムで表示が変わる」という部分です。これはReactAngularでも同じですが、初めてこの機能をつかったときはホントに魔法をつかえるようになったような高揚感がありました。

ぜひ皆さんも心の中で(気にならなければ実際に)ガッツポーズを取ってみてくださいね。

ではでは〜❗

「初めてVueを使ったとき、こんなカンジでした😊」

このエントリーをはてなブックマークに追加       follow us in feedly