Laravel + React でソフトウェアキーボードをつくる

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

さてさて、Laravelに標準搭載されたViteをきっかけとして、最近Reactをよく使うようになりましたが、昔触ってみたときと違いとても好感触です😄

そして、Reactを使った「ユーザビリティ」関連の何かをつくっていたときにあるアイデアが浮かびました。

それは・・・・・・

ソフトウェアキーボード

です。

ソフトウェアキーボードとは、ページ内にキーボードを表示しクリックしてもらうことで文字や数字を入力するというものです。(ネット銀行のログインに使われていますよね)

そして、過去にご依頼いただいた作業で「便利かな?」と思い、このソフトウェアキーボードを追加したところ、大変褒めていただいた経験があります。(マウス ↔ キーボードの行き来がなくなるから面倒くさがりの私にもピッタリ!)

そこで❗

今回は、Laravel + Reactで入力ボックスにソフトウェアキーボードを実装してみます。

ぜひ何かの参考になりましたら嬉しいです。😀✨

「大阪で『電動キックボード・シェア』
なるものを発見。
時代は動いてますね!」

開発環境: Laravel 9.x、Vite、React、Inertia.js、TailwindCSS

ルートをつくる

では、まずはLaravelにルートを追加します。
今回はひとつだけです。

routes/web.php

// 省略

Route::get('software_keyboard', fn() => Inertia::render('SoftwareKeyboard/Index'));

ビューをつくる

では、実際にブラウザで表示されるビューをつくります。
ちなみに今回はテストですので、入力部分は3つ作ることにします。(つまり、ソフトウェアキーボードの3つそれぞれ表示できるようにします)

resources/js/Pages/SoftwareKeyboard/Index.jsx

import {useEffect, useState} from 'react';
import SoftwareKeyboard from '@/Components/SoftwareKeyboard';

export default function Index() {

    // Data
    const [number1, setNumber1] = useState('1234567890');
    const [number2, setNumber2] = useState('0987654321');
    const [number3, setNumber3] = useState('');

    // Effect
    useEffect(() => {  // 入力内容が変更されたら実行される部分

        console.log('number1 →', number1);
        console.log('number2 →', number2);
        console.log('number3 →', number3);

    }, [number1, number2, number3]);

    return (
        <div className='p-5'>
            <h1 className="mb-4 font-bold">
                数字ソフトウェアキーボードつき入力ボックス
                <small>(Laravel + React + inertia)</small>
            </h1>
            <div className="mb-3">
                <SoftwareKeyboard
                    value={number1}
                    onChange={value => setNumber1(value)} />
            </div>
            <div className="mb-3">
                <SoftwareKeyboard
                    value={number2}
                    onChange={value => setNumber2(value)} />
            </div>
            <div className="mb-3">
                <SoftwareKeyboard
                    value={number3}
                    onChange={value => setNumber3(value)} />
            </div>
        </div>
    );

}

内容としては、これからつくるコンポーネントを3つ配置し、その入力内容に変更があったらコンソールに中身を表示するという流れになっています。

コンポーネントをつくる

では、今回のメインになるコンポーネント「SoftwareKeyboard.jsx」をつくっていきましょう。

resources/js/Components/SoftwareKeyboard.jsx

import {useEffect, useRef, useState} from 'react';

