Adding link to an element in Javascript? (Current method risks "bad javascript?") - javascript

Right now I have some element
<span class="CLASSNAME" id="FOO">Barack Obama</span>
Referenced by
document.getElementById('FOO')
And I want to make it so the text "Barack Obama" is turned into a blue link where the text is the same, but it links to (for example) www.google.com
I've seen this method, but innerHTML is apparently BAD especially since the link I'll be using is a value returned from an ajax call ("potential for bad js"?).
document.getElementById('FOO').innerHTML = desiredText.link(desiredLink);
What is the best way to go about this without a huge perfomance hit or potentially "bad js"? Will also be adding mouseover features to said element later on, so if this is worth consideration I figured I'd mention it. No jQuery.

var el=document.getElementById('FOO');
el.innerHTML="<a href='whitehouse.gov'>"+el.textContent+"</a>";
Simply wrap it into a link. Note that html injection is possible. And do not care about performance, were talking about milliseconds...
If you want to prevent html injectin, you may build it up manually:
var el=document.getElementById('FOO');
var a=document.createElement("a");
a.href="whitehouse.hov";
a.textContent=el.textContent;
el.innerHTML="";
el.appendChild(a);

You have two possibilities:
Add an <a> element as a child of the <span> element
Replace the text node ("Barack Obama") with an <a> element:
function addAnchor (wrapper, target) {
var a = document.createElement('a');
a.href = target;
a.textContent = wrapper.textContent;
wrapper.replaceChild(a, wrapper.firstChild);
}
addAnchor(document.getElementById('foo'), 'http://www.google.com');
<span id="foo">Barack Obama</span>
This will result in the following DOM structure:
<span id="foo">
Barack Obama
</span>
Replace the <span> element with an <a> element
Replace the entire <span> element with an <a> element:
function addAnchor (wrapper, target) {
var a = document.createElement('a');
a.href = target;
a.textContent = wrapper.textContent;
wrapper.parentNode.replaceChild(a, wrapper);
}
addAnchor(document.getElementById('foo'), 'http://www.google.com');
<span id="foo">Barack Obama</span>
This will result in the following DOM structure:
Barack Obama
Use methods like createElement, appendChild and replaceChild to add element instead of innerHTML.

var changeIntoLink = function(element, href) {
// Create a link wrapper
var link = document.createElement('a');
link.href = href;
// Move all nodes from original element to a link
while (element.childNodes.length) {
var node = element.childNodes[0];
link.appendChild(node);
}
// Insert link into element
element.appendChild(link);
};
var potus = document.getElementById('potus');
changeIntoLink(potus, 'http://google.com/');
<span id="potus">Barack Obama</span>

Related

Individually change element inside of a class through their ID [duplicate]

I am wanting something similar to this person, except the element I want to match might not be a direct sibling.
If I had this HTML, for example,
<h3>
<span>
<b>Whaddup?</b>
</span>
</h3>
<h3>
<span>
<b>Hello</b>
</span>
</h3>
<div>
<div>
<img />
</div>
<span id="me"></span>
</div>
<h3>
<span>
<b>Goodbye</b>
</span>
</h3>
I would want to be able to do something like this:
var link = $("#me").closestPreviousElement("h3 span b");
console.log(link.text()); //"Hello"
Is there an easy way to do this in jQuery?
EDIT: I should have made my specification a little bit clearer. $("#me") may or may not have a parent div. The code should not assume that it does. I don't necessarily know anything about the surrounding elements.
var link = $("#me").closest(":has(h3 span b)").find('h3 span b');
Example: http://jsfiddle.net/e27r8/
This uses the closest()[docs] method to get the first ancestor that has a nested h3 span b, then does a .find().
Of course you could have multiple matches.
Otherwise, you're looking at doing a more direct traversal.
var link = $("#me").closest("h3 + div").prev().find('span b');
edit: This one works with your updated HTML.
Example: http://jsfiddle.net/e27r8/2/
EDIT: Updated to deal with updated question.
var link = $("#me").closest("h3 + *").prev().find('span b');
This makes the targeted element for .closest() generic, so that even if there is no parent, it will still work.
Example: http://jsfiddle.net/e27r8/4/
see http://api.jquery.com/prev/
var link = $("#me").parent("div").prev("h3").find("b");
alert(link.text());
see http://jsfiddle.net/gBwLq/
I know this is old, but was hunting for the same thing and ended up coming up with another solution which is fairly concise andsimple. Here's my way of finding the next or previous element, taking into account traversal over elements that aren't of the type we're looking for:
var ClosestPrev = $( StartObject ).prevAll( '.selectorClass' ).first();
var ClosestNext = $( StartObject ).nextAll( '.selectorClass' ).first();
I'm not 100% sure of the order that the collection from the nextAll/prevAll functions return, but in my test case, it appears that the array is in the direction expected. Might be helpful if someone could clarify the internals of jquery for that for a strong guarantee of reliability.
No, there is no "easy" way. Your best bet would be to do a loop where you first check each previous sibling, then move to the parent node and all of its previous siblings.
You'll need to break the selector into two, 1 to check if the current node could be the top level node in your selector, and 1 to check if it's descendants match.
Edit: This might as well be a plugin. You can use this with any selector in any HTML:
(function($) {
$.fn.closestPrior = function(selector) {
selector = selector.replace(/^\s+|\s+$/g, "");
var combinator = selector.search(/[ +~>]|$/);
var parent = selector.substr(0, combinator);
var children = selector.substr(combinator);
var el = this;
var match = $();
while (el.length && !match.length) {
el = el.prev();
if (!el.length) {
var par = el.parent();
// Don't use the parent - you've already checked all of the previous
// elements in this parent, move to its previous sibling, if any.
while (par.length && !par.prev().length) {
par = par.parent();
}
el = par.prev();
if (!el.length) {
break;
}
}
if (el.is(parent) && el.find(children).length) {
match = el.find(children).last();
}
else if (el.find(selector).length) {
match = el.find(selector).last();
}
}
return match;
}
})(jQuery);
var link = $("#me").closest(":has(h3 span b)").find('span b').text();

