Converting tabs into dropdown menu (not select) - javascript

I am currently using Bootstrap to create tabs and the task is for mobile screen (800px <), it should automatically change into a "fake" dropdown. When i say fake, i mean no select box but utilising the the same ul as the tabs, it should automatically use the first item as a trigger.
I can get the tabs working fine: https://codepen.io/wizly/pen/BlKxo
What do i want to achieve?
On mobile view, when the tabs are in more of a dropdown format, i want to try and get the label for the active tab right at the top of the list.
Scenario: Mobile View Default
[ITEM 1 active >]
Scenario: Mobile view Clicked
[ITEM 1]
[ITEM 2]
[ITEM 3]
Scenario: Mobile view item 3 clicked
[ITEM 3 active >]
Scenario: Mobile view item 3 active clicked
[ITEM 1]
[ITEM 2]
[ITEM 3]
Where am i so far:
$('li a').on('click', function() {
const current = $(this);
const ul = current.parent().parent();
const li = current.parent();
if ($(window).width() < 1023.98) {
ul.toggleClass('expanded');
$('li').removeClass('active');
$('.fake-active').removeClass('fake-active');
if (ul.hasClass('expanded')) {
$('li').removeClass('active');
current.addClass('fake-active');
}
li.toggleClass('active');
// ul.prepend(current.parent());
}
});
#charset "UTF-8";
body {
padding: 10px;
}
#exTab2 h3 {
color: white;
background-color: #428bca;
padding: 5px 15px;
}
#media screen and (max-width: 800px) {
ul li {
display: block !important;
float: none !important;
}
ul li:first-child a {
text-align: left !important;
}
ul li:first-child a:after {
font-family: fontello;
content: "";
font-size: 1em;
font-weight: 400;
font-style: normal;
font-variant: normal;
line-height: 1;
min-width: 1em;
margin-left: 1em;
text-decoration: inherit;
text-transform: none;
float: right;
transform: rotate(90deg);
}
ul li:not(:first-child) {
display: none !important;
}
ul.expanded li:not(:first-child) {
display: block !important;
}
ul.expanded li:not(:first-child) a {
text-align: left !important;
}
ul.expanded li:first-child a:after {
content: "";
transform: rotate(270deg);
}
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet"/>
<div id="exTab2" class="container">
<ul class="nav nav-tabs">
<li class="active">
Overview
</li>
<li>Without clearfix
</li>
<li>Solution
</li>
</ul>
<div class="tab-content ">
<div class="tab-pane active" id="1">
<h3>Standard tab panel created on bootstrap using nav-tabs</h3>
</div>
<div class="tab-pane" id="2">
<h3>Notice the gap between the content and tab after applying a background color</h3>
</div>
<div class="tab-pane" id="3">
<h3>add clearfix to tab-content (see the css)</h3>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>

I know it might be too late...
based on your source code, i used .active instead of :first-child for css selector and changed JS a little, and it seems work properly.
here's the codepen https://codepen.io/lebniz/pen/zYdLXxJ
js:
$("li a").on("click", function () {
const current = $(this);
const ul = current.parent().parent();
let tab_id = current.attr("href");
if ($(window).width() < 1023.98) {
$(tab_id).addClass("active").siblings().removeClass("active");
ul.toggleClass("expanded");
}
});

Related

How can we close a toggle menu with an outside click

