Can't get HTMLCollection length [duplicate] - javascript

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
I have several list elements on my page that are created dynamically, and my goal now is to access all the <li> on my page using getElementsByTagName, but when I try to do it, console tells me that the length of my li array is 0.
I've read documentation and examples on getElementsByTagName use cases but didn't track the problem.
Here is my code:
window.addEventListener('DOMContentLoaded', function() {
let btn = document.querySelector('.sw-btn');
let content = document.querySelector('.content');
let filmsList = document.createElement('ul');
function getFilms() {
axios.get('https://swapi.co/api/films/').then(res => {
content.appendChild(filmsList);
for (var i = 0; i < res.data.results.length; i++) {
res.data.results.sort(function(a, b) {
let dateA = new Date(a.release_date),
dateB = new Date(b.release_date);
return dateA - dateB;
});
(function updateFilms() {
let addFilm = document.createElement('li');
filmsList.appendChild(addFilm);
let addFilmAnchor = document.createElement('a');
let addFilmId = document.createElement('p');
let addFilmCrawl = document.createElement('p');
let addFilmDirector = document.createElement('p');
let addFilmDate = document.createElement('p');
addFilmAnchor.textContent = res.data.results[i].title;
addFilmId.textContent = `Episode ID: ${res.data.results[i].episode_id}`;
addFilmCrawl.textContent = `Episode description: ${res.data.results[i].opening_crawl}`;
addFilmDirector.textContent = `Episode director: ${res.data.results[i].director}`;
addFilmDate.textContent = `Episode release date: ${res.data.results[i].release_date}`;
addFilm.append(addFilmAnchor, addFilmId, addFilmCrawl, addFilmDirector, addFilmDate);
})();
}
}).catch(err => {
console.log("An error occured");
})
let links = document.getElementsByTagName('li');
console.log(links.length);
};
btn.addEventListener('click', getFilms);
});
body {
max-height: 100vh;
padding: 0;
margin: 0;
font-family: Muli;
}
body::before {
background: url('https://upload.wikimedia.org/wikipedia/commons/thumb/6/6c/Star_Wars_Logo.svg/1200px-Star_Wars_Logo.svg.png') no-repeat center / cover;
background-size: cover;
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -2;
opacity: 0.1;
}
h1 {
text-align: center;
color: #660d41;
font-size: 3em;
margin-top: 10px;
letter-spacing: 1px;
}
main {
display: flex;
align-items: center;
flex-direction: column;
}
.content {
max-width: 55%;
overflow-y: scroll;
max-height: 75vh;
}
ul {
list-style-type: none;
padding: 10px 20px;
}
li {
border-bottom: 1px solid orangered;
margin-bottom: 30px;
}
li:last-child {
border-bottom: none;
margin-bottom: 0;
}
a {
font-size: 1.7em;
color: #b907d9;
cursor: pointer;
margin-bottom: 10px;
}
p {
font-size: 1.2rem;
color: #0f063f;
margin: 10px 0;
}
button {
padding: .5em 1.5em;
border: none;
color: white;
transition: all 0.2s ease-in;
background: #da2417;
border-radius: 20px;
font-size: 1em;
cursor: pointer;
margin-top: 15px;
}
button:focus {
outline: none;
}
button:hover {
background: #e7736b;
}
button:active {
box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.7) inset;
}
<link href="https://fonts.googleapis.com/css?family=Muli&display=swap" rel="stylesheet">
<h1>Star wars films</h1>
<main>
<div class="content"></div>
<button class="sw-btn">Find Films</button>
</main>
Here is the link on working pen. Thank you for your help.

So what's happening here is a classic mistake caused by an async ajax request. You are requesting data and then inserting elements based on that data. Then afterwards, you want to count elements.
The problem is though, that axios.get returns a Promise that will not immediately resolve, because a HTTP request is being made to another server. That obviously takes some time, hence the then method of the Promise interface.
What you want to do is move your counting code at the end of the .then() method, so that the elements are counted after they have been inserted.
tl;dr you are trying to count elements that simply aren't there at the time of the counting.

