Упрощение html-кода в Alpine.js

Использование Alpine делает html-код несколько сложнее, поскольку на уровень HTML по сути выносится логика JavaScript. Да, директивы несложные, но, если стоит задача повторить элемент несколько раз, то хочется всё-таки какого-то упрощения.

Для этого в Alpine.js используется директива x-spread, которая ссылается на функцию-обработчик текущего компонента. Общая схема получается такой: в x-dataпрописываем функцию инициализации, которая и содержит объект со всеми функциями компонента.

В таком варианте можно говорить о компонентом подходе в Alpine, где функции могут использоваться многократно. Рассмотрим пару примеров.

«Переключалка» css-класса

Этот пример я уже приводил, он простой.

<div x-data="{show: false}" @click="show = !show" :class="{'t-red': show}">toggle class click</div>
toggle class click

Если внимательно посмотреть, то мы увидим, что основной код — это логика компонента. Единственная вариативная часть — css-класс, который меняется. Если «спрятать» всю логику, то получится так:

<div x-data="toggleClick('t-red')" x-spread="trigger">toggle class click</div>
<div x-data="toggleClick('t-green')" x-spread="trigger" class="t-purple">toggle class click</div>
toggle class click
toggle class click

Сама функция:

<script>
function toggleClick(a) {
    return {
        show: false,
        
        trigger: {
            ['@click']() {
                this.show = !this.show;
            },
            
            [':class']() {
               return this.show ? a : '';
            },
        },
    }
}
</script>

Она возвращает обычный объект с полями для переменных (как show) и данные для x-spread, которые представляют собой Alpine-директивы с колбэк-функциями. То есть по сути мы перенести все директивы в js-функцию в исходном виде.

Разница разве что в том, что для переменных внутри нужно использовать this, а также часть логики нужно делать как обычно в JS (см. if-условие).

Функция, конечно, увеличила общий размер кода, но, с другой стороны, его использование в HTML существенно улучшилось. Мы не только спрятали логику, но и сократили общий размер html-кода.

Этот пример достаточно простой, поэтому рассмотрим что-то поинтересней.

Модальное окно

Возьмём код из предыдущей статьи и переделаем его на x-spread. Вначале сама функция.

<script>
function modalInit(classContainer = 'bg-op60') {
    return {
        modalShow: false,
        
        trigger: {
            ['@click']() {
                this.modalShow = true
            },
        },
        
        container: {
            ['x-show.transition.in.opacity.duration.500ms.out.duration.400ms']() {
                return this.modalShow
            },
            
            [':class']() {
                return 'pos-fixed pos0-l pos0-t h100 w100 z-index99 ' + classContainer;
            },
        },
        
        content: {
            ['@click.away']() {
                this.modalShow = false
            },
            
            [':class']() {
                return 'pos-absolute w100 w600px-max bg-white';
            },
            
            [':style']() {
                return 'left: 50%; top: 50%; transform: translate(-50%, -50%);';
            },
        },
        
        close: {
            ['@click']() {
                this.modalShow = false
            },
            
            [':class']() {
                return 'pos-absolute pos10-r pos0-t t150 t-white hover-t-gray200 cursor-pointer';
            },
        },
    }
}
</script>

Вот так можно использовать:

<div x-data="modalInit()">
    <button x-spread="trigger" class="button button1">Open Modal</button>
    
    <div x-spread="container" x-cloak>
        <div x-spread="content">
            <span x-spread="close">×</span>
            <div class="pad10 bg-blue600 t-white">Header</div>
            <div class="pad20-tb pad10-rl">Content</div>
            <div class="pad10 bg-gray100 t-gray600">Footer</div>
        </div>
    </div>
</div>
×
Header
Content
Footer

Согласитесь, использовать такой вариант намного приятней. Даже если мы взяли бы любой другой «плагин» (для jQuery или отдельную разработку), то HTML-разметка всё равно получилась бы похожей, где вместо x-spreadи x-dataпришлось бы использовать css-классы или id. Поскольку функция может использоваться многократно, то модальных форм можно разместить сколько угодно — они все будут работать не зависимо друг от друга.

Удивительно насколько автор Alpine.js Caleb Porzio продумал даже такие «мелочи». :-)

Когда упрощать не имеет смысла

В некоторых случаях введение функции для x-spreadтолько ухудшит код. Например я пытался сделать обычные табы, но в итоге код Alpine оказался самым простым, несмотря на то, что там присутствует логика.

<div x-data="{tab: 1}">
    <button :class="{'bg-blue': tab == 1}" @click="tab = 1" class="button">Foo</button>
    <button :class="{'bg-blue': tab == 2}" @click="tab = 2" class="button">Bar</button>
 
	<div class="pad20 bordered">
    	<div x-show="tab == 1" class="animation-fade">Вкладка Foo</div>
    	<div x-show="tab == 2" class="animation-fade" x-cloak>Вкладка Bar</div>
    </div>
</div>
Вкладка Foo
Вкладка Bar

Алгоритм таков, что в любом случае нужно задавать номер (имя) таба как для вкладки, так и для кнопки переключения. Так же мы не знаем, сколько именно будет вкладок — это уже решает конечный пользователь.

На самом деле этот код меньше, чем если использовать «традиционный» подход. Чтобы получить доступ к элементам табов, приходится использовать уникальные ID, а также предопределённые классы и data-атрибуты. А в коде с Alpine достаточно лишь задать произвольный номер слайда. Всё остальное — это обычные имена css-классов.

Оставьте комментарий!

Комментарий будет опубликован после проверки. Вы соглашаетесь с правилами сайта.

(обязательно)