Vueなら簡単!時・分が連携する時間入力コンポーネント(DL可)

さてさて、これは今に始まったわけじゃないのですが、やはりソフトウェア開発に携わる人間としていつも心掛けていることがあります。

それが、

ユーザビリティ(使いやすさ)

です。

・・・というのも、いかにいいシステムでも使う人が「うーん、ここ毎回めんどくさいなー」となってしまうと少しずつ使ってもらえなくなってしまい、本末転倒となことになってしまうからです。(とはいってもユーザーさんの気持ちを100%理解するのはとても難しいです。それぞれ好みもありますし)

そして、このあいだ懸案になったのが「時間の入力ボックス」です。

もちろんGoogle Chromeなどでは<input type="time">という時間専用の入力ボックスを用意してくれているのですが、スクロールで0からひとつずつ数字を変更したり、クリックでカチカチするのが結構めんどうだったりします。

こんなカンジですね(特に真ん中の30分に移動するのはなかなかのロングジャーニーです)

※ もちろんキーボード入力できますが、できるだけマウス ⇔ キーボードの動作を減らすのもユーザビリティと考えてます。

そこで!

今回はこのめんどくささを解消すべく、Vueで「時・分」2つのセレクトボックを並べ、マウスクリックだけで時間選択できるようなコンポーネントを作ってみます。

ぜひ皆さんのお役に立てると嬉しいです😊✨

開発環境: Vue 2.6

時・分を入力するVueコンポーネント

では今回は、どちらかといえばVueの基本的なテクニックを応用したものになりますので、順を追って説明していきたいと思います。

Vueを使えるようにする

まずはVueが使えるように準備します。

npmなどのパッケージ・マネージャーを使うといろいろとめんどくさい作業があるので今回はインターネット上で提供されているコード(いわゆるcdn)を通して読み込むことにします。

<html>
<head>
    <!-- Tailwind -->
    <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
    <div id="app">
     <!-- こんなカンジで使えるようにします -->
        <v-hour-minute></v-hour-minute>
    </div>
    <!-- Vue.js -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>
    <script>

        new Vue({
            el: '#app'
        });

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

※ なお、見た目を少しは良くしたいのでTailwindcdnで読み込んでいます。

「時・分」入力のコンポーネント本体をつくる

コンポーネントの基本をつくる

では、ここからコンポーネント本体をつくっていきますので、まず基本形をつくりましょう。

Vue.component('v-hour-minute', {
    props: {
        name: String,
        value: String
    }
});

この中でやっているのは、まずコンポーネント名の指定です。
つまり、実際にこのコンポーネントを使うときは以下のようになります。

<v-hour-minute></v-hour-minute>

そして、次にpropsnamevalueが使えるようにしています。
実際には次のようにして使います。

<v-hour-minute
    name="start_time"
    value="23:15">
</v-hour-minute>

※ なぜnameをつけるかは次のテンプレートの項目で説明します。

テンプレートをつくる

では続いて、コンポーネントで表示する「時・分」2つのセレクトボックスと、これらの値が入ってくる変数のhoursminutesをつくります。

Vue.component('v-hour-minute', {

    // 省略    

    template: `<span>
            <select v-model="hours"></select>
            <span>:</span>
            <select v-model="minutes"></select>
            <input :name="name" type="hidden" v-model="value">
        </span>`,

    data() {
        return {
            hours: '',
            minutes: ''
        };
    }
});

この中でポイントになるのが、dataの部分です。

通常のVueでdataを指定する場合は直接オブジェクトでOKでしたが、コンポーネントの場合は関数で return する必要があります。

(通常の場合)

new Vue({
    el: '#app',
    data: {
        time: '22:10'
    }
});

(コンポーネントの場合)

Vue.component('v-test', {
    data() {
        return {
            user: '山田太郎'
        };
    }
});

また、テンプレート内でわざわざ<input type="hidden">を用意しているのは、Ajax送信だけでなく通常どおり<form></form>の送信にも対応させるためです。つまり、Ajax送信だけを想定している場合は、以下のタグもnameプロパティも不要になります。

<input :name="name" type="hidden" v-model="value">

セレクトボックスの選択肢と変更イベントをつくる

続いて、テンプレートの中に用意した2つのセレクトボックスに関連する部分を作っていきます。