That's because length is outside of then.
That means you are logging length of empty html collection at the first rendering of the page.
put it inside of then and then it will show correct value
like this:
addFilm.append(addFilmAnchor, addFilmId, addFilmCrawl, addFilmDirector, addFilmDate);
let links = document.getElementsByTagName('li');
console.log(links.length);

Related

Autocomplete Search bar won't read my values

im trying to make a search bar for my website to redirect pepole on other pages of my website by selecting values from autocomplete suggestions, but when i select a suggestion (example:"Home") and hit search nothing happends, instead when i write the value (example:"chat") it works just fine and it redirects me to another page, my question is: what im doing wrong and why my autocompleted values are not seen by the searchbar?
Here is the code example for values "chat, Home, youtube"
function ouvrirPage() {
var a = document.getElementById("search").value;
if (a === "chat") {
window.open("/index.html");
}
if (a === "Home") {
window.open("/customizedalert.html");
}
if (a === "youtube") {
window.open("https://www.youtube.com/");
}
}
And here is the entire thing:
https://codepen.io/galusk0149007/pen/LYeXvww
Try this in your IDE : Clicking the search icon will navigate to your urls.
// getting all required elements
const searchWrapper = document.querySelector(".search-input");
const inputBox = searchWrapper.querySelector("input");
const suggBox = searchWrapper.querySelector(".autocom-box");
const icon = searchWrapper.querySelector(".icon");
let linkTag = searchWrapper.querySelector("a");
let webLink;
let suggestions = ['chat','home', 'youtube']
// if user press any key and release
inputBox.onkeyup = (e)=>{
let userData = e.target.value; //user enetered data
let emptyArray = [];
if(userData){
icon.onclick = ()=>{
webLink = `https://www.google.com/search?q=${userData}`;
linkTag.setAttribute("href", webLink);
linkTag.click();
}
emptyArray = suggestions.filter((data)=>{
//filtering array value and user characters to lowercase and return only those words which are start with user enetered chars
return data.toLocaleLowerCase().startsWith(userData.toLocaleLowerCase());
});
emptyArray = emptyArray.map((data)=>{
// passing return data inside li tag
return data = `<li>${data}</li>`;
});
searchWrapper.classList.add("active"); //show autocomplete box
showSuggestions(emptyArray);
let allList = suggBox.querySelectorAll("li");
for (let i = 0; i < allList.length; i++) {
//adding onclick attribute in all li tag
allList[i].setAttribute("onclick", "select(this)");
}
}else{
searchWrapper.classList.remove("active"); //hide autocomplete box
}
}
function select(element){
let selectData = element.textContent;
inputBox.value = selectData;
icon.onclick = ()=>{
webLink = `https://www.google.com/search?q=${selectData}`;
linkTag.setAttribute("href", webLink);
linkTag.click();
}
searchWrapper.classList.remove("active");
}
function showSuggestions(list){
let listData;
if(!list.length){
userValue = inputBox.value;
listData = `<li>${userValue}</li>`;
}else{
listData = list.join('');
}
suggBox.innerHTML = listData;
}
function ouvrirPage() {
var a = document.getElementById("search").value;
if (a === "chat") {
window.open("/index.html");
}
if (a === "Home") {
window.open("/customizedalert.html");
}
if (a === "youtube") {
window.open("https://www.youtube.com/");
}
}
#import url('https://fonts.googleapis.com/css2?family=Poppins:wght#200;300;400;500;600;700&display=swap');
*{
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body{
background: #544b8b;
padding: 0 20px;
}
::selection{
color: #fff;
background: #7c71bd;
}
.wrapper{
max-width: 450px;
margin: 150px auto;
}
.wrapper .search-input{
background: #fff;
width: 100%;
border-radius: 5px;
position: relative;
box-shadow: 0px 1px 5px 3px rgba(0,0,0,0.12);
}
.search-input input{
height: 55px;
width: 100%;
outline: none;
border: none;
border-radius: 5px;
padding: 0 60px 0 20px;
font-size: 18px;
box-shadow: 0px 1px 5px rgba(0,0,0,0.1);
}
.search-input.active input{
border-radius: 5px 5px 0 0;
}
.search-input .autocom-box{
padding: 0;
opacity: 0;
pointer-events: none;
max-height: 280px;
overflow-y: auto;
}
.search-input.active .autocom-box{
padding: 10px 8px;
opacity: 1;
pointer-events: auto;
}
.autocom-box li{
list-style: none;
padding: 8px 12px;
display: none;
width: 100%;
cursor: default;
border-radius: 3px;
}
.search-input.active .autocom-box li{
display: block;
}
.autocom-box li:hover{
background: #efefef;
}
.search-input .icon{
position: absolute;
right: 0px;
top: 0px;
height: 55px;
width: 55px;
text-align: center;
line-height: 55px;
font-size: 20px;
color: #644bff;
cursor: pointer;
}
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"/>
</head>
<body>
<div class="wrapper">
<div class="search-input">
<a href="" target="_blank" hidden></a>
<input type="text" placeholder="Type to search..">
<div class="autocom-box">
</div>
<div class="icon" onclick="ouvrirPage()"><i class="fas fa-search"></i></div>
</div>
</div>
</body>

Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node', on append on of an html div

I am trying to create a To-Do list with basic js
I have tried adding and deleting operation for the list
on adding I am getting this below error even though the items get added error :
Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
I am trying to append (addNewData.innerHTML) the HTML to the parent div data-list
can someone guide me, why I am getting this issue?
there are some more issues in this, currently on which I am working on
Code Snippet
function getData() {
var dataList = document.getElementById('add-items').value;
console.log("id :" + dataList);
// check if the input filed is empty
if (dataList == 0) {
alert("Please add something to the List");
console.log("if statement")
} else {
// add item to the list
var addNewData = document.getElementById('data-list');
addNewData.innerHTML += `
<div class="add-item added-item-list item-lists d-flex align-center f-wrap">
<div class="item-added f-grow"> ` + dataList + ` </div>
<div class="add-icon remove-data" onclick="removeRow(this)">
<p>Delete</p>
</div>
</div>
`
var b = addNewData.appendChild('addNewData.innerHTML');
document.getElementById('add-items').value='';
// update pending status
var pendingDataCount = document.getElementById('pending-task').innerHTML;
var getId = document.getElementById("pending-task");
var spans = getId.getElementsByTagName("span");
for (i = 0; i < spans.length; i++) {
console.log("for statement")
var spanVal = spans[i].innerHTML;
console.log("span val :" + spanVal);
var newSpanVal = document.getElementsByClassName('added-item-list').length + 1;
console.log("newSpanVal :" + newSpanVal);
var pendingDataCount = document.getElementById('pending-task');
pendingDataCount.innerHTML = `
<p>You have <span>` + newSpanVal + `</span> pending tasks </p>
`
var c = pendingDataCount.appendChild('pendingDataCount.innerHTML');
}
}
}
function removeRow(input) {
document.getElementById('data-list').removeChild(input.parentNode);
}
.wrapper {
background: linear-gradient(to bottom, #68EACC 0%, #497BE8 100%);
height: 100vh;
text-align: center;
}
.form-wrapper {
background: #ffffff;
max-width: 500px;
margin: 0 auto;
padding: 20px 30px;
width: 100%;
box-shadow: 0px 10px 15px rgba(0, 0, 0, 0.1);
}
.form-wrapper h1 {
text-align: left;
}
.add-item {
margin: 15px 0;
font-weight: 600;
color: #ffffff;
cursor: pointer;
}
.add-item input, .item-added {
padding: 10px 15px;
margin-right: 10px;
border: 2px solid #F2F2F2;
border-radius: 5px;
color: #000;
}
.add-icon {
background: #8E49E9;
padding: 10px;
width: 77px;
}
.add-icon:hover {
background-color: #55A6DD;
}
.item-added {
text-align: left;
}
.item-lists {
background: #F2F2F2;
}
.remove-data {
padding: 11px;
background: #E74D3D;
}
.remove-data:hover {
opacity: 0.8;
background: #E74D3D;
}
.d-flex{
display: flex;
}
.align-center{
align-items: center;
}
.justify-center{
justify-content: center;
}
.f-wrap{
flex-wrap: wrap;
}
.m-auto{
margin: 0 auto;
}
.f-grow{
flex-grow: 1;
}
p{
margin: 0;
}

Targeting and Manipulating setInterval Timer Value [duplicate]

This question already has answers here:
Changing the interval of SetInterval while it's running
(17 answers)
Closed 5 years ago.
I have a simple function that changes the color of a square from red to blue every 2 seconds. I need the speed of the setInterval to reflect the value in the counter. I can't seem to figure out how to target the time value in the setInterval. No jQuery, thanks. Heres a demo of my code.
function Tempo() {
var tempoVal = document.getElementById("tempo-value");
var tempoBtn = tempoVal.querySelectorAll("[data-btn]");
var tempoNum = tempoVal.querySelector("[data-value]");
for (var i = 0; i < tempoBtn.length; i++) {
tempoBtn[i].onclick = function() {
if (this.getAttribute("data-btn") == "-") {
tempoNum.innerHTML = parseFloat(tempoNum.innerHTML) - 1;
} else if (this.getAttribute("data-btn") == "+") {
tempoNum.innerHTML = parseFloat(tempoNum.innerHTML) + 1;
}
};
}
}
let myTempo = new Tempo(document.getElementById("tempo-value"));
//BLOCK
setInterval(function() {
var block = document.getElementById("block");
block.classList.toggle("color");
}, 2000);
#tempo-value {
font-family: Sans-Serif;
font-size: 24px;
text-align: center;
padding: 16px;
user-select: none;
}
.btn {
display: inline-flex;
cursor: pointer;
}
.value {
display: inline-block;
width: 40px;
text-align: right;
}
#block {
width: 100px;
height: 100px;
background-color: red;
margin: 0 auto;
}
#block.color {
background-color: blue;
}
<div id="tempo-value">
<div data-btn='-' class="btn">&#9669</div>
<div data-value class="value">1</div><span> second(s)</span>
<div data-btn='+' class="btn">&#9659</div>
</div>
<div id="block"></div>
You can assign setTimeout / setInterval to a variable. Then use clearTimeout / clearInterval to remove it when necessary. Then set new one again. See code below:
function Tempo() {
var toggle = function() {
document.getElementById('block')
.classList.toggle('color');
}
var t = 1000;
var blink = setInterval(toggle, t);
var tempoVal = document.getElementById('tempo-value');
var tempoBtn = tempoVal.querySelectorAll('[data-btn]');
var tempoNum = tempoVal.querySelector('[data-value]');
for (var i = 0; i < tempoBtn.length; i++) {
tempoBtn[i].onclick = function() {
clearInterval(blink);
tempoNum.innerHTML = +
this.getAttribute('data-btn') +
+tempoNum.innerHTML;
t = 1000 * tempoNum.innerHTML;
blink = setInterval(toggle, t);
}
}
};
let myTempo = new Tempo(document.getElementById('tempo-value'));
body,
html {
height: 100%;
}
#tempo-value {
font-family: Sans-Serif;
font-size: 24px;
text-align: center;
padding: 16px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.btn {
display: inline-flex;
cursor: pointer;
}
.value {
display: inline-block;
width: 40px;
text-align: right;
}
#block {
width: 100px;
height: 100px;
background-color: red;
margin: 0 auto;
}
#block.color {
background-color: blue;
}
<div id="tempo-value">
<div data-btn='-1' class="btn">&#9669</div>
<div data-value class="value">1</div><span> second(s)</span>
<div data-btn='+1' class="btn">&#9659</div>
</div>
<div id="block"></div>