export default function SoftwareKeyboard(props) {

    // Data
    const keyboardValues = [
        '7', '8', '9',
        '4', '5', '6',
        '1', '2', '3',
        '0', '←', 'C',
    ];
    const [showKeyboard, setShowKeyboard] = useState(false);
    const [value, setValue] = useState(props.value);
    const [selection, setSelection] = useState(0);
    const [initialized, setInitialized] = useState(false);

    // Methods
    const handleInputChange = newValue => {

        const inputKeyboardValues = keyboardValues.filter(keyboardValue => { // 入力可能な値のみを抽出

            const invalidValues = ['←', 'C'];

            return invalidValues.includes(keyboardValue) === false;

        });
        const pattern = new RegExp('^('+ inputKeyboardValues.join('|') +')*$');

        if(pattern.test(newValue)) { // 入力可能文字だけの場合

            setValue(newValue);

            if(typeof props.onChange === 'function') {

                props.onChange(newValue);

            }

        }

    };
    const handlerKeyboardClick = keyboardValue => {

        const input = inputRef.current;
        const selectionStart = input.selectionStart;
        const selectionEnd = input.selectionEnd;
        let fullValue = value.toString();

        if(selectionStart !== selectionEnd) { // 選択範囲があるときは文字を削除

            fullValue = fullValue.slice(0, selectionStart) + fullValue.slice(selectionEnd);

        }

        let beforeValue  = fullValue.slice(0, selectionStart);
        let middleValue = '';
        let afterValue = fullValue.slice(selectionStart);
        let newSelection = 0;

        if(keyboardValue === '←') { // 1文字削除

            beforeValue = beforeValue.slice(0, -1);
            newSelection = beforeValue.length;

        } else if(keyboardValue === 'C') { // クリア

            beforeValue = '';
            afterValue = '';

        } else {

            middleValue = keyboardValue;
            newSelection = selectionStart + 1;

        }

        setSelection(newSelection);

        const newValue = beforeValue + middleValue + afterValue;
        handleInputChange(newValue);

    };

    // Refs
    const inputRef = useRef();

    // Effect
    useEffect(() => {

        if(initialized === true) {

            const input = inputRef.current;
            input.focus();
            input.setSelectionRange(selection, selection);

        }

        setInitialized(true);

    }, [selection]);

    return (
        <>

            {/* 入力ボックス */}
            <input
                type="text"
                value={value}
                ref={inputRef}
                onChange={e => handleInputChange(e.target.value)}
                onFocus={() => setShowKeyboard(true)} />

            {/* ソフトウェアキーボード */}
            {showKeyboard && (
                <div className="px-3 pb-3 mt-3 bg-gray-100 w-48 border border-gray-50">
                    <div className="text-right">
                        <a href="#" className="text-blue-500 text-xs" onClick={() => setShowKeyboard(false)}>
                            &times; <small>閉じる</small>
                        </a>
                    </div>
                    <div className="grid grid-cols-3 gap-3 mt-1">
                        {keyboardValues.map((keyboardValue, i) => (
                            <button
                                key={i}
                                className="bg-cyan-900 text-white p-1"
                                onClick={() => handlerKeyboardClick(keyboardValue)}>
                                {keyboardValue}
                            </button>
                        ))}
                    </div>
                </div>
            )}

        </>
    );

}

なお、今回はコードはここまでですので、中身を詳しくご紹介します。

Data

コンポーネント内で使うデータ一覧です。
中身は次のとおりです。

    • keyboardValues: ここの中身がソフトウェアキーボードとして表示されます。
    • showKeyboard: ソフトウェアキーボードの表示/非表示を切り替えるために使います。
    • selection: 入力ボックスの中でカーソルがどこの位置にいるのかを保持する変数です。

  • initialized: ページが読み込まれ、初期化が完了しているかどうかをチェックする変数です。

Methods

次にメソッドです。
以下2つのイベント用メソッドを用意しています。

handleInputChange()

入力ボックスの中身に変化があったら呼ばれるメソッドです。

この中では、「予期しない文字があった場合は入力内容を変更しない(無視する)」ようにしています。

また、コンポーネントが呼び出される親側でonChangeイベントがセットされている場合は、その関数を実行するようになっています。

使用例は以下になります。

<SoftwareKeyboard
    value={number1}
    onChange={value => setNumber1(value)} />

handlerKeyboardClick()

ソフトウェアキーボードのボタンがクリックされたときに呼ばれるメソッドです。

まずこの中でやっているのは入力内容の「選択範囲の削除」です。

例えば、上の例で行くと「7」のボタンが押された場合は「12790」となるようにしています。

そして、それ以降は押されたボタンによって処理を変えています。
詳しい内容は次のとおりです。

  • ←: 1文字削除する(バックスペース)
  • C: 入力内容をすべてクリア
  • 0〜9: その数字を挿入する

