I am trying to create a general function that will extract a div content (with nested elements) and save it locally in an HTML file.
Basically I get the div innerHTML, wrap it in html/head/body tags and then save it:
function div2html() {
var inner=document.getElementById("div2save").innerHTML;
var html="<html><head></head><body>"+inner+"</body></html>";
saveTextAsFile("div2html.html", html);
}
See a working version here: jsfiddle
However I am not sure how to handle classes. As you can see the class in the sample (bigbold) is not embedded in the new HTML. I need some way to get all the classes used in the div and then add them (or the computed styles ?) to the html I generate .. is this possible ? is there any other way around it ?
Try including style element .outerHTML within saved html
function div2html() {
var inner=document.getElementById("div2save").innerHTML;
var style = document.getElementsByTagName("style")[0].outerHTML;
var html="<html><head>"+style+"</head><body>"+inner+"</body></html>";
saveTextAsFile("div2html.html", html);
}
jsfiddle http://jsfiddle.net/fb6s763w/1/
Alternatively, using window.getComputedStyle() to select only css of #div2save child node
function div2html() {
var inner = document.getElementById("div2save");
var style = window.getComputedStyle(inner.children[0]).cssText;
var html = "<html><head><style>"
+ "." + inner.children[0].className
+ "{" + style + "}"
+ "</style></head><body>"
+ inner.innerHTML + "</body></html>";
saveTextAsFile("div2html.html", html);
}
jsfiddle http://jsfiddle.net/fb6s763w/2/
Looks like this might be able to help you out:
https://github.com/Automattic/juice
If the CSS of the page is not big, a simple solution is to include it all in the saved html as suggested by guest271314 above with
var style = document.getElementsByTagName("style")[0].outerHTML;
see jsfiddle
A more comprehensive solution extracts the classes from the div and then adds only the rules of those classes to the div (Using code from How do you read CSS rule values with JavaScript?)
function div2html(divId) {
var html = document.getElementById(divId).innerHTML;
// get all css classes in html
var cssClasses = [];
var classRegexp = /class=['"](.*?)['"]/g;
var m;
while ((m = classRegexp.exec(html))) cssClasses = cssClasses.concat(cssClasses, m[1].split(" "));
// filter non unique or empty cssClasses
cssClasses = cssClasses.filter(function (item, pos, self) {
return item && self.indexOf(item) == pos;
});
// get html of classes
var cssHtml = '';
for (var i = 0; i < cssClasses.length; i++) cssHtml += getRule('.' + cssClasses[i]);
// assemble html
var html = "<html><head><style>" + cssHtml + "</style></head><body>" + html + "</body></html>";
console.log(html);
saveTextAsFile("div2html.html", html);
}
see jsfiddle
This question already has answers here:
Get the DOM path of the clicked <a>
(11 answers)
Closed 8 years ago.
When you inspect an element in Google Chrome you see something like this:
html body div #content #mainbar form#post-form.post-form div#question-form div#post-editor.post-editor div div.wmd-containier ... span
Is there a method or a solution in jQuery that displays the same string for any object?
Let's try and write one as a jQuery plugin - it should be trivial. All we need is to find the node's parents, and the node itself, and then generate a string containing the (lowercase) tag name, and (if supplied) the ID and classes:
(function($) {
function tag(el) {
var r = el.tagName.toLowerCase();
if (el.id) {
r += '#' + el.id;
}
if (el.className) {
r += '.' + el.className.replace(/\s+/g, '.');
}
return r;
}
$.fn.path = function() {
var node = this.get(0); // in case multiple elements are passed
return $(node).parents().add(node).get().map(tag).join(' ');
}
})(jQuery);
Trying that out here on this stackoverflow page gives:
$('.vote').path()
> "html body.question-page.new-topbar div.container div#content.snippet-hidden
div div#mainbar div#question.question table tbody tr td.votecell div.vote"
This will create a selector with ids and class names, you may add more attribute detection in the map function:
var getSelector = function(t){
var elements = [];
while( t.parentNode ){
elements.push( t );
t = t.parentNode;
}
var selector = elements.reverse().map( function( elm ){
var elementSelector = elm.nodeName.toLowerCase();
if( elm.id ){
elementSelector += '#' + elm.id
}
if( elm.className ){
elementSelector += '.' + elm.className.split(/\s/).join('.')
}
selector += elementSelector;
return elementSelector;
}).join(' ');
return selector;
}
http://jsfiddle.net/41dq3rbq/2/
Here's my issue. I made a function that resolves links in javascript, but the use-case I'm stuck with is that there may already be HTML in posts with links.
Users can not post true HTML, but moderators and administrators can, meaning I need to handle both cases.
Here's an example of your typical user post HTML:
<div class="teaser">
This is just your normal post http://google.com some other stuff
</div>
And administrator/moderator:
<div class="teaser">
<b>
THIS LINK
</b>
<br><br>
Supplemental reading: Link again
</div>
Normally, I'd use something like
function replaceURLWithHTMLLinks(text) {
var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&##\/%?=~_|!:,.;]*[-A-Z0-9+&##\/%=~_|])/ig;
return text.replace(exp,"<a href='$1' target='_blank'>$1</a>");
}
c.not('a').each(function() {
var html = $(this).html();
$(this).html(replaceURLWithHTMLLinks(html));
});
But this causes links to be parsed which exist inside of the href property. I need to be able to create links only when they are outside of tags, and it needs to be through all children as you'll notice that is the first child in a mod/admin post (if they so choose).
Mods and admins can put basically any HTML they desire in their posts, so the tag could be anywhere in the post hierarchy which is not at all consistent.
I could just not parse links on admin or mod posts, but sometimes some mods and admins use the proper HTML tags, and sometimes they don't, which is why I'd like to know the proper way of doing this.
Try this:
var exp = /^(\b(https?|ftp|file):\/\/[-A-Z0-9+&##\/%?=~_|!:,.;]*[-A-Z0-9+&##\/%=~_|])/ig;
$('.teaser').each(function() {
var i, words;
$this = $(this);
words = $this.html().split(' ');
for (i = 0; i < words.length; i++) {
if (exp.test(words[i])) {
words[i] = words[i].replace(exp, "<a href='$1' target='_blank'>$1</a>");
}
}
$this.html(words.join(' '));
});
Demo Link
I found the answer here it seems.
filterTeaserLinkContent: function(data) {
var exp = /\b((https?|ftps?|about|bitcoin|git|irc[s6]?):(\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/|magnet:\?(dn|x[lts]|as|kt|mt|tr)=)([^\s()<>]+|\([^\s()<>]+\))+(\([^\s()<>]+\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’])/g;
var nodes = data[0].childNodes;
for(var i = 0; i < nodes.length; i++) {
var n = nodes[i];
if(n.nodeType == n.TEXT_NODE || n.nodeName == 'BR') {
var g = n.textContent.match(exp);
while(g) {
var idx=n.textContent.indexOf(g[0]);
var pre=n.textContent.substring(0,idx);
var a=document.createElement("a");
if (!/^[a-z][\w-]+:/.test(g[0])) {
a.href = "http://" + g[0];
} else {
a.href = g[0];
}
a.innerText = g[0];
n.textContent = n.textContent.substring(idx+g[0].length);
n.parentElement.insertBefore(a,n);
g=n.textContent.match(exp);
}
} else {
Board.filterTeaserLinkContent($(n));
}
}
},
filterTeaserContent: function(data) {
// Jam into <div> so we can play with it
var c = $('<div>' + data + '</div>');
// Remove <wbr> tag which breaks links
c.find('wbr').each(function() {
$(this).remove();
});
// Re-parse the HTML after removing <wbr> or else the text nodes won't be joined
c = $('<div>' + c.html() + '</div>');
// I actually forget what this does, but fuck it. Shit.
c.not("div, s, span, a").each(function() {
var content = $(this).contents();
$(this).replaceWith(content);
});
Board.filterTeaserLinkContent(c);
// Remove images in post preview because they don't need to be here...
c.find('img').each(function() {
$(this).remove();
});
// Simplify line breaks
return c.html().replace(/<br ?\/?><br ?\/?>/g, "<br>");
},
This is for use in the 4chan API in case anyone was curious.
I need to be able to get an unqiue selector for each element on a page.
For example, when I click on an element I want to do something like this:
$(document).click(function(){
var sel = getUniqueSel(this);
});
So, after storing the sel value in a DB I can get that value and simply access the element by
var el = $(sel);
I can't change and don't know anything about the HTML structure of the page and I can't simply add unique ID's (using JS) to every element as this would be inefficient.
Another approach might be to wander up the dom tree and create a path to the element, which you can save and use it later as a selector again, although that might not be bulletproof, but maybe its a point where you can start off.
Edit: Updated the Answer with your suggestion in the comment, now it returns the id if available
Just visit the example on JSBin And click the document twice.
but notice what gets highlighted..
jQuery.fn.getPath = function () {
if (this.length != 1) throw 'Requires one element.';
var path, node = this;
if (node[0].id) return "#" + node[0].id;
while (node.length) {
var realNode = node[0],
name = realNode.localName;
if (!name) break;
name = name.toLowerCase();
var parent = node.parent();
var siblings = parent.children(name);
if (siblings.length > 1) {
name += ':eq(' + siblings.index(realNode) + ')';
}
path = name + (path ? '>' + path : '');
node = parent;
}
return path;
};
var sel;
$(document)
.click(function (e, a) {
if (!sel) {
sel = $("#comment-21702402")
.getPath();
alert("Path is: " + sel + ", hiding the Element -> Click again to highlight");
} else {
$(sel)
.css("background-color", "yellow");
}
});
One way to do this is to get all the information you can get on the element that was clicked.
So when you save it to the database you can save it as a text for example:
If the element you clicked on is: <div> I'm a div </div>
$(document).click(function(){
var tagName = $(this).prev().prop('tagName');
var attributes = {};
if( this.length ) {
$.each( this[0].attributes, function( index, attr ) {
attributes[ attr.name ] = attr.value;
} );
}
var elText=$(this).html();
saveToDB(tagName,attributes,elText);
});
You can later find the element using the attributes you have or simply use
$(tagName+'['+attribute+'="'+value+'"]:contains("'+elText+'")')
I think this should help
jQuery: how to change tag name?
For example:
<tr>
$1
</tr>
I need
<div>
$1
</div>
Yes, I can
Create DOM element <div>
Copy tr content to div
Remove tr from dom
But can I make it directly?
PS:
$(tr).get(0).tagName = "div";
results in DOMException.
You can replace any HTML markup by using jQuery's .replaceWith() method.
example: http://jsfiddle.net/JHmaV/
Ref.: .replaceWith
If you want to keep the existing markup, you could use code like this:
$('#target').replaceWith('<newTag>' + $('#target').html() +'</newTag>')
No, it is not possible according to W3C specification: "tagName of type DOMString, readonly"
http://www.w3.org/TR/DOM-Level-2-Core/core.html
Where the DOM renameNode() Method?
Today (2014) no browser understand the new DOM3 renameNode method (see also W3C)
check if run at your bowser: http://jsfiddle.net/k2jSm/1/
So, a DOM solution is ugly and I not understand why (??) jQuery not implemented a workaround?
pure DOM algorithm
createElement(new_name)
copy all content to new element;
replace old to new by replaceChild()
is something like this,
function rename_element(node,name) {
var renamed = document.createElement(name);
foreach (node.attributes as a) {
renamed.setAttribute(a.nodeName, a.nodeValue);
}
while (node.firstChild) {
renamed.appendChild(node.firstChild);
}
return node.parentNode.replaceChild(renamed, node);
}
... wait review and jsfiddle ...
jQuery algorithm
The #ilpoldo algorithm is a good start point,
$from.replaceWith($('<'+newname+'/>').html($from.html()));
As others commented, it need a attribute copy ... wait generic ...
specific for class, preserving the attribute, see http://jsfiddle.net/cDgpS/
See also https://stackoverflow.com/a/9468280/287948
The above solutions wipe out the existing element and re-create it from scratch, destroying any event bindings on children in the process.
short answer: (loses <p/>'s attributes)
$("p").wrapInner("<div/>").children(0).unwrap();
longer answer: (copies <p/>'s attributes)
$("p").each(function (o, elt) {
var newElt = $("<div class='p'/>");
Array.prototype.slice.call(elt.attributes).forEach(function(a) {
newElt.attr(a.name, a.value);
});
$(elt).wrapInner(newElt).children(0).unwrap();
});
fiddle with nested bindings
It would be cool to copy any bindings from the at the same time, but getting current bindings didn't work for me.
To preserve the internal content of the tag you can use the accessor .html() in conjunction with .replaceWith()
forked example: http://jsfiddle.net/WVb2Q/1/
Inspired by ericP answer, formatted and converted to jQuery plugin:
$.fn.replaceWithTag = function(tagName) {
var result = [];
this.each(function() {
var newElem = $('<' + tagName + '>').get(0);
for (var i = 0; i < this.attributes.length; i++) {
newElem.setAttribute(
this.attributes[i].name, this.attributes[i].value
);
}
newElem = $(this).wrapInner(newElem).children(0).unwrap().get(0);
result.push(newElem);
});
return $(result);
};
Usage:
$('div').replaceWithTag('span')
Working pure DOM algorithm
function rename_element(node, name) {
let renamed = document.createElement(name);
Array.from(node.attributes).forEach(attr => {
renamed.setAttribute(attr.name, attr.value);
})
while (node.firstChild) {
renamed.appendChild(node.firstChild);
}
node.parentNode.replaceChild(renamed, node);
return renamed;
}
You could go a little basic. Works for me.
var oNode = document.getElementsByTagName('tr')[0];
var inHTML = oNode.innerHTML;
oNode.innerHTML = '';
var outHTML = oNode.outerHTML;
outHTML = outHTML.replace(/tr/g, 'div');
oNode.outerHTML = outHTML;
oNode.innerHTML = inHTML;
To replace the internal contents of multiple tags, each with their own original content, you have to use .replaceWith() and .html() differently:
http://jsfiddle.net/kcrca/VYxxG/
JS to change the tag name
/**
* This function replaces the DOM elements's tag name with you desire
* Example:
* replaceElem('header','ram');
* replaceElem('div.header-one','ram');
*/
function replaceElem(targetId, replaceWith){
$(targetId).each(function(){
var attributes = concatHashToString(this.attributes);
var replacingStartTag = '<' + replaceWith + attributes +'>';
var replacingEndTag = '</' + replaceWith + '>';
$(this).replaceWith(replacingStartTag + $(this).html() + replacingEndTag);
});
}
replaceElem('div','span');
/**
* This function concats the attributes of old elements
*/
function concatHashToString(hash){
var emptyStr = '';
$.each(hash, function(index){
emptyStr += ' ' + hash[index].name + '="' + hash[index].value + '"';
});
return emptyStr;
}
Related fiddle is in this link
Since replaceWith() didn't work for me on an element basis (maybe because I used it inside map()), I did it by creating a new element and copying the attributes as needed.
$items = $('select option').map(function(){
var
$source = $(this),
$copy = $('<li></li>'),
title = $source.text().replace( /this/, 'that' );
$copy
.data( 'additional_info' , $source.val() )
.text(title);
return $copy;
});
$('ul').append($items);
Take him by the word
Taken the Question by Word "how to change tag name?" I would suggest this solution:
If it makes sense or not has to be decided case by case.
My example will "rename" all a-Tags with hyperlinks for SMS with span tags. Maintaining all attributes and content:
$('a[href^="sms:"]').each(function(){
var $t=$(this);
var $new=$($t.wrap('<div>')
.parent()
.html()
.replace(/^\s*<\s*a/g,'<span')
.replace(/a\s*>\s*$/g,'span>')
).attr('href', null);
$t.unwrap().replaceWith($new);
});
As it does not make any sense to have a span tag with an href attribute I remove that too.
Doing it this way is bulletproof and compatible with all browsers that are supported by jquery.
There are other ways people try to copy all the Attributes to the new Element, but those are not compatible with all browsers.
Although I think it is quite expensive to do it this way.
Jquery plugin to make "tagName" editable :
(function($){
var $newTag = null;
$.fn.tagName = function(newTag){
this.each(function(i, el){
var $el = $(el);
$newTag = $("<" + newTag + ">");
// attributes
$.each(el.attributes, function(i, attribute){
$newTag.attr(attribute.nodeName, attribute.nodeValue);
});
// content
$newTag.html($el.html());
$el.replaceWith($newTag);
});
return $newTag;
};
})(jQuery);
See : http://jsfiddle.net/03gcnx9v/3/
Yet another script to change the node name
function switchElement() {
$element.each(function (index, oldElement) {
let $newElement = $('<' + nodeName + '/>');
_.each($element[0].attributes, function(attribute) {
$newElement.attr(attribute.name, attribute.value);
});
$element.wrapInner($newElement).children().first().unwrap();
});
}
http://jsfiddle.net/rc296owo/5/
It will copy over the attributes and inner html into a new element and then replace the old one.
$(function(){
$('#switch').bind('click', function(){
$('p').each(function(){
$(this).replaceWith($('<div/>').html($(this).html()));
});
});
});
p {
background-color: red;
}
div {
background-color: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Hello</p>
<p>Hello2</p>
<p>Hello3</p>
<button id="switch">replace</button>
You can use this function
var renameTag = function renameTag($obj, new_tag) {
var obj = $obj.get(0);
var tag = obj.tagName.toLowerCase();
var tag_start = new RegExp('^<' + tag);
var tag_end = new RegExp('<\\/' + tag + '>$');
var new_html = obj.outerHTML.replace(tag_start, "<" + new_tag).replace(tag_end, '</' + new_tag + '>');
$obj.replaceWith(new_html);
};
ES6
const renameTag = function ($obj, new_tag) {
let obj = $obj.get(0);
let tag = obj.tagName.toLowerCase();
let tag_start = new RegExp('^<' + tag);
let tag_end = new RegExp('<\\/' + tag + '>$');
let new_html = obj.outerHTML.replace(tag_start, "<" + new_tag).replace(tag_end, '</' + new_tag + '>');
$obj.replaceWith(new_html);
};
Sample code
renameTag($(tr),'div');
Try this one also. in this example we can also have attributes of the old tag in new tag
var newName = document.querySelector('.test').outerHTML.replaceAll('h1', 'h2');
document.querySelector('.test').outerHTML = newName;
<h1 class="test">Replace H1 to H2</h1>