I was directed to the Linkify project on GitHub (https://github.com/cowboy/javascript-linkify) for finding and "linkifying" URLs and domains just floating in text.
It's awesome! It totally works on text!
However, I'm not quite sure how to make it work on a textNode which has the text I want to Linkify.
I understand the textNode only has textContent since.. it's all text. Since this Linkify function returns HTML as text, is there a way to take a textNode and "rewrite" the HTML within it with the Linkify output?
I've been playing with it on JSFiddle here: http://jsfiddle.net/AMhRK/9/
function repl(node) {
var nodes = node.childNodes;
for (var i = 0, m = nodes.length; i < m; i++) {
var n = nodes[i];
if (n.nodeType == n.TEXT_NODE) {
// do some swappy text to html here?
n.textContent = linkify(n.textContent);
} else {
repl(n);
}
}
}
You'll need to replace the textNode with an HTML element, like a span, and then set your linkified-text as that element's innerHTML.
var replacementNode = document.createElement('span');
replacementNode.innerHTML = linkify(n.textContent);
n.parentNode.insertBefore(replacementNode, n);
n.parentNode.removeChild(n);
Additionally to previous answer I propose more short way (based on jQuery):
$(n).replaceWith('Some text with <b>html</b> support');
where n - is textNode.
Or the native version
var txt = document.createElement("span");
txt.innerHTML = "Some text with <b>html</b> support";
node.replaceWith(txt);
where node is the textNode
Build on #AlexJeffcott's answer: Perf optimized version utilizing DocumentFragment instead of messing around with <span>, innerHTML and childNodesš
const enhanceNodes = (textNodes) => {
textNodes.forEach((node) => {
const oldText = node.textContent;
const newText = fancyTextTranformation(oldText);
const fragment = document.createRange().createContextualFragment(newText);
node.replaceWith(fragment);
})
}
Build on Will Scott's accepted answer, if you do not wish to have to wrap everything in a span, you could do the following:
const enhanceNodes = (textNodes) => {
const renderNode = document.createElement('span');
textNodes.forEach((node) => {
const oldText = node.textContent;
renderNode.innerHTML = fancyTextTranformation(oldText);
node.replaceWith(...renderNode.childNodes);
})
}
Related
I have multiple whole html code in variable cleanHTML and i need to strip specific tags from text.
let cleanHTML = document.documentElement.outerHTML
this:
<span class="remove-me">please</span>
<span class="remove-me">me too</span>
<span class="remove-me">and me</span>
to this:
please
me too
and me
IĀ“m trying to do it with:
var list = cleanHTML.getElementsByClassName("remove-me");
var i;
for (i = 0; i < list.length; i++) {
list[i] = list[i].innerHTML;
}
But iĀ“m getting error from React cleanHTML.getElementsByClassName is not a function
Any idea how to do it in a way React likes?
Your cleanHtml is a string and not a node. So getElementsByClassName does not apply as it is not a string function
You can use regular expressions to do this. Following code might help.
var stripedHtml = cleanHtml.replace(/<[^>]+>/g, '');
And stripedHtml will have your html content without tags.
I am guessing from your specification that your cleanHTML is a string, so you would need to convert the string to a node (eg: by creating a div from it), and then parsing the nodes appropriately.
Please note that you really need to request the textContent and not the innerHTML, as you do not want to have any html in your react output
const htmlContent = `<span class="remove-me">please</span>
<span class="remove-me">me too</span>
<span class="remove-me">and me</span>`;
const getNodesToRemoveFromElement = (stringContent) => {
const el = document.createElement('div');
el.innerHTML = stringContent;
return el.getElementsByClassName('remove-me');
};
for (let node of getNodesToRemoveFromElement( htmlContent ) ) {
console.log( node.textContent );
}
I'm working with an API that returns strings with inline links like so:
This is a question I'm asking on <my_link type="externalLink" data="https://stackoverflow.com/">StackOverflow</my_link> about splitting a string and reconstructing as a HTML link.
The reason for this is apparently so the API can be used by both web and native platforms and that HTML is kept away from the data. There are also internalLink types which will allow app developers to link to content within an app rather than opening a web browser.
I need to be able to pass this string into a function and return the full string with an tag like so:
This is a question I'm asking on StackOverflow about splitting a string and reconstructing as a HTML link.
Another thing to consider is that the string could have multiple links in it.
My initial attempt is basic and does get externalLink from the first link but I'm unsure of how to get the value of the data attribute and then re-run for any other links.
export default function convertLink(string) {
let stringWithLinks = string;
if (string.includes('<my_link')) {
const typeStart = string.indexOf('"') + 1;
const typeEnd = string.indexOf('"', typeStart);
const typeText = string.substring(typeStart, typeEnd); // externalLink
}
return stringWithLinks;
}
You can set the string as .innerHTML of a dynamically created element and use .getAttribute() to get the data attribute of <my_link> element, set .innerHTML of dynamically created <a> element and use .replaceChild() to replace <my_link> with <a> element
let str = `This is a question I'm asking on <my_link type="externalLink" data="https://stackoverflow.com/">StackOverflow</my_link> about splitting a string and reconstructing as a HTML link.`;
let div = document.createElement("div");
div.innerHTML = str;
let my_links = Array.prototype.map.call(div.querySelectorAll("my_link"), link =>
link.getAttribute("data"));
console.log(my_links);
for (let link of my_links) {
let a = document.createElement("a");
a.href = link;
a.target = "_blank";
a.innerHTML = div.querySelector("my_link").innerHTML;
div.replaceChild(a, div.querySelector("my_link"))
}
console.log(div.innerHTML);
Add the string as HTML of a new element. Loop over all the my_link elements extracting the relevant data, then build a new anchor that can then replace the my_link on each iteration.
function convertAllLinks(str) {
let el = document.createElement('div');
el.innerHTML = str;
el.querySelectorAll('my_link').forEach(link => {
let anchor = document.createElement('a');
anchor.href = link.getAttribute('data');
anchor.setAttribute('target', '_blank');
anchor.textContent = link.textContent;
link.parentNode.replaceChild(anchor, link);
});
return el.innerHTML;
}
convertAllLinks(str);
DEMO
Here's another solution using DOMParser(), in case you might need to do any more DOM modifications later on.
let stringWithLinks = `This is a question I'm asking on <my_link type="externalLink" data="https://stackoverflow.com/">StackOverflow</my_link> about splitting a string and reconstructing as a HTML link.`,
tempDOM = new DOMParser().parseFromString('<doc>' + stringWithLinks + '</doc>', "text/xml"),
linkElements = tempDOM.getElementsByTagName('my_link');
for (let i=0; i<linkElements.length; i++) {
let newA = document.createElement('a');
newA.setAttribute('src', linkElements[i].getAttribute('data'));
let linkType = linkElements[i].getAttribute('type');
if (linkType == 'externalLink') {
newA.setAttribute('target', '_blank');
}
newA.innerHTML = linkElements[i].innerHTML;
tempDOM.documentElement.replaceChild(newA, linkElements[i]);
}
console.log(tempDOM.documentElement.innerHTML);
I am trying to write a test to work out whether the text rendered inside an <input> has the same baseline as a label:
In order to do this, I would like to compute the baseline of the text that has been rendered in each element and compare the two values. Is this possible and if so, how is it done? If not, is there a better way to establish that the baseline of the two elements is the same?
I have found a way to determine the baseline of the label which seems to work reliably:
function getBaseline(element) {
var span = document.createElement("span");
span.setAttribute("style", "font-size:0");
span.innerText = "A";
jQuery(element).prepend(span);
return span.getBoundingClientRect().bottom;
}
However, this method doesn't work for the <input>, as I cannot insert the span in that case.
I found a way to get the computed baseline based on your function. The trick is to create a wrapper and apply all styles from the element to it. I can confirm that it works in Firefox (v38) and Chrome (v44) on OSX. Unfortunately it doesn't work proper in Safari.
DEMO
function getBaseline(element) {
element = jQuery(element);
var span = document.createElement("span");
span.setAttribute("style", "font-size:0");
span.innerText = "A";
var wrapper = document.createElement("span");
applyCSSProperties.call(wrapper, element);
element.wrap(wrapper);
element.before(span);
var computed = span.getBoundingClientRect().bottom;
span.remove();
element.unwrap(wrapper);
return computed;
}
function applyCSSProperties(element) {
var styles = {};
var comp = getComputedStyle(element[0]);
for(var i = 0; i < comp.length; i++){
styles[comp[i]] = comp.getPropertyValue(comp[i]);
}
jQuery(this).css(styles);
}
If I had something like the following:
$400/week
$800/week
With jQuery being available, what would be an ideal method of wrapping the "/week" part in an html element like <span class='small'>/week</span>?
You could do something like this:
var data = $("#text").html();
$("#text").html(data.replace(/\/\s*week/g, '<span class="small">/week</span>'));
Working example: http://jsfiddle.net/jfriend00/TKa8D/
Note: Replacing HTML will undo any event handlers assigned within that HTML so you should generally replace the HTML at the lowest level possible so you aren't replacing elements that already have event handlers on them. If you share your specific HTML, we could make more concrete recommendations for the easiest way.
It's not pretty, but the only safe way to do it (preserving event handlers and such) is to walk the tree, splitting text nodes where necessary. Unfortunately, jQuery does not like working with text nodes; it is very element-centric.
You can start by defining a helper function to do the recursion:
function recurse(node, fn) {
if(node.nodeType === Node.TEXT_NODE) {
fn(node);
}else if(node.nodeType === Node.ELEMENT_NODE) {
var children = Array.prototype.slice.call(node.childNodes);
for(var i = 0, l = children.length; i < l; i++) {
recurse(children[i], fn);
}
}
}
It walks every node in the tree, calling a given function for every text node. Then we can use that function to split the text nodes and insert a span in the middle:
recurse(document.documentElement, function(node) {
var re = /(\$\d+)(\/week)/;
var match;
while(match = re.exec(node.nodeValue)) {
var before = document.createTextNode(node.nodeValue.substring(0, match.index + match[1].length));
node.parentNode.insertBefore(before, node);
var span = document.createElement('span');
span.className = 'week';
span.textContent = node.nodeValue.substring(match.index + match[1].length, match.index + match[0].length);
node.parentNode.insertBefore(span, node);
node.nodeValue = node.nodeValue.substring(match.index + match[0].length);
}
});
Try it.
Hey i'm loading an html page using ajax into a string, now i want to find the title of the page and use it.
Now i did manage to get the <title> using regex but that returns the tag along with the title itself and i wish to extract that from the string or could there be a way to do that in the regex?
This is my code :
var title = result.match(/<title[^>]*>([^<]+)<\/title>/);
Now how do i get the actuall title after this/ instead of this?
.match() returns array of matches, use
var title = result.match(/<title[^>]*>([^<]+)<\/title>/)[1];
to get value in parentheses
load your response html string into a jQuery object like so and retrieve the text
$(response).find("title").text();
A relatively simple plain-JavaScript, and non-regex, approach:
var htmlString = '<head><title>Some title</title></head><body><p>Some text, in a paragraph!</p></body>',
html = document.createElement('html'),
frag = document.createDocumentFragment();
html.innerHTML = htmlString;
frag.appendChild(html);
var titleText = frag.firstChild.getElementsByTagName('title')[0].textContent || frag.firstChild.getElementsByTagName('title')[0].innerText;
console.log(titleText);ā
JS Fiddle demo.
I've, obviously, had to guess at your HTML string and removed the (presumed-present) enclosing <html>/</html> tags from around the content. However, even if those tags are in the string it still works: JS Fiddle demo.
And a slightly more functional approach:
function textFromHTMLString(html, target) {
if (!html || !target) {
return false;
}
else {
var fragment = document.createDocumentFragment(),
container = document.createElement('div');
container.innerHTML = html;
fragment.appendChild(container);
var targets = fragment.firstChild.getElementsByTagName(target),
result = [];
for (var i = 0, len = targets.length; i<len; i++) {
result.push(targets[i].textContent || targets[i].innerText);
}
return result;
}
}
var htmlString = '<html><head><title>Some title</title></head><body><p>Some text, in a paragraph!</p></body></html>';
var titleText = textFromHTMLString(htmlString, 'title');
console.log(titleText);ā
JS Fiddle demo.
CODE:
var title = result.match("<title>(.*?)</title>")[1];
Make the reg exp to case insensitive.
Here is the complete code:
var regex = /<title>(.*?)<\/title>/gi;
var input = "<html><head><title>Hello World</title></head>...</html>";
if(regex.test(input)) {
var matches = input.match(regex);
for(var match in matches) {
alert(matches[match]);
}
} else {
alert("No matches found!");
}
try this I think this will help. It perfectly works in my case. :)
var FindTag=(data='',tag='')=>{
var div=document.createElement('div');
div.innerHTML=data;
data=$(div).find(tag)[0].outerHTML;
return data;
}
var data=FindTag(data,"title");
Regular expressions aren't a good way to look for things in HTML, which is too complex for a simple one-off regex. (See the famous post on this topic.) Instead, use DOMParser's parseFromString and then look in the resulting document:
const html = "<!doctype html><head><title>example</title>";
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
const title = doc.querySelector("title");
console.log(title.textContent);