get opening tag including attributes - outerHTML without innerHTML
Asked Answered
B

9

17

I would like to retrieve a certain tag element with its attributes from the DOM. For example, from

<a href="#" class="class">
  link text
</a>

I want to get <a href="#" class="class">, optionally with a closing </a>, either as a string or some other object. In my opinion, this would be similar to retrieving the .outerHTML without the .innerHTML.

Finally, I need this to wrap some other elements via jQuery. I tried

var elem = $('#some-element').get(0);
$('#some-other-element').wrap(elem);

but .get() returns the DOM element including its content. Also

var elem = $('#some-element').get(0);
$('#some-other-element').wrap(elem.tagName).parent().attr(elem.attributes);

fails as elem.attributes returns a NamedNodeMap which does not work with jQuery's attr() and I was not able to convert it. Admitted that the above examples are not very senseful as they copy also the element's no-longer-unique ID. But is there any easy way? Thanks alot.

Barbee answered 7/3, 2012 at 15:25 Comment(4)
Just clone node with .clone(), then empty it with .html() and remove id and what else you want.Interested
$("a").clone().empty().attr("outerHTML"); To solve first problemSmashing
Thanks @kirilloid, guess josh was just quicker ;)Barbee
Thanks also @UlhasTuscano, should work great in conjunction with some outerHTML workarounds, e.g. #2420249Barbee
M
7
var wrapper = $('.class').clone().attr('id','').empty();
  • You might want to change the selector to more exactly match the <a> element you're looking for.
  • clone() creates a new copy of the matched element(s), optionally copying event handlers too.
  • I used attr to clear the element's ID so that we don't duplicate IDs.
  • empty() removes all child nodes (the 'innerHTML').
Mathamathe answered 7/3, 2012 at 15:29 Comment(3)
Amazing. Thanks very much, josh :) Any concerns about using .clone()? Some say it may not be that performant, but I cannot see any other solution.Barbee
i guess .clone() isn'e exactly copy the whole object and some sort of reference do exists and if we use .remove() to the cloned object, it actually removes the original object. I suffer this issue once.Kattegat
@RichardKiefer: Performance is obviously browser-dependent, but this code ranges from 3,000 ops/sec in IE8 to 25,000 ops/sec in FF10. You probably shouldn't be generating thousands of DOM elements anyway, so I don't see anything to worry about.Mathamathe
G
18

There is a way to do this without jQuery. This also works with <br> tags, <meta> tags, and other empty tags:

tag = elem.innerHTML ? elem.outerHTML.slice(0,elem.outerHTML.indexOf(elem.innerHTML)) : elem.outerHTML;

Because innerHTML would be empty in self-closing tags, and indexOf('') always returns 0, the above modification checks for the presence of innerHTML first.

Gerstner answered 6/1, 2016 at 17:26 Comment(5)
This doesn't work with empty elements: document.createElement("strong").outerHTML.slice(0, elem.outerHTML.indexOf(elem.innerHTML))Laze
@Laze put your createElement declaration in a variable called elem and use the ternary expression I used above, it returns <strong></strong>.Gerstner
@AaronGillion Good catch. That one could be remedied by searching for >< if innerHTML is an empty string. This makes me think of another issue, though: <span id="space"> </span> identifies the wrong index for the space.Scaife
This is very UNSAFE. E. g. when elem.outerHTML === '<p>p</p>'. Or any other situation that innerHTML's text is a subset of tagName or one of its attributes or even attributes' values!Lindsey
as another comment said: very Unsafe -- if multiple other text has the same content of innerHtml... otherwise (if you dont care that, and dont care empty tags), wouldnt split() be a lot easier?...Romain
M
7
var wrapper = $('.class').clone().attr('id','').empty();
  • You might want to change the selector to more exactly match the <a> element you're looking for.
  • clone() creates a new copy of the matched element(s), optionally copying event handlers too.
  • I used attr to clear the element's ID so that we don't duplicate IDs.
  • empty() removes all child nodes (the 'innerHTML').
Mathamathe answered 7/3, 2012 at 15:29 Comment(3)
Amazing. Thanks very much, josh :) Any concerns about using .clone()? Some say it may not be that performant, but I cannot see any other solution.Barbee
i guess .clone() isn'e exactly copy the whole object and some sort of reference do exists and if we use .remove() to the cloned object, it actually removes the original object. I suffer this issue once.Kattegat
@RichardKiefer: Performance is obviously browser-dependent, but this code ranges from 3,000 ops/sec in IE8 to 25,000 ops/sec in FF10. You probably shouldn't be generating thousands of DOM elements anyway, so I don't see anything to worry about.Mathamathe
S
4