removing the input text in the text field that matches the removed tab

In this example I made, since it uses keyup event, each input text (separated by comma) entered is converted into a tab. I want the input text to be deleted from the text field according to the tab I remove; for example, I enter "Item 1" but I suddenly change my mind and decide to remove the "Item 1" tab, the input text in the text field that has a string that matches the textContent of the removed tab should be automatically deleted from the text field.
var query = document.querySelector.bind(document);
query('#textfield').addEventListener('keyup', addTag);
function addTag(e) {
var evt = e.target;
if(evt.value) {
var items = evt.value.split(',');
if(items.length <= 10) {
evt.nextElementSibling.innerHTML = null;
for(var i = 0; i < items.length; i++) {
if(items[i].length > 0) {
var label = document.createElement('label'),
span = document.createElement('span');
label.className = 'tag';
label.textContent = items[i];
span.className = 'remove';
span.title = 'Remove';
span.textContent = 'x';
label.insertAdjacentElement('beforeend', span);
evt.nextElementSibling.appendChild(label);
span.addEventListener('click', function() {
var currentElement = this;
currentElement.parentNode.parentNode.removeChild(currentElement.parentNode);
})
}
}
}
} else {
evt.nextElementSibling.innerHTML = null;
}
}
section {
width: 100%;
height: 100vh;
background: orange;
display: flex;
align-items: center;
justify-content: center;
}
.container {
width: 50%;
}
input[name] {
width: 100%;
border: none;
border-radius: 1rem 1rem 0 0;
font: 1rem 'Arial', sans-serif;
padding: 1rem;
background: #272727;
color: orange;
box-shadow: inset 0 0 5px 0 orange;
}
input[name]::placeholder {
font: 0.9rem 'Arial', sans-serif;
opacity: 0.9;
}
.tags {
width: 100%;
height: 250px;
padding: 1rem;
background: #dfdfdf;
border-radius: 0 0 1rem 1rem;
box-shadow: 0 5px 25px 0px rgba(0,0,0,0.4);
position: relative;
}
.tags > label {
width: auto;
display: inline-block;
background: #272727;
color: orange;
font: 1.1rem 'Arial', sans-serif;
padding: 0.4rem 0.6rem;
border-radius: .2rem;
margin: 5px;
}
.tags > label > span {
font-size: 0.7rem;
margin-left: 10px;
position: relative;
bottom: 2px;
color: #ff4d4d;
cursor: pointer;
}
<section id="tags-input">
<div class="container">
<input type="text" name="items" id="textfield" placeholder="Enter any item, separated by comma(','). Maximum of 10" autofocus>
<div class="tags"></div>
</div>
</section>
How can I make that feature possible?
Replace the 'x' button listener with this one:
span.addEventListener('click', function () {
var text_field = document.getElementById("textfield");
var evt = this.parentNode;
var tags = text_field.value;
this.parentNode.removeChild(this); // remove the 'x' span so you can get the pure tag text with .innerHTML
var evname = evt.innerHTML;
var tags_array = tags.split(",");
var tag_position = tags_array.indexOf(evname);
if(tag_position > -1)
tags_array.splice(tag_position,1);
text_field.value = tags_array.join(',');
evt.parentNode.removeChild(evt);
})
// Coding this complexity in pure javascript when there is jQuery is ... like eating soup with a fork. You will get the job done, but it is dammn hard!

