Laravel + Pusher でスマホでパソコンの文字を入力

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

さてさて、これは私に限った話ではないかもしれませんが、「過去にお断りした案件」というのはいつまで経っても覚えていたりしないでしょうか。

私の場合、特に「技術力がなくてお断りせざるを得なかった」開発というものはたまに「ちっくしょう😣」と思い出したりします。

そして、少し前に「Z世代はスマホだけ使い、パソコンは使わない」という言葉を聞いた時にある「できなかった開発」のことを思い出しました。

それが・・・・・・

スマホを使って今見ているパソコン画面に入力をする

というものでした。

おそらく、最近の若い人たちは「キーボードよりフリック入力の方が早い」という方も多いと思うので、それに対応したいという案件だったようです。

そして、当時はお断りしましたが、今になって考えてみるとPusherのようなブロードキャストを使うとシンプルに実装できるんじゃないか、という考えになったのでぜひリベンジ的につくってみることにしました。

(ちょっと個人的な理由からですが)ぜひ何かのお役に立ちましたら嬉しいです。😄✨

「海外のプロジェクトも
参加してます👍」

開発環境: Laravel 8.x

前提として

今回はPusherを利用した「プライベート」なリアルタイム通知を使います。
そのため、ログイン機能がLaravelにインストールされていることが前提です。

もし、まだの方は以下を参考にしてインストールを完了させておいてください。

📝 参考ページ: Laravel Breezeで「シンプルな」ログイン機能をインストール

また、リアルタイム通知にPusherというサービスを使います。
以下のページを参考にしてLaravelからアクセスできるようにしておいてください。

📝 参考ページ: リアルタイム・チャットをつくる:リアルタイムに更新する部分をつくる

では、実際に作業をしていきましょう❗

Pusherへの通知イベントをつくる

まず、LaravelからPusherへ通知をするイベントをつくります。
必要になるのは、以下2つです。

  • PcFocused: パソコン側で入力ボックスにフォーカスされたとき。すでに入力されている文字をスマホ側へ送信します。
  • MobileEntered: スマホ側で入力が完了したとき。 入力内容を送信します。

では、以下のコマンドを実行してください。

php artisan make:event PcFocused
php artisan make:event MobileEntered

するとファイルが2つ作成されることになるので、中身を以下のように変更します。

app/Events/PcFocused.php

<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class PcFocused implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    private $user, $default_text;

    public function __construct(User $user, $default_text = '')
    {
        $this->user = $user;
        $this->default_text = $default_text;
    }

    public function broadcastOn()
    {
        $channel_name = 'mobile_input.'. $this->user->id; // ユーザー専用のチャンネル名

        return new PrivateChannel($channel_name);
    }

    public function broadcastWith()
    {
        return [
            'default_text' => $this->default_text // 初期テキストを送信する
        ];
    }
}

app/Events/MobileEntered.php

<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class MobileEntered implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    private $user, $entered_text;

    public function __construct(User $user, $entered_text = '')
    {
        $this->user = $user;
        $this->entered_text = $entered_text;
    }

    public function broadcastOn()
    {
        $channel_name = 'mobile_input.'. $this->user->id; // ユーザー専用のチャンネル名

        return new PrivateChannel($channel_name);
    }

    public function broadcastWith()
    {
        return [
            'entered_text' => $this->entered_text,
        ];
    }
}

コントローラーをつくる

では、まずはコントローラーをつくります。
以下のコマンドを実行してください。

php artisan make:controller MobileInputController

すると、ファイルが作成されるので中身を以下のように変更してください。

app/Http/Controllers/MobileInputController.php

<?php

namespace App\Http\Controllers;

use App\Events\MobileEntered;
use App\Events\PcFocused;
use Illuminate\Http\Request;

class MobileInputController extends Controller
{
    private $user;

    public function __construct()
    {
        $this->middleware(function($request, $next) {

            $this->user = auth()->user();
            return $next($request);

        });
    }

    /*  パソコン側  */
    public function pc_create()
    {
        return view('mobile_input.pc_create')->with([
            'user' => $this->user
        ]);
    }

    public function pc_store(Request $request)
    {
        // バリデーションは省略しています

        PcFocused::dispatch($this->user, $request->text);

        return ['status' => 'success'];
    }

    /*  スマホ側  */
    public function mobile_create()
    {
        return view('mobile_input.mobile_create')->with([
            'user' => $this->user
        ]);
    }

    public function mobile_store(Request $request)
    {
        // バリデーションは省略しています

        MobileEntered::dispatch($this->user, $request->text);

        return ['status' => 'success'];
    }
}

この中で重要なのはイベントを起動している部分です。
各イベントにはそれぞれ__construct()で定義した「ユーザー」と「テキスト」を引数としてセットしてます。

また、MobileInputControllerの中にある__construct()ではログインユーザーを取得しているのですが、少しトリッキーになっています。

というのも、直接__construct()内でログインユーザーを取得することはできないため、一旦middleware()をはさむ必要があるからです。

※ なお、今回は分かりやすく説明するためチャンネル名にはユーザーIDを使っていますが、なりすましでデータを取得される可能性があります。そのため、こういったことを防止したい場合はUUIDやワンタイムパスワードなどを使う方がいいでしょう。

ビューをつくる

では、コントローラー内で指定したビュー2つをつくっていきます。

resources/views/mobile_input/pc_create.blade.php