Vue.component('v-hour-minute', {

    // 省略

    template: `<span class="border p-1">
            <select v-model="hours" @change="onChange">
                <option></option>
                <option v-for="(text,value) in options(24)" :value="value" v-text="text"></option>
            </select>
            <span>:</span>
            <select v-model="minutes" @change="onChange">
                <option></option>
                <option v-for="(text,value) in options(60)" :value="value" v-text="text"></option>
            </select>
            <input :name="name" type="hidden" v-model="value">
        </span>`,

    // 省略

    methods: {

        // 「時・分」が変更されたら実行
        onChange() {

            let time = '';

            if(this.hours && this.minutes) {

                time = this.hours.toString().padStart(2, '0') +':'+
                    this.minutes.toString().padStart(2, '0');

            }

            this.$emit('input', time);

        },

        // 「時・分」の選択肢
        options(limitValue) {

            let options = {};

            for(let i = 0 ; i < limitValue ; i++) {

                options[i] = i.toString().padStart(2, '0');

            }

            return options;

        }
    }
});

まずセレクトボックスの選択肢を作るoptions()ですが、ポイントはpadStart()の部分です。

これは、例えば101というように桁数を強制的にに変更するものですが、気をつけないといけないのが、padStart()は文字列のみで使えるということです。わざわざtoString()で数字を文字列に変換しているのはこのためです。

そして、onChange()は「時・分」が変更されたときに実行されるイベント用のメソッドですが、この中ではhoursminutes両方が揃っている場合だけxx:yyという形式で時間の文字列をつくっています。(つまり2つの値が揃っていない場合は空白)

なお、Vueコンポーネントではvalueプロパティの値を変更するには$emitinputイベントなどを送出する必要があります。

this.$emit('input', 'xxx'); // これで「value」が「xxx」に更新されます

では今の時点で見た目がどのようになるかをチェックしておきましょう。

初期値を設定できるようにする

では最後に以下のように初期値を設定できるようにしておきましょう。

<v-hour-minute value="23:15"></v-hour-minute>

初期値はコンポーネントが読み込まれた時点で実行されるmounted()内で正規表現を使い「時・分」を取得します。

Vue.component('v-hour-minute', {

    // 省略

    methods: {

        // 省略

        setTime() {

            const matches = this.value.match(/([0-9]{2}):([0-9]{2})/);

            // すでに値が存在している場合
            if(matches) {

                this.hours = parseInt(matches[1]);
                this.minutes = parseInt(matches[2]);

            }

        }
    },
    watch: {
        value() {

            this.setTime();

        }
    },
    mounted() {

        this.setTime();

    }
});

【追記: 2019.09.30】

以前のままでは、動的に値が変更された場合に対応できなかったので、watch()mounted()両方で時間をセットするように変更しました。(ダウンロード・ファイルも修正済みです)

 

もちろんvaluev-modelは連動しているので次のような使い方もOKです。

(HTML側)

<v-hour-minute v-model="time"></v-hour-minute>

(JavaScript側)

new Vue({
    el: '#app',
    data: {
        time: '22:10'
    }
});

お疲れさまでした!

テストしてみる

では、実際にテストする様子を動画でご覧ください!

うまくいきました!

ちなみに:AMPMもつける場合

せっかくなので、「時・分」だけでなく午前午後を表す「AMPM」も選択できるコードも作ってみました。

基本的にはこれまでの内容と同じなので説明は省略しますが、興味がある方は以下の「ダウンロードする」からソースコードをダウンロードして実行してみてください!

こんなカンジです。

ダウンロードする

今回実際に開発したソースコードを以下からダウンロードすることができます。(CDNを使っているので展開したらすぐ実行できます)

時間入力Vueコンポーネント

おわりに

ということで今回は「時・分」が連携する時間の入力ボックスをつくってみました。

実際にはセレクトボックスなので、最短2クリックだけで入力ができ、ユーザビリティが向上するんじゃないかと思います。

なお、今回は作りませんでしたがstepというプロパティをつくって「分」を5分ごとや15分ごとにするとより入力が楽になると思います。

ぜひ皆さんも試してみてくださいね。

ではでは〜!

この記事が役立ちましたらシェアお願いします😊✨