Flexible tabs in VueJS [Part 1]

Hello everyone. In this post I’m going to show you how to create a tabbed navigation in VueJS. Tabs are a very flexible and easy way to switch between content. Users are very accustomed with them because, well, they use them already. In their browsers! Working with a tabbed layout is even easier with Vue.

To speed things up and reduce the time spent styling the tabs we are going to use Bootstrap 4. At the time of this writing, Bootstrap 4 is still in alpha version but it’s very stable and brings in some powerful new features. So I’ve ditched Bootstrap 3 in favor of its newer edition. After adding Bootstrap 4 let’s create 3 simple tabs using the ‘nav-tabs’ Bootstrap class:

<div class="container-fluid">
    <div class="row">
        <div class="col-12">
            <ul class="nav nav-tabs">
                <li class="nav-item">
                    <a class="nav-link active" href="#">Tab 1</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">Tab 2</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">Tab 3</a>
                </li>
            </ul>
        </div>
    </div>
</div>

You can see the result we get is very satisfying, taking into account the amount of code we had to write to produce this.

tabblog1

Now that we’ve created the tabs we need a way to fill in their contents. The best tool for this job is a Bootstrap card

So let’s place the following code snippet right under our tabs:

<div class="card tab-contents">
    <div class="card-block">
        <div class="card-title">
            Tab Contents here
        </div>
    </div>
</div>

The result is almost perfect:tabblog2

We need to remove this ugly line under the active tab so that our design can be a little bit more fluent and consistent.

.card.tab-contents {
    border-top: none;
}

Much better now:

tabblog3

Bootstrap gives us a stylistic starting point. But I think we have to improve our tabs to give them a more unique look and feel. To do that I changed some things in the HTML and I’ve added the following Sass styles and a font awesome icon on each tab that will serve as a ‘close button’. I’ve also made each tab a flex container, so that everything is aligned properly within it. A flex container will also be useful to us later when we will add a text box for renaming the tab

HTML:

<div class="container-fluid">
    <div class="row">
        <div class="col-12">
            <ul class="nav nav-tabs">
                <li class="nav-item">
                    <a class="nav-link d-flex align-items-center active tab" href="#">
                        <span>Tab 1</span>
                        <button class="icon-btn">
                            <i class="fa fa-times" aria-hidden="true"></i>
                        </button>
                    </a>
                </li>
                <li class="nav-item">
                    <a class="nav-link d-flex align-items-center tab" href="#">
                        <span>Tab 2</span>
                        <button class="icon-btn">
                            <i class="fa fa-times" aria-hidden="true"></i>    
                        </button>
                    </a>
                </li>
                <li class="nav-item">
                    <a class="nav-link d-flex align-items-center tab" href="#">
                        <span>Tab 3</span>
                        <button class="icon-btn">
                            <i class="fa fa-times" aria-hidden="true"></i>
                        </button>
                    </a>
                </li>
            </ul>
            <div class="card tab-contents">
                <div class="card-block">
                    <div class="card-title">
                        Tab Contents here
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

Sass:

$color-transition-duration: 0.8s;
$accent-color: #2980b9;
$x-hover-color: #c0392b;
$smaller-nav-item-padding: 8px;
$icon-size: 0.875rem;

ul.nav-tabs {
    margin-top: 12px;
}

.card.tab-contents {
    border-top: none;
    border-radius: 0;
} 

.nav-link.tab {
    border-radius: 0;
    
    //Override the 16px Bootstrap default to give it a more tab-like feel
    padding-right: $smaller-nav-item-padding;
    
    span {
        transition: color $color-transition-duration;    
        color: black;
        opacity: 0.54;
        &:hover {
            color: $accent-color;
        }
    }
    
    &.active {
        span {
            opacity: 1;
        }
    }
           
    .icon-btn {
        margin-left: 6px;
        text-decoration: none;    
        background-color: transparent;
        border: none;
        cursor: pointer;
        outline: none;
        font-size: $icon-size;

        .fa-times {
            opacity: 0.54;
            transition: color $color-transition-duration;
            
            &:hover {
                color: $x-hover-color;
            }
        }    
    }    
}

I’ve added some top margin, made the borders rectangular, grayed out the inactive tabs and added a nice transition. Nothing fancy, with enough room for further customization.

One last thing we are going to need is an additional button that will open up a new tab

<li class="nav-item">
    <a class="nav-link d-flex align-items-center tab add-btn" href="#">
        <button class="icon-btn">
            <i class="fa fa-plus" aria-hidden="true"></i>
        </button>
    </a>
</li>

And its styles:

.nav-link.tab {
	&.add-btn {
        padding-left: $smaller-nav-item-padding;        
        
        .icon-btn {
            color: $accent-color;
            margin: 0;    
        }
    }
}

Now that we’ve built the HTML/CSS part we need to move to the actual logic that will allow our tabs to work.

We will create a VueJS view model, add an array of objects where each objects represents a tab and we will dynamically load the tabs and their content on screen. We will also need an activeTab object to store the selected tab which by default will be the first one. All these requirements are expressed in the following view model:

