Tabs Widget page
Create a tabs widget using your own version of jQuery.
Overview
In this part, we will:
- Create a
$.fn.tabs
widget.
Slides
Exercise: $.fn.tabs
The problem
Create a progressively enhanced tabs widget. It will be called like:
$("#breeds, #tech").tabs();
Notice that .tabs()
can be called on multiple elements. An independent tabs widget should be created
on each element in the collection. The elements in the collection should be <ul>
elements with the
following structure:
<ul id="tech">
<li><a href="#canjs">CanJS</a></li>
<li><a href="#stealjs">StealJS</a></li>
<li><a href="#donejs">DoneJS</a></li>
</ul>
<div id="canjs">
<a href="https://canjs.com">CanJS</a>
</div>
<div id="stealjs">
<a href="https://stealjs.com">StealJS</a>
</div>
<div id="donejs">
<a href="https://donejs.com">DoneJS</a>
</div>
The <ul>
elements will have <li>
children which serve as the buttons. Each <li>
must contain an <a>
element. The <a>
element’s href
attributes reference the
id
of a tab element to show when the corresponding <li>
button element is
clicked.
For example when this <li>
is clicked:
<li><a href="#stealjs">StealJS</a></li>
The following tab element should be shown:
<div id="stealjs">
<a href="https://stealjs.com">StealJS</a>
</div>
Finally:
- Each
<ul>
should havetabs
added to itsclassName
. - Each tab element should have
tab
added to itsclassName
.
The following CodePen can be used to complete the exercise.
<style>
/* adapted from https://codepen.io/talleyran/pen/XoPLvy */
body {
font-family: Arial;
font-size: 18px;
}
.tabs {
display: flex;
margin: 1rem 0 0.4rem 0;
padding: 0;
position: relative;
}
.tabs li {
list-style: none;
padding: 0;
line-height: 1.3rem;
}
.tabs li a {
color: #173e4a;
padding: 1rem 1.7rem;
margin-left: -1rem;
text-decoration: none;
position: relative;
}
.tabs li a:link, .tabs li a:visited, .tabs li a:hover, .tabs li a:active, .tabs li a::selection {
outline: 0 !important;
background: none !important;
}
.tabs li:first-child a {
margin-left: 0.5rem;
}
.tabs li a:before {
content: "";
position: absolute;
top: -0.5rem; left: 0;
background: #8BC3E8;
height: 100%;
width: 100%;
z-index: -1;
border-top-left-radius: 1rem;
border-top-right-radius: 1rem;
transform: perspective(0.2rem) rotateX(2deg);
transform-origin: bottom;
box-shadow: 1px 2px 2px #293140;
}
.tabs li.active {
z-index: 1;
}
.tabs li.active a {
cursor: default;
}
.tabs li.active a:before {
background: #98F1FF;
}
.tabs li:not(.active) a:hover:before {
background: #A5C3FF;
}
.tab {
min-width: 30rem;
background: #98F1FF;
color: #173e4a;
border-radius: 0.2rem;
padding: 1.5rem;
z-index: 100;
position: relative;
}
.tab:before {
display: block;
position: absolute;
top: 1px; bottom: 0;
left: 0; right: 0;
z-index: -1;
content: "";
border-radius: 0.2rem;
box-shadow: 1px 1px 1px #293140;
}
img {width: 400px;}
</style>
<script src="//bitovi.github.io/academy/static/scripts/my-jquery.js"></script>
<ul id='breeds'>
<li><a href="#beagles">Beagles</a></li>
<li><a href="#doberman">Doberman</a></li>
<li><a href="#boxer">Boxer</a></li>
</ul>
<div id='beagles'>
Beagle: <img src='//bitovi.github.io/academy/static/img/dom/beagle.jpg'/>
</div>
<div id='doberman'>
Doberman: <img src='//bitovi.github.io/academy/static/img/dom/doberman.jpg'/>
</div>
<div id='boxer'>
Boxer: <img src='//bitovi.github.io/academy/static/img/dom/boxer.jpg'/>
</div>
<ul id='tech'>
<li><a href="#canjs">CanJS</a></li>
<li><a href="#stealjs">StealJS</a></li>
<li><a href="#donejs">DoneJS</a></li>
</ul>
<div id='canjs'>
<a href="https://canjs.com">CanJS</a>
</div>
<div id='stealjs'>
<a href="https://stealjs.com">StealJS</a>
</div>
<div id='donejs'>
<a href="https://donejs.com">DoneJS</a>
</div>
<script type="module">
const $ = window.$;
$.fn.tabs = function(){
};
$("#breeds, #tech").tabs()
</script>
What you need to know
An Immediately Invoked Function Expression (IFFE) can be used to prevent variables and functions from being added to the global scope:
(function () { function activate(li) { // DO STUFF return li; } $.fn.tabs = function () { // can use activate activate(); }; })(); // can NOT use activate activate(); //-> throws an error
The following jQuery functions will be useful:
$("#selector")
- Get a collection of elements using a CSS selector.$([element])
- Create a jQuery collection from an array of elements.collection.children()
- Return a collection of all direct descendants of elements in the source collection.collection.find(selector)
- Using a CSS selector, find elements descended from elements in the source collection.collection.addClass(className)
- Add a class name to elements in the collection.collection.removeClass(className)
- Remove elements in the collection.collection.show()
- Show elements in the collection.collection.hide()
- Hide elements in the collection.collection.bind(event, handler)
- Listen to an event.$.each(collection, cb(i, value) )
- Loop through an array-like collection of elements.
Completed Solution
Click to see completed solution
<style>
/* adapted from https://codepen.io/talleyran/pen/XoPLvy */
body {
font-family: Arial;
font-size: 18px;
}
.tabs {
display: flex;
margin: 1rem 0 0.4rem 0;
padding: 0;
position: relative;
}
.tabs li {
list-style: none;
padding: 0;
line-height: 1.3rem;
}
.tabs li a {
color: #173e4a;
padding: 1rem 1.7rem;
margin-left: -1rem;
text-decoration: none;
position: relative;
}
.tabs li a:link, .tabs li a:visited, .tabs li a:hover, .tabs li a:active, .tabs li a::selection {
outline: 0 !important;
background: none !important;
}
.tabs li:first-child a {
margin-left: 0.5rem;
}
.tabs li a:before {
content: "";
position: absolute;
top: -0.5rem; left: 0;
background: #8BC3E8;
height: 100%;
width: 100%;
z-index: -1;
border-top-left-radius: 1rem;
border-top-right-radius: 1rem;
transform: perspective(0.2rem) rotateX(2deg);
transform-origin: bottom;
box-shadow: 1px 2px 2px #293140;
}
.tabs li.active {
z-index: 1;
}
.tabs li.active a {
cursor: default;
}
.tabs li.active a:before {
background: #98F1FF;
}
.tabs li:not(.active) a:hover:before {
background: #A5C3FF;
}
.tab {
min-width: 30rem;
background: #98F1FF;
color: #173e4a;
border-radius: 0.2rem;
padding: 1.5rem;
z-index: 100;
position: relative;
}
.tab:before {
display: block;
position: absolute;
top: 1px; bottom: 0;
left: 0; right: 0;
z-index: -1;
content: "";
border-radius: 0.2rem;
box-shadow: 1px 1px 1px #293140;
}
img {width: 400px;}
</style>
<script src="//bitovi.github.io/academy/static/scripts/my-jquery.js"></script>
<ul id='breeds'>
<li><a href="#beagles">Beagles</a></li>
<li><a href="#doberman">Doberman</a></li>
<li><a href="#boxer">Boxer</a></li>
</ul>
<div id='beagles'>
Beagle: <img src='//bitovi.github.io/academy/static/img/dom/beagle.jpg'/>
</div>
<div id='doberman'>
Doberman: <img src='//bitovi.github.io/academy/static/img/dom/doberman.jpg'/>
</div>
<div id='boxer'>
Boxer: <img src='//bitovi.github.io/academy/static/img/dom/boxer.jpg'/>
</div>
<ul id='tech'>
<li><a href="#canjs">CanJS</a></li>
<li><a href="#stealjs">StealJS</a></li>
<li><a href="#donejs">DoneJS</a></li>
</ul>
<div id='canjs'>
<a href="https://canjs.com">CanJS</a>
</div>
<div id='stealjs'>
<a href="https://stealjs.com">StealJS</a>
</div>
<div id='donejs'>
<a href="https://donejs.com">DoneJS</a>
</div>
<script type="module">
const $ = window.$;
(function(){
function tabContent(li) {
return $(li.find("a").attr("href"));
}
function activate(li) {
li.addClass("active");
tabContent(li).show();
return li;
}
function deactivate(li) {
li.removeClass("active");
tabContent(li).hide();
return li;
}
$.fn.tabs = function(){
this.addClass("tabs");
return $.each(this, function(i, element) {
var active,
$lis = $([ element ]).children();
$.each($lis, function(i, li) {
var $li = $([ li ]);
var $tab = tabContent($li);
$tab.addClass("tab");
if (i === 0) {
active = activate($li);
} else {
$tab.hide();
}
});
$lis.bind("click", function(event) {
deactivate( active );
active = activate( $([ this ]) );
event.preventDefault();
});
});
};
})();
$("#breeds, #tech").tabs()
</script>