モーダルの切り替えのように、ページの相互作用が完全なサーバーラウンドトリップを保証しない場合がたくさんあります。
このような場合、AlpineJSはLivewireの完璧な友人になります。
これにより、JavaScriptの動作をVueJSと非常によく似た宣言的/反応的な方法で(あなたが慣れているならば)マークアップへ直接振り替えることができます。
インストール
Livewireで使用するために、Alpineをインストールする必要があります。
プロジェクトにAlpineをインストールするには、次のスクリプトタグをレイアウトファイルの
<head>
セクションに追加します。
<head>
...
<script src="https://cdn.jsdelivr.net/gh/alpinejs/[email protected]/dist/alpine.min.js" defer></script>
<!-- "defer"属性は、Alpineが最初にLivewireのロードされるのを待つために重要です。 -->
</head>
インストールの詳細については、AlpineJSのドキュメントへアクセスしてください。
Livewire内でAlpineを使用する
これはLivewireコンポーネントのビュー内の「ドロップダウン」機能にAlpineJSを使用する例です。
<div>
...
<div x-data="{ open: false }">
<button @click="open = true">Show More...</button>
<ul x-show="open" @click.away="open = false">
<li><button wire:click="archive">Archive</button></li>
<li><button wire:click="delete">Delete</button></li>
</ul>
</div>
</div>
再利用可能なBladeコンポーネントの抽出
両ツール自体にまだ慣れていない場合は、両方の構文を混在させると少し混乱するかもしれません。
このため可能であれば、Livewire内(およびアプリケーション内の任意の場所)で使用できるように、Alpineパーツを再利用可能なBladeコンポーネントに抽出する必要があります。
例を以降に示します(Laravel7のBladeコンポーネントタグ構文を使用)。
Livewireビュー
<div>
...
<x-dropdown>
<x-slot name="trigger">
<button>Show More...</button>
</x-slot>
<ul>
<li><button wire:click="archive">Archive</button></li>
<li><button wire:click="delete">Delete</button></li>
</ul>
</x-dropdown>
</div>
再利用可能な"dropdown" Bladeコンポーネント
<div x-data="{ open: false }">
<span @click="open = true">{{ $trigger }}</span>
<div x-show="open" @click.away="open = false">
{{ $slot }}
</div>
</div>
これで、LivewireとAlpineの構文が完全に分離され、他のコンポーネントから使用できる再利用可能なBladeコンポーネントができました。
AlpineからのLivewireの操作: $wire
Livewireコンポーネント内の任意のAlpineコンポーネントから、魔法の$wire
オブジェクトにアクセスして、Livewireコンポーネントを操作できます。
使用法を示すため、Livewireを完全に内部で使用する"counter"コンポーネントをAlpineに作成します。
class Counter extends Component
{
public $count = 0;
public function increment()
{
$this->count++;
}
}
<div>
<!-- Alpine counterコンポーネント -->
<div x-data>
<h1 x-text="$wire.count"></h1>
<button x-on:click="$wire.increment()">Increment</button>
</div>
</div>
これで、ユーザーが"Increment"をクリックすると、標準のLivewireラウンドトリップが起動され、AlpineはLivewireの新しい$count
値を反映します。
$wire
は内部でJavaScriptProxyを使用しているため、プロパティにアクセスしてメソッドを呼び出すことができ、それらの操作はLivewireに転送されます。この機能に加えて、$wire
には標準の組み込みメソッドも用意しています。
$wire
の完全なAPIは次のとおりです。
// Livewireプロパティへのアクセス
$wire.foo
// Livewireメソッドの呼び出し
$wire.someMethod(someParam)
// Livewireメソッドを呼び出し、その結果を使用して何かを実行する
$wire.someMethod(someParam)
.then(result => { ... })
// Livewireメソッドを呼び出し、async/awaitを使用してその応答を保存します
let foo = await $wire.getFoo()
// 名前が"some-event"で2つのパラメータを持つLivewireイベントを発行します
$wire.emit('some-event', 'foo', 'bar')
// 名前が"some-event"で発行されたLivewireイベントをリッスンします
$wire.on('some-event', (foo, bar) => {})
// Livewireプロパティの取得
$wire.get('property')
// Livewireプロパティに特定の値を設定する
$wire.set('property', value)
// 遅らせて、Livewireプロパティに特定の値を設定する
$wire.set('property', value, true)
// Livewireアクションの呼び出し
$wire.call('someMethod', param)
// ファイルをアップロードし、Livewireプロパティを設定する
$wire.upload(
'property',
file,
finishCallback = (uploadedFilename) => {},
errorCallback = () => {},
progressCallback = (event) => {}
)
// 複数のファイルをアップロードし、Livewireプロパティを設定する
$wire.uploadMultiple(
'property',
files,
finishCallback = (uploadedFilenames) => {},
errorCallback = () => {},
progressCallback = (event) => {}
)
// アップロードしたファイルを削除し、Livewireプロパティを更新する
$wire.removeUpload(
'property',
uploadedFilename,
finishCallback = (uploadedFilename) => {},
errorCallback = () => {}
)
// ベースとなるLivewireコンポーネントのJavaScriptインスタンスへのアクセス
$wire.__instance
LivewireとAlpineの間で状態を共有する: @entangle
Livewireには、"entangle"と言う名前の非常に強力な機能があり、LivewireとAlpineのプロパティを一緒に絡める(entangle)ことができます。エンタングルメントで一方の値が変更されると、もう一方の値も変更されます。
実例を示すために、以前のドロップダウンの例を考えてみましょう。ただし、LivewireとAlpineの間に絡み合ったshowDown
プロパティがあります。エンタングルメントを使用することで、AlpineとLivewireの両方からドロップダウンの状態を制御できるようになりました。
class Dropdown extends Component
{
public $showDropdown = false;
public function archive()
{
...
$this->showDropdown = false;
}
public function delete()
{
...
$this->showDropdown = false;
}
}
<div x-data="{ open: @entangle('showDropdown') }">
<button @click="open = true">Show More...</button>
<ul x-show="open" @click.away="open = false">
<li><button wire:click="archive">Archive</button></li>
<li><button wire:click="delete">Delete</button></li>
</ul>
</div>
これで、ユーザーはAlpineを使用してドロップダウンをすぐにオンに切り替えることができますが、"Archive"などのLivewireアクションをクリックすると、ドロップダウンはLivewireから閉じるように指示されます。AlpineとLivewireはどちらもそれぞれのプロパティを操作でき、もう一方は自動的に更新されます。
場合により、Alpineの変更ごとにLivewireを更新する必要はなく、変更を次のLivewireリクエストに伸ばす必要が起きます。このような場合、次のように.defer
プロパティをチェーンできます。
<div x-data="{ open: @entangle('showDropdown').defer }">
...
これでユーザーがドロップダウンのオープンとクローズを切り替えても、Livewireに対してAJAXリクエストを送信しませんが、Livewireアクションが"archive"や"delete"などのボタンから起動されると、"showDropdown"はリクエストと一緒にバンドルされます。
この違いを理解するのが難しい場合は。ブラウザの開発ツールを開き、.defer
を追加した場合と追加しない場合のXHRリクエストの違いを観察してください。
BladeコンポーネントからLivewireディレクティブへアクセスする
Livewireアプリケーション内で再利用可能なBladeコンポーネントを抽出するのは、不可欠なパターンでしょう。
Livewireコンテキスト内にBladeコンポーネントを実装する際に遭遇する可能性のある問題のひとつは、コンポーネント内からwire:model
などの属性の値にアクセスすることです。
たとえば、次のようなテキスト入力Bladeコンポーネントを作成したとしましょう。
<!-- 使用 -->
<x-inputs.text wire:model="foo"/>
<!-- 定義 -->
<div>
<input type="text" {{ $attributes }}>
</div>
このような単純なBladeコンポーネントは完全に正常に機能します。LaravelとBladeは、コンポーネントに追加した属性(この場合はwire:model
など)を自動的に転送し、属性バッグ($attributes
)をエコーアウトするため、それらは<input>
タグに配置されます
しかし、コンポーネントに渡されたLivewireの属性について、より詳細な情報を抽出する必要がある場合もあります。
Livewireはこうした手間を支援するために$attributes->wire()
メソッドを提供しています。
以下がBladeコンポーネントの使用方法です。
<x-inputs.text wire:model.defer="foo" wire:loading.class="opacity-25"/>
次のように、Bladeの$attribute
バッグからLivewireディレクティブ情報にアクセスできます。
$attributes->wire('model')->value(); // "foo"
$attributes->wire('model')->modifiers(); // ["defer"]
$attributes->wire('model')->hasModifier('defer'); // true
$attributes->wire('loading')->hasModifier('class'); // true
$attributes->wire('loading')->value(); // "opacity-25"
これらのLivewireディレクティブを個別に「転送(forward)」することもできます。例をご覧ください。
<!-- 元 -->
<x-inputs.text wire:model.defer="foo" wire:loading.class="opacity-25"/>
<!-- 次のようにwire:model.defer = "foo"ディレクティブを転送できます。 -->
<input type="text" {{ $attributes->wire('model') }}>
<!-- 出力 -->
<input type="text" wire:model.defer="foo">
このユーティリティを使用する方法はたくさんありますが、よくある例は、前述の
@entangle
ディレクティブと組み合わせて使用することです。
<!-- 使用 -->
<x-dropdown wire:model="show">
<x-slot name="trigger">
<button>Show</button>
</x-slot>
Dropdown Contents
</x-dropdown>
<!-- 定義 -->
<div x-data="{ open: @entangle($attributes->wire('model')) }">
<span @click="open = true">{{ $trigger }}</span>
<div x-show="open" @click.away="open = false">
{{ $slot }}
</div>
</div>
{note}
.defer
修飾子がwire:model.defer
を介して渡された場合、@entangle
ディレクティブはそれを自動的に認識し、@entangle('...').defer
修飾子を内部で追加します。
DatePickerコンポーネントの作成
Livewire内のJavaScriptの一般的な使用例は、カスタムフォーム入力です。日付ピッカー、カラーピッカーなどのようなものは、大抵の場合アプリケーションに不可欠です。
上記のような同じパターンを使用する(そして追加ソースを追加する)ことで、Alpineを利用して、これらのタイプのJavaScriptコンポーネントとのやり取りを簡単にできます。
wire:model
を使用してLivewireで一部のデータをバインドするために使用できるdate-picker
という名前の再利用可能なBladeコンポーネントを作成しましょう。
使用方法は次のとおりです。
<form wire:submit.prevent="schedule">
<label for="title">Event Title</label>
<input wire:model="title" id="title" type="text">
<label for="date">Event Date</label>
<x-date-picker wire:model="date" id="date"/>
<button>Schedule Event</button>
</form>
このコンポーネントは、Pikadayライブラリを使用します。
ドキュメントによると、パッケージの最も基本的な使用法(アセットをインクルードした後)は次のようになります。
<input type="text" id="datepicker">
<script>
new Pikaday({ field: document.getElementById('datepicker') })
</script>
必要なのは
<input>
要素だけで、Pikadayはすべての追加の日付ピッカー動作を追加します。
次に、このライブラリで再利用可能なBladeコンポーネントを作成する方法を見てみましょう。
再利用可能なdate-picker
Bladeコンポーネント:
<input
x-data
x-ref="input"
x-init="new Pikaday({ field: $refs.input })"
type="text"
{{ $attributes }}
>
{note} {{ $attributes }}式は、コンポーネントタグで宣言された追加のHTML属性を転送するLaravel7以降のメカニズムです。
wire:model
input
イベントの転送
内部的には、wire:model
はイベントリスナーを追加し、input
イベントが要素上または要素の下にディスパッチされるたびにプロパティを更新します。LivewireとAlpineの間で通信する別の方法は、Alpineを使用し、wire:model
を含む要素内または要素上に、データを含むinput
イベントをディスパッチすることです。
ユーザーが最初のボタンをクリックすると、$foo
プロパティがbar
に設定され、ユーザーが2番目のボタンをクリックすると$foo
がbaz
に設定される不思議なサンプルを作成してみましょう。
Livewireコンポーネントビューの中
<div>
<div wire:model="foo">
<button x-data @click="$dispatch('input', 'bar')">Set to "bar"</button>
<button x-data @click="$dispatch('input', 'baz')">Set to "baz"</button>
</div>
</div>
より現実的な例は、Livewireコンポーネント内で使用される可能性のある"color-picker" Bladeコンポーネントの作成です。
Color-pickerコンポーネント使用例
<div>
<x-color-picker wire:model="color"/>
</div>
コンポーネントの定義には、Vanilla Pickerという名前のサードパーティカラーピッカーライブラリを使用します。
このサンプルは、ページにロードされていることを前提としています。
Color-picker Bladeコンポーネント定義(コメントなし)
<div
x-data="{ color: '#ffffff' }"
x-init="
picker = new Picker($refs.button);
picker.onDone = rawColor => {
color = rawColor.hex;
$dispatch('input', color)
}
"
wire:ignore
{{ $attributes }}
>
<span x-text="color" :style="`background: ${color}`"></span>
<button x-ref="button">Change</button>
</div>
Color-picker Bladeコンポーネント定義(コメントあり)
<div
x-data="{ color: '#ffffff' }"
x-init="
// 'change'ボタンをクリックすると、ピッカーが表示されるようにバインドします。
picker = new Picker($refs.button);
// 新しい色が選択されるたびに、このコールバックを実行します。
picker.onDone = rawColor => {
// Alpineの`color`プロパティを設定します。
color = rawColor.hex;
// Dispatch the color property for 'wire:model' to pick up.
// ピックアップするために'wire:model'のcolorプロパティをディスパッチします。
$dispatch('input', color)
}
"
// Vanilla Pickerはこの要素内に自身のDOMをアタッチするため、`wire:ignore`を
// 追加して、LivewireにDOM差分をスキップするように指示する必要があります。
wire:ignore
// `wire:model=color`のようにコンポーネントタグに追加された属性をすべて転送します
{{ $attributes }}
>
<!-- 背景色を選択した色に設定して、現在の色の値を表示します。 -->
<span x-text="color" :style="`background: ${color}`"></span>
<!-- このボタンをクリックすると、カラーピッカーダイアログが表示されます。 -->
<button x-ref="button">Change</button>
</div>
DOMの変更を無視する(wire:ignore
を使用)
幸いにしてPikadayのようなライブラリは、ページの最後に追加のDOMを追加します。他の多くのライブラリは、初期化されるとすぐにDOMを操作し、それらを操作するときにDOMを変更し続けます。
これが発生した場合にLivewireは、コンポーネントの更新時に保持したいDOM操作と、破棄するDOM操作を追跡するのが困難になります。
コンポーネント内のHTMLのサブセットへの変更を無視するようにLivewireに指示するには、wire:ignore
ディレクティブを追加します。
Select2ライブラリは、DOMの一部を乗っ取るライブラリの1つです(<select>
タグを多くのカスタムマークアップに置き換えます)。
これはLivewireコンポーネント内でSelect2ライブラリを使用して、wire:ignore
の使用法を示す例です。
<div>
<div wire:ignore> {{-- [tl! highlight] --}}
<select class="select2" name="state">
<option value="AL">Alabama</option>
<option value="WY">Wyoming</option>
</select>
<!-- Select2はここにDOMを挿入する -->
</div>
</div>
@push('scripts')
<script>
$(document).ready(function() {
$('.select2').select2();
});
</script>
@endpush
{tip} 要素への変更を無視すると便利な場合もありますが、その子は無視したくない場合は注意してください。この場合、
wire:ignore.self
のように、self
修飾子をwire:ignore
ディレクティブに追加できます。