JavaScript:

let app = new Vue({
	el: '#app',
    data: {
    	activeTab: null,
    	tabs: [
        	{
            	id: 1,
            	title: 'Tab 1',
                content: {
                	header: 'Tab 1 Header',
                    content: 'Tab 1 Content: Lorem ipsum dolor sit amet, consectetur adipiscing elit'
                }
            },
            {
            	id: 2,
            	title: 'Tab 2',
                content: {
                	header: 'Tab 2 Header',
                    content: 'Tab 2 Content: Praesent feugiat aliquam odio, at dictum nibh. Ut vitae quam nec nunc rhoncus sodales. In luctus venenatis auctor'
                }
            },
            {
            	id: 3,
            	title: 'Tab 3',
                content: {
                	header: 'Tab 3 Header',
                    content: 'Tab 3 Content:  Praesent consectetur luctus tortor vel feugiat. Vestibulum vitae tempor ipsum, quis pharetra augue. '
                }
            }
        ]
    },
    created: function() {
    	this.activeTab = this.tabs[0];
    }
})

Now that we can dynamically load our tabs, the view greatly simplifies:

HTML:

<div id="app" class="container-fluid" v-cloak>
    <div class="row">
        <div class="col-12">
            <ul class="nav nav-tabs">
                <li v-for="tab of tabs" class="nav-item">
                    <a v-bind:class="{'nav-link d-flex align-items-center tab': true, 'active': (tab.id == activeTab.id) }" href="#">
                        <span>{{tab.title}}</span>
                        <button class="icon-btn">
                            <i class="fa fa-times" aria-hidden="true"></i>
                        </button>
                    </a>
                </li>
                <li class="nav-item">
                    <a class="nav-link d-flex align-items-center tab add-btn" href="#">
                        <button class="icon-btn">
                            <i class="fa fa-plus" aria-hidden="true"></i>
                        </button>
                    </a>
                </li>
            </ul>
            <div class="card tab-contents">
                <div class="card-block">
                    <div class="card-title">
                        {{activeTab.content.header}}
                    </div>
                    <div class="card-text">
                        {{activeTab.content.content}}
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

As you can see we have a v-for loop that populates a template of an li with all the information about the tab. Check the created() method, which is a Vue lifecycle hook called immediately after the view model is initialized. There we make activeTab point, by default, to the first tab

Next, we want a way to cycle through tabs, select one and view its contents. So basically ,we need to change the activeTab whenever we click on a tab and Vue will take care of the rest. To do that we create an ‘activateTab’ in our methods object with just one line of code:

activateTab: function(tab) {
       this.activeTab = tab;
}

Now we only need to handle the event click in the view and pass the tab object to activateTab like this:

 v-on:click="activateTab(tab)"

So our list item template now looks like this:

<li v-for="tab of tabs" class="nav-item">
    <a v-bind:class="{'nav-link d-flex align-items-center tab': true, 'active': (tab.id == activeTab.id) }" href="#" v-on:click="activateTab(tab)">
        <span>{{tab.title}}</span>
        <button class="icon-btn">
            <i class="fa fa-times" aria-hidden="true"></i>
        </button>
    </a>
</li>

You can check the code from the first part of this tutorial in the following fiddle:

In the next part of this tutorial, I’m going to show you how to add, edit and delete tabs. Stay tuned!

Overloading the indexer operator in C#

There’s a feature in C# that’s very useful for some use cases, yet many people seem to overlook it. I’m talking about the indexer operator. What this operator does is, it makes it possible to access elements of an enumerable data structure that your class makes use of, with the familiar [] operator.

Let’s say we have a class named Car:

Now let’s say that we have multiple cars, multiple instances of the class Car and that we want to organize them in a collection. What should we use? A plain old Generic List you might say and you would be correct, but what happens if we want to augment that list with more operations, more car-specific operations. Perhaps we want to clean the cars, or repair them. All that logic can nicely go inside a class named Garage:

As you can see we still use a List to store the cars, but we’re using the delegate pattern. A generic list has a bunch of methods but we only need three of them, the Add(), ElementAt() and Remove() methods. Oh and we also need the Count property so that we know how many cars we have.

This is an initial draft of our garage class showing the use of the delegate pattern. We can also include other stuff to make the class resemble real-life garage even more

The addition of the Clean() and Repair() methods improves our Garage implementation but there’s something missing. Every time we want to access a specific car by index we are forced to use the Get() method. It’s counter-intuitive. Here’s where the powerful indexer operator comes into play. After we’ve implemented an indexer operator in our collection class we will be able to access car elements with array-like syntax, like this:

Let’s see how simple it is to do that

So basically, you override the indexer operator like a property, with a getter and a setter. In this particular example we don’t want to be able to manipulate the contents of the garage so we don’t need a setter. But using a setter is a totally legitimate operation and one you will probably find pretty helpful once you start using it. That’s how easy it is 🙂

Check the whole project on this GitHub repo