I'm new to coding, I am have a made a decent looking website (https://garibpathshala.in/) with a toggle nav menu for mobiles.
is there any way so that if we click outside the menu it'll close the menu.
Please have a look at my code and help me :)
HTML
<ul class="header-nav-links">
<li class="active">HOME</li>
<li>PROJECTS</li>
<li>TEAM</li>
<li>ABOUT</li>
<li>GALLERY</li>
<li>CONTACT</li>
<li>DONATE</li>
<li>JOIN US</li>
</ul>
<div class="burger">
<div line1></div>
<div line2></div>
<div line3></div>
</div>
JS
const burger = document.querySelector(".burger");
const navLinks = document.querySelector(".header-nav-links");
const links = document.querySelectorAll(".header-nav-links li");
//Toggle Nav
burger.addEventListener("click", () => {
navLinks.classList.toggle("open");
//Animate Links
links.forEach((link, index) => {
if (link.style.animation) {
link.style.animation = ""
}else{
link.style.animation = `navLinkFade 0.5s ease forwards ${index / 7+0.2}s`;
}
});
});
Here is a screenshot of the nav menu
You could remove "open" class from the menu if the event.CurrentTarget is not the hamburger menu and anything else in the document (html or body) is clicked.
You would also need to stopImmediatePropagation on the .hamburger and navLinks itself to stop those from being registered as clicks to the body, since they are children of the body and the event would otherwise bubble up to the body.
MDN reference: https://developer.mozilla.org/en-US/docs/Web/API/Event/bubbles
const burger = document.querySelector(".burger");
const navLinks = document.querySelector(".header-nav-links");
const links = document.querySelectorAll(".header-nav-links li");
const body = document.querySelector('html');
//Toggle Nav
burger.addEventListener("click", (e) => {
navLinks.classList.toggle("open");
e.stopImmediatePropagation();
//Animate Links
links.forEach((link, index) => {
if (link.style.animation) {
link.style.animation = "";
}else{
link.style.animation = `navLinkFade 0.5s ease forwards ${index / 7+0.2}s`;
}
});
});
navLinks.addEventListener("click", (eve) => {
eve.stopImmediatePropagation();
});
body.addEventListener("click", (ev) => {
if (ev.currentTarget != burger) {
navLinks.classList.remove("open");
}
});
.burger {
display: block;
cursor:pointer;
}
.header-nav-links {
display: block;
}
.header-nav-links.open {
transform: translateX(0%);
}
.header-nav-links {
right: 0;
position: fixed;
height: 92vh;
top: 16vh;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
align-items: center;
width: 50%;
transform: translateX(100%);
transition: transform 0.5s ease-in;
}
.header-nav-links li {
list-style-type: none;
}
.header-nav-links li:hover {
border: 1px solid #fff;
border-radius: 6pc;
background-color: #007bff;
}
.header-nav-links a {
color: whitesmoke;
text-decoration: none;
font-family: Arial, sans-serif;
font-weight: normal;
font-size: 16px;
border: 0px solid white;
transition: 400ms;
padding: 5px 15px;
border-radius: 19px;
}
<ul class="header-nav-links">
<li class="active">HOME</li>
<li>PROJECTS</li>
<li>TEAM</li>
<li>ABOUT</li>
<li>GALLERY</li>
<li>CONTACT</li>
<li>DONATE</li>
<li>JOIN US</li>
</ul>
<div class="burger">
BURGER
<div line1></div>
<div line2></div>
<div line3></div>
</div>
You could add a click listener on body or document and rely on event delegation to take the appropriate action, as in the sample code below.
(See the in-code comments for further clarification.)
// Selects some DOM elements and converts the collection to an array
const listItems = Array.from(document.querySelectorAll(".header-nav-links > li"));
// Calls `handleMenuDisplay` when anything is clicked
document.addEventListener("click", handleMenuDisplay);
// Defines `handleMenuDisplay`
function handleMenuDisplay(event){ // Listeners can access their triggering events
const clickedThing = event.target; // The event's `target` property is useful
// Depending on what was clicked, takes an appropriate action
if(listItems.includes(clickedThing)){ // Arrays have an `includes` method
openMenu(clickedThing);
}
else{
closeMenu();
}
}
function openMenu(clickedLi){
demo.textContent = clickedLi.dataset.demoText;
}
function closeMenu(){
demo.textContent = "Menu is closed";
}
li{ margin: 7px; padding: 3px; border: 1px solid grey; }
#demo{ margin-left: 2ch; font-size: 1.5em; font-weight: bold; }
<ul class="header-nav-links">
<li data-demo-text="Home menu is open">HOME</li>
<li data-demo-text="Projects menu is open">PROJECTS</li>
<li data-demo-text="Team menu is open">TEAM</li>
<li data-demo-text="About menu is open">ABOUT</li>
</ul>
<p id="demo">Menu is closed</p>
Note: My use of custom data-attributes was just to make the sample code a bit cleaner -- it's not part of event delegation, and the display text for each li could just have easily been written out manually in the script.
Based on the other solutions:
An even simpler way is to use the focus and blur states of DOM elements to handle the state for your menu.
document.querySelectorAll('.menu').forEach((menu) => {
const items = menu.querySelector('.menu-items');
menu.addEventListener('click', (e) => {
items.classList.remove("hide");
menu.focus(); // Probably redundant but just in case!
});
menu.addEventListener('blur', () => {
items.classList.add("hide");
});
});
.menu {
cursor: pointer;
display: inline-block;
}
.menu > span {
user-select: none;
}
.menu:focus {
outline: none;
border: none;
}
.hide {
display: none;
}
.menu-items > * {
user-select: none;
}
<div class="menu" tabindex="0">
<span>Menu +</span>
<div class="menu-items hide">
<div>Item 0</div>
<div>Item 1</div>
<div>Item 2</div>
</div>
</div>
The secret here is to give the .menu div a tabindex so that it's focusable by the browser.
First things first for the JS:
Search the page for any instance of the menu class document.querySelectorAll
Get the items element that's currently hidden by .hide
When somebody clicks on the menu remove the .hide class from the items
This should focus the div, but just in case menu.focus is called!
Then whenever somebody loses focus of the div, AKA clicks away etc. Add the .hide class back.
This could be expanded to change the text of the button, do all sorts of other things. It also keeps your code super clean because you're relying on the browsers own internal state management, so you don't have to do any checks.
Handle a second click close
Right so it works great, but in order to make it function as most people expect UI we need to close it when the menu div is clicked again too (replace span with any class you need):
...
menu.querySelector('span').addEventListener('click', (e) => {
e.stopPropagation();
menu.blur();
});
menu.addEventListener('click', (e) => {
items.classList.remove("hide");
menu.focus(); // Probably redundant but just in case!
});
menu.addEventListener('blur', () => {
items.classList.add("hide");
});
...

