Условные блоки в Alpine.js
02-12-2020Время чтения ~ 6 мин.Alpine.js 5843
Чтобы показать или скрыть блок используется директива 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
.