Add target="_blank" to link with JavaScript - javascript

I need to write a method that takes a String and parses it for links (a href). If it finds a link it should add target="_blank" to the link, if it is not already there.
Example:
The Inputstring "
Google and target="_blank">Yahoo are search engines
... should result in the output String
Google and Yahoo are search engines
Any idea how to realize this?

Not very difficult with plain js.
var links = document.getElementsByTagName('a');
var len = links.length;
for(var i=0; i<len; i++)
{
links[i].target = "_blank";
}

Fraught with problems but usable with plain JavaScript:
function addBlankTargets(s) {
return (""+s).replace(/<a\s+href=/gi, '<a target="_blank" href=');
}
Or with jQuery:
function addBlankTargets(s) {
var p = $('<p>' + s + '</p>');
p.find('a').attr('target', '_blank');
return p.html();
}
var s = 'Google and '
+ 'Yahoo '
+ 'are search engines.';
var x = addBlankTargets(s);
x; // => 'Google and
// Yahoo
// are search engines.'

If you are targeting at modern browsers, instead of manipulating a string containing HTML and having to handle all the many special cases, you can transform the string into DOM.
At this point manipulating the DOM is trivial and you can then convert it back to a serialised string.
function decorateRichText(html) {
const domParser = new DOMParser()
const document = domParser.parseFromString(html, `text/html`)
const serializer = new XMLSerializer()
// Handles external links
const links = document.querySelectorAll(`a`)
links.forEach((link) => {
if (link.href) {
if (isExternalUrl(link.href)) {
link.target = `_blank`
link.rel = `noopener noreferrer`
}
}
})
return serializer.serializeToString(document)
}
Leave the browser JS engine do the heavy stuff and remember: code that you don't write is code you have not to debug :-)

You can use jQuery to parse the element, add the attribute, and then read out the HTML, like so:
var addTarget = function(input) {
return $('<span>' + input + '</span>').find('a').attr('target', '_blank').html();
};
console.log(addTarget('Google'));

in two lines
var links = document.getElementsByTagName('a');
for(i in links)
links[i].target=="_blank"?links[i].style.color="#f0f" : links[i].style.color ='#0f0'
jsfiddle

Related

Splitting a string to create a HTML link

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);

How can I assign a prefix to an XML node in default namespace in javascript?