Add Trailing Ellipsis To Breadcrumb Depending On Device Width

On my user interface I have a breadcrumb of which shows on the top bar. Upon the device width being below a defined width, it'll drop below the top bar and be it's own bar, however what I do not know how to do is add a trailing ellipsis upon the breadcrumb length being larger than the device width.
Example Breadcrumb:
<nav>
<ul>
<li>Home</li>
<li>>></li>
<li>User</li>
<li>>></li>
<li>Inbox</li>
<li>>></li>
<li>Mail_ID</li>
</ul>
</nav>
Note: >> represents a FontAwesome icon in an i tag
Upon the breadcrumb being larger than the device width, the best I can describe what I would like to happen is demonstrated below:
Home >> User >> Inbox >> Mail_ID
... User >> Inbox >> Mail_ID
... Inbox >> Mail_ID
... Mail_ID
This is still a partial code but might help you.
Idea
On load, call a function that checks for with of ul and its parent container.
If ul has greater width, hide first 2 visible li. Also add an li for ellipsis and make it hidden initially and make it visible only if any of other divs are hidden.
Repeat this process recursively and you will get what you are looking for.
Sample
$(function() {
$(".content").resizable();
$(".content").on("resize", function() {
var ul = $(this).find('ul');
if (ul.width() > $(this).width()) {
var lis = ul.find('li:not(.hide):not(.ellipsis)');
for (var i = 0; i < 2; i++) {
$(lis[i]).addClass("hide");
}
if ($(".ellipsis").not(":visible"))
$(".ellipsis").removeClass("hide")
}
})
});
.content {
text-align: left;
border: 1px solid gray;
width: 300px;
height: 40px;
max-height: 40px;
}
.content ul {
padding: 0px;
position: fixed;
overflow: hidden;
white-space: nowrap;
}
.content ul li {
display: inline-block;
text-decoration: none;
}
.hide {
display: none!important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet" />
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div class="content">
<ul>
<li class="hide ellipsis">...</li>
<li>Home</li>
<li>>></li>
<li>User</li>
<li>>></li>
<li>Inbox</li>
<li>>></li>
<li>Mail_ID</li>
</ul>
</div>
You can try to use the CSS-only ellipsis, but I don't know if it also works with <ul><li>. For sure it works with simple strings:
Use this HTML:
<ul class="ellipsis">
And this CSS:
ul.ellipsis
{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

Dropdown menu css / js on Ipad

I'm working on a website with a simple and pure css dropdown menu. That website is supposed to be used on desktop and on Ipads. My dropdown menu uses :hover pseudo-class and issue appears on the touch screen: the menu expands well but it never collapse. The only way to close it is to open another submenu from the same dropdown menu. My goal is that my menu collapse when I touch anywhere in the DOM (except in the menu of course). After many researches, it appears that we can not do this with css, js is obligatory. I am a beginner and I have no skill in JS, is it possible to do it with only few vanilla js lines ? I don't want to use jquery. Here is my code:
/* ========================================================================= */
/* Global styles */
/* ========================================================================= */
html {
box-sizing: border-box;
font-size: 62.5%;
}
*, *:before, *:after {
margin: 0;
padding: 0;
box-sizing: inherit;
}
body, input {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
img {
border: none;
}
/* ========================================================================= */
/* Layout styles */
/* ========================================================================= */
body > header, body > main {
margin: auto;
}
body > header {
padding-top : 20px;
width: 768px;
}
body > header > img {
width: 150px;
margin-left: 10px;
}
/* ========================================================================= */
/* Nav styles */
/* ========================================================================= */
body > header > nav {
min-width: 768px;
margin: 0 auto;
padding-top: 20px;
font-size: 1.5em;
text-align: center;
}
nav > ul ul {
position: absolute;
display: none;
text-align: left;
}
nav li {
width: 150px;
}
nav > ul > li {
display: inline-block;
}
nav a {
display: block;
text-decoration: none;
}
nav > ul > li > a {
padding: 10px 0;
color: #44546A;
}
nav > ul ul li {
background-color: #333F50;
list-style-type: none;
}
nav > ul ul li a {
padding: 10px 0 10px 30px;
color: #FAFAFA;
font-size: 0.9em;
}
nav > ul li:hover ul {
display: block;
}
nav > ul ul li:hover {
background-color: #51647f;
}
<!DOCTYPE html>
<html>
<head>
<base href="/"/>
<meta charset="UTF-8"/>
<title>Test Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="stylesheet" type="text/css" href="css/normalize.css"/>
<link rel="stylesheet" type="text/css" href="css/styles.css"/>
</head>
<body>
<header>
<img src="img/test.svg" alt="test"/>
<nav>
<ul>
<li>
Menu 1
<ul class="subMenu">
<li>
SubMenu 1.1
</li>
</ul>
</li>
<li>
Menu 2
<ul>
<li>
SubMenu 2.1
</li>
<li>
SubMenu 2.2
</li>
<li>
SubMenu 2.3
</li>
</ul>
</li>
<li>
Menu 3
<ul>
<li>
SubMenu 3.1
</li>
</ul>
</li>
<li>
Menu 4
<ul class="subMenu">
<li>
SubMenu 4.1
</li>
</ul>
</li>
</ul>
</nav>
</header>
</body>
</html>
Edit : That code works well on tablets but not on Ipads
The :hover pseudo-class behaves differently on touch screen devices. When the user touches an element, the browser triggers and keeps the state :hover until another pseudo-class is triggered. Thus, when the user touches another element on the page, a different pseudo-class is triggered by the browser and the drop-down menu becomes hidden. Most of the time, it is the :active pseudo-class that is triggered.
However, as explained on the Safari Developer Library, Mobile Safari doesn't trigger the :active pseudo-class unless a touch event is attached to the element:
On iOS, mouse events are sent so quickly that
the down or active state is never received. Therefore, the :active
pseudo state is triggered only when there is a touch event set on the
HTML element—for example, when ontouchstart is set on the element...
To fix this, you can add a touchstart listener to your document in order to trigger the :active pseudo-class:
document.addEventListener('touchstart', function() {});
Here a solution, ask if you want explanation.
<nav id='nav'>
...
<script>
function hideDropDownMenu(e) {
var element = e.target;
var parent = element.parentNode;
var mustHide = false;
while (parent != null && !mustHide) {
mustHide = element.id === 'nav';
element = element.parentNode;
}
var subMenus = document.getElementsByClassName('subMenu');
var i = 0;
for (i = 0; i < subMenus.length; i++) {
var subMenu = subMenus[i];
subMenu.style = mustHide ? 'none !important' : 'block'; // not sure if the !import is optionnal
}
}
document.body.addEventListener('click', hideDropDownMenu);
</script>
As I posted in another question:
I solved this problem by adding a tabindex to the <body> tag, like this:
<body tabindex="0">
This little trick allowed iPad Safari to focus on the body when it's tapped on, and remove the focus from the dropdown menu.
No Javascript required. 😊

cannot make list item visible with > CSS selector

I'm unable to make the popups 'redItem', 'blueItem' and 'greenItem' below visible again after setting their display to 'none'. I'm using a CSS selector to get them visible again when the mouse hovers over a node higher up in the nested list to no avail.
Here's the code:
<ul class="popups" style="vertical-align: bottom; padding: 0px; margin: 0px;">
<li style="width: 165px"><a id="topmostBox" href="#">One_high-up_item</a>
<ul class="popups">
<li>First-lower-item
<ul class="popups">
<li name="redItem" >Red</li>
<li name="blueItem">Blue</li>
<li name="greenItem">Green</li>
</ul>
</li>
</ul>
</li>
</ul>
.popups:hover > li {
display: block;
}
.popups {
background-color: white;
font-family: sans-serif;
font-size: 13.5px;
list-style: none;
position: relative;
border: 1px solid lightgray;
border-width: .05em;
border-top-color: rgb(165,165,165);
line-height: 1.2em;
display: inline-table;
}
function setTopColorAndVis(theNestedPopupAnchor)
{
var theColorName = theNestedPopupAnchor.innerHTML;
var topMenuBox = document.getElementById('topmostBox');
topMenuBox.innerHTML = theColorName ;
theNestedPopupAnchor.parentNode.style.display = "none";
}
What happens is this:
1) I select the color 'Red' (the 1st list item)
2) my call to setTopColorAndVis(this) makes the popup disappear (because the user selected an item, the color "Red", and now the popup is not needed for now)
3) but when I later hover the mouse over the "First-lower-item" list item, the child li that has the ul containing 'redItem', 'greenItem', 'blueItem' does not appear.
So my experience here is that I'm successfully able to hide the list items named 'redItem', 'blueItem' and 'greenItem' -- but when I hover over the "First-lower-item", despite my CSS code:
.popups:hover > li {
display: block;
}
The 'redItem', 'greenItem' and 'blueItem' do NOT reappear.
What am I missing here?
The inline style overrides you style in your css code. you should use onmouseover event and onmouseout instead.
Try
<li name="redItem" >Red</li>
function show(elem){
elem.parentNode.style.display = "block";
}
function hide(elem){
elem.parentNode.style.display = "none";
}
You cannot :hover over an element with display:none as it has no size...
instead of working with display, you can work with visibility - which will leave an area to hover over.
like so:
theNestedPopupAnchor.parentNode.style.visibility = 'hidden'
.popups:hover > li {
visibility: visible;
}
http://www.w3schools.com/cssref/pr_class_visibility.asp

