javascript tabs not working correctly - javascript

I have a question. Everything works fine, but if I add more tabs, so "isVisible" class is added to all other tabs. How do I fix this problem?
HTML:
<div class="content">
<button class="nextTab">Next Tab</button>
<div class="tabs">
<div class="tabs__item isActive">
<p>1 Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
</div>
<div class="tabs__item">
<p>2 Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
</div>
</div>
</div>
Javascript (Babel)
(() => {
const tabs = document.querySelector('.tabs')
, tabsItem = document.querySelectorAll('.tabs__item')
, nextTab = document.querySelector('.nextTab')
nextTab.addEventListener('click', () => {
for(let i = 0; i < tabsItem.length; i++) {
tabsItem[i].classList.toggle('isActive')
}
})
})()

You should track a current tab index in order to be able to switch to next one.
(() => {
const tabs = document.querySelector('.tabs')
, tabsItem = document.querySelectorAll('.tabs__item')
, nextTab = document.querySelector('.nextTab')
, currentTab = 0;
nextTab.addEventListener('click', () => {
if (++currentTab > tabsItem.length - 1) currentTab = 0;
document.querySelector('.isActive').classList.remove('isActive'));
tabsItem[currentTab].classList.add('isActive'))
});
})()

Related

Select tag not firing the event handler

For a project, I am using vanilla HTML/CSS/JS. I am trying to hide all items in a page, have a default option to be selected in the select tag, only show the element that has the selected id and use the dropdown menu to select periods of time and only show that particular element.
In my files, I have structured the function in this way. I have a console log to make sure to see if the code gets ran or not. It puts the log out only once and does not trigger no matter what I choose in the filter. I think the problem is with my event handler not firing but I am sure I followed the examples. What could be the problem here?
var day = document.getElementById("day");
var week = document.getElementById("week");
var month = document.getElementById("month");
period = document.getElementById("filter");
function loadPost(period) {
// Hide all posts
day.style.display = "none";
week.style.display = "none";
month.style.display = "none";
switch (period) {
case day:
showDay();
break;
case week:
showWeek();
break;
case month:
showMonth();
break;
}
console.log(period);
}
function showDay() {
day.style.display = "block";
}
function showWeek() {
week.style.display = "block";
}
function showMonth() {
month.style.display = "block";
}
period.addEventListener("change", loadPost(period.value));
<div class="most-popular">
<h1>Most Popular</h1>
<label for="most-popular-filter">Filter:</label>
<select name="most-popular-filter" id="filter">
<option value="day">Day</option>
<option value="week">Week</option>
<option value="month">Month</option>
</select>
<div class="content-box" id="day">
<img src="images/placeholder.png" alt="most-popular">
<h2>Most Popular 1</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quae.</p>
</div>
<div class="content-box" id="week">
<img src="images/placeholder.png" alt="most-popular">
<h2>Most Popular 2</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quae.</p>
</div>
<div class="content-box" id="month">
<img src="images/placeholder.png" alt="most-popular">
<h2>Most Popular 3</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quae.</p>
</div>
</div>
Use callback function and inside invoke other function:
period.addEventListener("change", ()=>loadPost(period.value));

How can I loop through a list of elements and toggle a class on them in jQuery, with a delay in between each toggle?

