Laravel поставляется с AlpineJS, а с помощью Breeze пользовательская панель инструментов настраивается с несколькими компонентами Blade, использующими AlpineJS. Они служат хорошим примером того, насколько простым может быть создание выпадающих меню или кнопок, но недостаточно подробно объясняют, как их можно использовать внутри существующего компонента, где данные манипулируются и изменяются. Надеюсь, этот пост поможет вам понять, что возможно, когда речь идет о многоразовых компонентах Alpine.

Создадим компонент Flatpickr

Flatpickr — это очень простой плагин для выбора даты/даты/времени/времени. Он не имеет каких-либо конкретных зависимостей и отлично работает. Варианты тоже очень простые. Вы можете создать ввод flatpickr всего за пару строк кода.

<input type="text" id="flatpickr-input">
<script>
    new flatpickr(document.getElementById('flatpickr-input', {});
</script>

Если мы хотим получить значение, мы можем вызвать document.getElementById('flatpickr-input').value, и мы также можем довольно легко установить значение с помощью document.querySelector("#myInput")._flatpickr.setDate("2022-01-01"), что установит выбранную дату на 1 января 2022 года.

Если мы хотим использовать немного альпийского чутья и сделать это реактивным, чтобы мы не вызывали его постоянно, нам нужно создать компонент Alpine.data() вместе с некоторым HTML, чтобы он работал. Вот хороший пример:

Мы инициализируем наш вход flatpickr, мы заполняем глобальную переменную данными для легкой отладки позже. Мы добавляем обработчик onChange для обновления свойства selectedDate, которое является нашей привязкой x-model.

Обработчик onChange на самом деле не нужен, так как flatpickr устанавливает свойство .value элемента и запускает событие ввода, за которым следит директива x-model. Это может быть необходимо, если вы привязываете свой flatpickr к <div> вместо ввода.

Если мы добавим это в файл Blade, он будет работать нормально, но мы не сможем с ним взаимодействовать. Мы могли бы получить значения при отправке из глобальных параметров, но это не идеально. Что бы мы предпочли, так это передать переменную или x-model в шаблон Blade и заставить его нормально работать с внутренним x-model (имеется в виду двусторонняя привязка).

Компоненты Blade спешат на помощь

Шаблоны Blade подходят для статических вещей, таких как макеты и формы, но их нельзя использовать повторно. Компоненты Blade, однако, можно использовать повторно и заменять, и это то, что нам нужно. В идеале у нас должна быть форма с некоторыми полями, которая выглядит так:

<form x-data="{startDate:'', endDate:''}" @submit.stop.prevent="handleSubmission()">
    ...
    <div class="form-group">
        <label>Start Date</label>
        <x-flatpickr x-model="startDate"></x-flatpickr>
    </div>
    <div class="form-group">
        <label>End Date</label>
        <x-flatpickr x-model="endDate"></x-flatpickr>
    </div>
    ...
</form>

Если это то, что мы хотим, то нам просто нужно придумать работающий компонент. Он должен принять x-model и создать экземпляр flatpickr и связать их вместе. Если мы объединим то, что мы сделали ранее, это не сработает. Если вы посмотрите на отрендеренный HTML, это будет выглядеть так:

Мы можем быстро увидеть, что это ужасно. JavaScript отображается дважды (это легко исправить, если мы используем директиву Blade @once), а x-model=”startDate” и x-model=”endDate” нигде не найти. Что нам нужно сделать, так это использовать свойства и атрибуты компонента Blade. Затем мы можем прочитать атрибут x-model в <x-flatpickr x-model="startDate"> и поместить его в нужное место в коде. Вот модифицированный файл компонента flatpickr:

Теперь мы заменили x-model внутри компонента нашим внешним x-model. Это сработает. Проблема в том, что у вас пока не будет двусторонней привязки. В этой конфигурации все изменения уходят/внедряются, но любые изменения, сделанные вами извне, не попадут в компонент без наблюдателя.

Чтобы понять, почему это важно, представьте себе средство выбора даты начала и окончания. Каждый раз, когда пользователь выбирает дату начала, вы хотите взять эту дату, добавить 1 месяц и автоматически установить ее в качестве даты окончания. Дата начала 1 января, дата окончания сразу становится 1 февраля. Вы хотите, чтобы пользователь мог выбрать другую дату окончания, если это не то, что он искал, поэтому он должен иметь возможность взаимодействовать с ней.

Теперь, когда мы определили, что ищем, давайте попробуем создать наш наблюдатель внутри компонента:

init() {
    ...
    ...
    this.$watch(???, dateVal => fp.setDate(dateVal));
}

Что мы смотрим? Мы не знаем имя наблюдаемого значения, потому что оно было установлено вне компонента. Мы можем получить доступ к {{$xModel}}, но у нас есть Javascript, завернутый в директиву @once, так что это не сработает.

x-modelable с x-model

Alpine предоставляет очень полезную директиву именно для этого сценария: x-modelable. Документы Alpine довольно тонкие и не очень помогают показать, как это может быть полезно. По сути, x-modelable связывает локальное значение данных со значением x-model. Это означает, что мы можем использовать x-modelable в нашем коде и взаимодействовать/привязывать/отслеживать x-model изменений для каждого компонента.

<input 
    type="text" 
    x-model="{{$xModel}}"
    x-modelable="selectedDate" 
    x-ref="flatpickrInput">

В этот момент каждый раз, когда x-model изменяется снаружи, selectedDate будет обновляться внутри. Если selectedDate изменится, то внешние x-model отразят эти изменения.

Давайте соберем все это вместе:

Теперь мы можем поместить этот блейд-компонент несколько раз в наш HTML, и он будет правильно отображать данные и будет привязан в обоих направлениях.

При желании мы могли бы продолжить расширение этого компонента:

  • Передача классов и других атрибутов со свойством {{$attributes}}
  • Передача объекта параметров через внешнее свойство x-data
  • Отправка определенных событий, когда что-то происходит
  • Перемещение фрагмента Javascript в файл app.js

Тем не менее, главное то, что мы можем использовать свойства компонентов и x-modelable для получения многоразовых компонентов Blade. Одно только это знание должно быть достаточно большим трамплином для запуска любых других компонентов, о которых вы только могли подумать.