Условные блоки в Alpine.js
02-12-2020Reading time ~ 6 min.Alpine.js 5590
Чтобы показать или скрыть блок используется директива x-show
, однако существует ещё одна альтернатива — x-if
. Работают они одинаково, с той разницей, что x-show
можно применить к любому html-тэгу, а x-if
работает только с тэгом TEMPLATE.
Этот тэг имеет особое значение в HTML 5 — по умолчанию он скрыт и применяется для «отложенного создания клиентского контента». Его содержимое является шаблоном/фрагментом, который инициализируется (отображается) через JavaScript. Тэг TEMPLATE является «базовым» при использовании веб-компонентов. В Alpine.js он играет туже самую роль, хотя и с небольшим ограничением — в него должен быть вложен тэг-контейнер (об этом ниже).
Таким образом вопрос использования x-if
лежит скорее в плоскости предпочтений вебмастера.
Базовый пример
Сделаем аля-табы с использованием x-if
.
<div x-data="{b: 0}"> <button @click="b = 1" class="button">First</button> <button @click="b = 2" class="button">Second</button> <button @click="b = 0" class="button">Hide</button> <template x-if="b == 1"> <div>First content</div> </template> <template x-if="b == 2"> <div>Second content</div> </template> </div>
Здесь алгоритм аналогичен предыдущему с x-show
, только применяется к TEMPLATE.
Внутри этого тэга всегда должен быть корневой элемент. В данном случае это DIV. А вот такой вариант — ошибочный:
<template x-if="b == 2"> Second content </template>
Здесь нет корневого элемента и блок может работать неверно.
При использовании TEMPLATE мы можем использовать его html-разметку совершенно отдельно без привязки к Alpine. Это ещё одно отличие от x-show
, где директивы прописываются в самом блоке. Здесь же можно вынести контент каждого блока в отдельные файлы и подключать их например через php-функцию require
без опаски, что это сломает сам Alpine-код.
Формы
Использование x-if
делает код немного понятней. Это можно использоваться например в формах, которые меняются в зависимости от выбора пользователя. Простой пример.
<div x-data="{b: false, s: 1}"> <label><input type="checkbox" x-model="b"> Check me</label> <template x-if="!b"> <span class="t-gray600 t-italic mar10-l animation-fade">Please, select this...</span> </template> <template x-if="b"> <div class="mar15-tb animation-fade"> <select x-model="s"> <option value="1">First</option> <option value="2">Second</option> </select> <template x-if="s == 1"> <span>First content</span> </template> <template x-if="s == 2"> <span>Second content</span> </template> </div> </template> </div>
Здесь два интерактивных элемента: чекбокс и выпадающий список. Когда чекбокс отмечен, появляется блок списка. В зависимости от его выбора, появляется и подсказка.
Двустороннее связывание данных в Alpine
Уж коли я привёл пример с полями ввода, то обратите внимание на директиву x-model
. Она используется специально для полей ввода: input, textarea и select. В x-model
указывается переменная, которая будет автоматом связана со значением этого поля (value
).
<div x-data="{t: ''}"> <div><input type="text" x-model="t"> <span x-text="t"></span></div> <div><button @click="t = 'Hi!'" class="button">Hi!</button></div> </div>
Переменная t
служит хранилищем значения input. Когда пользователь вводит какой-то текст, то он автоматически сохраняется в этой переменной. Но при этом, если мы ещё где-нибудь её изменим, то она автоматом изменит и значение input. То есть работает в двух направлениях, отсюда и название «двусторонняя передача данных» (two-way data binding).
Другие примеры
Вернёмся к x-if
. Иногда при вёрстке необходимо поменять вывод в зависимости от каких-то условий, например для телефона нужно отобразить другой блок. Обычно мы это делаем с помощью @media-условий, но при желании можно сделать это и на Alpine.
Для затравки простой пример:
<div x-data="{small: false}" x-on:resize.window="small = window.outerWidth < 768"> <template x-if="!small"> <div>wide content</div> </template> <template x-if="small"> <div>small content</div> </template> </div>
Если поиграть шириной браузера, то будет выводиться либо один, либо другой контент. В данном примере мы «прицепились» к событию onresize
и отслеживаем изменения ширины браузера.
С практической точки зрения, если нам потребуется больше вариантов (phone, tablet и т.д.), удобней было бы вынести всю «программную» часть в js-функцию и использовать её в x-spread
.
Вначале код функции:
<script> function screenInit() { return { small: false, phone: false, tablet: false, desktop: false, setWidth: function() { w = window.outerWidth; this.small = w <= 360; this.phone = (w > 360 && w <= 667); this.tablet = (w > 667 && w <= 768); this.desktop = w > 768; }, trigger: { ['x-on:resize.window']() { this.setWidth(); }, ['x-on:load.window']() { this.setWidth(); }, }, } } </script>
В ней мы задаём логические переменные, которые меняются в зависимости от ширины экрана и установленных пороговых значений. Событие x-on:load.window
инициализирует их в момент загрузки страницы.
К сожалению директивуx-init
вx-spread
использовать нельзя.
Применять можно так:
<div x-data="screenInit()" x-spread="trigger"> <template x-if="small"> <div>small content</div> </template> <template x-if="phone"> <div>phone content</div> </template> <template x-if="tablet"> <div>tablet content</div> </template> <template x-if="desktop"> <div>desktop content</div> </template> <template x-if="phone || tablet"> <div>phone & tablet content</div> </template> </div>
Вёрстка с таким кодом достаточно приятное занятие. :-)
Итого
С точки зрения вёрстки можно использовать и x-show
, и x-if
— поведение у них одинаково. Если html-код нужно оставить «чистым», то лучше его разместить с x-if
.
Наверное ещё стоит отметить, что x-show
работает с display: none;
(чего хватает в большинстве случаев), а x-if
— как настоящий веб-компоненты — через клонирование DOM-элемента. В каких-то сложных ситуациях, когда блок использует свойство display
, которое не должно меняться, придётся использовать x-if
.