If someone is not using jQuery . . .

elem.outerHTML
"<a href="#" class="class">
  link text
</a>"
elem.cloneNode().outerHTML
"<a href="#" class="class"></a>"

If you want to be safe for Firefox etc. released before mid-2014, use cloneNode(false) to avoid getting inner stuff.

Reference: https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode

Schaab answered 26/6, 2020 at 18:32 Comment(1)
This is the correct answer. All the other distracting answers should be deleted.Biostatics
L
3

Here is my solution:

opentag=elem.outerHTML.slice(0, elem.outerHTML.length-elem.innerHTML.length-elem.tagName.length-3);

I suppose, that close tag is of the form: "</"+elem.tagName+">".

Lelalelah answered 9/10, 2018 at 11:50 Comment(0)
L
3

Unfortunately, @AaronGillion's answer isn't reliable as I said in my comment. Thank @sus. I recommend his/her way with a little change to support <self-closing tags />:

function getOpenTag(element: HTMLElement): string {
    const outerHtml = element.outerHTML;
    const len = outerHtml.length;
    
    const openTagLength = outerHtml[len - 2] === '/' ? // Is self-closing tag?
            len :
            len - element.innerHTML.length - (element.tagName.length + 3);
    // As @sus said, (element.tagName.length + 3) is the length of closing tag. It's always `</${tagName}>`. Correct?
    
    return outerHtml.slice(0, openTagLength);
}

The code is in TypeScript. Remove types (HTMLElement and number) if you want JavaScript.

Lindsey answered 26/4, 2019 at 2:19 Comment(0)
R
2

Here's a solution I've used:

const wrap = document.createElement('div')
wrap.appendChild(target.cloneNode(true))
const openingTag = wrap.innerHTML.split('>')[0] + '>'
Ramin answered 15/8, 2019 at 15:11 Comment(0)
G
0

This can be done without any String manipulation.

Instead, you can use the element's internals and build the string yourself from there:

function getTagHTML(el) {
  if (!el instanceof HTMLElement) return null;
  let result = `<${el.tagName.toLowerCase()}`;
  for (const attribute in el.attributes) {
    if (el.attributes[attribute].nodeValue) 
      result += ` ${el.attributes[attribute].name}="${el.attributes[attribute].nodeValue.replace(/"/g, "&quot;")}"`
  }
  result += `></${el.tagName.toLowerCase()}>`;
  return result;
}

console.log(getTagHTML(document.getElementById('outer')));
<div id="outer" class='i-want-"this"'>
  <span>I do not want this</span>
</div>

Please note that for self-closing elements like <img /> this would give you an unwanted and incorrect closing tag. Feel free to adjust the code accordingly.

Gleeful answered 9/4, 2020 at 11:4 Comment(0)
N
0

+1 for cloneNode answer above

  • create HTML without content
  • Looping over attributes
  • optional end tag
  • optional self-closing tag

function getTagHTML(el, includeClosingTag=true , selfClosing = false) {
  //return el.cloneNode(false).outerHTML;
  if (el.attributes)
    return `<${el.localName}` + 
      [...el.attributes].map(
             attr => ` ${attr.name}="${attr.nodeValue.replace(/"/g, "&quot;")}"`
      ).join`` +
      `${selfClosing ? "/" : ""}>` + 
      (!selfClosing && includeClosingTag ? `</${el.localName}>` : "");
  else 
    return null;
}

document.body.querySelectorAll("*:not(script)").forEach(el=>{
  console.log(getTagHTML(el));
  console.log(getTagHTML(el,false));
  console.log(getTagHTML(el,false,true))
});
<div id="ONE" class='CLASSNAMES' attr1='"string"'>
   INNERHTML
  </div>
  <img id="TWO" src="https://svgshare.com/i/Uhq.svg"/>
Narco answered 4/3, 2021 at 13:11 Comment(0)
C
0

My solution using Reg Exp to replace all possible closing tags inside element attributes:

const outerHTML = div.outerHTML;
const content = outerHTML.slice(0, outerHTML.length - div.innerHTML.length);
const dummy = content.replace(/'.*?'|".*?"/g, x => 'x'.repeat(x.length));
return content.slice(0, 1 + dummy.indexOf('>'));
Convivial answered 9/8, 2021 at 16:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.