I am looking to loop through a list of elements and toggle a class on each one (.active), changing this every 4 seconds. The active class will change the background color of the icon and display it's relevant text underneath.
So, the first element in my list will have the class of active by default (and it's info will be shown by default) and all other info for the other icons will be hidden, then after 4 secs I want to remove this class and add the class to the next element (thus changing it's background color and displaying it's text) and so on, looping back to the first element once the last element has toggled the class.
I am trying to use jquery each() to accomplish this but can't seem to get it to work.
<div id="outer">
<div id="inner">
<div class="item wifi-icon"><i class="fa fa-wifi" aria-hidden="true"></i></div>
<div class="item plug-icon"><i class="fa fa-plug " aria-hidden="true"></i></div>
<div class="item suitcase-icon"><i class="fa fa-suitcase" aria-hidden="true"></i></div>
<div class="item wheelchair-icon"><i class="fa fa-wheelchair" aria-hidden="true"></i></div>
<div class="item play-icon"><i class="fa fa-youtube-play" aria-hidden="true"></i></div>
</div>
<div id="info">
<div id="wifi-text" class="perk-text ">
<h3>Free WiFi</h3>
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit</p>
</div>
<div id="plug-text" class="perk-text ">
<h3>Power Sockets</h3>
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit</p>
</div>
<div id="suitcase-text" class="perk-text ">
<h3>Luggage</h3>
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit</p>
</div>
<div id="wheelchair-text" class="perk-text ">
<h3>Wheelchair Accessible</h3>
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit</p>
</div>
<div id="play-text" class="perk-text ">
<h3>Onboard Entertainment</h3>
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit</p>
</div>
</div>
</div>
.active {
background-color:#74BDE9;
color: #ffffff;
cursor: pointer;
}
$(document).ready(function(){
let itemsLength = $('#inner > .item').length;
let active = $('.active');
let item = $('.fa');
setInterval( function() {
item.each( function(index) {
if( $(this).hasClass('active') ) {
$(this).removeClass('active');
$(this).next().addClass('active');
}
});
}, 4000);
$('.perk-text').hide();
if ( $('.fa-wifi').hasClass('active') ) {
$('#wifi-text').show();
};
if ( $('.fa-plug').hasClass('active') ) {
$('#plug-text').show();
};
if ( $('.fa-suitcase').hasClass('active') ) {
$('#suitcase-text').show();
};
if ( $('.fa-wheelchair').hasClass('active') ) {
$('#wheelchair-text').show();
};
if ( $('.fa-play').hasClass('active') ) {
$('#play-text').show();
};
});
Please find below a working example in vanilla JS
const icons = document.getElementsByClassName("fa");
const texts = document.getElementsByClassName("perk-text");
Array.from(texts).forEach(textElement => {
textElement.style.display = "none";
});
setInterval(() => {
const iconsArray = Array.from(icons);
const activeIconIndex = iconsArray.findIndex(icon => icon.classList.contains("active"));
const nextActiveIndex = (activeIconIndex + 1) % iconsArray.length
iconsArray[nextActiveIndex].classList.add("active");
iconsArray[activeIconIndex].classList.remove("active");
toggleText(iconsArray[nextActiveIndex].classList);
}, 4000);
function toggleText(activeIconClasses) {
let textIdToActivate = "";
switch (activeIconClasses[1]) {
case "fa-wifi":
textIdToActivate = "wifi-text";
break;
case "fa-plug":
textIdToActivate = "plug-text";
break;
case "fa-suitcase":
textIdToActivate = "suitcase-text";
break;
case "fa-wheelchair":
textIdToActivate = "wheelchair-text";
break;
}
Array.from(texts).forEach(textElement => {
textElement.style.display = "none";
});
const textElement = document.getElementById(textIdToActivate);
textElement.style.display = "block";
}
.active {
background-color: #74BDE9;
color: #ffffff;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/js/fontawesome.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="outer">
<div id="inner">
<div class="item wifi-icon"><i class="active fa fa-wifi" aria-hidden="true"></i></div>
<div class="item plug-icon"><i class="fa fa-plug " aria-hidden="true"></i></div>
<div class="item suitcase-icon"><i class="fa fa-suitcase" aria-hidden="true"></i></div>
<div class="item wheelchair-icon"><i class="fa fa-wheelchair" aria-hidden="true"></i></div>
</div>
<div id="info">
<div id="wifi-text" class="perk-text ">
<h3>Free WiFi</h3>
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit</p>
</div>
<div id="plug-text" class="perk-text ">
<h3>Power Sockets</h3>
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit</p>
</div>
<div id="suitcase-text" class="perk-text ">
<h3>Luggage</h3>
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit</p>
</div>
<div id="wheelchair-text" class="perk-text ">
<h3>Wheelchair Accessible</h3>
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit</p>
</div>
</div>
</div>
setInterval + toggleClass will do this for you. An example changing text colour every 2 seconds:
setInterval(function() {
$("#para").toggleClass("red")
}, 2000)
.red {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p id="para">Hi there</p>

JavaScript: How to generate nested ordered list

How to generate nested ordered lists from the following content? I have searched the forum and worked for a few hours now to generate ordered lists based on the different classes from the source content. The content may have up to 6 nesting
level.
What I need is to generate ordered lists based on the different classes. As shown in the sample content to get something like below outlined example content.
.firstclass => 1.
.secondclass => 1.
.thirdclass => 1.
.fourthclass => 1.
The code:
var cheerio = require('cheerio');
var $ = cheerio.load('<h1 class="header">First Header</h1><p class="firstclass">First Lorem ipsum dolor sit.</p><p class="firstclass">First Qui consequatur labore at.</p><p class="secondclass">Second Lorem ipsum dolor sit amet.</p> <p class="thirdclass">Third Lorem ipsum dolor sit amet.</p><p class="thirdclass">Third Molestias optio quasi ipsam unde!</p><p class="secondclass">Second Lorem ipsum dolor sit amet, consectetur.</p><p class="fourthclass">Fourth Lorem ipsum dolor sit.</p><p class="firstclass">First Lorem ipsum dolor sit amet.</p>', {
normalizeWhitespace: true,
xmlMode: true,
decodeEntities: false,
});
var myContent = $('p').each(function() {
var para = $(this).text();
return para;
});
var olClass = ['.firstclass', '.secondclass', '.thirdclass', '.fourthclass'];
function arrToOl(arr) {
var ol = $('<ol />'),
li = $('<li />');
for (var i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
li.append(arrToOl(arr[i]));
} else {
li = $('<li />');
li.append($(arr[i]));
ol.append(li);
}
}
return $.html(ol);
}
console.dir(arrToOl(olClass));
The above code produces the following:
'<ol><li><p class="firstclass">First Lorem ipsum dolor sit.</p><p class="firstclass">First Qui consequatur labore at.</p><p class="firstclass">First Lorem ipsum dolor sit amet.</p></li><li><p class="secondclass">Second Lorem ipsum dolor sit amet.</p><p class="secondclass">Second Lorem ipsum dolor sit amet, consectetur.</p></li><li><p class="thirdclass">Third Lorem ipsum dolor sit amet.</p><p class="thirdclass">Third Molestias optio quasi ipsam unde!</p></li><li><p class="fourthclass">Fourth Lorem ipsum dolor sit.</p></li></ol>'
The desired result should be:
<ol>
<li>
<p class="firstclass">First Lorem ipsum dolor sit.</p>
</li>
<li>
<p class="firstclass">First Qui consequatur labore at.</p>
</li>
<li>
<p class="firstclass">First Lorem ipsum dolor sit amet.</p>
<ol>
<li>
<p class="secondclass">Second Lorem ipsum dolor sit amet.</p>
</li>
<li>
<p class="secondclass">Second Lorem ipsum dolor sit amet, consectetur.</p>
<ol>
<li>
<p class="thirdclass">Third Lorem ipsum dolor sit amet.</p>
</li>
<li>
<p class="thirdclass">Third Molestias optio quasi ipsam unde!</p>
<ol>
<li>
<p class="fourthclass">Fourth Lorem ipsum dolor sit.</p>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
Your help is really appreciated.
Here's what I got.
let array = ["a", "b", "c", "d"];
var nested;
function create_nested()
{
var old_ol;
for (let i = 0; i < array.length; i++)
{
let new_ol = document.createElement("ol");
let new_li = document.createElement("li");
new_li.innerHTML = array[i];
new_ol.appendChild(new_li);
if (i !== 0)
{
let nest_li = document.createElement("li");
let new_p = document.createElement("p");
new_p.innerHTML = "new stuff";
nest_li.appendChild(new_p);
nest_li.appendChild(old_ol);
new_ol.appendChild(nest_li);
}
old_ol = new_ol;
nested = new_ol;
}
}
create_nested();
document.getElementById('main').appendChild( nested);
<div id='main'>
</div>
This is just an example and not exactly the data that you have (you can figure that out).
What's happening is that I'm creating new elements using document.createElement, after which I am inserting them into their corresponding ol/li using appendChild.
The most important part is the if (i !== 0) (Change this to suit whether you want to start from the beginning or end of your array). This is the part where I am creating the nests.
I am creating a new li, which has the <p> and the old_ol which is the nesting li. So what this function is doing, is creating the innermost ol, and expanding it upward.
There might be a clear/better way of doing this, but this is as far as I know in vanilla JS. I hope everything is clear enough.

Appending 5 divs at once?

My code runs below so that the correct div is appended to the page when I click the '.show-more' button. I want to be able to append 5 of these divs every time I click this button, but not sure how to do that?
HTML
<div class="content-section news-preview clearfix">
<div class="title">Title of News Article</div>
<div class="clearfix">
<div class="image-container">
<img src="images/news_sample208x135.jpg" width="208" height="135">
</div>
<div>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Dui luctus lectus eget libero volupat, a tempor velit malesuada. Lorem ipsum dolor sit amet, conectetur adipiscing elit. Vivamus mattis egestas lorem a sodales.</p>
</div>
</div>
<a class="article-link">http://www.lintothenewsarticle.com/news/article/title-of-news-article</a>
</div>
<!-- /content-section -->
<div class="show-more-container">
<div id="content-news-container"></div>
<button class="btn show-more">Show 5 More</button>
</div>
jQuery
$('.show-more').click(function() {
var contentNews = $('div.content-section:last').prop('outerHTML');
console.log("contentNews", contentNews);
$('#content-news-container').append(contentNews).slideDown(slow);
});
You can use a DocumentFragment to append all of the elements in one go in an efficient manner
$('.show-more').click(function () {
var contentNews = $('div.content-section:last').prop('outerHTML');
var frag = document.createDocumentFragment();
for (var i = 0; i < 5; i++) {
frag.appendChild($(contentNews).get(0));
}
$('#content-news-container').append(frag).slideDown(slow);
});
EDIT: fiddle - http://jsfiddle.net/sc5585x7/
Just create a loop and clone the DIV five times:
$('.show-more').click(function () {
for (var i = 0; i < 5; i++) {
var contentNews = $($('div.content-section:last')[0].cloneNode(true));
$('#content-news-container').append(contentNews).slideDown('slow');
}
});
Fiddle here: http://jsfiddle.net/ToddT/364y88af/
EDIT:
As was pointed out, using a DocumentFragment is the more efficient way to do this sort of manipulation:
$('.show-more').click(function () {
var contentNews = $('div.content-section:last').prop('outerHTML');
var frag = document.createDocumentFragment();
for (var i = 0; i < 5; i++) {
frag.appendChild($(contentNews).get(0));
}
$('#content-news-container').append(frag).slideDown(slow);
});
Per #Travis Kaufman's answer.

Only drop to element that is seen

I have the following:
I am trying to set it up so that when you drag the item, it only gets dropped to the div element which you can see, and is not covered up.
So I used this js:
$(".draggable").draggable({
helper: "clone"
})
$("#bottom, .draggable").droppable({
drop: function(event, ui) {
var $this = $(this),
$dragged = $(ui.draggable);
$this.append($dragged.clone());
},
hoverClass: "dragHover"
})​
But it drops the element in both places even though only one of the drop zones is not visible!
How do I fix it so that this does not happen?
Fiddle: http://jsfiddle.net/maniator/Wp4LU/
Extra Info to recreate the page without the fiddle:
HTML:
<div id="top">
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
<div class="draggable">
Lorem ipsum dolor sit amet
</div>
</div>
<div id="bottom"></div>
CSS:
.draggable {
border: 1px solid green;
background: white;
padding: 5px;
}
.dragHover{
background: blue;
}
#top {
height: 500px;
overflow-y: scroll;
}
#bottom {
height: 150px;
overflow-y: scroll;
border: red solid 4px;
}
​
Try setting with accept function.
The working demo.
$("#bottom, .draggable").droppable({
drop: function(event, ui) {
var $this = $(this),
$dragged = $(ui.draggable);
$this.append($dragged.clone());
},
accept: function () {
var $this = $(this), divTop= $("#top");
if ($this.is(".draggable")) {
return $this.offset().top < divTop.offset().top + divTop.height() ;
}
return true;
},
hoverClass: "dragHover"
})​;​
If I got you right - this one should solve your problem - http://jsfiddle.net/Wp4LU/60/
Also you could write custom accept function - http://jqueryui.com/demos/droppable/#option-accept
Code:
var draggableList = $('#top');
$(".draggable").draggable({
helper: "clone"
});
$("#bottom, .draggable").droppable({
drop: function(event, ui) {
var $this = $(this),
$dragged = $(ui.draggable);
if ($this.hasClass("draggable")) {
if ($this.position().top >= draggableList.height() ||
$this.position().top + $this.outerHeight() >=
draggableList.height())
return;
}
$this.append($dragged.clone());
},
hoverClass: "dragHover"
})​;​
According to the sources (jquery.ui.droppable.js), the drop operation will search for every eligible droppable and apply the drop function to every one that intersects with it:
drop: function(draggable, event) {
var dropped = false;
$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
if(!this.options) return;
if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance))
dropped = this._drop.call(this, event) || dropped;
(Old versions had the last "OR" condition reversed, so it would only apply to a single droppable. Try your fiddle using jQuery 1.5.2 / jQuery UI 1.8.9, and see that it only drops to one element, albeit the "wrong" one...)
And every tolerance mode currently implemented in the $.ui.intersect function only take into account the (x,y) coordinates:
switch (toleranceMode) {
case 'fit':
return (l <= x1 && x2 <= r
&& t <= y1 && y2 <= b);
break;
case 'intersect':
return (l < x1 + (draggable.helperProportions.width / 2) // Right Half
&& x2 - (draggable.helperProportions.width / 2) < r // Left Half
&& t < y1 + (draggable.helperProportions.height / 2) // Bottom Half
&& y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
break;
...
So, unless someone adds a z-index aware tolerance mode, your only option is to work around the issue somehow. I'd suggest first adding every droppable candidate to a set and, when it's time to drop, select only the one that is "closest" to the screen:
$("#bottom, .draggable").droppable({
over: function(event, ui) {
if ( !ui.draggable.data("drop-candidates") )
ui.draggable.data("drop-candidates",[]);
ui.draggable.data("drop-candidates").push(this);
},
out: function(event, ui) {
var that = this,
candidates = ui.draggable.data("drop-candidates") || [];
ui.draggable.data("drop-candidates", $.grep(candidates, function(e) {
return e != that;
});
},
drop: function(event, ui) {
var $this = $(this),
$dragged = $(ui.draggable);
var candidates = $.data("drop-candidates").sort(closestToScreen);
if ( candidates[0] == this )
$this.append($dragged.clone());
},
hoverClass: "dragHover"
})​
Now, implementing the closestToScreen comparator is the tricky part. The W3C CSS Specification describes how rendering engines should sort elements to paint, but I wasn't able to find so far an easy way to access this information. I also asked this question here at SO, maybe someone will find a good way.
P.S. If modifying the jQuery UI source is an option, you could try implementing a z-index aware tolerance mode using document.getElementFromPoint, as this answer to said question suggested:
var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height;
switch (toleranceMode) {
...
case 'z-index-aware':
return document.elementFromPoint(x1,y1) == droppable;
break;
(that would ensure only the element right below the upper-left corner of the draggable would be considered "good enough" as a drop target - not ideal, but better than what we have so far; a similar solution could be adapted to use the mouse pointer coordinates instead)
And, no, you can't use this method with the workaround presented before: at the moment the drop happens, the drag helper is the element closest to the screen... (Edit: d'oh! It wouldn't work if implemented as a tolerance mode either, for the same reason...)
If you just want to drop on element you can see you could change your selector :
$(".draggable:visible").draggable({
helper: "clone"
});
$("#bottom, .draggable:visible").droppable({
drop: function(event, ui) {
var $this = $(this),
$dragged = $(ui.draggable);
$this.append($dragged.clone());
},
hoverClass: "dragHover"
})​;
Or when you hide an element change its' draggable class by something else.

Categories

Resources