Preface
Goal: Constructing component with Vue.js.
Article Steps
The Vue.js
is divided into two parts.
-
Inline
Vue.js
, then -
Extracted
Vue.js
.
The first one is very similar to Alpine.js
counterpart,
but the extracted component is, a whole new level.
Examples
For tutorial purpose we need two layouts:
-
Simple layout, to explain
Vue.js
. -
Enhance layout, as real live example.
Simple Tabs : Inline Structure
Document: HTML Head
I don’t think I should explain it over and over again.
Document: HTML Body
It is very similar with alpine
.
In fact I copy-paste this vue
from alpine
.
The difference is vue
is using v-
prefix,
while alpine
is using x-
prefix.
<main class="tabs">
<div class="tab-headers">
<div v-on:click="selected = 'home'"
v-bind:class="{
'active bg-blue-500' : selected == 'home',
'bg-gray-700' : selected != 'home' }"
>Home
</div>
…
</div>
<div class="tab-spacer"></div>
<div class="tab-contents">
<div v-show="selected == 'home'"
v-bind:class="{ 'bg-blue-500' : selected == 'home' }">
<h3>Home</h3>
<p>Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
Quisque in faucibus magna.</p>
</div>
…
</div>
</main>
<script>
new Vue({
el: '.tabs',
data: { selected: 'news' }
})
</script>
And vue
doesn’t have x-data
,
but instead we do this at the bottom of the script:
new Vue({
el: '.tabs',
data: { selected: 'news' }
})
It is clear.
Mockup Tabs : Constructing Component
We are going to go back to mockup mode. I mean I just want to explain how to render template, but without any functionality yet.
Document: HTML Head
We can reuse previous tailwind
stylesheet.
<title>Mockup Tabs - Vue.js (component)</title>
<link rel="stylesheet" type="text/css"
href="../css/03-tailwind-simple.css">
<script src="../js/vendors/vue.min.js"></script>
<script src="../js/custom/vue-mockup.js"></script>
For simplicity I separate the vue
part into external file.
No inline javascript at all in HTML body
.
Document: HTML Body
This is very different with all of the previous articles:
This is already a complete body.
<div id="tabs">
<tabs>
<tab name="home" title="Home" color="bg-blue-500">
<h3>Home</h3>
<p>Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
Quisque in faucibus magna.</p>
</tab>
<tab name="team" title="Team" color="bg-teal-500">
<h3>Team</h3>
<p>Nulla luctus nisl in venenatis vestibulum.
Nam consectetur blandit consectetur.</p>
</tab>
<tab name="news" title="News" color="bg-red-500">
<h3>News</h3>
<p>Phasellus vel tempus mauris,
quis mattis leo.
Mauris quis velit enim.</p>
</tab>
<tab name="about" title="About" color="bg-orange-500">
<h3>About</h3>
<p>Interdum et malesuada fames ac ante
ipsum primis in faucibus.
Nulla vulputate tortor turpis,
at eleifend eros bibendum quis.</p>
</tab>
</tabs>
</div>
<script>
new Vue({ el: '#tabs' })
</script>
We have this new tags:
-
<tabs>
component, and -
<tab>
component.
And also there is no distinction,
between tab headers
and tab contents
.
We are going to transform this later with vue
.
Component Template
Vue handle component template in this fashioned:
Component Template
Vue handle component template in this fashioned:
Vue.component('tabs', {
template: `
<main class="tabs">
…
</main>
`,
…
data() {
return {
tabs: this.$children
};
},
…
});
``
First we have the HTML
part,
then we have the declarative javascript
parts.
data() {
return {
tabs: this.$children
};
},
Vue
component require the data to be a method rather than object.
This is why we have the data() { return … }
format,
instead of data: { … }
.
The Parent Template: Tabs
The tab header part, could be transformed into something like this:
<main class="tabs">
<div class="tab-headers">
<div v-for="tab in tabs">{{ tab.title }}
</div>
</div>
</main>
Vue goes further with <slot>
.
Note that we talk about tabs
element,
not just tab-header
only or tab-content
only.
Now the real template can be shown as below:
<main class="tabs">
<div class="tab-headers">
<div class="bg-gray-700" v-for="tab in tabs">{{ tab.title }}
</div>
</div>
<div class="tab-spacer"></div>
<div class="tab-contents">
<slot></slot>
</div>
</main>
Now we know how to pass content from the template to tab-content
.
The Child Template: Tab
How to handle tab-content
?
We also need component to handle the child tab
.
For mockup reason I only show home
content.
<div class="bg-gray-700" v-show="name == 'home'">
<slot></slot>
</div>
Note that this child component also handle data,
for both tab-header
and, tab-content
.
Tab: Properties
We should start from properties from the child element. Because we are going to use the properties in parent method.
Vue.component('tab', {
template: …,
props: {
name: { required: true },
title: { required: true }
}
});
Confused?
It takes adaptation from the conventional thinking, to modern website logic. The next part will explain more.
Complete Code
Now we can gathered both as below code:
Vue.component('tabs', {
template: `
<main class="tabs">
<div class="tab-headers">
<div class="bg-gray-700"
v-for="tab in tabs">{{ tab.title }}
</div>
</div>
<div class="tab-spacer"></div>
<div class="tab-contents">
<slot></slot>
</div>
</main>
`,
data() {
return {
tabs: this.$children
};
}
});
Vue.component('tab', {
template: `
<div class="bg-gray-700" v-show="name == 'home'">
<slot></slot>
</div>
`,
props: {
name: { required: true },
title: { required: true }
}
});
``
Preview
You can open your favorite browser to test the result.
We still have no functionality yet. Nor fancy color.
Simple Tabs : Constructing Component
On the other hand, component is different.
It looks like x-for
template, but more advance.
Document: HTML Head
We can reuse previous tailwind
stylesheet.
<title>Simple Tabs - Vue.js (component)</title>
<link rel="stylesheet" type="text/css"
href="../css/03-tailwind-simple.css">
<script src="../js/vendors/vue.min.js"></script>
<script src="../js/custom/vue-simple.js"></script>
Document: HTML Body
The same with mockup document. No change at all.
The Template: Tabs
The real template can be shown as below:
<main class="tabs">
<div class="tab-headers">
<div
v-for="tab in tabs"
v-on:click="selectTabByName(tab.name)"
v-bind:class="[activeClass(tab), colorClass(tab)]"
>{{ tab.title }}
</div>
</div>
<div class="tab-spacer"></div>
<div class="tab-contents">
<slot></slot>
</div>
</main>
We have v-on
to handle event.
And v-bind
to handle class names.
The Template: Tab
How to handle tab-content
?
We also need component to handle the child tab
.
<div
v-show="isActive"
v-bind:class="this.color">
<slot></slot>
Tab: Properties
Just like previous, but we need additional color
.
And we also need to manage active
class.
Vue.component('tab', {
template: …,
props: {
name: { required: true },
title: { required: true },
color: { default: '' }
},
data() {
return {
isActive: false
};
}
});
We have these three name
, title
, color
for each tab.
That is defined in each ekement as example below:
<tab name="home" title="Home" color="bg-blue-500">
<h3>Home</h3>
<p>Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
Quisque in faucibus magna.</p>
</tab>
Tabs: On Click Event
As written in the template.
<div
v-for="tab in tabs"
v-on:click="selectTabByName(tab.name)"
>{{ tab.title }}
</div>
The actual javascript
can be written as below:
methods: {
selectTabByName(tabName) {
this.selected = tabName;
this.tabs.forEach(tab => {
tab.isActive = (tab.name == tabName);
});
}
}
If you wonder what each variable are, you can have a look at this complete javascript declaration:
Vue.component('tabs', {
data() {
return {
selected: 'news',
tabs: this.$children
};
},
mounted() {
this.selectTabByName(this.selected);
},
methods: {
selectTabByName(tabName) {
this.selected = tabName;
this.tabs.forEach(tab => {
tab.isActive = (tab.name == tabName);
});
}
}
});
As shown above, all the logic written in a more declarative way.
Tabs: Class Binding
Along with the click event, we have class binding. As written in the template.
<div
v-for="tab in tabs"
v-on:click="selectTabByName(tab.name)"
v-bind:class="[activeClass(tab), colorClass(tab)]"
>{{ tab.title }}
</div>
This can also be written in declarative way.
Vue.component('tabs', {
data() {
return {
selected: 'news'
};
},
methods: {
activeClass : function (tab) {
return tab.name == this.selected ? 'active' : '';
},
colorClass : function (tab) {
return tab.name == this.selected ? tab.color : 'bg-gray-700';
}
}
});
Preview
You can open your favorite browser to test the result.
Complete Code
As a summary, here is the complete code:
Vue.component('tabs', {
template: `
<main class="tabs">
<div class="tab-headers">
<div
v-for="tab in tabs"
v-on:click="selectTabByName(tab.name)"
v-bind:class="[activeClass(tab), colorClass(tab)]"
>{{ tab.title }}
</div>
</div>
<div class="tab-spacer"></div>
<div class="tab-contents">
<slot></slot>
</div>
</main>
`,
data() {
return {
selected: 'news',
tabs: this.$children
};
},
mounted() {
this.selectTabByName(this.selected);
},
methods: {
selectTabByName(tabName) {
this.selected = tabName;
this.tabs.forEach(tab => {
tab.isActive = (tab.name == tabName);
});
},
activeClass : function (tab) {
return tab.name == this.selected ? 'active' : '';
},
colorClass : function (tab) {
return tab.name == this.selected ? tab.color : 'bg-gray-700';
}
}
});
Vue.component('tab', {
template: `
<div
v-show="isActive"
v-bind:class="this.color">
<slot></slot>
</div>
`,
props: {
name: { required: true },
title: { required: true },
color: { default: '' }
},
data() {
return {
isActive: false
};
},
});
``
I give the complete code, just in case you miss something.
Enhanced Tabs: Inline Structure
How about real live component?
Document: HTML Head
I don’t think I should explain it over and over again.
Document: HTML Body
It is very similar with previous one. But with more attached complexity.
<main class="tabs">
<div class="tab-headers">
<div v-on:click="selected = 'home'; hovered = selected;"
v-on:mouseenter="hovered = (selected == 'home') ? selected : false"
v-on:mouseleave="hovered = false"
v-bind:class="{
'active bg-blue-500' : selected == 'home',
'bg-gray-700' : selected != 'home' }">
<div v-bind:class="{ 'bg-white' : selected == 'home' }"
>Home</div>
</div>
…
</div>
<div class="tab-spacer"></div>
<div class="tab-contents">
<div v-show="selected == 'home'"
v-bind:class="{ 'bg-blue-500' : selected == 'home' }">
<div class="tab-content"
v-bind:class="{ 'is-hovered' : hovered == 'home' }">
<h3>Home</h3>
<p>Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
Quisque in faucibus magna.</p>
</div>
</div>
…
</div>
</main>
<script>
new Vue({
el: '.tabs',
data: { selected: 'news', hovered: false }
})
</script>
Do not worry. We are not going to use it anyway. We are going to jump right into templates.
Enhanced Tabs : Constructing Component
Scale up without complexity.
Just like before,
but unlike small lightweight alpine
,
we can add functionality without too many complexity.
How do we implement these two features 🤔?
-
hover
and, -
bg-white
.
Document: HTML Head
We can reuse previous tailwind
stylesheet.
<title>Enhanced Tabs - Vue.js (component)</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/vue.min.js"></script>
<script src="../js/custom/vue-enhanced.js"></script>
Document: HTML Body
Surprise?
This is exactly the same with the simple component. Nothing has been changed at all.
<div id="tabs">
<tabs>
<tab name="home" title="Home" color="bg-blue-500">
<h3>Home</h3>
<p>Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
Quisque in faucibus magna.</p>
</tab>
…
</tabs>
</div>
<script>
new Vue({ el: '#tabs' })
</script>
Now you can imagine how scalable Vue is.
The Template: Tabs
Vue handle component template in this fashioned:
The tab header part, could be transformed into something like this:
<main class="tabs">
<div class="tab-headers">
<div
v-for="tab in tabs"
v-on:click="selectTabByName(tab.name)"
v-bind:class="[activeClass(tab), colorClass(tab)]">
<div v-bind:class="{ 'bg-white' : tab.name == selected }"
>{{ tab.title }}</div>
</div>
</div>
<div class="tab-spacer"></div>
<div class="tab-contents">
<slot></slot>
</div>
</main>
That is all.
The bg-white
feature ha been solved.
Tab: Properties
We need to add isHovered
data.
Vue.component('tab', {
data() {
return {
isHovered: false,
isActive: false
};
}
});
Then write the child tab
template,
so the content tab background color also be changed,
when the header tab
get hovered.
<div
v-show="isActive"
v-bind:class="this.color">
<div class="tab-content"
v-bind:class="{ 'is-hovered' : this.isHovered }">
<slot></slot>
</div>
</div>
And also add the events in parent tabs
template.
<div class="tab-headers">
<div
v-for="tab in tabs"
v-on:click="selectTabByName(tab.name)"
v-on:mouseenter="tab.isHovered = true"
v-on:mouseleave="tab.isHovered = false"
v-bind:class="[activeClass(tab), colorClass(tab)]">
<div v-bind:class="{ 'bg-white' : tab.name == selected }"
>{{ tab.title }}</div>
</div>
</div>
And that is all. Done.
Preview
You can open your favorite browser to test the result.
Complete Code
It is a good time to compare the code with
jQuery
orplain javascript
.
As a summary, here is the complete code:
Vue.component('tabs', {
template: `
<main class="tabs">
<div class="tab-headers">
<div
v-for="tab in tabs"
v-on:click="selectTabByName(tab.name)"
v-on:mouseenter="tab.isHovered = true"
v-on:mouseleave="tab.isHovered = false"
v-bind:class="[activeClass(tab), colorClass(tab)]">
<div v-bind:class="{ 'bg-white' : tab.name == selected }"
>{{ tab.title }}</div>
</div>
</div>
<div class="tab-spacer"></div>
<div class="tab-contents">
<slot></slot>
</div>
</main>
`,
data() {
return {
selected: 'news',
tabs: this.$children
};
},
mounted() {
this.selectTabByName(this.selected);
},
methods: {
selectTabByName(tabName) {
this.selected = tabName;
this.tabs.forEach(tab => {
tab.isActive = (tab.name == tabName);
});
},
activeClass : function (tab) {
return tab.name == this.selected ? 'active' : '';
},
colorClass : function (tab) {
return tab.name == this.selected ? tab.color : 'bg-gray-700';
}
}
});
Vue.component('tab', {
template: `
<div
v-show="isActive"
v-bind:class="this.color">
<div class="tab-content"
v-bind:class="{ 'is-hovered' : this.isHovered }">
<slot></slot>
</div>
</div>
`,
props: {
name: { required: true },
title: { required: true },
color: { default: '' }
},
data() {
return {
isHovered: false,
isActive: false
};
}
});
``
Writing Vue.js
is like writing configuration,
while jQuery
or plain javascript
is writing logic.
Since Vue.js
has systematically manage the logic,
all we need is to declare the configuration.
What’s Next?
It is a good time to embrace modern web development.
Starting with vue-cli
along with NPM bundler.
Consider continue reading [ Tabs - JS - Vue Router ].