I would like to tag a string of innerText/textContents for an element that corresponds to a specific regular expression (for example, /#\d+/
) and set up an event handler.For example, see <p>#123 for that matter.If you hover your mouse over
, the corresponding Issue details pop up.#123
in the HTML called </p>
One idea is to replace innerHTML.
target.innerHTML = target.innerHTML
.replace(/#(\d+)/g,(s,id)=>`<a href=".../${id}">${s}</a>`);
You can replace it with a simple link, but if you have more attributes and event handlers to configure, it's troublesome to tag and it's hard to read, but it's a string at this point and you can't access the Element object.
Due to the convenience of creating UserScript, I would like to avoid using class attributes if possible (because it may conflict with the parts used on the target page).
Range.prototype.surroundContents()
was available when I did the same thing for the selection, so it would be easy to get the match range in Range again...
The XPath expression descendant::text()[contains(., "#123")]
allows you to obtain a text node containing the string "#123"
.
However, since the escape sequence of "
is not available in XPath 1.0, you can use concat()
to help (see the link below for more information).
(Added March 3, 2017 08:00)
I'm sorry. I misunderstood the question.
If you want to match it with a regular expression, you can use XPath expression :matches()
or /#\d+/.test(textNode.data)
with a while
statement after browsing all text nodes
'use strict';
vartoXPathStringLiteral=(function(){
function replacefn(match,p1){
if(p1){
return',\u0027'+p1+'\u0027';
}
return', "'+match+'";
}
return function toXPathStringLiteral (string) {
string = String(string);
if(/^"+$/g.test(string)){
return '\u0027' + string + '\u0027';
}
switch(string.indexOf('"')}{
case-1:
return '' '' + string + '' ;
case0:
return'concat('+string.replace(/(+)|[^"]+/g, replacefn).slice(1)+')';
default:
return'concat('+string.replace(/(+)|[^"]+/g,replacefn)+')';
}
};
}());
function handleClick (event) {
console.log(event.target.textContent);
}
function markupHighlight(targetString, contextNode) {
var doc=contextNode.nodeType===contextNode?contextNode:contextNode.ownerDocument,
xpathResult=doc.evaluate('descendant::text()[contains(., '+toXPathStringLital(targetString)+')]', contextNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null),
df = doc.createDocumentFragment(),
mark = doc.createElement('mark');
mark.appendChild(doc.createTextNode(targetString)));
for (vari=0, xLen=xpathResult.snapshotLength, currentTextNode, stringList, textNode;i<xLen;++i){
currentTextNode=xpathResult.snapshotItem(i);
stringList=currentTextNode.data.split(targetString);
textNode = df.appendChild(doc.createTextNode(stringList[0])));
for (varj=1, stringLen=stringList.length, textNode;j<stringLen;++j){
mark = mark.cloneNode(true);
df.appendChild(mark);
mark.addEventListener('click',handleClick,false);
textNode = textNode.cloneNode(false);
textNode.data=stringList[j];
df.appendChild(textNode);
}
currentTextNode.parentNode.replaceChild(df, currentTextNode);
}
}
markupHighlight('foo', document.body);
mark{
color:black;
background-color:#ddf;
border —solid1px#55f;
}
<ulid="foo">
<li>foo1</li>
<li>foo2</li>
<li>foo3</li>
<li>
<ul>
<li>foo4-1</li>
<li>foo4-2foo4-2</li>
<li>
<ul>
<li>foo4-3-1</li>
<li>foo4-3-2foo4-3-2foo4-3-2</li>
</ul>
</li>
</ul>
</li>
</ul>
Dear Re:user204
DOM is very bad at manipulating things that are not represented by DOM objects.I mean, I can hardly do it.On the other hand, if you drop it into DOM, you can operate it.
A string in HTML is configured in DOM as a Node object of nodeType===TEXT_NODE
, so-called textNode, which can be divided into any length of partial string (no matter how many textNodes the child element has/the first textNode does not match the .textContent).In this approach, we first divide the target string into the textNode of the partial string.
However, textNode does not inherit the EventTarget (Node instance of EventTarget===false
), so you cannot register events unlike regular DOM elements (HTMLElement).In order to solve this problem, what we call "replace with HTML elements" is the solution.
Below, the sample code generates and replaces the partial string at the same time.Substring searches extract textNodes by enumerating them by NodeIterator and search, segment, and replace the contents of each textNode.The function __replaceTextNodes
returns a reference array to the replaced element, so subsequent applications can use it to register event handlers, etc.Overall, I felt like a polyfill in Range.prototype.surroundContents().
const__getTextNodesByContent=(target, pattern)=>{
// getiterator
consttextNodeIterator= document.createNodeIterator(
// search for
target,
// enumerate for
NodeFilter.SHOW_TEXT,
// filter
{
acceptNode: node=>pattern.test(node.textContent)? NodeFilter.FILTER_ACCEPT: NodeFilter.FILTER_REJECT,
}
);
// into array
const = [ ];
let current;
while(current=textNodeIterator.nextNode()){
ret.push (current);
}
return;
};
const__replaceTextNodes=(target, pattern, replace)=>{
// get the list of textNodes that contains/pattern/
const matchedTextNodes=__getTextNodesByContent(target, pattern);
// for each textnode
const = [ ];
for (const node of matchedTextNodes) {
// replace each part of textNode
let currentNode=node;
let matches;
while(pattern.lastIndex=0, matches=pattern.exec(currentNode.textContent)){
// separate [ currentNode | <before><match><after>]
// into [ currentNode | <before>] [ nextNode | <match><after>]
const nextNode=currentNode.splitText(matches.index);
// slice <match> part; [ currentNode | <before>] [ nextNode | <after>]
nextNode.textContent=nextNode.textContent.slice(matches[0].length);
// insert surround < match > part as a new sibling;
// [ currentNode | <before>] [ surroundNode | <match>] [ nextNode | <after>]
const surroundNode=replace(matches[0]);
nextNode.parentElement.insertBefore(surroundNode, nextNode);
// store reference
ret.push(surroundNode);
// next
currentNode = nextNode;
}
}
// return a list of surround elements
return;
};
//
// for example
//
const$target= document.body;
const targetPattern=/Lorem| massa|ridiculus|pellentesque/gi;
const surroundElements=__replaceTextNodes($target, targetPattern, text=>{
// surround with <span>, colored with red, add title attribute
constel= document.createElement('span');
el.textContent=text;
el.style.color='red';
el.title='surround with <span>, text content is "'+text+';";';
return;
});
// event handler filterd by surround elements
window.addEventListener('click', e=>{
if(surroundElements.includes(e.target)){
console.log('click', e.target);
}
});
// or for each surround elements
/*
for (constse of surroundElements) {
se.addEventListener(evt,fn);
}
*/
<h1>Lorem Lorem ipsum color site connector adipiscing
elit</h1>
<p>Lorem ipsum color sitamet, connectetuer adipiscing
elit.Aenean commodoligula eget color.Aenean massa
<strong>strong>/strong>.Cumsocis natoque penatibus
et magnis disparturious montes, nasceturridiculus
mus. Donecquam felis, ultricies nec, pellentesque
eu, pretium quis, sem. Nulla consquat massa quis
enim.Donecpede justo, fringillavel, aliquet nec,
volputate get, arcu.Inenim justo,rhoncusut,
immediatea, venenatis vitae, justo.</p>
<h2>Lorem ipsum color site connector adipiscing
elit</h2>
<h3>Aenean commodoligula eget color aenean massa</h3>
<p>Lorem ipsum color sitamet, connectetuer adipiscing
elit. Aenean commodoligula eget color. Aenean massa.
Cum sociis natoque penatibus et magnis dispartrent
montes, nasceturridiculus mus. Donecquam felis,
ultricies nec, pellentsqueueu, pretium quis, sem.</p>
<ul>
<li>Lorem ipsum color site connector.</li>
<li>Aenean commodoligula get dolor.</li>
<li>Aenean mass sociis natoque penatibus.</li>
</ul>
If you have any other questions, such as not the expected behavior, bugs, etc., please comment.
Note:
innerHTMLBe aware that DOM operations due to property changes are extremely costly and risky.My personal opinion is that it should be used only if it cannot be realized without using anything, and I would like to ban it for purposes other than reflection purposes.In the following example, no changes appear to have been made, and all elements below the body have been replaced.
const$target= document.querySelector('#target');
$target.addEventListener('click', e=>console.log(e));
// will be true
console.log($target===document.querySelector('#target'));
// reconstruct; SCRAP AND REBUILD ALL DOM ELEMENTS
document.body.innerHTML = document.body.innerHTML;
// will be false
console.log($target===document.querySelector('#target'));
// now no event listener attached to <div#target>
<divid="target">target element</div>
© 2024 OneMinuteCode. All rights reserved.