Preface
Goal: Attaching event to element with Alpine.js.
Article Steps
The alpine.js
is divided into two parts.
-
Inline
Alpine.js
, then -
Extracted
Alpine.js
.
The first one is a little bit of messy and so obstrusive, but once we extract properly, the document would looks tidy.
Examples
For tutorial purpose we need two layouts:
-
Simple layout, to explain
Alpine.js
. -
Enhance layout, as real live example.
Credits
For my friend ‘Sholeh Udin, who taught me to write proper
alpine.js`.
Preview
Simple Tabs : Inline Structure
Document: HTML Head
We can reuse previous tailwind
stylesheet.
<title>Simple Tabs - jQuery</title>
<link rel="stylesheet" type="text/css"
href="../css/03-tailwind-simple.css">
<script src="../js/vendors/alpine.min.js"></script>
Yes, we do not need any external javascript yet. All will be written inline.
Document: HTML Body: Tab Headers
Compared with plain javascript
and jQuery
,
alpine.js
is radically different.
In fact most modern web framework using this style.
<main class="tabs" x-data="{ tab: 'news' }">
<div class="tab-headers">
<div x-on:click="tab = 'home'"
x-bind:class="{
'active bg-blue-500' : tab == 'home',
'bg-gray-700' : tab != 'home' }"
>Home
</div>
…
</div>
…
</main>
So simple, that there is no need to explore DOM at all.
How Does It Works?
We are going to initialize stuff with x-data
,
so we do not need to create logic to initialize stuff.
In contrast with click event in previous unobtrusive
example.
Then there is this x-bind:class
to manage class addiition, class removal, and class toggling.
The beauty of the event x-on:click
is, we just set variable.
And that’s all it takes to get this code works.
Javascript: Handling Headers Event
<div x-on:click="tab = 'home'"
x-bind:class="{
'active bg-blue-500' : tab == 'home',
'bg-gray-700' : tab != 'home' }">
Home
</div>
Document: HTML Body: Tab Contents
The same writing style, also applied the tab contents.
<div class="tab-contents">
<div x-show="tab == 'home'"
x-bind:class="{ 'bg-blue-500' : tab == 'home' }">
<h3>Home</h3>
<p>Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
Quisque in faucibus magna.</p>
</div>
<div x-show="tab == 'team'"
x-bind:class="{ 'bg-teal-500' : tab == 'team' }">
<h3>Team</h3>
<p>Nulla luctus nisl in venenatis vestibulum.
Nam consectetur blandit consectetur.</p>
</div>
<div x-show="tab == 'news'"
x-bind:class="{ 'bg-red-500' : tab == 'news' }">
<h3>News</h3>
<p>Phasellus vel tempus mauris,
quis mattis leo.
Mauris quis velit enim.</p>
</div>
<div x-show="tab == 'about'"
x-bind:class="{ 'bg-orange-500' : tab == 'about' }">
<h3>About</h3>
<p>Interdum et malesuada fames ac ante
ipsum primis in faucibus.
Nulla vulputate tortor turpis,
at eleifend eros bibendum quis.</p>
</div>
</div>
I know it is long. But that is all.
Your code should be working by now.
Javascript: Handling Headers Event to Tab Content
Consider this example below, for home
tab:
<main class="tabs" x-data="{ tab: 'news' }">
<div class="tab-headers">
<div x-on:click="tab = 'home'">
Home
</div>
…
</div>
<div class="tab-spacer"></div>
<div class="tab-contents">
<div x-show="tab == 'home'"
x-bind:class="{ 'bg-blue-500' : tab == 'home' }">
<h3>Home</h3>
<p>Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
Quisque in faucibus magna.</p>
</div>
…
</div>
</main>
Preview
The same as previous example.
Simple Tabs : Using Template
We can go further with <template x-for="">
.
The Riddle: Issue with Alpine!
Can we make this simpler?
We have to write down the declaration, for each element such as below:
<div class="tab-headers">
<div x-on:click="tab = 'home'"
x-bind:class="{
'active bg-blue-500' : tab == 'home',
'bg-gray-700' : tab != 'home' }"
>Home
</div>
<div x-on:click="tab = 'team'"
x-bind:class="{
'active bg-teal-500' : tab == 'team',
'bg-gray-700' : tab != 'team' }"
>Team
</div>
<div x-on:click="tab = 'news'"
x-bind:class="{
'active bg-red-500' : tab == 'news',
'bg-gray-700' : tab != 'news' }"
>News
</div>
<div x-on:click="tab = 'about'"
x-bind:class="{
'active bg-orange-500' : tab == 'about',
'bg-gray-700' : tab != 'about' }"
>About
</div>
</div>
Common Solution
Consider transform from code above into template.
First we setup the data:
x-data="{
tab: 'news',
list: {
1: { name: 'home', title: 'Home' },
2: { name: 'team', title: 'Team' },
3: { name: 'news', title: 'News' },
4: { name: 'about', title: 'About' }
}
}"
Then we define the template.
<div class="tab-headers">
<template x-for="item in Object.values(list)" :key="item">
<div
x-on:click="tab = item.name"
x-bind:class="{
'active bg-blue-500' : tab == item.name,
'bg-gray-700' : true
}"
x-text="item.title"></div>
</template>
</div>
As shown above, we cannot process dynamic class.
We have to use active bg-blue-500
.
While all I wanted is something like:
x-bind:class="{
item.color : tab == item.name,
'bg-gray-700' : true
}"
</main>
The color result is not match, between left and right, as shown below:
Solving Dynamic Class
Actually we have a workaround for this.
Use your friendly neighbourhood tools, the brain
and the logic
.
All we need to do is replacing with these lines below:
x-bind:class="{
'active bg-blue-500' : item.name == 'home' && tab =='home',
'active bg-teal-500' : item.name == 'team' && tab =='team',
'active bg-red-500' : item.name == 'news' && tab =='news',
'active bg-orange-500' : item.name == 'about' && tab =='about',
'bg-gray-700' : true
}"
And voila, we have the right color, right.
Complete Template
There are limitation for my requirement.
But there is also workaround.
I guess alpine.js
is good enough for simple project.
The complete template can written as below codes:
<main class="tabs" x-data="{
tab: 'news',
list: {
1: { name: 'home', title: 'Home' },
2: { name: 'team', title: 'Team' },
3: { name: 'news', title: 'News' },
4: { name: 'about', title: 'About' }
}
}">
<div class="tab-headers">
<template x-for="item in Object.values(list)" :key="item">
<div
x-on:click="tab = item.name"
x-bind:class="{
'active bg-blue-500' : item.name == 'home' && tab =='home',
'active bg-teal-500' : item.name == 'team' && tab =='team',
'active bg-red-500' : item.name == 'news' && tab =='news',
'active bg-orange-500' : item.name == 'about' && tab =='about',
'bg-gray-700' : true
}"
x-text="item.title"></div>
</template>
</div>
…
</main>
Document: HTML Body: Tab Contents
By this time of this writing, there is no improvement for tab contents.
Enhanced Tabs: Structure
How about real live component?
The features that we add are just hover
and bg-white
.
But it is going to be messier than above.
Document: HTML Head
We can add the javascript at the beginning. We still can reuse previous stylesheet.
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tabs - Alpine.js (inline)</title>
<link rel="stylesheet" type="text/css" href="../css/04-tailwind-enhanced.css">
<link rel="stylesheet" type="text/css" href="../css/02-border-radius.css">
<script src="../js/vendors/alpine.min.js"></script>
Document: HTML Body: Initialization
<main class="tabs" x-data="{ tab: 'news', hover: false }">
/* … elements here … */
</main>
- .
Document: HTML Body: Tab Headers
We have the third level div
.
along with the x-on:mouseenter
and x-on:mouseleave
event.
<div class="tab-headers">
<div x-on:click="tab = 'home'; hover = tab;"
x-on:mouseenter="hover = (tab == 'home') ? tab : false"
x-on:mouseleave="hover = false"
x-bind:class="{
'active bg-blue-500' : tab == 'home',
'bg-gray-700' : tab != 'home' }">
<div x-bind:class="{ 'bg-white' : tab == 'home' }"
>Home</div>
</div>
…
</div>
Whoaaa… This looks complex.
Document: HTML Body: Tab Content
We also have the third level div
here.
<div class="tab-contents">
<div x-show="tab == 'home'"
x-bind:class="{ 'bg-blue-500' : tab == 'home' }">
<div class="tab-content"
x-bind:class="{ 'is-hovered' : hover == 'home' }">
<h3>Home</h3>
<p>Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
Quisque in faucibus magna.</p>
</div>
</div>
…
</div>
I also feel, not comfortable with this. But we have templates right?
Enhanced Tabs : Using Template
Apply the <template x-for="">
further
The Solution
Of course we can make this simpler.
<main class="tabs" x-data="{
tab: 'news',
hover: false,
list: {
1: { name: 'home', title: 'Home' },
2: { name: 'team', title: 'Team' },
3: { name: 'news', title: 'News' },
4: { name: 'about', title: 'About' }
}
}">
<div class="tab-headers">
<template x-for="item in Object.values(list)" :key="item">
<div
x-on:click="tab = item.name; hover = tab;"
x-on:mouseenter="hover = (item.name == tab) ? tab : false"
x-on:mouseleave="hover = false"
x-bind:class="{
'active bg-blue-500' : item.name == 'home' && tab =='home',
'active bg-teal-500' : item.name == 'team' && tab =='team',
'active bg-red-500' : item.name == 'news' && tab =='news',
'active bg-orange-500' : item.name == 'about' && tab =='about',
'bg-gray-700' : true
}">
<div
x-bind:class="{ 'bg-white' : item.name == tab, '': true }"
x-text="item.title"></div>
</div>
</template>
</div>
…
</main>
There is nothing new here except this line below:
<div
x-bind:class="{ 'bg-white' : item.name == tab, '': true }"
x-text="item.title"></div>
</div>
Logic is your friend.
Preview
You can open your favorite browser to test the result.
What’s Next?
We need to extract the alpine.js
attributes to separate javascript class.
Consider continue reading [ Tabs - JS - Alpine.js (extracted) ].