I've created a JS function that hides a certain amount of breadcrumbs if there are too many. They are replaced by a button (ellipsis), when you click the button the hidden breadcrumbs are revealed.
The Problem: I loop through the breadcrumbs to see if there are enough to hide. If there are I hide them. But I can't figure out how to then call the code to create the button. If I call the button code in the loop I get more than 1 button generated.
Right now the button will always appear whether there are enough breadcrumbs to hide or not.
In my mind, I would have the for loop with the if statement return true to what would then be the button function. But I can't figure out how to do this. Please offer any pointers for restructuring this code if you can.
Here's a Codepen: https://codepen.io/sibarad/pen/GRvpEbp
Basic HTML:
<nav aria-label="breadcrumb">
<ol class="c-breadcrumb mb-7 md:mb-8">
<li class="c-breadcrumb-item">
Breadcrumb 1
</li>
<li class="c-breadcrumb-item">
Breadcrumb 2
</li>
<li class="c-breadcrumb-item">
Longer Breadcrumb Name 03
</li>
</ol>
</nav>
Javascript:
function breadcrumb() {
// Target specific breadcrumbs, not 1st or last 2
let hiddenbreadcrumb = document.querySelectorAll('.c-breadcrumb-item:nth-child(1n+2):nth-last-child(n+3)');
// Loop through select breadcrumbs, if length is greater than x hide them.
for (var i = 0; i < hiddenbreadcrumb.length; i++) {
if(hiddenbreadcrumb.length >= 3) {
hiddenbreadcrumb[i].style.display = "none";
}
}
// This would be the button function, but I don't know how to engage this only if the if statement above was met.
let li = document.createElement('li');
li.className = 'c-breadcrumb-item';
let ellipbutton = document.createElement('button');
ellipbutton.type = 'button';
ellipbutton.innerHTML = '...';
ellipbutton.className = 'c-breadcrumb_btn u-btn-clear';
ellipbutton.onclick = function() {
console.log("clicked");
for (var i = 0; i < hiddenbreadcrumb.length; i++) {
hiddenbreadcrumb[i].style.display = "flex";
}
li.style.display = "none";
};
li.appendChild(ellipbutton);
let container = document.querySelector('.c-breadcrumb-item:first-child');
container.insertAdjacentElement("afterend", li);
}
breadcrumb();
We can refactor your code slightly to achieve this - the if statement which checks whether there are more than 3 breadcrumbs doesn't need to be inside the for loop - it's redundant to keep checking the same value multiple times.
If we move that outside the loop then it can
a) prevent unnecessary looping when there aren't enough breadcrumbs, and
b) wrap around the button creation code as well, which should solve your problem.
For example:
if (hiddenbreadcrumb.length >= 3) {
for (var i = 0; i < hiddenbreadcrumb.length; i++) {
hiddenbreadcrumb[i].style.display = "none";
}
let li = document.createElement('li');
li.className = 'c-breadcrumb-item';
let ellipbutton = document.createElement('button');
ellipbutton.type = 'button';
ellipbutton.innerHTML = '...';
ellipbutton.className = 'c-breadcrumb_btn u-btn-clear';
ellipbutton.onclick = function() {
console.log("clicked");
for (var i = 0; i < hiddenbreadcrumb.length; i++) {
hiddenbreadcrumb[i].style.display = "flex";
}
li.style.display = "none";
};
let container = document.querySelector('.c-breadcrumb-item:first-child');
container.insertAdjacentElement("afterend", li);
}
It looks like some small initialization issues. This should correct it:
Change this:
let hiddenbreadcrumb = document.querySelectorAll('.c-breadcrumb-item:nth-child(1n+2):nth-last-child(n+3)');
// Loop through select breadcrumbs, if length is greater than x hide them.
for (var i = 0; i < hiddenbreadcrumb.length; i++) {
if(hiddenbreadcrumb.length >= 3) {
hiddenbreadcrumb[i].style.display = "none";
}
}
to this:
let hiddenbreadcrumb = document.querySelectorAll('.c-breadcrumb-item');
if(hiddenbreadcrumb.length < 3)
return
for (var i = 1; i < hiddenbreadcrumb.length - 1; i++) {
hiddenbreadcrumb[i].style.display = "none";
}
Try this... it allows 3 li items as item1 ... item2ndLast, itemLast
(function () {
"use strict";
function breadcrumb() {
let hiddenbreadcrumb = document.querySelectorAll(".c-breadcrumb-item:nth-child(1n+2)");
if (hiddenbreadcrumb.length <= 3) return;
for (var i = 1; i < hiddenbreadcrumb.length - 1; i++) {
hiddenbreadcrumb[i].style.display = "none";
}
let li = document.createElement("li");
li.className = "c-breadcrumb-item";
let ellipbutton = document.createElement("button");
ellipbutton.type = "button";
ellipbutton.innerHTML = "...";
ellipbutton.className = "c-breadcrumb_btn u-btn-clear";
ellipbutton.onclick = function () {
console.log("clicked");
for (var i = 0; i < hiddenbreadcrumb.length; i++) {
hiddenbreadcrumb[i].style.display = "flex";
}
li.style.display = "none";
};
li.appendChild(ellipbutton);
let container = document.querySelector(".c-breadcrumb-item:first-child");
container.insertAdjacentElement("afterend", li);
}
breadcrumb();
})();
Related
I have this code, repeating 20 times with only change of variables prefix. How can i possibly make a loop and iterate by it to avoid this huge block of code?
It is used in my website and I want to make the development process more clear.
var keepElements = document.getElementsByName("keep-type");
for(var i = 0; i < keepElements.length; i++){
keep = document.getElementById(keepElements[i].value);
if(keepElements[i].checked == true){
keep.style.display = "block";
}
else{keep.style.display = "none";}
}
var offworkshopElements = document.getElementsByName("offworkshop-type");
for(var i = 0; i < offworkshopElements.length; i++){
offworkshop = document.getElementById(offworkshopElements[i].value);
if(offworkshopElements[i].checked == true){
offworkshop.style.display = "block";
}
else{offworkshop.style.display = "none";}
}
var defworkshopElements = document.getElementsByName("defworkshop-type");
for(var i = 0; i < defworkshopElements.length; i++){
defworkshop = document.getElementById(defworkshopElements[i].value);
if(defworkshopElements[i].checked == true){
defworkshop.style.display = "block";
}
else{defworkshop.style.display = "none";}
}
Move the logic to a function:
function keepElements(name) {
var keepElements = document.getElementsByName(name);
for (var i = 0; i < keepElements.length; i++) {
keep = document.getElementById(keepElements[i].value);
if (keepElements[i].checked == true) {
keep.style.display = "block";
}
else { keep.style.display = "none"; }
}
}
keepElements("keep-type");
keepElements("offworkshop-type");
keepElements("defworkshop-type");
If I didn't miss any details, you're repeating the same block 3 times:
["keep-type", "offworkshop-type", "defworkshop-type"].forEach(name => {
var elements = document.getElementsByName(name);
for(var i = 0; i < elements.length; i++){
element = document.getElementById(elements[i].value);
if(elements[i].checked == true){
element.style.display = "block";
}
else{element.style.display = "none";}
}
});
I prefer the array / forEach syntax, because the block starts with the list of name. But you could use a for...of loop instead.
for (const name of ["keep-type", "offworkshop-type", "defworkshop-type"]) {
var elements = document.getElementsByName(name);
for(var i = 0; i < elements.length; i++){
element = document.getElementById(elements[i].value);
if(elements[i].checked == true){
element.style.display = "block";
}
else{element.style.display = "none";}
}
}
The answers saying you should put the common functionality into a single function are absolutely right.
I just wanted to add that there are also solutions on the query side of things:
you could have all the elements be collected in one javascript function, querySelectorAll()
const elements = document.querySelectorAll('[name="keep-type"], [name="offworkshop-type"], [name="defworkshop-type"]')
Alternatively you could give them all the same class and select by that class.
const elements = document.getElementsByClassName("requiredElements");
And this way you have all your elements in one array.
I'm trying to make a simple Javascript pagination function, but I'm having this issue where instead of iterating through the array, it keeps adding new list items to the innerhtml.
I have tried creating an element and appending it to the DOM.
I have tried using if/else statements to display the list items I
want.
<body>
<div class='result'></div>
<button class="add">+</button>
<script src='pretty.js'></script>
</body>
let dogs = [
'goldendoodle',
'poodle',
'afghan hound',
'golden retriever',
'labrador',
'chihuahua',
'pitbull',
'german shepherd',
'greyhound',
'bull terrier'
]
let high = 1;
let low = 0;
let result = document.querySelector('.result');
let add = document.querySelector('.add');
function Pagination(low,high) {
for(var i = 0 ; i < dogs.length;i++) {
let answer = document.createElement('div');
answer.classList.add('dogs-dom');
answer.innerHTML = dogs[i];
result.appendChild(answer);
if(i >= low && i < high) {
answer.style.display ='block';
}
if(i < low || i > high) {
answer.style.display ='none';
}
}
}
Pagination(low,high);
add.addEventListener('click', () => {
low += 2;
high += 2;
Pagination(low,high);
});
When I click the button, I want the next two array items to appear and replace the last two shown.
To use the approach you've outlined above you'll need to clear the innerHtml of the result element before appending new children. At the top of your Pagination function try result.innerHtml = '';.
That said if you are using a hide/show approach to paginate the list it would be more efficient to create the dom elements only once and modify the style.display property of each instead of clearing out the result and re-creating all of the answer divs on every click.
Your Pagination function only adds elements to the dom each time it is called.
You can either remove the existing elements every time Pagination is called, and render only those that should be displayed, e.g.:
function Pagination(low,high) {
result.innerHTML = ''; // remove all children of result
// only render the children which should be visible
for(var i = low ; i < high;i++) {
let answer = document.createElement('div');
answer.classList.add('dogs-dom');
answer.innerHTML = dogs[i];
result.appendChild(answer);
}
}
Or you can use display: block; / display: none. (Will not scale very well with large lists)
function Pagination(low,high) {
// only append all dogs once
if(result.childElementCount === 0) {
for(var i = 0; i < dogs.length;i++) {
let answer = document.createElement('div');
answer.classList.add('dogs-dom');
answer.style.display ='none';
answer.innerHTML = dogs[i];
result.appendChild(answer);
}
}
// toggle display: none / block for each element
for(var i = 0; i < dogs.length;i++) {
if(i >= low && i < high)
answer.style.display ='block';
else
answer.style.display ='none';
}
}
As a bonus, heres a reusable pagination class example:
function Pagination(container, items) {
this.container = container;
this.result = container.querySelector('.result');
this.prevBtn = container.querySelector('.prev');
this.nextBtn = container.querySelector('.next');
this.items = items;
this.offset = 0;
this.limit = 5;
this.updateDom();
this.prevBtn.onclick = this.prevPage.bind(this);
this.nextBtn.onclick = this.nextPage.bind(this);
}
Pagination.prototype.nextPage = function() {
if((this.offset + this.limit) < this.items.length)
this.offset += this.limit;
this.updateDom();
};
Pagination.prototype.prevPage = function() {
if(this.offset >= this.limit)
this.offset -= this.limit;
this.updateDom();
};
Pagination.prototype.updateDom = function() {
this.result.innerHTML = '';
let stop = Math.min(this.offset + this.limit, this.items.length);
for(let i = this.offset; i < stop; i++) {
let el = document.createElement("div");
el.appendChild(document.createTextNode(this.items[i]));
this.result.appendChild(el);
}
let hasPrev = this.offset > 0;
if(hasPrev)
this.prevBtn.classList.remove('hide');
else
this.prevBtn.classList.add('hide');
let hasNext = (this.offset + this.limit) < this.items.length;
if(hasNext)
this.nextBtn.classList.remove('hide');
else
this.nextBtn.classList.add('hide');
};
let items = [];
for (let i = 1; i <= 50; i++)
items.push(`Item ${i}`);
let pagination = new Pagination(document.querySelector(".paginate"), items);
// You can also programatically switch to the next / prev page:
// pagination.nextPage();
// pagination.prevPage();
.hide { visibility: hidden; }
<div class="paginate">
<div class="result"></div>
<button class="prev">PREV</button>
<button class="next">NEXT</button>
</div>
Maybe this is along the lines of what you want to do?
It tracks only a globalIndex (which would be like like your 'low' variable).
The showNextTwoItems function:
- Notes the indexes where we should start and end
- Clears the container div
- Enters a while loop that appends items and increments the current index
- Updates the globalIndex when enough items have been added
let dogs = [ 'goldendoodle', 'poodle', 'afghan hound', 'golden retriever', 'labrador', 'chihuahua', 'pitbull', 'german shepherd', 'greyhound', 'bull terrier' ],
containerDiv = document.querySelector('.result'),
addBtn = document.querySelector('.add'),
globalIndex = 0; // Tracks where we left off (starts at zero)
const NUMBER_TO_SHOW = 2;
addBtn.addEventListener("click", showNextTwoItems); // Calls function on click
function showNextTwoItems(){
let numberToShow = NUMBER_TO_SHOW, // In case we ever want to change numberToShow
currentIndex = globalIndex, // Gets local copy of globalIndex (always < dogs.length)
// Lets us stop looping when we've shown enough or reach the end of the array
stopBeforeIndex = Math.min(currentIndex + numberToShow, dogs.length);
containerDiv.innerHTML = ""; // Clears div
while(currentIndex < stopBeforeIndex){
// Creates and appends a text node with the next dog
const newItem = document.createTextNode(dogs[currentIndex]);
containerDiv.appendChild(newItem);
// Creates and appends a line break
const lineBreak = document.createElement("BR");
containerDiv.appendChild(lineBreak);
// Moves on to the next index
currentIndex++;
}
// Updates global index (making sure it is not too big for the array)
globalIndex = currentIndex < dogs.length ? currentIndex : 0;
}
<button class="add">+</button>
<div class='result'></div>
I'm in the process of learning JS and have made a collapsable navbar. I got the menu to open upon clicking the button, but it will not close. I've searched this site for answers but anything I've found refers to Bootstrap. This is just pure JS. I've also looked over my code for hours trying to spot a syntax error or anything that could be going wrong. Any help would be greatly appreciated!
Here is my code:
let toggleNavStatus = false;
let toggleNav = function() {
let getSidebar = document.querySelector(".nav-sidebar");
let getSidebarUl = document.querySelector(".nav-sidebar ul");
let getSidebarTitle = document.querySelector(".nav-sidebar span");
let getSidebarLinks = document.querySelectorAll(".nav-sidebar a");
if (toggleNavStatus === false) {
getSidebarUl.style.visibility = "visible";
getSidebar.style.width = "315px";
getSidebarTitle.style.opacity = "0.5";
let arrayLength = getSidebarLinks.length;
for (i = 0; i < array.length; i++) {
getSidebarLinks[i].style.opacity = "1";
}
toggleNavStatus = true;
}
else if (toggleNavStatus === true) {
getSidebar.style.width = "50px";
getSidebarTitle.style.opacity = "0";
let arrayLength = getSidebarLinks.length;
for (i = 0; i < array.length; i++) {
getSidebarLinks[i].style.opacity = "0";
}
getSidebarUl.style.visibility = "hidden";
toggleNavStatus = false;
}
}
In your for-loops you need to change array.length to arrayLength.
See this codepen
My goal is to append button for hiding replies on comments and don't show this button if there are no replies for each comment.
Please, don't blame me so much for this code because I'm newbie and learning hard.
Here's the code:
var replies = document.querySelectorAll(".comments_list > .comment_item > .reply_comments"); //should check if .childNodes.length === 1
var comments = document.querySelectorAll(".comments_list > .comment_item");
var combodys = document.querySelectorAll(".comments_list > .comment_item > .comment_body");
addBtn();
function addBtn() {
for (var i = 0; i < comments.length; i++) {
var combody = combodys[i];
var btn = document.createElement("input");
btn.type = "button";
btn.className = "hidereplies";
btn.value = "show replies";
combody.appendChild(btn); //don't show if replies.childNodes.length === 1
}
}
After button added I want to check if comments contain replies and hide button when replies block is empty. Tried to check childNodes method and faced with problem that I have to "+1" to current "hidereplies" button value:
if (replies[6 + 1].childNodes.length === 1) {
document.querySelectorAll(".hidereplies")[6].style.display = "none";
}
So, for now I don't know how to cycle through all comments and hide "hidereplies" button if there are no replies.
Hope for a help to solve this problem in plain Javascript.
Thanks in advance!
Try
function getChildrenByClassName(el, className){
var children = [];
for (var i = 0; i < el.childNodes.length; i++) {
if (el.childNodes[i].className == className) {
children.push(el.childNodes[i]);
}
}
return children;
}
function addBtn() {
var comments = document.querySelectorAll(".comments_list > .comment_item"), comment, combody;
for (var i = 0; i < comments.length; i++) {
comment = comments[i];
var replies = comment.querySelectorAll('.reply_comments .comment_body');
if(replies.length > 0){
combody = getChildrenByClassName(comment, 'comment_body')[0];
if(combody){
var btn = document.createElement("input");
btn.type = "button";
btn.className = "hidereplies";
btn.value = "show replies";
combody.appendChild(btn); //don't show if replies.childNodes.length === 1
}
}
}
}
addBtn();
Demo: Fiddle
I am looking to create a very simple functionality of clicking on a menu tab and it changes color to let you know what page you are on. I am a novice so please take it easy on me...lol
/Menu in php header file/
<ul class="tabs" id="tabs">
<li class="selected">Home</li>
<li class="inactive">Bio</li>
<li class="inactive">Photo</li>
<li class="inactive">Thank</li>
<li class="inactive">Contact</li>
</ul>
/*This is the JavaScript file*/
window.onload = initPage;
function initPage() {
var tabs = document.getElementById("tabs").getElementsByTagName("li");
for (var i=0; i<tabs.length; i++){
var links = tabs[i];
links.onclick = tabClicked;
}
}
function tabClicked(){
var tabId = this.id;
document.getElementById(tabId).classList.toggle("selected");
var tabs = document.getElementById("tabs").getElementsByTagName("li");
for (var i=0; i < tabs.length; i++){
var currentTab = tabs[i];
if (currentTab.id !== tabId){
currentTab.class = "selected";
} else {
currentTab.class = "inactive";
}
}
}
element.setAttribute("class", "className");
You are using ids in your code but you don't have provided it in your markup. so give ids to li elements and try this.
function tabClicked(){
var tabId = this.id;
document.getElementById(tabId).classList.toggle("selected");
var tabs = document.getElementById("tabs").getElementsByTagName("li");
for (var i=0; i < tabs.length; i++){
var currentTab = tabs[i];
if (currentTab.id !== tabId){
currentTab.className = "inactive";
} else {
currentTab.className= "selected";
}
}
}
JS Fiddle Demo
Store a reference to each of the list items.
Create a variable to keep track of the current tab.
In an onclick function for each element (or you could use one onclick and just use some conditions), change the class attribute of the element by using the setAttribute() method.
Like this:
function onFirstTabClick() {
clearSelected();
tabVariable1.setAttribute("class","some-new-class");
}
function() clearSelected() {
switch(currentSelectedTrackerVariable) {
case 1: tabVariable1.setAttribute("class","some-new-class");
break;
// Do this for the amount of tabs that you have.
}
}
Working FIDDLE Demo
There is no need to define functions globally. Write all them in one package. The code below, works correctly with your HTML markup.
<script>
window.onload = function () {
var tab = document.getElementById('tabs');
var lis = tab.getElementsByTagName('li');
for (var i = 0, l = lis.length; i < l; i++) {
lis[i].onclick = function () {
for (var j = 0; j < l; j++) {
lis[j]["className"] = "inactive";
}
this["className"] = "selected";
};
}
};
</script>
If you use jQuery, then tabClicked can run:
jQuery('.selected').removeClass('selected').addClass('inactive');
jQuery(this).removeClass('inactive').addClass('selected');