I have an XML fragment that I parse using jQuery parseXML. Most nodes don't have prefixes, they are in the default namespace and some have a prefixes.
I need all the nodes that are in the default namespaces to be associated with a prefix instead. I've made sure that this prefix is already declared in the string version of the XML with a magical string replace (i.e. xmlns:my="http://mydefaulns.com" is declared at the root level when I load the XML.)
I tried the following:
var defaultNs="http://mydefaulns.com";
var xmlDoc = $.parseXML(stringXML);
$(xmlDoc).find("*").each(function() {
if (this.namespaceURI=== defaultNs) {
this.prefix = "my";
}
}
But it has no impact, when I write the XML back there's still no prefix.
I've also tried to just load the XML and call:
xmlDoc.firstChild.removeAttribute("xmlns")
but the attribute wasn't removed so the prefixes were not magically updated.
At that point, I think the only way to get the result that I want would be to recreate all the nodes with the new prefixed name, copying all the attributes.
This seem really extreme, is there another way?
Input (string):
<abc xmlns="http://mydefaulns.com" xmlns:my="http://mydefaulns.com"xmlns:other="http://other.com">
<node1>Value</node1>
<other:node2>Value2</other:node2>
</abc>
Desired output:
<my:abc xmlns:my="http://mydefaulns.com"xmlns:other="http://other.com">
<my:node1>Value</my:node1>
<other:node2>Value2</other:node2>
</my:abc>
The actual XML is more complex, but this gives you an idea.
I parse the XML with jQuery.parse and and get back the string version by using
function XMLDocumentToString(oXML) {
if (typeof oXML.xml != "undefined") {
return oXML.xml;
} else if (XMLSerializer) {
return (new XMLSerializer().serializeToString(oXML));
} else {
throw "Unable to serialize the XML";
}
}
No need to parse the xml string just use replace with regular expressions like:
var prefix = "my:";
stringXML = stringXML.replace(/(<\/?)(\w+)(?!:)(\b)/g, "$1" + prefix + "$2$3");
this will match any sequence of letters (valid tag name) just after a < or </ (< is necessary, / is optional) and not followed by a : but followed by \b.
var stringXML = '<abc xmlns="http://mydefaulns.com" xmlns:my="http://mydefaulns.com" xmlns:other="http://other.com"><node1>Value</node1><other:node2>Value2</other:node2></abc>';
console.log("BEFORE: ", stringXML);
var prefix = "my:";
stringXML = stringXML.replace(/(<\/?)(\w+)(?!:)(\b)/g, "$1" + prefix + "$2$3");
console.log("AFTER: ", stringXML);
At that point, I think the only way to get the result that I want would be to recreate all the nodes with the new prefixed name, copying all the attributes.
Yes, that's exactly what you have to do if you want do do it cleanly.
The solutions using regular expressions are brittle. This is the example you gave:
<abc xmlns="http://mydefaulns.com" xmlns:my="http://mydefaulns.com" xmlns:other="http://other.com">
<node1>Value</node1>
<other:node2>Value2</other:node2>
</abc>
Now consider the following document, which equivalent to your original one:
<abc xmlns="http://mydefaulns.com" xmlns:my="http://mydefaulns.com">
<node1>Value</node1>
<node2 xmlns="http://other.com">Value2</node2>
</abc>
The only thing that changed is how the element node2 which is in namespace http://other.com was assigned a namespace. In your original document it was through the other prefix, which was defined on the root node. Here it is by redefining the default namespace on node2. From the standpoint of XML, the two documents are the same. It does not matter how node2's namespace is defined. The problem though is that neither of the two regexp-based answers you got will convert this document properly.
Here is an implementation that manipulates the DOM tree to produce the final result:
var stringXML = '<abc xmlns="http://mydefaulns.com" xmlns:my="http://mydefaulns.com" xmlns:other="http://other.com">\n\
<node1>Value</node1>\n\
<other:node2>Value2</other:node2>\n\
</abc>';
var defaultNS = "http://mydefaulns.com";
var xmlDoc = $.parseXML(stringXML);
xmlDoc.firstChild.removeAttribute("xmlns");
// Make sure we do have xmlns:my defined.
xmlDoc.firstChild.setAttribute("xmlns:my",defaultNS);
function transformChildren(parent) {
// We take a copy of childNodes before clearing it.
var childNodes = Array.prototype.slice.call(parent.childNodes);
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
var newChild;
var limit = childNodes.length;
for (var childIx = 0; childIx < limit; ++childIx) {
newChild = undefined;
var node = childNodes[childIx];
if (node.nodeType === Node.ELEMENT_NODE && node.namespaceURI === defaultNS) {
newChild = xmlDoc.createElementNS(defaultNS, "my:" + node.tagName);
// Copy the attributes.
var attributes = node.attributes;
for (var attrIx = 0; attrIx < attributes.length; ++attrIx) {
var attr = attributes[attrIx];
newChild.setAttributeNS(attr.namespaceURI, attr.name, attr.value)
}
// Move the children.
while (node.firstChild) {
newChild.appendChild(node.firstChild);
}
transformChildren(newChild);
}
parent.appendChild(newChild || node);
}
}
transformChildren(xmlDoc);
// This is just reused from the question.
function XMLDocumentToString(oXML) {
if (typeof oXML.xml != "undefined") {
return oXML.xml;
} else if (XMLSerializer) {
return (new XMLSerializer().serializeToString(oXML));
} else {
throw "Unable to serialize the XML";
}
}
console.log(XMLDocumentToString(xmlDoc));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
this may help you
var inputXmlText='<abc xmlns="http://mydefaulns.com" xmlns:my="http://mydefaulns.com" xmlns:other="http://other.com"><node1>Value</node1><other:node2>Value2</other:node2></abc>'
var inputXml=jQuery.parseXML(inputXmlText);
var list=[];
var prefix="my";
$(inputXml).find("*").each(function(){
if(this.tagName.indexOf(":")<0){
inputXmlText=inputXmlText.replace(new RegExp("<" + this.tagName,'g') ,"<"+prefix+":" + this.tagName);
inputXmlText=inputXmlText.replace(new RegExp("</" + this.tagName,'g') ,"</"+prefix+":" + this.tagName);
}
});
console.log(inputXmlText);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Show search result URL as breadcrumb

I need to display the URLs in a list of search results in a breadcrumb style (i.e. such as Google does - Google Breadcrumbs), and have limited knowledge of JavaScript (and haven't touched it in nearly two years). Can you help me please? I can apply the code if it's provided with clear instruction, and am very comfortable with HTML and CSS, but have not attempted to create breadcrumbs without lists before.
Where do I start?
Input would be page's URL (class is .slimBreadcrumbLink) - e.g. https://www.example.com/level1/level2/level3/level4 - and output would be as below:
Level 2 > Level 3 > Level 4
I haven't tried anything of significance yet, I'm starting here. I've read through the other breadcrumb questions posed, but it's not helped so far. I found the below but don't know how to implement it.
var path = location.pathname;
var here = location.href.split('/').slice(3);
var parts = [{
"text": 'Home',
"link": '/'
}];
for (var i = 0; i < here.length; i++) {
var part = here[i];
var text = part.toUpperCase();
var link = '/' + here.slice(0, i + 1).join('/');
parts.push({
"text": text,
"link": link
});
}
Thank you.
I rolled my own demo snippet. The code is quite a bit to explain, but if anything doesn't appear self-explanatory feel free to let me know and I'll elaborate more here.
// Utility functions
function createBreadcrumbs (origin, path) {
var levels = path.slice(1).split('/')
return levels.map(function (e, i, levels) {
var text = e.replace(/-+/g, ' ').replace(/\s*\b\S/g, function (e) {
return e.toUpperCase()
})
return anchor(origin + '/' + levels.slice(0, i+1).join('/'), text)
})
}
function anchor (href, text) {
var a = document.createElement('a')
a.href = href
a.textContent = text
return a
}
function render (breadcrumbs) {
var ol = document.createElement('ol')
ol.className = 'breadcrumbs'
breadcrumbs.forEach(function (anchor) {
var li = document.createElement('li')
anchor.className = 'breadcrumbs__link'
li.className = 'breadcrumbs__crumb'
li.appendChild(anchor)
ol.appendChild(li)
})
return ol
}
// How to use
var breadcrumbs = createBreadcrumbs('//example.com', '/example-thing/location-stuff/level-2'),
list = render(breadcrumbs)
console.log(breadcrumbs)
document.body.appendChild(list)
<p>Breadcrumbs for //example.com/example-thing/location-stuff/level-2</p>
Here is a function that will create the HTML structure for the breadcrumbs:
const getLevels = url => url.replace(/http(s.):\/\//, "").split("/");
const createBreadcrumb = function(url, elem) {
const ol = document.createElement("ol");
getLevels(url).forEach((e, i) => {
if(i > 2) {
const li = document.createElement("li");
const a = document.createElement("a");
a.href = url.substring(0, url.indexOf(e) + e.length);
a.innerText = e;
li.appendChild(a)
ol.appendChild(li);
}
}
});
elem.appendChild(ol);
};
Because it is ES6, you will have to use a transpiler like babel to make it compatible with older browsers. Also, because you are parsing the URL, you cannot customize the title of the links.
You can then use the function like this, to parse the url and create the ol list in the element with the id.
createBreadcrumb(url, document.getElementById("id"));

jQuery remove string from string doesn't work

Everytime an certain image is clicked I fetch some information, to add that information to a string and after that replace the current url with the new one. This is my code so far:
jQuery(document).ready(function(){
var kleuren = [];
jQuery('.swatch-category-container img').click(function(){
var kleur = jQuery(this).attr('title');
console.log("Selected kleur: " + kleur);
var link = jQuery(this).closest('.popup').find('.photo a').prop('href');
kleuren.push(kleur);
console.log(kleuren);
console.log(kleuren.length);
console.log("Fetched link: " + link);
var length = kleuren.length -1;
var avoid ="?kleur="+kleuren[length];
console.log("Remove string: " + avoid);
var news_link = link.replace(avoid,'');
var new_link = news_link + "?kleur="+kleur;
console.log("Cut string: " + news_link);
jQuery('.photo').find('.sendkleur').attr("href", new_link);
});
});
This works fine, but the previous data doesn't get removed.
For example
When the first image is clicked the kleur = zwart that info is put in the url.
But when the user clicks another image after that the url will look like ?kleur=zwart?kleur=beige instead just of ?kleur=beige
How can I remove the first part?
Although we could debug the replacement logic, I think I'd come at this a totally different way: Have a data-* attribute on the link that gives the raw version without kleur, then just reuse that; you can even initialize it on the first pass so you don't have to update your markup:
var linkElement = jQuery(this).closest('.popup').find('.photo a');
var link = linkElement.attr("data-rawhref");
if (!link) {
link = linkElement.attr("href");
linkElement.attr("data-rawhref", link);
}
// ...add kleur to `link`
If you don't need it to actually be an attribute, you can use data instead, but there's probably no real advantage over the above as the link is just a string.
var linkElement = jQuery(this).closest('.popup').find('.photo a');
var link = linkElement.data("rawhref");
if (!link) {
link = linkElement.attr("href");
linkElement.data("rawhref", link);
}
// ...add kleur to `link`
But the problem with your code is that you're using the current kleur, rather than the old one, in avoid:
kleuren.push(kleur); // Pushes the current kleur at the end
var length = kleuren.length -1; // Will be the index of the *current* kleur
var avoid ="?kleur="+kleuren[length];// ...and so this is looking for the wrong one
instead:
var avoid, new_link;
if (kleuren.length) {
avoid = "?kleur="+kleuren[kleuren.length - 1];
new_link = link.replace(avoid,'') + "?kleur="+kleur;
} else {
new_link = link + "?kleur="+kleur;
}
kleuren.push(kleur);
That's assuming you really need an array of previously-selected colors. If you don't, just use a variable:
var avoid, new_link;
if (letzteKleur) { // Meant to be "last color", my German is non-existant
avoid = "?kleur="+letzteKleur;
new_link = link.replace(avoid,'') + "?kleur="+kleur;
} else {
new_link = link + "?kleur="+kleur;
}
letzteKleur = kleur;
Maybe because you never do a pop on the array?
How does your log look like?
It seems that you try to replace the current value not the previous.
kleuren.push(kleur);
console.log(kleuren);
console.log(kleuren.length);
console.log("Fetched link: " + link);
var length = kleuren.length -1;
var avoid ="?kleur="+kleuren[length];
maybe that will do (of course only if there is at least 1 item in the array)
console.log(kleuren);
console.log(kleuren.length);
console.log("Fetched link: " + link);
var length = kleuren.length -1;
var avoid ="?kleur="+kleuren[length];
kleuren.push(kleur);

How to get title tag in a string of html?

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);

Categories