JavaScript - Get size from class name and open new window

For a long time, I have been using a simple JavaScript file along with in-line, onclick events in 'a' tags to open a new window when the link is clicked. As you can see from the example below, I add the type and size of the window in the HTML. In the example below, the window cannot be resized by the user, is centered and is 1010 wide by 730 high.
Example HTML:
<a href="https://example.com" target='_blank' onclick="popUp(this.href,'elasticNoC',1010,730);return false;">
JavaScript file:
var newWin = null;
function popUp(strURL, strType, strWidth, strHeight) {
LeftPosition = (screen.width) ? (screen.width-strWidth)/2 : 0;
TopPosition = (screen.height) ? (screen.height-strHeight)/2 : 0;
if (newWin !== null && !newWin.closed)
newWin.close();
var strOptions="";
if (strType=="consoleC")
strOptions="resizable,top="+TopPosition+',left='+LeftPosition+",height="+
strHeight+",width="+strWidth;
if (strType=="fixedC")
strOptions="status,top="+TopPosition+',left='+LeftPosition+",height="+
strHeight+",width="+strWidth;
if (strType=="elasticC")
strOptions="toolbar,menubar,scrollbars,"+
"resizable,location,top="+TopPosition+',left='+LeftPosition+",height="+
strHeight+",width="+strWidth;
if (strType=="elasticNoC")
strOptions="scrollbars,"+
"resizable,top="+TopPosition+',left='+LeftPosition+",height="+
strHeight+",width="+strWidth;
if (strType=="console")
strOptions="resizable,height="+
strHeight+",width="+strWidth;
if (strType=="fixed")
strOptions="status,height="+
strHeight+",width="+strWidth;
if (strType=="elastic")
strOptions="toolbar,menubar,scrollbars,"+
"resizable,location,height="+
strHeight+",width="+strWidth;
if (strType=="elasticNo")
strOptions="scrollbars,"+
"resizable,height="+
strHeight+",width="+strWidth;
newWin = window.open(strURL, 'newWin', strOptions);
newWin.focus();
}
A recent update of a web application is now stripping in-line JavaScript so my old way of doing things no longer works.
I can still include separate JavaScript files but no in-line JavaScript.
I am thinking that the best option is to replace the in-line, onclick events with specific class names and use JavaScript to get the window type and size from the class name.
Example new HTML:
<a class="red elasticNoC-1010-730" href="https://example.com" target="_blank">
I can't figure out the correct JavaScript to use. Can someone please provide JavaScript code that can be used as a replacement? As you can see in the new HTML example, some of my links may contain more than one class name. In this example, the class name "red" would be ignored because it does not match any of the 'strType' in the JavaScript file.
Don't use classes, use data-* attribute:
<a class="red" data-popup="elasticNoC-1010-730" href="https://example.com" target="_blank">TEST CLICK</a>
and than the JS would be like:
// var newWin = null; function popUp( ............etc
const handlePopup = (ev) => {
ev.preventDefault(); // Prevent browser default action
const EL = ev.currentTarget; // Get the element
const args = EL.dataset.popup.split("-"); // Get the data-* parts
args.unshift(EL.getAttribute("href")); // Prepend HREF to parts
return popUp(...args); // Call popUp with arguments
};
const EL_popup = document.querySelectorAll('[data-popup]');
EL_popup.forEach(el => el.addEventListener('click', handlePopup));
Handling classes
Handling classes for custom attributes values is never a great idea, since it mostly becomes a parsing-things problem, because HTML attribute class can contain a multitude of classes in any order and number.
But luckily you could create a specific prefixed classname (with popUp-) like
popUp-elasticNoC-1010-730
and than use that specific prefix as your reference for splitting the specific string parts of interest, but also as your Elements selector:
querySelectorAll('[class*=" popUp-"], [class^="popUp-"]')
Here's an example:
const handlePopup = (ev) => {
ev.preventDefault(); // Prevent browser default action
const EL = ev.currentTarget; // Get the element
const classPopUp = [...EL.classList].filter(cl => cl.startsWith("popUp-"))[0];
const args = classPopUp.split('-'); // Convert class item to array
args.shift(); // remove "popUp-" prefix
args.unshift(EL.getAttribute("href")); // Prepend HREF to parts
return popUp(...args); // Call popUp with arguments
};
const EL_popup = document.querySelectorAll('[class*=" popUp-"], [class^="popUp-"]');
EL_popup.forEach(el => el.addEventListener('click', handlePopup));
<a class="red popUp-elasticNoC-1010-730 foo-bar" href="https://example.com" target="_blank">TEST CLICK</a>

