Taby za 5 minut s Alpine.js

V tomto článku si ukážeme, jak si velmi jednoduše vytvořit taby s obsahem bez jediného řádku javascriptu! To vše díky javascriptovému frameworku Alpine.js. Jeho autor, Caleb Porzio, jej popisuje takto:

Apline.js nabízí reaktivní a deklarativní možnosti velkým frameworků, jako jsou Vue nebo React, nicméně s mnohem menší režií.

Alpine.js je někde mezi vanilla javascriptem a velkými frameworky. Efektivně řeší problémy s psaním řady posluchačů a funkcí, které je potřeba vykonat. Samozřejmě to neznamená, že by byl schopen nahradit velké a robustní frameworky jako jsou Vue a React. Namísto toho může sloužit pro menší účely, kdy potřebujete něco menšího vytvořit a nezapojovat do toho celý komplexní framework.

Příprava

Nejjednodušší cesta k instalaci Alpine.js je prostě vložit skript do HTML, nicméně je možné ho i nainstalovat klasicky přes NPM. Společně s touto knihovnou využijeme i Tailwind pro jednoduchost. Připravíme si tedy základní HTML:

<!DOCTYPE html>
<html lang="en">

<head>
  <!-- Tailwind -->
  <link href="https://unpkg.com/tailwindcss/dist/tailwind.min.css" rel="stylesheet">
</head>

<body>
  <div class="p-6">
    <!-- Content goes here -->
    <ul class="flex border-b">
      <li class="-mb-px mr-1">
        <a class="bg-white inline-block border-l border-t border-r rounded-t py-2 px-4 text-blue-700 font-semibold" href="#">Active</a>
      </li>
      <li class="mr-1">
        <a class="bg-white inline-block py-2 px-4 text-blue-500 hover:text-blue-800 font-semibold" href="#">Tab</a>
      </li>
      <li class="mr-1">
        <a class="bg-white inline-block py-2 px-4 text-blue-500 hover:text-blue-800 font-semibold" href="#">Tab</a>
      </li>
    </ul>
    <div class="w-full">
      <div>Tab #1</div>
      <div>Tab #2</div>
      <div>Tab #3</div>
    </div>
  </div>

  <!-- Alpine.js -->
  <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
</body>

</html>

Definování scope komponenty

Jak zjistíme, že je tab aktivní? Jako první si potřebujeme vytvořit komponentu. S Alpine.js je toto možné pomocí definování scopu (česky asi něco jako pole rozsahu) komponenty na HTML element, který obsahuje všechny části, se kterými má komponenta pracovat. Všechno se píše inline, takže výsledek vidíte hned (bez potřeby nějaké kompilace) a jednoduše se s tím dá tak hrát či testovat.

Scope si definujeme pomocí x-data. Jakmile toto přidáte k HTML elementu, automaticky můžete definovat data, které budou v této komponentně k dispozici. V našem případě si jako první definujeme proměnnou openTab, která bude defaultně obsahovat hodnotu tabu, který má být aktivní při načtení stránky.

<div x-data="{ openTab: 1 }">
  <ul>
    <li>
      <a href="#">Tab 1</a>
    </li>
    <li>
      <a href="#">Tab 2</a>
    </li>
    <li>
      <a href="#">Tab 3</a>
    </li>
  </ul>
  <div>
    <div>Tab #1</div>
    <div>Tab #2</div>
    <div>Tab #3</div>
  </div>
</div>

Klik, data na dálkové ovládání

Hodnota proměnné openTab je viditelná v celé komponentě a výchozí hodnota je 1. Samozřejmě může použít jakýkoliv jiný index. Další věcí, kterou potřebujeme řešit, je nastavovat proměnné správnou hodnotu, aby bylo jasné, který tab je aktuálně aktivní. Toho dosáhneme jednoduše pomocí metody @click, ve které si definujeme co se má provést při kliku:

<div x-data="{ openTab: 1 }" class="p-6">
  <ul class="flex border-b">
    <li @click="openTab = 1" class="-mb-px mr-1">
      <a class="bg-white inline-block border-l border-t border-r rounded-t py-2 px-4 text-blue-700 font-semibold" href="#">Tab 1</a>
    </li>
    <li @click="openTab = 2" class="mr-1">
      <a class="bg-white inline-block py-2 px-4 text-blue-500 hover:text-blue-800 font-semibold" href="#">Tab 2</a>
    </li>
    <li @click="openTab = 3" class="mr-1">
      <a class="bg-white inline-block py-2 px-4 text-blue-500 hover:text-blue-800 font-semibold" href="#">Tab 3</a>
    </li>
  </ul>
  <div class="w-full">
    <div>Tab #1</div>
    <div>Tab #2</div>
    <div>Tab #3</div>
  </div>
</div>

