I'm matching HTML elements based on their textContent. Then surronding that match with <strong> tags:
const element = [...document.querySelectorAll('a')]
.find(element => element.textContent.match('b'))
element.innerHTML = element.innerHTML.replace('b', '<strong>$&</strong>')
<a href="#">
<br>
<h3>blog</h3>
</a>
There's a problem, though. The code also matches HTML elements. So I get this:
<a href="#">
<<strong>b</strong>r>
<h3>blog</h3>
</a>
Instead of the desired result:
<a href="#">
<br>
<h3>blog</h3>
</a>
How to change my code so it doesn't match HTML elements? Only the text inside them?
Iterate over the children elements of your anchors and reset the HTML based on whether the textContent of the element contains "b".
Note: find will only find the first instance of the thing you're looking for. You need to explicitly iterate over all of the things.
The find() method returns the value of the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function, undefined is returned.
const elements = [...document.querySelectorAll('a')];
function embolden(elements, str) {
elements.forEach(element => {
[...element.children].forEach(child => {
if (child.textContent.includes('b')) {
child.innerHTML = child.textContent.replace('b', '<strong>b</strong>');
}
});
});
}
embolden(elements, 'b');
<a href="#">
<br>
<p>blog</p>
</a>
<a href="#">
<br>
<p>blog</p>
<p>Peterborough</p>
</a>
Use innerText instead of innerHTML to match. Insert the replaced values in the innerHTML
const element = [...document.querySelectorAll('#example')]
.find(element => element.textContent.match('b'))
element.innerHTML = element.innerText.replace('b', '<strong>$&</strong>')
<div id="example">
<div>This is some text.</div>
<br>
<div> This is part of the body </div>
</div>
You can use this regex: /(?!<[^>]+)b(?![^<]+>)/g
const element = [...document.querySelectorAll('a')]
.find(element => element.textContent.match('b'))
const string = "b";
const reg = new RegExp("(?!<[^>]+)" + string + "(?![^<]+>)", "g");
element.innerHTML = element.innerHTML.replace(reg, '<mark>$&</mark>')
mark
{
background-color: lightgreen;
}
<a href="#">
<br>
<h3 title="attributes are not affected bbbb">blog hover blog</h3>
</a>
Related
I'm iterating on every element(post) on an array and each HTML tag name has a value of post._id. In the DOM, the outputs has different values as excepted but when I try to capture those values on the react Profile component, the console.log(el) outputs the same value for every different click of the elements in the DOM.
I have a links on every post on the profile, and when onClick event occurs on every link, I'm trying for each of them to have the id I gave to the tag name (post._id)
function handleReplyTweet(e) {
e.preventDefault();
appDispatch({
type: 'openReply',
});
let el = document.getElementById('reply').name;
console.log(el);
setTweetId(el);
console.log(tweetId);
}
HTML
tweetsData
.map((post, index) => {
return (
<div key={index} className="main-view__user-feed">
<img src={post.avatar} alt="image tweet"></img>
<div className="main-view__user-feed__author">
<div className="name">
<h2>
{post.name} {post.surname}
</h2>
<a href="#" className="username__author">
#{post.username}
</a>
<span> 9 June</span>
</div>
<Link
to={`/status/${post._id}`}
className="author_post-viewpost"
>
{post.text}
</Link>
<div className="icons">
<section>
<li className="top-nav__item top-nav__item--active">
// Tyring to capture the names here from the DOM.
<Link
id="reply"
to={''}
onClick={handleReplyTweet}
className="top-nav__link"
name={post._id}
>
<svg className="top-nav__icon">
<use xlinkHref="img/sprite.svg#icon-bubble2"></use>
</svg>
<span>{post.comments.length}</span>
</Link>
</li>
</section>
For getting the element in react (like input value etc) it is better to use useRef hook. But in your case you don't need to use it.
You can simply pass a function in onClick and call handleReplyTweet function from there with post id.
Hope below code sample I created for you helps:
https://playcode.io/926104/
I have been trying to get this solved for a while, but can't figure it out.
I received some products from an API and after deconstructing, I passed them into some HTML tags to perform DOM manipulation on them from JS like so...
displayProducts(products){
let result = '';
products.forEach(product => {
result += `
<article class="product">
<div class="img-container">
<img src=${product.image} alt="product" class="product-img">
<button class="bag-btn" data-id= ${product.id}>
<img src="./images/icons8_add_shopping_cart_100px.png"
width="16px" max-width= "18px" alt="add to cart"/>
Add to cart
</button>
</div>
<div class="goodsinfo">
<span class="description"> <img src="./images/icons8_eye_100px.png" class="view" data-class=${product.id}/>
</span>
<span class="titleprice">
<h3>${product.title}</h3>
<h4>#${product.price}</h4>
</span>
</div>
</article>
`
});
productsDOM.innerHTML = result;
}
Then I started manipulating, but I ran into a problem using the .find array method to match an id with the id from the array of objects even though they're exactly the same.
compileDescription(products){
console.log(products) // this works fine and displays 16 products which are 16 objects in arrays.
const eyeView = [...document.querySelectorAll('.view')];
eyeView.forEach(viewBtn=>{
viewBtn.addEventListener('click', (event)=>{
const btnId = event.target.dataset.class
console.log(btnId); //this works and shows the ID of the clicked button
const productFind = products.find(product=> product.id === btnId)
console.log(productFind); //returns undefined
})
})
}
What I want is that when a button with a particular id is clicked, the id is matched with an id of an object in the array of objects and the object is returned to me for manipulation.
Please help me guys. Thanks ahead.
I am trying to make a chat just like instagram, for the threads to appear I have a series of divs wrapped around anchor tags.
<ul id="Threadbox">
<li>
<a class="thread" href="javascript:get_messages(atag)">//"atag" refers to the current anchor tag element
<div>
dummy
<p>Doing great man</p>
</div>
</a>
</li>
<li>
<a class="thread" href="javascript:get_messages(atag)"> //"atag" refers to the current anchor tag element
<div>
sufyan
<p>Hello</p>
</div>
</a>
</li>
This is all generated with javascript, what I want to do is trigger a function whenever this main anchor tag is clicked which will retrieve all the children of the anchor tag but when I try to do it it only gives me the anchor tag, not its children nodes
Following is the used js script
var threads={{ Threads|safe }}
var messages={{ Messages|safe }}
const l=threads.length
const threadholder=document.querySelector("#Threadbox")
for(i=0;i<=l;i++){
var thread=document.createElement("li")
var main=document.createElement("a")
var div=document.createElement("div")
var a=document.createElement("a")
main.className="thread"
var data=JSON.parse(threads[i])
var Message=JSON.parse(messages[i])
var username =data.second.username
if (username=="{{ user.username }}"){
username=data.first.username
}
var p=document.createElement("p")
p.innerText=Message.message
a.href= `/inbox/${username}`
a.innerText=username
div.appendChild(a)
div.appendChild(p)
main.appendChild(div)
thread.appendChild(main)
threadholder.appendChild(thread)
main.href="javascript:"+"get_messages(main)"
};
function get_messages(a){
console.log(a.children)
}
It feels like I am doing things the wrong way, any help would be much appreciated.
Markdown-like functionality for tooltips
Problem:
Using Vanilla JavaScript I want to:
Change this:
<div>
<p>
Hello [world]{big round planet we live on}, how is it [going]{verb that means walking}?
</p>
<p>
It is [fine]{a word that expresses gratitude}.
</p>
</div>
To this:
<div>
<p>
Hello <mark data-toggle="tooltip" data-placement="top" title="big round planet we live on">world</mark>, how is it <mark data-toggle="tooltip" data-placement="top" title="verb means walking">world</mark>?
</p>
<p>
It is fine <mark data-toggle="tooltip" data-placement="top" title="a word that expresses gratitude">thanks</mark>.
</p>
</div>
so it looks visually like this:
is somehow similar to "markdown" edit functionalities.
Solution:
Mark the strings to replace in a different way:
<p>It is fine *[thanks]{a word that expresses gratitude}*!</p>
Initiate Bootstrap and tooltip functionality.
Grab all paragraphs
var p = document.getElementsByTagName('p')
Apply REGEX
tooltip = original.match(/(\{)(.*?)(\})/gi)[0].slice(1, -1);
hint = original.match(/(\[)(.*?)(\])/gi)[0].slice(1, -1);
Change their inside-text
replaced = original.replace(/(\*)(.*?)(\*)/gi,
`<mark data-toggle="tooltip" data-placement="top" title="${tooltip}">${hint}</mark>`);
elem.innerHTML = replaced;
Alltogether in one function:
[].forEach.call(p, elem => {
let original = elem.innerHTML;
let replaced, tooltip, hint
tooltip = original.match(/(\{)(.*?)(\})/gi)[0].slice(1, -1);
hint = original.match(/(\[)(.*?)(\])/gi)[0].slice(1, -1);
replaced = original.replace(/(\*)(.*?)(\*)/gi,
`<mark data-toggle="tooltip" data-placement="top" title="${tooltip}">${hint}</mark>`);
elem.innerHTML = replaced;
});
but I fail
Miserable when there is more paragraphs or when I just want to do it in an easy way with 2 pair of brackets instead of additional asterix. Fails also hen the innerTEXT has more phrases / words that should have the tooltip.
Any ideas?
Do you have any suggestions?
Existing ways of doing it?
Libraries?
Scripts?
One very easily can stumble at coming up with the right approach of how to replace a text node with other unknown HTML content.
A generic solution takes into account a more complex HTML content.
Thus, starting from a source-node, one stepwise needs to insert each of its child-nodes (either text- or element-nodes) before the targeted text-node. Once all nodes got inserted, one finally removes the targeted text-node.
Regarding the regex and the markup template, one can create the markup-string within a single replace call from a single regex and a single template string both making use of Capturing Groups.
// text node detection helper
function isNonEmptyTextNode(node) {
return (
(node.nodeType === 3)
&& (node.nodeValue.trim() !== '')
&& (node.parentNode.tagName.toLowerCase() !== 'script')
);
}
// text node reducer functionality
function collectNonEmptyTextNode(list, node) {
if (isNonEmptyTextNode(node)) {
list.push(node);
}
return list;
}
function collectTextNodeList(list, elmNode) {
return Array.from(
elmNode.childNodes
).reduce(
collectNonEmptyTextNode,
list
);
}
// final dom render function
function replaceTargetNodeWithSourceNodeContent(targetNode, sourceNode) {
const parentNode = targetNode.parentNode;
Array.from(sourceNode.childNodes).forEach(function (node) {
parentNode.insertBefore(node, targetNode);
});
parentNode.removeChild(targetNode);
}
// template and dom fragment render function
function findMarkdownCreateMarkupAndReplaceTextNode(node) {
const regX = (/\[([^\]]+)\]\{([^\}]+)\}/g);
const text = node.nodeValue;
if (regX.test(text)) {
const template = '<mark data-toggle="tooltip" data-placement="top" title="$2">$1</mark>'
const renderNode = document.createElement('div');
renderNode.innerHTML = text.replace(regX, template);
replaceTargetNodeWithSourceNodeContent(node, renderNode);
}
}
const elementNodeList = Array.from(document.body.getElementsByTagName('*'));
const textNodeList = elementNodeList.reduce(collectTextNodeList, []);
textNodeList.forEach(findMarkdownCreateMarkupAndReplaceTextNode);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<div>
<p>
<span>Hello [world]{big round planet we live on}, how is it [going]{verb that means walking}?</span>
<span>Hello [world]{big round planet we live on}, how is it [going]{verb that means walking}?</span>
</p>
<p>
<span>It is [fine]{a word that expresses gratitude}.</span>
It is [fine]{a word that expresses gratitude}.
<span>It is [fine]{a word that expresses gratitude}.</span>
</p>
</div>
<!--
// does get rerendered into:
<div>
<p>
<span>
Hello
<mark data-toggle="tooltip" data-placement="top" title="big round planet we live on">
world
</mark>
, how is it
<mark data-toggle="tooltip" data-placement="top" title="verb that means walking">
going
</mark>
?
</span>
<span>
Hello
<mark data-toggle="tooltip" data-placement="top" title="big round planet we live on">
world
</mark>
, how is it
<mark data-toggle="tooltip" data-placement="top" title="verb that means walking">
going
</mark>
?
</span>
</p>
<p>
<span>
It is
<mark data-toggle="tooltip" data-placement="top" title="a word that expresses gratitude">
fine
</mark>
.
</span>
It is
<mark data-toggle="tooltip" data-placement="top" title="a word that expresses gratitude">
fine
</mark>
.
<span>
It is
<mark data-toggle="tooltip" data-placement="top" title="a word that expresses gratitude">
fine
</mark>
.
</span>
</p>
</div>
//-->
Below is a 'View Cart' link on a shopping site where I don't control the HTML:
<div class="cart-link">
<a href="#">
View Cart <span class="total"></span>
</a>
</div>
When items are added to cart, currently the span tag is being updated to contain a space then a number in brackets, like so:
View Cart <span class="total"> (1)</span>
This is occurring instantly without a page reload (AJAX?).
Not sure if this is possible, but can I use JS/jQuery to 'keep a watch' on that span tag, and as soon as it contains a number, then perform two things: (a) remove the parentheses from around the number, and (2) add an extra class name to the span tag. So it would end up something like this:
View Cart <span class="total new-class"> 1</span>
Thanks
You're looking for MutationObserver. Observe the <span>, and when it changes, check its textContent for a number. If it does, perform your desired operations:
const total = document.querySelector('.total');
const observer = new MutationObserver(() => {
const numMatch = total.textContent.match(/\d+/);
if (numMatch === null) return;
changing = true;
total.textContent = numMatch[0];
total.classList.add('new-class');
});
observer.observe(total, { childList: true });
setTimeout(() => {
total.textContent = '(3)';
}, 2000);
.new-class {
background-color: yellow;
}
<div class="cart-link">
<a href="#">
View Cart <span class="total"></span>
</a>
</div>