How to wrap the content of DOM Element into another Element with minimal performance overhead?

I would like to wrap the live content of a DOM element into another, keeping all the structure and all attached event listeners unchanged.
For example, I want this
<div id="original">
Some text <i class="icon></i>
</div>
to become
<div id="original">
<div id="wrapper">
Some text <i class="icon></i>
</div>
</div>
Preferably without jQuery.
If there is nothing else other than ID to distinguish your nodes, and given that #original has multiple child nodes, it would probably be simpler to create a new parent node and insert that:
var original = document.getElementById('original');
var parent = original.parentNode;
var wrapper = document.createElement('DIV');
parent.replaceChild(wrapper, original);
wrapper.appendChild(original);
and then move the IDs to the right place:
wrapper.id = original.id;
original.id = 'wrapper';
noting of course, that the variables original and wrapper now point at the 'wrong' elements.
EDIT oh, you wanted to leave the listeners attached... Technically, they still are, but they're now attached to the inner element, not the outer one.
EDIT 2 revised answer, leaving the event listeners attached to the original element (that's now the outer div):
var original = document.getElementById('original');
var wrapper = document.createElement('DIV');
wrapper.id = 'wrapper';
while (original.firstChild) {
wrapper.appendChild(original.firstChild);
}
original.appendChild(wrapper);
This works simply by successively moving each child node out of the original div into the new parent, and then moving that new parent back where the children were originally.
The disadvantage over the previous version of this answer is that you have to iterate over all of the children individually.
See https://jsfiddle.net/alnitak/d0jss2yu/ for demo
Alternatively, do it this way. It also displays result in the adjacent result div.
<div id="original">
Some text <i class="icon"></i>
</div>
<button onclick="myFunction()">do it</button>
<p type="text" id="result"></p>
<script>
function myFunction() {
var org = document.getElementById("original");
var i = org.innerHTML; //get i tag content
var wrap = document.createElement("div"); //create div
wrap.id="wrapper"; //set wrapper's id
wrap.innerHTML= i //set it to i tag's content
org.innerHTML=""; // clear #orignal first
org.appendChild(wrap); //append #wrapper and it's content
var result = org.outerHTML;
document.getElementById("result").innerText = result;
}
</script>
Updated answer:
This should work better and with less code than my previous answer.
var content = document.getElementById("myList").innerHTML;
document.getElementById("myList").innerHTML = "<div id='wrapper'></div>"
document.getElementById("wrapper").innerHTML = content;
EDIT: This will destroy any event listener attached to the child nodes.
Previous answer:
I don't tried it, but something like this should work:
var wrapper = document.createElement("DIV");
wrapper.id = "wrapper";
var content = document.getElementById("myList").childNodes;
document.getElementById("myList").appendChild(wrapper);
document.getElementById("wrapper").appendChild(content);
Create the wrapper element.
Get myList contents.
Add the wrapper element to myList.
Add myList contents to be child of the wrapper element.

How to replace anchor element with the innerHTML of itself

I try to write a script based on JavaScript for replacing the current selected anchor element with it's inner HTML.
You can also find a simple running example in JSFiddle. To run the example, click on the first link, and the click the button.
So, for example, if I have the following HTML:
<p>
Wawef awef <em>replace</em> <strong>me</strong>
falwkefi4hjtinyoh gf waf eerngl nregsl ngsekdng selrgnlrekg slekngs ekgnselrg nselrg
<a href="http://www.anothersite.com/>replace me</a> klserng sreig klrewr
</p>
and I like when I click on some of the two anchors to remove the anchor with it's inner HTML. This mean, that if I click on the first anchor element, and click the appropriate button to replace the anchor the result should be like that:
<p>
Wawef awef <em>replace</em> <strong>me</strong> falwkefi4hjtinyoh gf waf eerngl
nregsl ngsekdng selrgnlrekg slekngs ekgnselrg nselrg <a href="http://www.anothersite.com/>replace me</a>
klserng sreig klrewr
</p>
My JavaScript code for this functionality is the following:
// Start tracking the click event on the document
document.addEventListener(
'click',
function(event)
{
// If right click, return
if(event.button == 2)
{
return;
}
// Get the current clicked document element
var link = event.target;
while(link && !(link instanceof HTMLAnchorElement))
{
link = link.parentNode;
}
// Get the element with ID wpf-remove-element-now
var clickedLink = document.getElementById("wpf-remove-element-now");
// If the element exists
if(clickedLink !== null)
{
// By executing this code, I am ensuring that I have only
// one anchor element in my document with this ID
// Remove the id attribute
clickedLink.removeAttribute('id');
}
// If ther is no link element
if(!link)
{
// Disable my "unlink" button
editor.commands.customunlinkcmd.disable();
// and return
return;
}
event.preventDefault();
event.stopPropagation();
// If the user has clickde on an anchor element then
// enable my "unlink" button in order to allow him to
// to replace the link if he like to.
editor.commands.customunlinkcmd.enable();
// Set the id attribute of the current selected anchor
// element to wpf-remove-element-now
link.setAttribute('id', 'wpf-remove-element-now');
}
);
var $unlink_button = document.getElementById('unlink');
$unlink_button.addEventListener(
'click',
function(event)
{
// Get the element with ID wpf-remove-element-now
var link = document.getElementById("wpf-remove-element-now");
// Create a new text node that contains the link inner HTML
var text = document.createTextNode(link.innerHTML);
// Make the replacement
link.parentNode.replaceChild(text, link);
}
);
Everything until now is correct, appart of the replacement of the link. I have try the above code, but the result I get is like the following one:
Wawef awef <em>replace</em> <strong>me</strong> falwkefi4hjtinyoh gf waf eerngl
nregsl ngsekdng selrgnlrekg slekngs ekgnselrg nselrg replace me klserng sreig klrewr
I mean the anchor is replaced with the text form of the inner HTML and not with the HTML form of the inner HTML.
So the question is, how can I do this kind of replacement.
You're creating a text node, so whatever you put in it will be interpreted as text. Instead, since you have the replacement tags predefined, you should create actual DOM elements to replace it with. Something like this could work: JSFiddle
var em_elem = document.createElement('em');
em_elem.appendChild(document.createTextNode("replace"));
var strong_elem = document.createElement('strong');
strong_elem.appendChild(document.createTextNode("me"));
var container_span = document.createElement('span');
container_span.appendChild(em_elem);
container_span.appendChild(strong_elem);
// Make the replacement
link.parentNode.replaceChild(container_span, link);
The answer was much simpler that I thought. I placed the solution below for anybody that need an equivalent solution :) :
$unlink_button.addEventListener(
'click',
function(event)
{
// Get the element with ID wpf-remove-element-now
var link = document.getElementById("wpf-remove-element-now");
// By this code you replace the link outeHTML (the link itself) with
// the link innerHTML (anything inside the link)
link.outerHTML = link.innerHTML;
}
);
Here you can find the running solution : JSFiddle
Note: The inspiration for this solution found in the web page.

