I have a HTML/CSS/JS script that is allowing me to rotate text for a certain part of it. I'm just having the problem of making it work for multiple sections as the script targets a span[data-up] & span[data-show].
Any help appreciated, code shown below.
<section class="rotating-text-section">
<h2>
We educate by
<div class="wrapper">
<span data-up>teaching.</span>
<span data-show>showing.</span>
<span>doing.</span>
<span>repeating.</span>
</div>
</h2>
</section>
setInterval(() => {
const up = document.querySelector('span[data-up]');
const show = document.querySelector('span[data-show]');
const down = show.nextElementSibling || document.querySelector('span:first-child');
up.removeAttribute('data-up');
show.removeAttribute('data-show');
show.setAttribute('data-up', '');
down.setAttribute('data-show', '');
}, 2000);
I just changed the class names of each span element and targeted them by this in the JavaScript script.
My second snippet of HTML was:
<div class="wrapper-two">
<span class="second-span" data-up>technology.</span>
<span class="second-span" data-show>experience.</span>
<span class="second-span">listening.</span>
<span class="second-span">experimenting.</span>
</div>
My second JS script is as follows:
setInterval(() => {
const up = document.querySelector('.second-span[data-up]');
const show = document.querySelector('.second-span[data-show]');
const down = show.nextElementSibling ||
document.querySelector('.second-span:first-child');
up.removeAttribute('data-up');
show.removeAttribute('data-show');
show.setAttribute('data-up', '');
down.setAttribute('data-show', '');
}, 1000);
Related
I'm trying to get some text using Cheerio that is placed after a single <br> tag.
I've already tried the following lines:
let price = $(this).nextUntil('.col.search_price.discounted.responsive_secondrow').find('br').text().trim();
let price = $(this).nextUntil('.col.search_price.discounted.responsive_secondrow.br').text().trim();
Here is the HTML I'm trying to scrape:
<div class="col search_price_discount_combined responsive_secondrow" data-price-final="5039">
<div class="col search_discount responsive_secondrow">
<span>-90%</span>
</div>
<div class="col search_price discounted responsive_secondrow">
<span style="color: #888888;"><strike>ARS$ 503,99</strike></span><br>ARS$ 50,39
</div>
</div>
I would like to get "ARS$ 50,39".
If you're comfortable assuming this text is the last child element, you can use .contents().last():
const cheerio = require("cheerio"); // 1.0.0-rc.12
const html = `
<div class="col search_price_discount_combined responsive_secondrow" data-price-final="5039">
<div class="col search_discount responsive_secondrow">
<span>-90%</span>
</div>
<div class="col search_price discounted responsive_secondrow">
<span style="color: #888888;"><strike>ARS$ 503,99</strike></span><br>ARS$ 50,39
</div>
</div>
`;
const $ = cheerio.load(html);
const sel = ".col.search_price.discounted.responsive_secondrow";
const text = $(sel).contents().last().text().trim();
console.log(text); // => ARS$ 50,39
If you aren't comfortable with that assumption, you can search through the children to find the first non-empty text node:
// ...
const text = $([...$(sel).contents()]
.find(e => e.type === "text" && $(e).text().trim()))
.text()
.trim();
console.log(text); // => ARS$ 50,39
If it's critical that the text node immediately follows a <br> tag specifically, you can try:
// ...
const contents = [...$(sel).contents()];
const text = $(contents.find((e, i) =>
e.type === "text" && contents[i-1]?.tagName === "br"
))
.text()
.trim();
console.log(text); // => ARS$ 50,39
If you want all of the immediate text children, see:
How to get a text that's separated by different HTML tags in Cheerio
cheerio: Get normal + text nodes
You should be able to get the price by using:
$('.col.search_price.discounted.responsive_secondrow').html().trim().split('<br>')
This gets the inner HTML of the element, trims extra spaces, then splits on the <br> and takes the 2nd part.
See example at https://jsfiddle.net/b7nt0m24/3/ (note: uses jquery which has a similar API to cheerio)
So, I am trying to pull the volume info from the JSON array from the URL provided: https://www.googleapis.com/books/v1/volumes?q=HTML5
Trying to pull author, title, images, page numbers and description.
This specific class of my HTML code I want to put the JSON data that I have mentioned above in is the 'b-card' class:
<div class="booklist">
<div class="booklist-cards">
<div class="b-card">
</div>
<div class="b-card">
</div>
<div class="b-card">
</div>
<div class="b-card">
</div>
<div class="b-card">
</div>
<div class="b-card">
</div>
<div class="b-card">
</div>
<div class="b-card">
</div>
</div>
</div>
<script src="https://www.googleapis.com/books/v1/volumes?q=HTML5"></script>
<script src="assets/js/script.js"></script>
The script.js file I have tried is below:
function handleResponse(obj) {
const book = Objects.keys(obj).map(item => obj['items']).reduce(
(acc, rec, id, array) => {
let singleBookCover = rec[id].volumeInfo.imageLinks.thumbnail;
let singleBookTitle = rec[id].volumeInfo.title;
let singleBookAuthor = rec[id].volumeInfo.authors[0];
return [...acc, {singleBookCover, singleBookTitle, singleBookAuthor}]
},
[]
).forEach( item => {
let title = document.createElement('h1');
title.textContent = `${item.singleBookTitle}`;
let author = document.createElement('strong');
author.textContent = `${item.singleBookAuthor}`;
let img = document.createElement('img');
img.src = item.singleBookCover;
img.alt = `${item.singleTitle} by ${item.singleBookAuthor}`;
let container = document.getElementsByClassName('b-card');
container.appendChild(title).appendChild(author).appendChild(img);
})
return book
}
The above code only adds the title image and author, but I cant get them to load into my HTML.
What are ways to resolve this? Am i calling the URL correctly in the HTML script tag?
Forgot to mention - would like to achieve this without using JQuery & AJAX. I have also tried inputting the callback to handleResponse in the script tag url but it doesnt work.
you can't append to the HTML because container is array so it need index of the element
container[index].appendChild(title).appendChild(author).appendChild(img);
but here simple version, and don't forget to add &callback=handleRespons to the API URL
function handleResponse(obj) {
obj.items.forEach((item, index) => {
if(index > 7) return; // limit 8 result
let div = document.createElement('div');
div.className = 'b-card';
div.innerHTML = `<h1>${item.volumeInfo.title}</h1>
<p><strong>${item.volumeInfo.authors[0]}</strong></p>
<img src="${item.volumeInfo.imageLinks.thumbnail}" alt="${item.singleTitle} by ${item.volumeInfo.authors[0]}" />`
let container = document.querySelector('.booklist-cards');
container.append(div);
})
}
<div class="booklist">
<div class="booklist-cards">
</div>
</div>
<script src="//www.googleapis.com/books/v1/volumes?q=HTML5&callback=handleResponse" async></script>
I'm trying to make an infinite scroll (without jQuery) to show more results in a page. I'm using an IntersectionObserver to detect a div called #paginate and everytime it enters the screen, the #result div will be refreshed.
var result = document.querySelector('#result');
var paginate = document.querySelector('#paginate');
var observer = new IntersectionObserver(entries => {
if (entries.some(entry => entry.isIntersecting))
{
var pagination = 10;
fetch('/kernel/search.php?pagination='+pagination)
.then((response) => {
return response.text();
})
.then((html) => {
result.innerHTML = html;
});
}
});
observer.observe(paginate);
Here's the full code view with HTML:
<html>
<body>
<div class="row justify-content-sm-center justify-content-md-center justify-content-lg-center justify-content-xl-start no-gutters min-vw-100" id="result">
<div class="col-sm-11 col-md-11 col-lg-9-result col-xl-4-result order-0">
<div class="card mx-3 mt-3">
<div class="card-body">
<a class="text-decoration-none" href="?topic=result-1">
<h5 class="card-title">
Result 1
</h5>
</a>
<p class="card-text text-truncate">
Result 1 description.</p>
</div>
</div>
<div class="card mx-3 mt-3">
<div class="card-body">
<a class="text-decoration-none" href="?topic=result-2">
<h5 class="card-title">
Result 2
</h5>
</a>
<p class="card-text text-truncate">
Result 2 description.</p>
</div>
</div>
<div class="alert alert-light text-dark text-center border mx-3 my-3" id="paginate">
More results
</div>
</div>
</div>
<script>
var result = document.querySelector('#result');
var paginate = document.querySelector('#paginate');
var observer = new IntersectionObserver(entries => {
if (entries.some(entry => entry.isIntersecting))
{
var pagination = 10;
fetch('/kernel/search.php?pagination='+pagination)
.then((response) => {
return response.text();
})
.then((html) => {
result.innerHTML = html;
});
}
});
observer.observe(paginate);
</script>
</body>
</html>
It works, but it only works the first time and it doesn't refresh the #result div thereafter. I can see the fetch working in Web Browser > Inspect > Network tab, but there's no activity after the first refresh of the #result div meaning it doesn't detect the #paginate div anymore.
What's going on here? I assume it's because that I'm using an innerHTML and the observer somehow can't detect the #paginate div after the first refresh of the #result div. How can I solve this?
I did it with jQuery and .scroll function and used ajax like this, maybe my code can help you and adapt it to your needs.
$('#customersList').scroll(function () {
if ($(this).scrollTop() + $(this).innerHeight() >= $(this)[0].scrollHeight - 5000) {
// Do your stuff here.
getCustomers();
}
})
function getCustomers(){
let $customerList = $('#customersList');
let offset = ($customerList.attr('data-offset')) ?
$customerList.attr('data-offset') : 0;
if ($customerList.data('requestRunning')) {
return;
}
$customerList.data('requestRunning', true);
return $.ajax({
type: "GET",
data: {offset: offset},
url : routes.routes.customers.get
})
.done(function (data) {
let _htmlCustomersList = ($customerList.is(':empty')) ? '' : $customerList.html();
let response = data.data;
if (response) {
for (const i in response) {
let v = JSON.parse(response[i]);
_htmlCustomersList += '<div class="client-group edit" data-id="' + v['id'] + '"></div><hr>';
}
offset = parseInt(offset) + 200;
$customerList.attr('data-offset', offset).html(_htmlCustomersList);
}
})
.always(function () {
$customerList.data('requestRunning', false);
});
}
my getCustomer function runs before reaching the end of the page and loads 200 more items each time.
I hope this can help you a little bit
It seems you are removing #paginate after the first update, because it is in the #result.
Please, use indentation :)
The #result div is the main wrapper of the content
using innerHTML to update the contents of the div, will result in replacing the entire content inside of the div...
Beyond the fact that innerHTML is absolute, and erases any DOM objects (and their events) hence bad practice, it's not that good solution either, since you'd like to append rather then replace the data, upon scrolling
I would suggest to add a div above the paginate, which will hold the added data, something like:
...
<div id="result"></div>
<div class="alert alert-light text-dark text-center border mx-3 my-3" id="paginate">
More results
</div>
Then use some sort of appending, for the content added
so something like:
.then((html) => {
let res = new DOMParser().parseFromString(html, "text/xml");
document.getElementById("result").appendChild(res);
});
Hope that helps
I have removed innerHTML and replaced it with insertAdjacentHTML.
Because innerHTML seems to reset/forget #paginate after the first refresh of the #result, so the observer can't detect #paginate anymore to trigger the next #result refresh.
I could have used innerHTML with appendChild to do the same but appendChild adds a new div on each #result refresh, which I didn't really like.
As the solution, I'm inserting the refreshed html data before the beginning of #paginate without resetting/forgetting it's element id that's required for the observer to trigger the next #result refresh.
.then((html) => {
paginate.insertAdjacentHTML('beforebegin', html);
});
I am new in Unit Test JS. I want create test in jasmine. I dynamically create element HTML in JS.
data.map((channel) => {
const { url, width, height } = channel.thumbnails.medium;
const { title, customUrl } = channel;
const { subscriberCount, videoCount, viewCount } = channel.statistics;
output += `
<li class="channel-wrraper">
<a href='${customUrl}' target="_blank">
<img src='${url}' alt="img-channel" height='${width}' width='${height}' class="channel-img">
</a>
<p class="channel-title">${title}</p>
<div class="channel-statistic">
<div class="statistic-wrraper">
<span class="statistic-name">subscribers:</span>
<span class="subscirber-count">${formatNumber(subscriberCount)}</span>
</div>
<div class="statistic-wrraper">
<span class="statistic-name">videos:</span>
<span class="video-count">${formatNumber(videoCount)}</span>
</div>
<div class="statistic-wrraper">
<span class="statistic-name">views:</span>
<span class="veiw-count">${formatNumber(viewCount)}</span>
</div>
</div>
</li>`
});
channelsList.innerHTML = output;
Then some element will be ordered. This is sort function:
const list = document.querySelector('.channels-list');
const sortNumber = (selector) => {
[...list.children]
.sort((a,b) => a.querySelector(selector).innerText.replace(/,/g, '') - b.querySelector(selector).innerText.replace(/,/g, ''))
.map(node => list.appendChild(node))
}
I read about JSDOM and I watched the tutorials in which they tested the DOM, however, these elements were in the html file...
I want test function sortNumber
But I don`t know how start this task..
You can try using jsdom-global, then you will have document.body setup for you:
require('jsdom-global')()
// you can now use the DOM
document.body.innerHTML = 'put your html here'
An alternative will be to use jest, which comes with JSDOM configured as default
There's a page with some HTML as follows:
<dd id="fc-gtag-VARIABLENAMEONE" class="fc-content-panel fc-friend">
Then further down the page, the code will repeat with, for example:
<dd id="fc-gtag-VARIABLENAMETWO" class="fc-content-panel fc-friend">
How do I access these elements using an external script?
I can't seem to use document.getElementByID correctly in this instance. Basically, I want to search the whole page using oIE (InternetExplorer.Application Object) created with VBScript and pull through every line (specifically VARIABLENAME(one/two/etc)) that looks like the above two into an array.
I've researched the Javascript and through trial and error haven't gotten anywhere with this specific page, mainly because there's no tag name, and the tag ID always changes at the end. Can someone help? :)
EDIT: I've attempted to use the Javascript provided as an answer to get results, however nothing seems to happen when applied to my page. I think the tag is ALSO in a tag so it's getting complicated - here's a major part of the code from the webpage I will be scanning.
<dd id="fc-gtag-INDIAN701" class="fc-content-panel fc-friend">
<div class="fc-pic">
<img src="http://image.xboxlive.com/global/t.58570942/tile/0/20400" alt="INDIAN701"/>
</div>
<div class="fc-stats">
<div class="fc-gtag">
<a class="fc-gtag-link" href='/en-US/MyXbox/Profile?gamertag=INDIAN701'>INDIAN701</a>
<div class="fc-gscore-icon">3690</div>
</div>
<div class="fc-presence-text">Last seen 9 hours ago playing Halo 3</div>
</div>
<div class="fc-actions">
<div class="fc-icon-actions">
<div class="fc-block">
<span class="fc-buttonlabel">Block User</span>
</div>
</div>
<div class="fc-text-actions">
<div class="fc-action"> </div>
<span class="fc-action">
View Profile
</span>
<span class="separator-icon">|</span>
<span class="fc-action">
Compare Games
</span>
<span class="separator-icon">|</span>
<span class="fc-action">
Send Message
</span>
<span class="separator-icon">|</span>
<span class="fc-action">
Send Friend Request
</span>
</div>
</div>
</dd>
This then REPEATS, with a different username (the above username is INDIAN701).
I tried the following but clicking the button doesn't yield any results:
<script language="vbscript">
Sub window_onLoad
Set oIE = CreateObject("InternetExplorer.Application")
oIE.visible = True
oIE.navigate "http://live.xbox.com/en-US/friendcenter/RecentPlayers?Length=12"
End Sub
</script>
<script type="text/javascript">
var getem = function () {
var nodes = oIE.document.getElementsByTagName('dd'),
a = [];
for (i in nodes) {
(nodes[i].id) && (nodes[i].id.match(/fc\-gtag\-/)) && (a.push(nodes[i]));
}
alert(a[0].id);
alert(a[1].id);
}
</script>
<body>
<input type="BUTTON" value="Try" onClick="getem()">
</body>
Basically I'm trying to get a list of usernames from the recent players list (I was hoping I wouldn't have to explain this though :) ).
var getem = function () {
var nodes = document.getElementsByTagName('dd'),
a = [];
for (var i in nodes) if (nodes[i].id) {
(nodes[i].id.match(/fc\-gtag\-/)) && (a.push(nodes[i].id.split('-')[2]));
}
alert(a[0]);
};
please try it by clicking here!
var getem = function () {
var nodes = document.getElementsByTagName('dd'),
a = [];
for (var i in nodes) if (nodes[i].id) {
(nodes[i].id.match(/fc\-gtag\-/)) && (a.push(nodes[i]));
}
alert(a[0].id);
alert(a[1].id);
};
try it out on jsbin
<body>
<script type="text/javascript">
window.onload = function () {
var outputSpan = document.getElementById('outputSpan'),
iFrame = frames['subjectIFrame'];
iFrame.document.location.href = 'http://live.xbox.com/en-US/friendcenter/RecentPlayers?Length=1';
(function () {
var nodes = iFrame.document.getElementsByTagName('dd'),
a = [];
for (var i in nodes) if (nodes[i].id) {
(nodes[i].id.match(/fc\-gtag\-/)) && (a.push(nodes[i].id.split('-')[2]));
}
for (var j in a) if (a.hasOwnProperty(j)) {
outputSpan.innerHTML += (a[j] + '<br />');
}
})();
};
</script>
<span id="outputSpan"></span>
<iframe id="subjectIFrame" frameborder="0" height="100" width="100" />
</body>
What does "I can't seem to use document.getElementsByID correctly in this instance" mean? Are you referring to the fact that you are misspelling getElementByID?
So...something like this (jQuery)?
var els = [];
$('.fc-content-panel.fc-friend').each(function() {
els.push(this));
});
Now you have an array of all the elements that have both of those classes.