<html>
<head>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div id="app" class="p-5">
    <div class="h3">スマホからテキスト入力するサンプル(パソコン側)</div>
    <div class="row">
        <div class="col-6 p-3">
            <textarea class="form-control mobile-input" rows="5">テキスト 1</textarea>
        </div>
        <div class="col-6 p-3">
            <textarea class="form-control mobile-input" rows="5">テキスト 2</textarea>
        </div>
        <div class="col-6 p-3">
            <textarea class="form-control mobile-input" rows="5">テキスト 3</textarea>
        </div>
        <div class="col-6 p-3">
            <textarea class="form-control mobile-input" rows="5">テキスト 4</textarea>
        </div>
    </div>
</div>
<script src="/js/app.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>

    window.onload = () => {

        const elements = document.querySelectorAll('.mobile-input');
        let focusingElement = null;

        [].forEach.call(elements, element => {

            element.addEventListener('focus', e => { // フォーカスしたとき

                focusingElement = e.target; // 入力するテキストエリアをキープ

                const url = '{{ route('mobile_input.pc_store') }}';
                const params = { 'text': e.target.value };

                axios.post(url, params)
                    .then(response => {

                        if (response.data.status === 'success') {

                            console.log('スマホ側へ送信しました');

                        }

                    });

            });

            element.addEventListener('blur', () => { // フォーカスが外れたとき

                focusingElement = null;

            });

        });

        Echo.private('mobile_input.{{ $user->id }}')
            .listen('MobileEntered', e => {

                if(focusingElement) {

                    focusingElement.value = e.entered_text;

                } else {

                    console.error('入力ボックスにフォーカスされていません');

                }

            });

    };

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

この中で重要なのが以下の部分です。

<meta name="csrf-token" content="{{ csrf_token() }}">

詳しい理由につきましては、以下をご覧ください。

📝 参考ページ: Pusher 用認証用に csrf トークンをセット

resources/views/mobile_input/mobile_create.blade.php

<html>
<head>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div id="app" class="p-5">
    <div class="h3">スマホからテキスト入力するサンプル(スマホ側)</div>
    <div class="row">
        <div class="col-12 p-3">
            <textarea id="textarea" class="form-control mobile-input" rows="15"></textarea>
        </div>
    </div>
</div>
<script src="/js/app.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>

    window.onload = () => {

        document.querySelector('#textarea')
            .addEventListener('input', e => {

                const text = e.target.value;
                const url = '{{ route('mobile_input.mobile_store') }}';
                const params = { text: text };

                axios.post(url, params)
                    .then(response => {

                        if (response.data.status === 'success') {

                            console.log('PC側へ送信しました');

                        }

                    });

            });

        Echo.private('mobile_input.{{ $user->id }}')
            .listen('PcFocused', e => {

                document.querySelector('#textarea').value = e.default_text;

            });

    };

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

ルートをつくる

最後にルートです。

use App\Http\Controllers\MobileInputController;

// 省略

Route::prefix('mobile_input')->middleware('auth')->group(function(){

    Route::get('pc/create', [MobileInputController::class, 'pc_create'])->name('mobile_input.pc_create');
    Route::post('pc', [MobileInputController::class, 'pc_store'])->name('mobile_input.pc_store');
    Route::get('mobile/create', [MobileInputController::class, 'mobile_create'])->name('mobile_input.mobile_create');
    Route::post('mobile', [MobileInputController::class, 'mobile_store'])->name('mobile_input.mobile_store');

});

ログインが必須なので、middleware('auth')をつけています。

テストしてみる

では、実際にテストしてみましょう❗
まずは、(ログインを済ませてから)PC&スマホ用の画面にアクセスします。

(パソコン側)

(スマホ側)

では、この状態でパソコン側の「テキスト1」と入力されているテキストエリアにフォーカスを当ててみましょう。

(パソコン側)

すると・・・・・・

(スマホ側)

はい❗「テキスト1」がスマホ側へ自動的に転送されてきました。

では、この初期テキストに何か追加してみましょう。

(スマホ側)

パソコン側ではどうなるでしょうか・・・・・・・??

(パソコン側)

はい❗

今度は、パソコン側のテキストが自動的に変更になりました。(ちなみにIME変換前でもリアルタイムで表示されました)

では、続いてパソコン側の「テキスト3」と入力されているテキストエリアにフォーカスしてみます。

(パソコン側)

すると・・・・・・

(スマホ側)

はい❗
先ほどと同じように「テキスト3」という初期テキストが転送されてきました。

では、こちらも何かテキストを追加してみます。

(スマホ側)

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

(パソコン側)

はい❗

こちらも同様に新しいテキストをスマホ側からテキストを入力することができました。

成功です😄✨

企業様へのご提案

今回のテクノロジーを使うと、キータイピングに慣れていない人でもパソコン画面を見ながらスマホで入力をすることができます。

また、汎用的に利用するために入力ボックスにフォーカスが当たった時点でQRコードを表示するようにすれば、すぐスマホからの入力も可能です。

もしそういったシステムをご希望でしたら、ぜひお問い合わせからご連絡ください。

どうぞよろしくお願いいたします。m(_ _)m

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

おわりに

ということで、今回はPusherを使ってスマホからパソコンの入力を実装してみました。

ちなみに今回やってみて「ブロードキャスト」って通知以外にもいろんな使い方ができるんだなと実感しました。

Pusherは無料枠も大きいですし、使い方次第ではすごくユーザビリティが向上するんじゃないかと感じています。

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

ではでは〜❗

「結局、お正月関係なく
プログラムしてました…😂」

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