Should I use sessionStorage and if so, how?

I have built a simple task app that allows you to add different tasks. It works fine. I am not sure what is the best approach however to retain the data/HTML once the page is refreshed. I have heard of HTML5 session/localStorage but I am not sure if this would be the best method to use in this situation. Also, I would need help making this work if sessionStorage was a good choice.
window.onload = init;
function init() {
var generateBtn = document.getElementById("generate");
generateBtn.onclick = addTask;
var tasksWrapper = document.getElementById("tasksWrapper");
var taskDesc = document.getElementById("taskDesc");
}
var taskId = 0;
var taskBarArray = [];
function addTask() {
taskId++;
var taskBar = document.createElement("div");
var taskBarInput = document.createElement("input");
var taskBarDeleteBtn = document.createElement("input");
taskBar.setAttribute("id", taskId);
taskBar.setAttribute("class", "taskBar");
taskBarInput.setAttribute("class", "taskDesc");
taskBarInput.setAttribute("type", "text");
taskBarInput.setAttribute("placeholder", "Enter task");
function rmPlaceholder() {
taskBarInput.removeAttribute("placeholder", "Enter task");
}
function addPlaceholder() {
taskBarInput.setAttribute("placeholder", "Enter task");
}
taskBarInput.onfocus = rmPlaceholder;
taskBarInput.onblur = addPlaceholder;
taskBarInput.setAttribute("name", "taskDesc");
taskBarInput.setAttribute("value", taskDesc.value);
taskBarDeleteBtn.setAttribute("class", "deleteBtn");
taskBarDeleteBtn.setAttribute("type", "button");
taskBarDeleteBtn.setAttribute("value", "x");
var addTaskBar = tasksWrapper.appendChild(taskBar);
var targetTaskId = document.getElementById(taskId);
var addTaskBarInput = targetTaskId.appendChild(taskBarInput);
var AddTaskBarDeleteBtn = targetTaskId.appendChild(taskBarDeleteBtn);
taskBarArray.push(taskBar);
taskDesc.value = "";
taskBarDeleteBtn.onclick = removeTask;
function removeTask(e) {
taskBarDeleteBtn = e.target;
tasksWrapper.removeChild(taskBar);
taskBarArray.pop(e);
if (taskBarArray.length < 1) {
taskId = 0;
}
}
}
#main_wrapper {
margin: 0 auto;
max-width: 528px;
width: 100%;
height: 20px;
}
.taskBar {
width: 100%;
background: #333230;
border-bottom: 1px solid #fff;
border-radius: 10px;
}
.taskDesc {
margin: 10px 0 10px 10px;
background: none;
border: none;
outline: none;
font-size: 20px;
color: #fff;
text-transform: uppercase;
z-index: 9999;
}
.deleteBtn {
margin: 6px 6px 0 0;
padding: 6px;
width: 32px;
background: #8F0A09;
font-size: 15px;
color: #fff;
border-radius: 100px;
border-color: #000;
float: right;
outline: none;
}
#header {
padding: 10px;
background: #000;
border-bottom: 1px solid #fff;
border-radius: 10px;
}
#taskDesc {
padding: 2px 0;
width: 50%;
font-size: 20px;
}
#generate {
padding: 5px 83px;
background: #82CC12;
font-size: 20px;
border-color: #000;
border-radius: 5px;
outline: none;
}
::-webkit-input-placeholder {
color: #4C4B48;
}
::-moz-placeholder {
color: #4C4B48;
}
:-ms-placeholder {
color: #4C4B48;
}
<div id="main_wrapper">
<div id="header">
<input type="text" id="taskDesc"></input>
<input type="button" id="generate" value="Add task">
</div>
<div id="tasksWrapper">
</div>
</div>
Here I would use localStorage, it will be remembered even after the session has timed out. A session is probably ended if the user restarts their browser.
The only problems I see with localStorage is the 10 MB size limit on desktops (2 MB om mobile devices I think), and that it's not easy enough to get data from localStorage to the server. But localStorage would be a perfect fit for a TODO app with simple items.

Categories

Resources