Dalším krokem je zobrazit správný obsah aktivního tabu. To provedeme pomocí direktivy x-show. Funguje to stejně jako ve Vue (kde je akorát namísto toho v-show) a to tak, že to danému HTML elementu nastaví CSS display: none a naopak - podle toho jak se vyhodnotí daný výraz. V našem případě potřebujeme vyhodnotit, jestli aktuální tab je tab, který by měl být aktivní s použitím indexu, který jsme předtím přidali pomocí kliku:

<div x-data="{ openTab: 1 }" class="p-6">
    <ul class="flex border-b">
      <li @click="openTab = 1" class="-mb-px mr-1">
        <a class="bg-white inline-block border-l border-t border-r rounded-t py-2 px-4 text-blue-700 font-semibold" href="#">Tab 1</a>
      </li>
      <li @click="openTab = 2" class="mr-1">
        <a class="bg-white inline-block py-2 px-4 text-blue-500 hover:text-blue-800 font-semibold" href="#">Tab 2</a>
      </li>
      <li @click="openTab = 3" class="mr-1">
        <a class="bg-white inline-block py-2 px-4 text-blue-500 hover:text-blue-800 font-semibold" href="#">Tab 3</a>
      </li>
    </ul>
    <div class="w-full pt-4">
      <div x-show="openTab === 1">Tab #1</div>
      <div x-show="openTab === 2">Tab #2</div>
      <div x-show="openTab === 3">Tab #3</div>
    </div>
  </div>

Jako poslední nám zbývá upravit stylování tab tlačítek. Když je na tab kliknuto a je aktivní, měl by se zobrazit jako aktivní tab. Toho můžeme dosáhnout pomocí direktivy :class, která vyhodnotí, jestli je aktuální tab aktivní a automaticky přidá potřebné CSS třídy:

<div x-data="{ openTab: 1 }" class="p-6">
    <ul class="flex border-b">
      <li @click="openTab = 1" :class="{ '-mb-px': openTab === 1 }" class="-mb-px mr-1">
        <a :class="openTab === 1 ? 'border-l border-t border-r rounded-t text-blue-700' : 'text-blue-500 hover:text-blue-800'" class="bg-white inline-block py-2 px-4 font-semibold" href="#">
          Tab 1
        </a>
      </li>
      <li @click="openTab = 2" :class="{ '-mb-px': openTab === 2 }" class="mr-1">
        <a :class="openTab === 2 ? 'border-l border-t border-r rounded-t text-blue-700' : 'text-blue-500 hover:text-blue-800'" class="bg-white inline-block py-2 px-4 font-semibold" href="#">Tab 2</a>
      </li>
      <li @click="openTab = 3" :class="{ '-mb-px': openTab === 3 }" class="mr-1">
        <a :class="openTab === 3 ? 'border-l border-t border-r rounded-t text-blue-700' : 'text-blue-500 hover:text-blue-800'" class="bg-white inline-block py-2 px-4 font-semibold" href="#">Tab 3</a>
      </li>
    </ul>
    <div class="w-full pt-4">
      <div x-show="openTab === 1">Tab #1</div>
      <div x-show="openTab === 2">Tab #2</div>
      <div x-show="openTab === 3">Tab #3</div>
    </div>
  </div>

Refaktor

Kód si můžeme trošku zrefaktorovat tím, že si aktivní a neaktivní třídy tabů (které se stále opakují) uložíme do dat komponenty a pak se na ně pouze budeme odkazovat:

<div 
    x-data="{
      openTab: 1,
      activeClasses: 'border-l border-t border-r rounded-t text-blue-700',
      inactiveClasses: 'text-blue-500 hover:text-blue-800'
    }" 
    class="p-6"
  >
    <ul class="flex border-b">
      <li @click="openTab = 1" :class="{ '-mb-px': openTab === 1 }" class="-mb-px mr-1">
        <a :class="openTab === 1 ? activeClasses : inactiveClasses" class="bg-white inline-block py-2 px-4 font-semibold" href="#">
          Tab 1
        </a>
      </li>
      <li @click="openTab = 2" :class="{ '-mb-px': openTab === 2 }" class="mr-1">
        <a :class="openTab === 2 ? activeClasses : inactiveClasses" class="bg-white inline-block py-2 px-4 font-semibold" href="#">Tab 2</a>
      </li>
      <li @click="openTab = 3" :class="{ '-mb-px': openTab === 3 }" class="mr-1">
        <a :class="openTab === 3 ? activeClasses : inactiveClasses" class="bg-white inline-block py-2 px-4 font-semibold" href="#">Tab 3</a>
      </li>
    </ul>
    <div class="w-full pt-4">
      <div x-show="openTab === 1">Tab #1</div>
      <div x-show="openTab === 2">Tab #2</div>
      <div x-show="openTab === 3">Tab #3</div>
    </div>
  </div>

Výsledek

Na závěr si samozřejmě ukážeme celý výsledek: