I'm trying to come up with a solution to press keys virtually for a typing test. What I want is to get the text and store individual letters in an array and then press all keys with some delay in between each presses.
This is the HTML of the text layout. Imagine it has the word hello.
<div class="digit-container">
<div>
<span>h</span>
</div>
</div>
<div class="digit-container">
<div>
<span>e</span>
</div>
</div>
<div class="digit-container">
<div>
<span>l</span>
</div>
</div>
<div class="digit-container">
<div>
<span>l</span>
</div>
</div>
<div class="digit-container">
<div>
<span>o</span>
</div>
</div>
This is the JavaScript code I have come up with. I have managed to store these letters in an array.
wordList = document.querySelectorAll(".digit-container > div > span");
wordArray = [];
for (let i = 0; i < wordList.length; i++) {
individualWord = wordList[i].innerHTML;
wordArray.push(individualWord);
}
Now I would like JavaScript to send keypresses for this array of letters { "h", "e", "l", "l", "o" }. Basically when I'm pasting this code and press enter in the console, I would like JavaScript to press "Hello" with a milliseconds of delay in between keystrokes. How can I get JavaScript into press keys?
Here is the solution.
const elements = document.querySelectorAll(".digit-container > div > span");
const chars = Array.from(elements).map((item) => item.textContent);
const delay = 1000;
let i = 0;
const pressKey = () => {
setTimeout(() => {
const char = chars[i];
const event = new KeyboardEvent("keyup", {
key: char
});
document.body.dispatchEvent(event);
if (i !== chars.length - 1) {
i++;
pressKey();
}
}, delay);
};
pressKey();
document.body.addEventListener("keyup", (e) => {
console.log(e.key);
});
<div class="digit-container">
<div>
<span>h</span>
</div>
</div>
<div class="digit-container">
<div>
<span>e</span>
</div>
</div>
<div class="digit-container">
<div>
<span>l</span>
</div>
</div>
<div class="digit-container">
<div>
<span>l</span>
</div>
</div>
<div class="digit-container">
<div>
<span>o</span>
</div>
</div>
Related
I am a novice and I have a page that displays job offers, I want to create a function that changes the logo of a company according to the company name mentionned in first 50 characters. The function below works only for the first element of a class. How can I make it work with all the class elements independently?
function logo_switch() {
let anonce = document.querySelector(".anonce").querySelector(".anonceText").innerHTML;
console.log(anonce);
let logo_A = anonce.indexOf('A');
let logo_B = anonce.indexOf('B');
let logo_C = anonce.indexOf('C');
let logo_D = anonce.indexOf('D');
let logo_E = anonce.indexOf('E');
let logo_F = anonce.indexOf('F');
let logo_G = anonce.indexOf('G');
var img = document.querySelector(".anonceLogo");
if ((logo_A > 0) && (logo_A < 50)) {
img.setAttribute("src", "img/a.png");
} else {
console.log(0);
};
if ((logo_B > 0) && (logo_B < 50)) {
img.setAttribute("src", "img/b.jpg");
} else {
console.log(0);
};
if ((logo_C > 0) && (logo_C < 50)) {
img.setAttribute("src", "img/c.jpg");
} else {
console.log(0);
};
if ((logo_D > 0) && (logo_D < 50)) {
img.setAttribute("src", "img/d.jpg");
} else {
console.log(0);
};
if ((logo_E > 0) && (logo_E < 50)) {
img.setAttribute("src", "img/e.jpg");
} else {
console.log(0);
};
if ((logo_F > 0) && (logo_F < 50)) {
img.setAttribute("src", "img/f.png");
} else {
console.log(0);
};
if ((logo_G > 0) && (logo_G < 50)) {
img.setAttribute("src", "img/g.png");
} else {
console.log(0);
};
};
<body onload="logo_switch()">
<div class="anonce">
<h2>Job 1</h2>
<div class="anonceBody">
<img class="anonceLogo">
<p class="anonceText">
A
</p>
</div>
</div>
<div class="anonce">
<h2>Job 2</h2>
<div class="anonceBody">
<img class="anonceLogo">
<p class="anonceText">
B
</p>
</div>
</div>
<div class="anonce">
<h2>Job 3</h2>
<div class="anonceBody">
<img class="anonceLogo">
<p class="anonceText">
C
</p>
</div>
</div>
<div class="anonce">
<h2>Job 4</h2>
<div class="anonceBody">
<img class="anonceLogo">
<p class="anonceText">
D
</p>
</div>
</div>
</body>
One approach is as below, with explanatory comments in the JavaScript:
const logo_switch = () => {
// using document.querySelectorAll() to retrieve all matching elements, along with
// and Array-literal and the spread operator to convert the iterable NodeList into
// an Array to provide access to Array methods:
let anonceTextElements = [...document.querySelectorAll(".anonceText")];
// the letters (or company names) you're looking for:
const logoLetters = ['A', 'B', 'c', 'D', 'E', 'F', 'G'];
// iterating over the anonceTextElements using Array.prototype.forEach()
// (note that this is entirely possible with NodeList.prototype.forEach(),
// but I use Array methods on this type of collection often enough - to
// filter, map, slice... - that I find it worth always converting to an
// Array for further modification, but that's purely my bias):
anonceTextElements.forEach(
// passing the current element (not the text, the element):
(elem) => {
// retrieve the text of the element, using String.prototype.trim() to
// remove leading and trailing white-space, and then trimming that to
// to the first 50 character with String.prototype.slice():
let text = elem.textContent.trim().slice(0, 50)
// using Array.prototype.filter() to filter the Array to keep only the
// relevant array-elements:
logoLetters
// we keep only the letters for which the provided assessment returns
// true/truthy values; here we're looking to see if the retrieved
// element text contains the current letter/company name:
.filter((letter) => text.includes(letter))
// the remaining elements are passed on to Array.prototype.forEach():
.forEach(
(matchedLetter) => {
elem
// here we navigate to closest ancestor element matching the '.anonce'
.closest('.anonce')
// from there we find the first of any elements matching the supplied
// 'img' selector, and update/set its src property using a template-
// literal, to interpolate the variable (matchedLetter) into the string:
.querySelector('img').src = `img/${matchedLetter}.png`;
});
});
}
logo_switch();
<div class="anonce">
<h2>Job 1</h2>
<div class="anonceBody">
<img class="anonceLogo">
<p class="anonceText">
A
</p>
</div>
</div>
<div class="anonce">
<h2>Job 2</h2>
<div class="anonceBody">
<img class="anonceLogo">
<p class="anonceText">
B
</p>
</div>
</div>
<div class="anonce">
<h2>Job 3</h2>
<div class="anonceBody">
<img class="anonceLogo">
<p class="anonceText">
C
</p>
</div>
</div>
<div class="anonce">
<h2>Job 4</h2>
<div class="anonceBody">
<img class="anonceLogo">
<p class="anonceText">
D
</p>
</div>
</div>
References:
Array.prototype.forEach().
Array.prototype.filter().
Array.prototype.some().
document.querySelector().
document.querySelectorAll().
Element.closest().
Element.querySelector().
Element.querySelectorAll().
NodeList.prototype.forEach().
String.prototype.includes().
String.prototype.slice().
I have been trying to search with multiple tags.
But for some reason, it isn't working, it shows just the h5 tags as results.
How would you change the code that it works?
Is it because of the NodeList? For a single tag it is working with Document.getElementsByTagName()
Can i convert a NodeList to a HTMLCollection?
const searchBar = document.forms['search-webinare'].querySelector('input');
searchBar.addEventListener('keyup', function(e) {
const term = e.target.value.toLocaleLowerCase();
const webinare = Array.prototype.slice.call(document.querySelectorAll('h3,h5'), 0);
var hasResults = false;
webinare.forEach(function(webinar) {
const title = webinar.textContent;
if (title.toLowerCase().indexOf(term) != -1) {
webinar.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.style.display = 'flex';
hasResults = true;
} else {
webinar.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.style.display = 'none';
}
});
});
<form id="search-webinare">
<input type="text" placeholder="Suchen Sie ein Webinar ... "/>
</form>
<div>
<div>
<div>
<div>
<div>
<div>
<h3>text1</h3>
<h5>text2</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<div>
<div>
<div>
<h3>text3</h3>
<h5>text4</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<div>
<div>
<div>
<h3>text5</h3>
<h5>text6</h5>
</div>
</div>
</div>
</div>
</div>
</div>
You can use forEach on a NodeList. (doc) An array is not needed.
Lars Flieger has a good explanation on why your code is not doing what your want but I think it's easier to loop through parent divs and then search for your term in children elements. (Note that finding parents of an element is also possible with the element.closest() function)
querySelector functions are also available on elements (read more)
So I propose to loop through parent divs and then try to find headings in each div which match your term.
If your term is not found anywhere then your can directly show or hide your parent div element.
Note that I added a class to parent divs to select them but other selectors can be used depending on there positions in the document.
const searchBar = document.querySelector('#search-webinare input');
searchBar.addEventListener('keyup', function(e) {
const term = e.target.value.toLocaleLowerCase();
const parentDivs = document.querySelectorAll('.parent');
parentDivs.forEach(function(parentDiv) {
const headings = parentDiv.querySelectorAll('h3, h5');
let match = false;
headings.forEach(function(heading) {
const title = heading.textContent.toLowerCase();
if (title.indexOf(term) > -1) {
match = true
}
});
if (match) {
parentDiv.style.display = 'flex';
} else {
parentDiv.style.display = 'none';
}
});
});
/* just for easier visualisation of parents */
.parent {
margin: 5px;
padding: 5px;
background: rgba(0, 0, 0, 0.1);
}
<form id="search-webinare">
<input type="text" placeholder="Suchen Sie ein Webinar ... "/>
</form>
<div class="parent">
<div>
<div>
<div>
<div>
<div>
<h3>text1</h3>
<h5>text2a</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="parent">
<div>
<div>
<div>
<div>
<div>
<h3>text2b</h3>
<h5>text3a</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="parent">
<div>
<div>
<div>
<div>
<div>
<h3>text3b</h3>
<h5>text4</h5>
</div>
</div>
</div>
</div>
</div>
</div>
Try this when you are able to modify the parent div of h3/h5:
const searchBar = document.forms['search-webinare'].querySelector('input');
searchBar.addEventListener('input', (e) => {
const term = e.target.value.toLocaleLowerCase()
const webinare = document.querySelectorAll('div.content')
let hasResults = false;
webinare.forEach(webinar => {
const titleOfh3 = webinar.querySelector('h3').textContent;
const titleOfh5 = webinar.querySelector('h5').textContent;
if (titleOfh3.toLowerCase().includes(term) || titleOfh5.toLowerCase().includes(term)) {
webinar.style.display = 'block';
hasResults = true;
} else {
webinar.style.display = 'none';
}
})
});
<form id="search-webinare">
<input type="text" placeholder="Suchen Sie ein Webinar ... " />
</form>
<div>
<div>
<div>
<div>
<div>
<div class="content">
<h3>text1</h3>
<h5>text2</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<div>
<div>
<div class="content">
<h3>text3</h3>
<h5>text4</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<div>
<div>
<div class="content">
<h3>text5</h3>
<h5>text6</h5>
</div>
</div>
</div>
</div>
</div>
</div>
Or this if not:
const searchBar = document.forms['search-webinare'].querySelector('input');
searchBar.addEventListener('input', (e) => {
const term = e.target.value.toLocaleLowerCase()
const webinare = document.querySelectorAll('h3, h5')
let hasResults = false;
for (let i = 0; i < webinare.length; i += 2) {
const titleOfh3 = webinare[i].textContent;
const titleOfh5 = webinare[i + 1].textContent;
if (titleOfh3.toLowerCase().includes(term) || titleOfh5.toLowerCase().includes(term)) {
webinare[i].parentElement.style.display = 'block';
webinare[i + 1].parentElement.style.display = 'block';
hasResults = true;
} else {
webinare[i].parentElement.style.display = 'none';
webinare[i + 1].parentElement.style.display = 'none';
}
}
});
<form id="search-webinare">
<input type="text" placeholder="Suchen Sie ein Webinar ... " />
</form>
<div>
<div>
<div>
<div>
<div>
<div>
<h3>text1</h3>
<h5>text2</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<div>
<div>
<div>
<h3>text3</h3>
<h5>text4</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<div>
<div>
<div>
<h3>text5</h3>
<h5>text6</h5>
</div>
</div>
</div>
</div>
</div>
</div>
What is the problem with your solution?
The problem with your code is the order you check if the term is matching: If you try to search for a h5:
The parent element gets displayed since the last operation of it was to set the display to block.
If a search result matches the first, the second will override the result with display none:
You can solve this issue by: Adding a class to the parent div (no there is no parent selector based on childs yet) and select this one and change the search logic to check the h3 and h5 in one step. (This is the simplest one and if you are able to add stuff to the parent div: I recommend you this.)
I'm having a hard time with some JS DOM traversal. I'm stuck with html that's something like this:
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
</div>
<h2>Header 2</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
This is all hidden by default. I'm trying to use a text field so that if it matches an h4 person-name, it displays the some-content container, as well as the preceding h2. I can make it work for the some-content bit, but I'm having trouble targeting the h2 that's above it. I've tried various combinations of jQuery parent(), siblings(), and prev(). I do not have the ability to add additional class names.
Edit: here is the script I have for the text field event:
$('#text-field').keyup(function() {
var nameSearch = $(this).val().toUpperCase();
$('.person-name').each(function() {
var x = $(this).text().toUpperCase();
if (x.includes(nameSearch)) {
$(this).prev('h2').show();
$(this).closest('.some-content').show();
}
})
});
Edit 2:
I apologize, my code example was oversimplified. Some very good answers by the way. If for example there was a search done for Emily Jones in this bit, would there need to be something extra done?
<div class="container">
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
</div>
If the header/content is not nested withing a wrapping div, you will need to step over every two child nodes and toggle class.
const triggerEvent = (el, eventName) => {
var event = document.createEvent('HTMLEvents');
event.initEvent(eventName, true, false);
el.dispatchEvent(event);
};
const
search = document.querySelector('.search'),
container = document.querySelector('.container');
const onSearch = (e) => {
const
searchValue = e.target.value,
nodes = container.children;
for (let i = 0; i < nodes.length; i += 2) {
const
h2 = nodes[i],
someContent = nodes[i + 1],
matches = someContent.querySelector('.person-name').textContent === searchValue;
h2.classList.toggle('hidden', !matches);
someContent.classList.toggle('hidden', !matches);
}
};
search.addEventListener('change', onSearch);
triggerEvent(search, 'change');
.hidden {
color: #DDD; /* Replace with -: display: none */
}
<input type="text" class="search" value="Emily Jones" />
<div class="container">
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
</div>
<h2>Header 2</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
</div>
Alternatively, you can start with the names and work your way back to the corresponding h2.
const h2 = child
.closest('.inner-content')
.closest('.some-content')
.previousElementSibling; // h2
const triggerEvent = (el, eventName) => {
var event = document.createEvent('HTMLEvents');
event.initEvent(eventName, true, false);
el.dispatchEvent(event);
};
const
search = document.querySelector('.search'),
container = document.querySelector('.container');
const onSearch = (e) => {
const searchValue = e.target.value;
[...container.querySelectorAll('.person-name')].forEach(child => {
const
matches = child.textContent === searchValue,
h2 = child.closest('.inner-content')
.closest('.some-content').previousElementSibling;
[child, h2].forEach(el => el.classList.toggle('hidden', !matches));
});
};
search.addEventListener('change', onSearch);
triggerEvent(search, 'change');
.hidden {
color: #DDD; /* Replace with -: display: none */
}
<input type="text" class="search" value="Emily Jones" />
<div class="container">
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
</div>
<h2>Header 2</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
</div>
Edit
Here is an altered version of the first example. If you have multiple names within .some-content you will have to find all the names that match and keep the parent, if at least one child matches.
const triggerEvent = (el, eventName) => {
var event = document.createEvent('HTMLEvents');
event.initEvent(eventName, true, false);
el.dispatchEvent(event);
};
const
search = document.querySelector('.search'),
container = document.querySelector('.container');
const onSearch = (e) => {
const
searchValue = e.target.value,
nodes = container.children;
for (let i = 0; i < nodes.length; i += 2) {
const
h2 = nodes[i],
someContent = nodes[i + 1],
names = [...someContent.querySelectorAll('.person-name')],
found = names.filter(name => name.textContent === searchValue);
h2.classList.toggle('hidden', found.length === 0);
names.forEach(name => {
const matches = name.textContent === searchValue;
name.closest('.inner-content').classList.toggle('hidden', !matches);
});
}
};
search.addEventListener('change', onSearch);
triggerEvent(search, 'change');
.hidden {
color: #DDD; /* Replace with -: display: none */
}
<input type="text" class="search" value="Emily Jones" />
<div class="container">
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
<h2>Header 2</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Doe</h4>
</div>
<div class="inner-content">
<h4 class="person-name">Erica Jones</h4>
</div>
</div>
</div>
I have some javascript function - shows me a popup with some texts. I try to rotate two "section" elements, but if I add to HTML one more section with class custom, the page shows only first element. Please, help me to add 1-2 more elements and to rotate it. The idea is to have 2 or more elements with class custom and to show it in random order, after last to stop. Thanks.
setInterval(function () {
$(".custom").stop().slideToggle('slow');
}, 2000);
$(".custom-close").click(function () {
$(".custom-social-proof").stop().slideToggle('slow');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section class="custom">
<div class="custom-notification">
<div class="custom-notification-container">
<div class="custom-notification-image-wrapper">
<img src="checkbox.png">
</div>
<div class="custom-notification-content-wrapper">
<p class="custom-notification-content">
Some Text
</p>
</div>
</div>
<div class="custom-close"></div>
</div>
</section>
Set section display none of page load instead of first section. Check below code of second section:
<section class="custom" style=" display:none">
<div class="custom-notification">
<div class="custom-notification-container">
<div class="custom-notification-image-wrapper">
<img src="checkbox.png">
</div>
<div class="custom-notification-content-wrapper">
<p class="custom-notification-content">
Mario<br>si kupi <b>2</b> matraka
<small>predi 1 chas</small>
</p>
</div>
</div>
<div class="custom-close"></div>
</div>
</section>
And you need to make modification in your jQuery code as below:
setInterval(function () {
var sectionShown = 0;
var sectionNotShown = 0;
$(".custom").each(function(i){
if ($(this).css("display") == "block") {
sectionShown = 1;
$(this).slideToggle('slow');
} else {
if (sectionShown == 1) {
$(this).slideToggle('slow');
sectionShown = 0;
sectionNotShown = 1;
}
}
});
if (sectionNotShown == 0) {
$(".custom:first").slideToggle('slow');
}
}, 2000);
Hope it helps you.
I have 3 sliders on my page I'm a building but i am just curious to know the best way to go about targeting only the active one so the javascript below would work for all of them. Everything seems to work fine if i disable the other 2 sliders. First time I have done something like this.
I'm guessing my javascript selectors may need to change some what to get it to work.
Appreciate any advice on the best way forward.
var sliderSlide = document.querySelectorAll('.slider__slide');
var nextSlide = document.querySelector('.slider__button--next');
var previousSlide = document.querySelector('.slider__button--previous');
var currentSlide = 0;
var currentSlideImg = 0;
//Reset slides
function resetSlides() {
for (var s = 0; s < sliderSlide.length; s++) {
sliderSlide[s].classList.remove('active');
}
for (var i = 0; i < sliderSlideImg.length; i++) {
sliderSlideImg[i].classList.remove('active');
}
}
//Start slides
function startSlide() {
resetSlides();
sliderSlide[0].classList.add('active');
sliderSlideImg[0].classList.add('active');
}
//Previous slide
function slidePrevious() {
resetSlides();
sliderSlide[currentSlide - 1].classList.add('active');
currentSlide--;
sliderSlideImg[currentSlideImg - 1].classList.add('active');
currentSlideImg--;
}
previousSlide.addEventListener('click', function() {
if (currentSlide === 0 && currentSlideImg === 0) {
currentSlide = sliderSlide.length;
currentSlideImg = sliderSlideImg.length;
}
slidePrevious();
});
//Next slide
function slideNext() {
resetSlides();
sliderSlide[currentSlide + 1].classList.add('active');
currentSlide++;
sliderSlideImg[currentSlideImg + 1].classList.add('active');
currentSlideImg++;
}
nextSlide.addEventListener('click', function() {
if (currentSlide === sliderSlide.length - 1 && currentSlideImg === sliderSlideImg.length - 1) {
currentSlide = -1;
currentSlideImg = -1;
}
slideNext();
});
<div class="slider slider--1 active">
<div class="slider__slide">
<p class="slider__text">some text</p>
</div>
<div class="slider__slide">
<p class="slider__text">some text</p>
</div>
<div class="slider__slide">
<p class="slider__text">some text</p>
</div>
<div class="slider__buttons">
<span class="slider__button--previous">Previous</span>
<span class="slider__button--next">Next</span>
</div>
</div>
<div class="slider slider--2">
<div class="slider__slide">
<p class="slider__text">some text</p>
</div>
<div class="slider__slide">
<p class="slider__text">some text</p>
</div>
<div class="slider__slide">
<p class="slider__text">some text</p>
</div>
<div class="slider__buttons">
<span class="slider__button--previous">Previous</span>
<span class="slider__button--next">Next</span>
</div>
</div>
<div class="slider slider--3">
<div class="slider__slide">
<p class="slider__text">some text</p>
</div>
<div class="slider__slide">
<p class="slider__text">some text</p>
</div>
<div class="slider__slide">
<p class="slider__text">some text</p>
</div>
<div class="slider__buttons">
<span class="slider__button--previous">Previous</span>
<span class="slider__button--next">Next</span>
</div>
</div>
You can create loop over .slider and then use querySelector on each item, that way you will have variables for each slider
Array.from(document.querySelectorAll('.slider')).forEach(function(slider) {
var sliderSlide = slider.querySelectorAll('.slider__slide');
var nextSlide = slider.querySelector('.slider__buttons--next');
var previousSlide = slider.querySelector('.slider__buttons--previous');
...
});
or if you prefer for loop:
var sliders = document.querySelectorAll('.slider');
for (var i = 0; i < sliders.length; ++i) {
var slider = sliders[i];
...
}
document.querySelector('.slider--3 .slider__slide')
but I would recommend to put id's on your sliders and then select
document.querySelector('#slider--3')