Is this javasScript sorting function correct? - javascript

I'm a javaScript begginer. This function works and allows me to sorting list by ascendant and descendant by clicking in a button but I would like to know if it's properly written or maybe it can be simplified or written differently. Thanks in advance.
index.html
<p id="sort">Sort</p>
<ul>
<li>Phosphorus</li>
<li>Polonium</li>
<li>Radium</li>
<li>Fluorine</li>
<li>Iron</li>
<li>Hydrogen</li>
<li>Germanium</li>
<li>Mercury</li>
<li>Actinium</li>
<li>Barium</li>
<li>Calcium</li>
<li>Cadmium</li>
</ul>
<script src="main.js"></script>
main.js
const sortButton = document.querySelector("#sort");
let counter = 0;
eventListeners();
function eventListeners() {
sortButton.addEventListener("click", sortList);
}
function sortList() {
const elementsList = new Array();
const elements = document.querySelectorAll("ul li");
for (const element of elements) {
elementsList.push(element.textContent);
}
const ul = document.querySelector("ul");
ul.innerHTML = "";
if (counter % 2 === 0) {
const orderedList = elementsList.sort();
for (let i = 0; i < orderedList.length; i++) {
const li = document.createElement("li");
li.innerHTML = orderedList[i];
ul.appendChild(li);
}
counter++;
} else {
const orderedList = elementsList.reverse();
for (let i = 0; i < orderedList.length; i++) {
const li = document.createElement("li");
li.innerHTML = orderedList[i];
ul.appendChild(li);
}
counter++;
}
}

you don't have to clear the innerHTML of the ul, nor even ever use innerHTML
element.appendChild will move existing elements - as below
const sortButton = document.querySelector("#sort");
let counter = 0;
eventListeners();
function eventListeners() {
sortButton.addEventListener("click", sortList);
}
function sortList() {
const elementsList = Array.from(document.querySelectorAll("ul li"));
const ul = document.querySelector("ul");
elementsList.sort(({textContent:a}, {textContent:b})=>(counter%2?-1:1)*a.localeCompare(b))
.forEach(el => ul.appendChild(el));
counter++;
}
<p id="sort">Sort</p>
<ul>
<li>Phosphorus</li>
<li>Polonium</li>
<li>Radium</li>
<li>Fluorine</li>
<li>Iron</li>
<li>Hydrogen</li>
<li>Germanium</li>
<li>Mercury</li>
<li>Actinium</li>
<li>Barium</li>
<li>Calcium</li>
<li>Cadmium</li>
</ul>
In Es2015 (i.e. old school javascirpt) - this function looks like:
function sortList() {
var elementsList = Array.from(document.querySelectorAll("ul li"));
var ul = document.querySelector("ul");
elementsList.sort(function (_ref, _ref2) {
var a = _ref.textContent;
var b = _ref2.textContent;
return (counter % 2 ? -1 : 1) * a.localeCompare(b);
}).forEach(function (el) {
return ul.appendChild(el);
});
counter++;
}

Related

Javascript passing info from one function to another

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();
})();

Loop through <li> and display list containing input value