"Current" tab in tabbed content will not show when "all tabs" are toggled

Fiddle
I'm trying to create a jQuery script that allows for two events to occur:
Create tabbed content, where only the first tab's content (.current) is shown when the page is loaded
Create a "show all tabbed content" button (.toggle_tabs) that toggle's the visibility of all tabbed content
The code is successfully tabbing the content of each tab; the issue is this:
If I load the page, and then click .toggle_tabs, the tab that has class .current when the page first loads is the only tab whose content is not visible
If I load the page, click ANY of the tabs once, and then click .toggle_tabs, all tabbed content will be visible.
How do I get all tabbed content to be visible when clicking .toggle_tabs WITHOUT having to first click one of the tabs?
Current code:
/* jQuery */
$('#hb_container div.tab-link').click(function () {
var tab_id = $(this).data('tab');
$('#hb_container div').removeClass('current');
$('.tab-content').removeClass('current').removeClass('active');
$(this).addClass('current');
$("#" + tab_id).addClass('current').addClass('active');
});
$('.toggle_tabs').click(function () {
$('.tab-content').each(function () {
if ( ($(this).hasClass('current')) && ( $(this).hasClass('active') === false) ) {
$(this).removeClass('current');
} else {
$(this).addClass('current');
}
});
});
/* Tabs */
<div id="hb_container">
<div class="tab-link current" data-tab="tab-1"><div class="tab-link-inner">Job<br>Info</div></div>
<div class="tab-link" data-tab="tab-2"><div class="tab-link-inner">Asb.<br>By</div></div>
<div class="tab-link" data-tab="tab-3"><div class="tab-link-inner">Apl.<br>No.</div></div>
<div class="tab-link" data-tab="tab-4"><div class="tab-link-inner">Struc.<br>Eng.</div></div>
</div>
/* Tabbed content */
<div id="tab-1" class="tab-content current" style="margin-top: 24px">
<div class="tab-content-title">Attachment Upload</div>
<div class="single_col_container">
<div id="frm_field_[id]_container" class="frm_form_field form-field [required_class][error_class]">
[input]
</div>
</div>
</div>
<div id="tab-2" class="tab-content" style="margin-top: 24px">
<div class="tab-content-title">Job Info</div>
<div class="single_col_container">
<div id="frm_field_[id]_container" class="frm_form_field form-field [required_class][error_class]">
[input]
</div>
</div>
</div>
/* CSS */
#hb_container {
margin: 8px 0 8px 8px;
width: 984px;
height: 54px;
}
.tab-content {
display: none;
}
.tab-content.current {
display: inherit;
}
.tab-link {
background: #222;
color: #ddd;
cursor: pointer;
float: left;
font-family: Roboto;
font-size: 12px;
font-weight: bold;
letter-spacing: 1px;
line-height: 14px;
margin: 0 8px 0 0;
width: 54px !important;
height: 54px;
}
.tab-link:hover {
background: #181818;
color: #ffcc00;
}
.tab-link-inner {
margin-top: 11px;
}
I'd suggest storing a status that determines whether the tabs are all being toggled or not as toggling here is doing:
// The tabs are not toggling by default
var toggling = false;
$('#toggle_tabs').click(function () {
if (toggling) {
// If they are being toggled, remove the current class for all tabs except the one that is active
$('.tab-content').each(function () {
if (!$(this).hasClass('active')) {
$(this).removeClass('current');
}
});
toggling = false;
} else {
// If they aren't being toggled, add the current class for all tabs
$('.tab-content').each(function () {
$(this).addClass('current');
});
toggling = true;
}
});
And in the HTML, add the active class to your first tab.
<div id="tab-1" class="tab-content current active" style="margin-top: 24px">
Updated fiddle here.
Ideally, the toggle button would show all content without affecting the "selected" state of the individual tabs. I'd recommend wrapping your tabs in a container and toggling a class on that element instead of messing with the classes on individual tabs:
<div id="hb_wrapper">
<div id="tab-1">
...
</div>
...
<div id="tab-N">
...
</div>
</div>
Clicking the toggle link would toggle a show-all class:
$('#toggle_tabs').click(function () {
$('#hb_wrapper').toggleClass('show-all');
});
And in the CSS, you would override the display property for show-all:
.show-all .tab-content {
display: inherit;
}
Updated Fiddle

Categories

Resources