その他には、新しい入力内容の更新や、入力ボックスのカーソル位置を更新しています。

Refs

入力ボックスの<input>にアクセスするためにセットしています。

Effect

handlerKeyboardClick()内でselection(入力ボックスのカーソル位置)が更新されると実行される部分です。

具体的には入力ボックスにフォーカスし(ボタンをクリックした時点でフォーカスが外れているので元に戻しています)、カーソル位置を更新しています。

なお、なぜinitializedでチェックしているかというと、Reactの場合、ページが読み込まれた時点useEffectが実行されるので、すべてのソフトウェアキーボードが表示されてしまうためです。(Vueの場合、watchにはimmediate: trueをセットしないとページ読み込み時には実行されません)

テストしてみる

では、実際にテストしてみましょう❗

まずViteを起動させてから「http://******/software_keyboard」へアクセスします。

すると、記事の途中でも書いたとおり、入力ボックスが3つ表示され、さらに初期値は以下のとおりになっています。

  • 123456789
  • 0987654321
  • (なし)

では、まずは一番上の入力ボックスにカーソルを合わせてみましょう。

どうなるでしょうか・・・・・・

はい❗
まずは、うまくソフトウェアキーボードが表示されました。

では、この状態で「1」「2」「3」と順にクリックしていきましょう。

はい❗
123」が末尾に挿入され、「1234567890123」になりました。

成功です😄✨

では、次にカーソルを「5」と「6」の間に移動させ、

この状態で「0」「0」「0」とクリックしてみます。

どうなったでしょうか・・・・・・?

はい❗
うまく「5」の後ろに「000」が挿入されました。

次に以下のように先頭と最後以外を選択して、

この状態で「9」をクリックしてみましょう。
うまく選択範囲は削除されるでしょうか??

はい❗
選択範囲な削除され、最終的に「193」になりました。

次に「」ボタンをクリックしてみます。

はい❗
カーソルの前にあった「9」が削除され、「13」が残りました。

そして、続いて「C」ボタンをクリックしてみます。

入力内容はうまくクリアされるでしょうか。

はい❗
入力ボックスの中身がクリアされました。

成功です😄👍

では、最後に2つ目のソフトウェアキーボードも表示されるかチェックしておきましょう。

はい❗
2つ目もちゃんと区別されソフトウェアキーボードが表示されました。

すべて成功です✨😄👍

デモページをつくりました

以下にデモページを作ったのでぜひ実際に試してみてください。

📝 デモページ

企業様へのご提案

今回のようにちょっとした手間を加えておくと、以下のように何度も実行する作業が楽にできるようになり、業務効率化することができます。(他のもっと重要な作業に注力できる)

  • 電話番号の入力
  • 郵便番号の入力
  • 個別番号や商品コードなどの入力
  • 年齢の入力

また、ソフトウェアキーボードを使うと「キーロガー※」の対策にもなると言われており、セキュリティの向上にも役に立つでしょう。

※ キーロガー: キーボードの入力履歴(例えばパスワードなど)を保存したり外部へ送信する悪意のあるソフトウェア。銀行でソフトウェアキーボードがよく使われているのはこういった理由です。

もしこういった業務効率化やセキュリティの対策をご希望でしたら、ぜひお問い合わせからご連絡ください。お待ちしてます。m(_ _)m

おわりに

ということで、今回は「Laravel + React」でソフトウェアキーボードをつくってみました。

コンポーネント化しておくと、いつでも好きなときに使えるので便利だと思います。ぜひ活用してみてくださいね。

ちなみに、当初はuseMemo(処理の結果がキャッシュされる機能。Vuecomputedのようなもの)で変数をつくっていましたが、コンソールで見る限り再レンダリングされるたびに実行されているようでしたので、使うのをやめました(何か他に使い方があるのでしょうか…🤔)

ぜひ皆さんもいろいろとチャレンジしてみてくださいね。

ではでは〜❗

「新たに美味しそうな
ビール醸造を発見❗」

開発のご依頼お待ちしております 😊✨
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly  

開発効率を上げるための機材・まとめ