I have a list of items like a todo list and i have a search input where i want users to search through the list'
I am using the keyup event and if input matches i am trying to use css to set the matched li to display:'' while the list items that do not match i want to set display:none.
so far i am able to console.log my results but cant effect changes to my li list as the css to set display none doesn't work.
here is my code below.
const Ul = document.querySelector('.clipBoard');
const search = document.querySelector('.search');
array.forEach((item) => {
let button = document.createElement('button');
button.className =
'list-group-item buttonLi';
button.innerText += item;
Ul.appendChild(button);
//Search function
search.addEventListener('keyup', () => {
let filterValue = search.value;
const li = Ul.querySelectorAll('.buttonLi');
for (let i = 0; i < li.length; i++) {
let List = li[i];
if (List.innerHTML.indexOf(filterValue) > -1) {
console.log(li[i].innerHTML);
li[i].style.display = '';
} else {
li[i].style.display = 'none';
}
}
});
I would suggest you to assign an id to each element using the index at that level:
array.forEach((item, INDEX) => {
let button = document.createElement('button');
var id = 'el-'+INDEX
button.setAttribute("id", id);
button.setAttribute("class", "list-group-item buttonLi");
button.innerText += item;
});
Then on your search function use that id to hide the elements
search.addEventListener('keyup', () => {
let filterValue = search.value;
const li = Ul.querySelectorAll('.buttonLi');
for (let i = 0; i < li.length; i++) {
let List = li[i];
if (List.innerHTML.indexOf(filterValue) > -1) {
console.log(li[i].innerHTML);
// something like that
var idToShow = '#el-'+i;
document.querySelector(idToShow).style.display = 'block';
} else {
// something like that
var idToHide = '#el-'+i;
document.querySelector(idToHide).style.display = 'none';
}
}
});
I did not test this code, just trying to point you to an eventual solution!
You could also do the filtering on your array then for each search you re-render your list!

Trying to create a button with an li and it's not working

I'm trying to make a grocery list app using an array similar to a todo list. I've got it working with an add button, and had a remove button that would remove an item from the list. But now I'm trying to make it so that a remove button is created with each li so that each grocery item can be selectively removed. I gave it a shot but I'm not quite sure what I've done wrong here.
let addButton = document.getElementById('add-button');
addButton.addEventListener('click', add);
let addInput = document.getElementById('add-input');
//let removeButton = document.getElementById('remove-button');
//removeButton.addEventListener('click', remove);
let groceryList = [
]
function add() {
groceryInput = addInput.value;
groceryList.push(groceryInput);
addInput.value = '';
displayGroceries();
}
function remove(event) {
let position = event.currentTarget.id;
groceryList.splice(position, 1);
displayGroceries();
}
function displayGroceries() {
let groceryUl = document.getElementById('grocery-ul');
groceryUl.innerHTML = '';
for (let i = 0; i < groceryList.length; i++) {
let groceryLi = document.createElement('li');
groceryLi.innerHTML = groceryList[i];
groceryUl.appendChild(groceryLi);
}
let removeButton = document.createElement('button');
removeButton.innerText = "Remove";
removeButton.addEventListener('click', remove);
removeButton.id = i;
groceryLi.appendChild(removeButton);
}
<div class="container">
<h1>Grocery List</h1>
<input id="add-input" placeholder="Add Groceries" autocomplete="off">
<button id="add-button">Add</button>
<!--<button id="remove-button">Remove</button>-->
<div>
<ul id="grocery-ul"></ul>
</div>
Its not working as groceryLi.appendChild(removeButton) you are calling outside for loop.
You have defined groceryLi using let and let have a block scope.
Moving code to add button inside resolves issue
Find fixed method for displayGroceries as follows
function displayGroceries() {
let groceryUl = document.getElementById('grocery-ul');
groceryUl.innerHTML = "";
for (let i = 0; i < groceryList.length; i++) {
let groceryLi = document.createElement("li");
groceryLi.innerHTML = groceryList[i];
let removeButton = document.createElement("button");
removeButton.innerText = "Remove";
removeButton.addEventListener("click", remove);
removeButton.id = i;
groceryLi.appendChild(removeButton);
groceryUl.appendChild(groceryLi);
}
}
In your example you are calling on the incrementing value of i outside of the scope of its loop.
You can create the button using the same method you are using to create your list item tag, then add the button to the UL element tag using .insertAdjacentElement('beforeend', removeBtn).
Then you can use a removeEl function that looks at the event.target parentNode and firstChild --> li that will contain the grocery item and its remove button to both remove the element from the DOM and the array.
function removeEl(event) {
event.target.parentNode.remove(event.target)
if (groceryList.includes(event.target.parentNode.firstChild.textContent)) {
let k = groceryList.indexOf(event.target.parentNode.firstChild.textContent);
if (k !== -1) {
groceryList.splice(k, 1);
}
}
displayGroceries();
}
//and the for loop that creates the new elements in displayGroceries()
for (let i = 0; i < groceryList.length; i++) {
let groceryLi = document.createElement('LI');
let removeBtn = document.createElement('BUTTON');
groceryUl.classList.add('flex-display')
removeBtn.textContent = `remove ${groceryList[i]}`;
removeBtn.setAttribute('onclick', `removeEl(event)`)
groceryLi.innerHTML = groceryList[i];
groceryUl.appendChild(groceryLi);
groceryLi.insertAdjacentElement('beforeend', removeBtn);
}
let addButton = document.getElementById('add-button');
addButton.addEventListener('click', add);
let addInput = document.getElementById('add-input');
//let removeButton = document.getElementById('remove-button');
//removeButton.addEventListener('click', remove);
let groceryList = [
]
function add() {
groceryInput = addInput.value;
groceryList.push(groceryInput);
addInput.value = '';
displayGroceries();
}
function removeEl(event) {
//this targets the LI element, parent of the button
event.target.parentNode.remove(event.target)
//event.target.parentNode.firstChild.textContent -> the grocery item
if (groceryList.includes(event.target.parentNode.firstChild.textContent)) {
// get the index
let k = groceryList.indexOf(event.target.parentNode.firstChild.textContent);
if (k !== -1) {
groceryList.splice(k, 1);
}
}
displayGroceries();
}
function displayGroceries() {
let groceryUl = document.getElementById('grocery-ul');
groceryUl.innerHTML = '';
for (let i = 0; i < groceryList.length; i++) {
let groceryLi = document.createElement('LI');
let removeBtn = document.createElement('BUTTON');
groceryUl.classList.add('flex-display')
removeBtn.textContent = `remove ${groceryList[i]}`;
removeBtn.setAttribute('onclick', `removeEl(event)`)
groceryLi.innerHTML = groceryList[i];
groceryUl.appendChild(groceryLi);
groceryLi.insertAdjacentElement('beforeend', removeBtn);
}
}
.flex-display {
display: flex;
align-items: start;
flex-direction: column;
}
li {
list-style: none;
}
button {
margin-left: 1rem;
}
<div class="container">
<h1>Grocery List</h1>
<input id="add-input" placeholder="Add Groceries" autocomplete="off">
<button id="add-button">Add</button>
<!--<button id="remove-button">Remove</button>-->
<div>
<ul id="grocery-ul">
</ul>
</div>

How can i make the loops and create it dynamically?

How to correct the javascript code to create li dynamically
to make loops?
I use this code, how to do it?
Because in this code I have to repeat for each section
let content = document.createElement('span');
let liststart = document.createElement('span');
let listelement1 = document.createElement('li');
let listelement2 = document.createElement('li');
let listelement3 = document.createElement('li');
let listelement4 = document.createElement('li');
listelement1.innerHTML = "section 1";
listelement2.innerHTML = "section 2";
listelement3.innerHTML = "section 3";
listelement4.innerHTML = "section 4";
let section1 = document.getElementById("section1");
let section2 = document.getElementById("section2");
let section3 = document.getElementById("section3");
let section4 = document.getElementById("section4");
listelement1.addEventListener("click", function() {
section1.scrollIntoView(true);
section1.classList.add("my-active");
section2.classList.remove("my-active");
section3.classList.remove("my-active");
section4.classList.remove("my-active");
});
listelement2.addEventListener("click", function() {
section2.scrollIntoView(true);
section1.classList.remove("my-active");
section2.classList.add("my-active");
section3.classList.remove("my-active");
section4.classList.remove("my-active");
});
listelement3.addEventListener("click", function() {
section3.scrollIntoView(true);
section1.classList.remove("my-active");
section2.classList.remove("my-active");
section3.classList.add("my-active");
section4.classList.remove("my-active");
});
listelement4.addEventListener("click", function() {
section4.scrollIntoView(true);
section1.classList.remove("my-active");
section2.classList.remove("my-active");
section3.classList.remove("my-active");
section4.classList.add("my-active");
});
liststart.appendChild(listelement1);
liststart.appendChild(listelement2);
liststart.appendChild(listelement3);
liststart.appendChild(listelement4);
content.appendChild(liststart);
let navbar__list = document.querySelector('#navbar__list');
navbar__list.append(content);
let tabs = document.getElementsByTagName("li");
for (let i = 0; i < tabs.length; i++) {
tabs[i].addEventListener("click", function() {
let current = document.getElementsByClassName("active");
current[0].className = current[0].className.replace("active");
this.className += "active";
});
}
Hopefully this will work as an example for you:
let content = document.createElement('span');
let liststart = document.createElement('span');
var sections = [];
for(let i = 1; i <= 4; i++) {
const listElement = document.createElement('li');
listElement.innerText = `Section ${i}`
const section = document.getElementById(`section${i}`)
sections.push(section)
listElement.addEventListener("click", () => {
section.scrollIntoView(true)
sections.forEach(el => el.classList.remove("my-active"))
section.classList.add("my-active")
})
liststart.appendChild(listElement)
}
content.appendChild(liststart);
let navbar__list = document.querySelector('#navbar__list');
navbar__list.append(content);
let tabs = document.getElementsByTagName("li");
for (let i = 0; i < tabs.length; i++) {
tabs[i].addEventListener("click", function() {
let current = document.getElementsByClassName("active");
current[0].className = current[0].className.replace("active");
this.className += "active";
});
}
If I was to do this myself, knowing there were an "unknown" number of sections, I would use JavaScript code that used every section whose id started with "secton":
let content = document.createElement('span');
let liststart = document.createElement('span');
// this line puts all elements that have an id starting with 'section' into an array
var sections = [...document.querySelectorAll('[id^=section]')]
sections.forEach(section => {
const listElement = document.createElement('li');
listelement.innerText = `Section ${i}`
listElement.addEventListener("click", () => {
section.scrollIntoView(true)
sections.forEach(el => el.classList.remove("my-active"))
section.classList.add("my-active")
})
liststart.appendChild(listElement)
})
content.appendChild(liststart);
let navbar__list = document.querySelector('#navbar__list');
navbar__list.append(content);
let tabs = document.getElementsByTagName("li");
for (let i = 0; i < tabs.length; i++) {
tabs[i].addEventListener("click", function() {
let current = document.getElementsByClassName("active");
current[0].className = current[0].className.replace("active");
this.className += "active";
});
}
This solution is quite similar, but to me is a bit more readable.

create and add "li" element after click

In an exercise, I have created a new element li after each click of my button.
It doesn't work. In the console it reports "li is not defined".
What did I do wrong?
My code:
const btn = document.querySelector("button");
let number = 1;
const ul = document.querySelector("ul");
const fcn = function() {
const li = document.createElement('li');
li.textContent = number;
}
if (number % 3 == 0) {
li.classList.add('big');
}
ul.appendChild(li);
number += 2;
btn.addEventListener("click", fcn);
try this:
const btn = document.querySelector("button");
let number = 1;
const ul = document.querySelector("ul");
const fcn = function () {
const li = document.createElement('li');
li.textContent = number;
if (number % 3 == 0) {
li.classList.add('big');
}
ul.appendChild(li);
number += 2;
}
btn.addEventListener("click", fcn);
This problem is related to closures in javascript. li is not accessible outside function fcn.
following code should solve your problem.
const btn = document.querySelector("button");
let number = 0;
const ul = document.querySelector("ul");
const fcn = function () {
number += 1;
const li = document.createElement('li');
li.textContent = number;
ul.appendChild(li);
if (number % 3 == 0) {
li.classList.add('big');
}
}
btn.addEventListener("click", fcn);
li is defined within anonymous function.
When you define a variable/constant within function it's local to that function and can't be accessed outside it. It has function level scope.
To fix your code either define li outside of function at global scope or move the piece of code that use it within function itself as follows :
const btn =
document.querySelector("button");
let number = 1;
const ul =
document.querySelector("ul");
const fcn = function () {
const li =
document.createElement('li');
li.textContent = number;
if(number % 3 == 0){
li.classList.add('big');
}
ul.appendChild(li);
number += 2;
}
btn.addEventListener("click", fcn);

Categories

Resources