Inserting HTML elements with JavaScript

Instead of tediously search for workarounds for each type of attribute and event when using the following syntax:
elem = document.createElement("div");
elem.id = 'myID';
elem.innerHTML = ' my Text '
document.body.insertBefore(elem,document.body.childNodes[0]);
Is there a way where I can just declare the entire HTML element as a string? like:
elem = document.createElement("<div id='myID'> my Text </div>");
document.body.insertBefore(elem,document.body.childNodes[0]);
Instead of directly messing with innerHTML it might be better to create a fragment and then insert that:
function create(htmlStr) {
var frag = document.createDocumentFragment(),
temp = document.createElement('div');
temp.innerHTML = htmlStr;
while (temp.firstChild) {
frag.appendChild(temp.firstChild);
}
return frag;
}
var fragment = create('<div>Hello!</div><p>...</p>');
// You can use native DOM methods to insert the fragment:
document.body.insertBefore(fragment, document.body.childNodes[0]);
Benefits:
You can use native DOM methods for insertion such as insertBefore, appendChild etc.
You have access to the actual DOM nodes before they're inserted; you can access the fragment's childNodes object.
Using document fragments is very quick; faster than creating elements outside of the DOM and in certain situations faster than innerHTML.
Even though innerHTML is used within the function, it's all happening outside of the DOM so it's much faster than you'd think...
You want this
document.body.insertAdjacentHTML( 'afterbegin', '<div id="myID">...</div>' );
Have a look at insertAdjacentHTML
var element = document.getElementById("one");
var newElement = '<div id="two">two</div>'
element.insertAdjacentHTML( 'afterend', newElement )
// new DOM structure: <div id="one">one</div><div id="two">two</div>
position is the position relative to the element you are inserting adjacent to:
'beforebegin'
Before the element itself
'afterbegin'
Just inside the element, before its first child
'beforeend'
Just inside the element, after its last child
'afterend'
After the element itself
In old school JavaScript, you could do this:
document.body.innerHTML = '<p id="foo">Some HTML</p>' + document.body.innerHTML;
In response to your comment:
[...] I was interested in declaring the source of a new element's attributes and events, not the innerHTML of an element.
You need to inject the new HTML into the DOM, though; that's why innerHTML is used in the old school JavaScript example. The innerHTML of the BODY element is prepended with the new HTML. We're not really touching the existing HTML inside the BODY.
I'll rewrite the abovementioned example to clarify this:
var newElement = '<p id="foo">This is some dynamically added HTML. Yay!</p>';
var bodyElement = document.body;
bodyElement.innerHTML = newElement + bodyElement.innerHTML;
// note that += cannot be used here; this would result in 'NaN'
Using a JavaScript framework would make this code much less verbose and improve readability. For example, jQuery allows you to do the following:
$('body').prepend('<p id="foo">Some HTML</p>');
To my knowledge, which, to be fair, is fairly new and limited, the only potential issue with this technique is the fact that you are prevented from dynamically creating some table elements.
I use a form to templating by adding "template" elements to a hidden DIV and then using cloneNode(true) to create a clone and appending it as required. Bear in ind that you do need to ensure you re-assign id's as required to prevent duplication.
As others said the convenient jQuery prepend functionality can be emulated:
var html = '<div>Hello prepended</div>';
document.body.innerHTML = html + document.body.innerHTML;
While some say it is better not to "mess" with innerHTML, it is reliable in many use cases, if you know this:
If a <div>, <span>, or <noembed> node has a child text node that includes the characters (&), (<), or (>), innerHTML returns these characters as &amp, &lt and &gt respectively. Use Node.textContent to get a correct copy of these text nodes' contents.
https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
Or:
var html = '<div>Hello prepended</div>';
document.body.insertAdjacentHTML('afterbegin', html)
insertAdjacentHTML is probably a good alternative: https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
If you want to insert HTML code inside existing page's tag use Jnerator. This tool was created specially for this goal.
Instead of writing next code
var htmlCode = '<ul class=\'menu-countries\'><li
class=\'item\'><img src=\'au.png\'></img><span>Australia </span></li><li
class=\'item\'><img src=\'br.png\'> </img><span>Brazil</span></li><li
class=\'item\'> <img src=\'ca.png\'></img><span>Canada</span></li></ul>';
var element = document.getElementById('myTag');
element.innerHTML = htmlCode;
You can write more understandable structure
var jtag = $j.ul({
class: 'menu-countries',
child: [
$j.li({ class: 'item', child: [
$j.img({ src: 'au.png' }),
$j.span({ child: 'Australia' })
]}),
$j.li({ class: 'item', child: [
$j.img({ src: 'br.png' }),
$j.span({ child: 'Brazil' })
]}),
$j.li({ class: 'item', child: [
$j.img({ src: 'ca.png' }),
$j.span({ child: 'Canada' })
]})
]
});
var htmlCode = jtag.html();
var element = document.getElementById('myTag');
element.innerHTML = htmlCode;

Categories