
Изображение Анни Руйгт
Допустим, у нас есть отдельные компоненты Livewire для:
- Элемент карты Google - интерактивная карта для добавления и удаления маркеров местоположения
- Элемент поисковой строки - для перефокусировки элемента карты Google на заданное пользователем местоположение
- Элемент выпадающего списка - для фильтрации маркеров, показанных в элементе карты Google
- Наконец, форма — для отправки данных, предоставленных пользователем, из всех вышеперечисленных компонентов.
Как именно мы можем обмениваться данными между этими отдельными компонентами Livewire?
Оглавление
Возможности совместного использования
В этой статье мы рассмотрим различные возможности обмена данными между компонентами Livewire:
- Обмен данными через событие dispatchBrowserEvent в Livewire: полезно, когда данные из компонента A на сервере необходимы для внесения изменений в пользовательский интерфейс компонента B.
- Обмен данными через переменные JavaScript: полезно, когда данные, собранные из пользовательского интерфейса компонента A, необходимы для внесения изменений в пользовательский интерфейс компонента B.
- Обмен данными через события emit в Livewire: полезно, когда обработанные данные из компонента A на сервере необходимы компоненту B на сервере
- Обмен данными через Html Query Selectors: полезно, когда нам нужно передать данные из пользовательского ввода, предоставленного в элементах отдельных компонентов, в родительский компонент.
Мы рассмотрим все эти возможности, объединив вместе следующие компоненты Livewire: элемент Google Maps, поисковую строку, выпадающий фильтр и форму.
Создание компонента карты Google
Давайте начнем с центрального элемента нашей сегодняшней статьи: Компонент карты Google. Мы будем использовать его для того, чтобы наши пользователи могли добавлять маркеры в различные точки мира.
Для начала создайте компонент с помощью php artisan make:livewire map. Не забудьте добавить в его вид элемент div с заданными шириной и высотой для отображения карты:
<!--app\resources\views\livewire\map.blade.php-->
<div>
<div
wire:ignore id="map"
style="width:500px;height:400px;">
</div>
Затем добавьте скрипт для инициализации элемента Google Maps в этот div. Обязательно авторизуйте доступ к Google Maps api, используя либо встроенный загрузчик bootstrap, либо унаследованный тег загрузки скрипта.
<script>
/* Add Inline Google Auth Boostrapper here */
/* How to initialize the map */
let map;
async function initMap() {
const { Map } = await google.maps.importLibrary("maps");
map = new Map(document.getElementById("map"), {
zoom: 4,
center: { lat: @js( $lat ), lng: @js( $lng ) },
mapId: "DEMO_MAP_ID",
});
}
/* Initialize map when Livewire has loaded */
document.addEventListener('livewire:load', function () {
initMap();
});
</script>
</div>
Заметили @js( $lat ) и @js( $lng ) в приведенном выше фрагменте кода? Это помощник Livewire, который позволяет нам использовать PHP-атрибуты $lat и $lng в JavaScript нашего представления. Нам придется объявить их в компоненте Map:
/* App\Http\Livewire\Map.php */
class Map extends Component{
public $lat = -25.344;
public $lng = 131.031;
Выше мы объявили координаты местоположения по умолчанию. Это покажет местоположение по умолчанию в нашем компоненте карт. В следующем разделе ниже мы добавим компонент поисковой строки, чтобы пользователи могли легко переместить фокус в нужное им место. А затем мы реализуем первый способ обмена данными между компонентами.
Создание компонента поисковой строки
Создайте отдельный компонент с помощью команды: php artisan make:livewire map-search-box. Его вид будет состоять из двух элементов, текстового поля ввода и кнопки:
<!--app\resources\views\livewire\map-saerch-box.blade.php-->
<div>
<input type="text" wire:model.defer="address" />
<button wire:click="search">Search</button>
</div>
Элемент text подключен к атрибуту $address компонента и использует model.defer, чтобы убедиться, что Livewire не отправляет свои запросы по умолчанию при каждом изменении элемента. С другой стороны, элемент кнопки подключен к методу поиска в компоненте Livewire через слушатель клика.
Этот метод search() преобразует строковое значение элемента ввода (подключенного к $address) в координаты местоположения:
/* App\Http\Livewire\MapSearchBox.php */
class MapSearchBox extends Component{
public $address;
public function search()
{
// Use a custom service to get address' lat-long coordinates
// Either through Google GeoCoder or some other translator
$coordinates = new \App\Http\Services\GoogleLocationEncoder(
$this->address
);
}
После получения наших координат из компонента MapSearchBox, нам нужно будет повторно отцентрировать местоположение, видимое из представления компонента Map.
Хм-м-м. Но. Компонент MapSearchBox является отдельным компонентом от компонента Map, так как же именно мы можем обмениваться данными между двумя отдельными компонентами?
Обмен данными с помощью событий браузера
Обычный способ обмена данными о событиях между компонентами - это использование функции emit в Livewire. Однако этот подход фактически делает два немедленных запроса для каждого emit: первый запрос - вызов компонента, от которого исходило событие, а второй - вызов компонента, слушающего испускаемое событие.
В нашем случае (перестановка карты на основе координат из окна поиска) второй запрос нам не нужен. Нам нужно только передать полученные координаты в пользовательский интерфейс нашей карты, чтобы изменить местоположение, на котором центрируется карта.
Поэтому вместо emit давайте воспользуемся функцией dispatchBrowserEvent в Livewire:
/* \App\Http\Livewire\MapSearchBox */
public function search()
{
/* Get coordinates */
// Dispatch event to the page
+ $this->dispatchBrowserEvent( 'updatedMapLocation',[
+ 'lat' => $coordinates->getLatitude(),
+ 'lng' => $coordinates->getLongitude()
+ ]);
}
Событие окна браузера, updatedMapLocation от MapSearchBox, передается на текущую страницу, где все доступные компоненты могут прослушать его. Поскольку представление нашего компонента Map также находится на этой странице, он может легко прослушать отправленное событие и перецентрировать местоположение на основе полученных координат:
/* \app\resources\views\livewire\ */
<script>
// Listen to location update from search box
window.addEventListener('updatedMapLocation',function(e){
// Defer set lat long values of component
@this.set( 'lat', e.detail.lat, true);
@this.set( 'lng', e.detail.lng, true);
// Translate to Google coord
let coord = new google.maps.LatLng(e.detail.lat, e.detail.lng);
// Re-center map
map.setCenter( coord );
});
Это занимает всего один запрос к компоненту MapSearchBox для обработки координат из заданной пользователем строки и обновления представления компонента карты для повторного центрирования местоположения - замечательно!
Пересмотр компонента Maps для маркировки булавками
Важной функциональностью элементов карты является их функция “Pin Dropping”, позволяющая пользователям отмечать места на карте. Для этого нам понадобится слушатель события click в нашем элементе карты:
let map;
// Dictionary of markers, each marker identified by its lat lng string
+ let markers = {};
async function initMap() {
/* Map initialization logic here... */
// Add marker listener
+ map.addListener("click", (mapsMouseEvent) => {
// Get coordinates
let coord = mapsMouseEvent.latLng.toJSON();
// Generate id based on lat lng to record marker
let id = coord.lat.toString()+coord.lng.toString();
// Add Marker to coordinate clicked on, identified by id
markershttps://fly.io/laravel-bytes/map-livewire/ = new google.maps.Marker({
position: mapsMouseEvent.latLng,
map,
title: "Re-Click to Delete",
});
// Delete marker on re-click
markershttps://fly.io/laravel-bytes/map-livewire/.addListener("click", () => {
markershttps://fly.io/laravel-bytes/map-livewire/.setMap(null);
delete markers.id;
});
});
});
Шаг за шагом, что происходит выше? Во-первых, мы добавляем словарь JavaScript под названием markers для хранения ссылок на уникально идентифицированные маркеры местоположения в представлении компонента Map. Затем мы перерабатываем функцию initMap(), чтобы перехватывать события щелчка на карте.
Для каждого места, на котором был сделан щелчок, мы получаем координаты, генерируем уникальный идентификатор на основе этих координат и присваиваем этот уникальный идентификатор в качестве ключа в словаре markers, устанавливая его значение как ссылку на новый маркер Google Map. Мы также добавляем слушателя при нажатии на каждый новый маркер, чтобы по желанию удалять его при повторном нажатии.
В следующем разделе мы перейдем ко второму способу обмена данными, фильтруя вышеуказанные маркеры с помощью отдельного компонента фильтра.
Создание компонента фильтра
Допустим, нам нужен способ массового удаления маркеров карты в компоненте Map на основе условий фильтрации с сервера. Для этого мы можем добавить компонент: php artisan make:livewire map-marker-filter.
В этом примере мы будем удалять маркеры, которые не находятся в пределах ”области”. Мы будем использовать эти опции “области” для фильтрации маркеров карты. Чтобы получить эти параметры, мы можем объявить их в компоненте MapMarkerFilter:
/* \App\Http\Livewire\MapMarkerFilter */
class MapMarkerFilter extends Component
{
public $options = [
['id'=>1, 'label'=>'Area1'],
['id'=>2, 'label'=>'Area2'],
];
Мы можем предоставить эти ”фильтры области” в качестве $options в элементе select в представлении:
/* app\resources\views\livewire\map-marker-filter.blade.php */
<div>
<select id="area" onchange="filterChange( this )">
<option>Select an Area</option>
@foreach( $options as $opt )
<option value="{{ $opt['id'] }}">
{{ $opt['label'] }}
</option>
@endforeach
</select>
</div>
Когда новый фильтр выбран, мы отправим две вещи компоненту MapMarkerFilter на сервере: 1️⃣the значение выбранного параметра, и, 2️⃣the список координат из словаря маркеров JavaScript.
Обмен данными через переменные JavaScript
Мы можем легко получить опцию 1️⃣selected при изменении, поскольку она находится в нашем текущем компоненте MapMarkerFilter. Но как насчет JS-переменной 2️⃣markers, хранящей наши булавки, которые находятся в представлении карты? Она объявлена в другом компоненте, так как же нам передать ее текущему представлению MapMarkerFilter?
Чтобы передать список маркеров из Map в MapMarkerFilter, давайте попробуем использовать прямой подход: посмотрим, в какой области видимости доступна переменная markers. Будучи объявленной в JavaScript представления компонента Map, посмотрим, можем ли мы получить это значение из JavaScript MapMarkerFilter.
/* app\resources\views\livewire\map-marker-filter.blade.php */
<script>
function filterChange( objVal ){
let filterId = objVal.value;
+ let coords = Object.keys(markers);
+ console.log( coords );
+ @this.filterMarkers( filterId, coords );
}
</script>
Сохраните изменения и выберите вариант из фильтра…и. И - работает! Мы действительно имеем доступ к маркеру JS переменной Map из MapMarkerFilter! Здесь нет ничего удивительного. Это переменные JavaScript, доступные для других скриптов на той же странице:
Обратите внимание на утверждение @this.filterMarkers(). Это встроенный способ вызова метода компонента из JavaScript представления. В приведенном выше случае две переменные, filterId и coords, из JavaScript представления отправляются в метод filterMarkers() в компоненте:
public function filterMarkers( $filterId, $coords )
{
// Using filterId, get marker ids that should be removed from the map
$toRemove = (new \App\Http\Services\MapMarkerFilter)
->getCoordsToRemove( $filterId, $coords );
// Send this back to the view
$this->dispatchBrowserEvent( 'removeMarkers', [
'coords' => $toRemove
]);
}
Из этого метода мы можем использовать выбранный $filterId, чтобы определить, какие маркеры в списке $coordinates должны быть удалены из представления карты.
Как только мы определили координаты маркеров для удаления, мы можем использовать еще один вызов dispatchBrowserEvent для передачи события removeMarkers обратно на страницу клиента. Наши маркеры находятся в компоненте Map, поэтому из JavaScript его представления добавим слушателя этого события и удалим указанный идентификатор маркеров, отправленный из события:
/* app\resources\views\livewire\map.blade.php */
/* Listen to location update from search box */
window.addEventListener('removeMarkers',function(e){
// Delete each cooordinate by id
for( i in e.detail.coords ){
let id = e.detail.coords[i];
markershttps://fly.io/laravel-bytes/map-livewire/.setMap(null);
delete markershttps://fly.io/laravel-bytes/map-livewire/;
}
});
Финал формы
В финале формы мы передадим все введенные пользователем данные из каждого компонента, которые мы объявили выше: ключевое слово поиска из компонента Search, выбранный вариант фильтра из компонента Filter и, наконец, маркеры карты, выбранные в компоненте Map.
Давайте создадим компонент формы с помощью php artisan make:livewire map-form. Для его вида мы просто включим все остальные компоненты, которые мы создали, с дополнительной кнопкой:
<!--app\resources\views\livewire\map-form.blade.php-->
<div>
<h1>Form</form>
<form wire:submit.prevent="submit">
<div class="flex flex-row justify-between">
<livewire:map-search-box />
<livewire:map-territory-filter />
</div>
<livewire:map />
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
Теперь мы можем определенно сделать сигнал emit submit от родителя, который будут слушать все дочерние компоненты и выдавать свои значения в ответ на него:
/* App\Http\Livewire\MapForm.php */
public function submit(): void
{
$this->emit('submit');
}
/* App\Http\Livewire\<somechildcomponenthere> */
// Listen to submit from parent
public $listeners = [
'submit'
];
public function submit(): void
{
$this->emitUp('map-form', $this->valueNeedToPass);
}
Но. Это будет тонна запросов, один запрос от формы, и два запроса к каждому слушающему компоненту (1. для получения родительского события emit, и 2. для отправки значения родительскому компоненту). Ай-яй-яй.
Ладно, поскольку вышеописанное не так уж и экономно, давайте попробуем другой подход к обмену данными, не так ли?
Совместное использование данных через селектор запросов HTML-элемента
Подобно тому, как js-переменные доступны от одного компонента к другому компоненту на той же странице, так и html-элементы! Итак, мы просто получаем значения элементов ввода из компонентов MapSearch и MapMarkerFilter в JavaScript нашей формы MapForm.
Сначала измените элемент формы, чтобы вызвать JS-функцию:
<!--app\resources\views\livewire\map-form.blade.php-->
- <form wire:submit.prevent="submit">
+ <form onsubmit="return process()">
Затем обязательно добавьте идентификатор к их элементам ввода:
<!--app\resources\views\livewire\map-search-box.blade.php-->
<input id="searchString" type="text" placeholder="Location" wire:model.defer="address" />
<!--app\resources\views\livewire\map-marker-filter.blade.php-->
<select id="filterId" onchange="filterChange( this )">
Поскольку маркеры доступны как переменная на текущей странице, мы можем просто вызвать ее и из JavaScript MapForm:
function process()
{
// Get Search keyword
@this.set( 'searchKey', document.getElementById('searchId').value, true );
// Get Selected Filter option
@this.set( 'filterId', document.getElementById('filterId').value, true );
// Get markers keys list, as the key themselves are the coordinates
@this.set( 'coords', Object.keys(markerList), true );
// Trigger Submit
@this.submit();
return false;
}
Затем просто определите метод submit в нашем компоненте MapForm:
/* \App\Http\Livewire\MapForm.php*/
public $searchKey;
public $filterId;
public $coords;
public function submit()
{
// Validate entries
$this->validate([
'searchKey' => 'string',
'filterId' => 'numeric',
'coords' => 'required',
]);
// Do processing
// Redirect
}
Прежде чем вызвать метод submit() на сервере, мы сначала установили значения $searchKey, $filterId и $coords из разных компонентов через выборку запроса и переменные JavaScript. И теперь эти значения из отдельных компонентов находятся в нашем родительском компоненте MapForm!
Возможности обучения
Итак, что мы узнали сегодня?
-Возможности!
Мы узнали о четырех различных, полезных для пользователя возможностях обмена данными между компонентами Livewire.
И хотя не все они являются чисто специфическими для Livewire подходами, они довольно хорошо справляются со своей задачей в конкретных областях применения. Попробуйте их как-нибудь!