Append stuff the right way - javascript

I'm building myself a little js library and I've come across a problem with the append and prepend methods. This is my current setup
prepend: function(str) {
this.node.innerHTML = str + this.node.innerHTML;
},
append: function(str) {
this.node.innerHTML += str;
}
They both do work as expected, content is being added, but unfortunately all event listeners of elements that are inside the tag I'm modifying are being lost. It's funny how I didn't figure that may cause an issue. Anyway I tried looking at jQuery to see how they're doing it but their code is apparently not meant to be understood by me. I tried looking up other questions but didn't really find a solution for this.
How can I achieve this without losing my listeners?

If you move the elements around the DOM as objects rather than a primitive string representation, their attached events and other meta will be maintained.
You'll need to utilize the DOM's appendChild and insertBefore methods. Fiddle: http://jsfiddle.net/QMLpL/2/
Here is your updated snippet:
prepend: function(newNode) {
this.node.insertBefore(newNode,this.node.firstChild);
},
append: function(newNode) {
this.node.appendChild(newNode);
}
The updated snippet expects a DOM node rather than a string.
If you are expecting a string, consider utilizing the string as a CDATA node:
node = document.createTextNode(str);
Enhanced fiddle: http://jsfiddle.net/QMLpL/3/
Usage:
prepend: function(str) {
newNode = document.createTextNode(str);
this.node.insertBefore(newNode,this.node.firstChild);
},
append: function(str) {
newNode = document.createTextNode(str);
this.node.appendChild(newNode);
}
The contents of the linked fiddle are as follows.
HTML:
<div id="one">
<p><a id="clicky" href="#">Hello World</a></p>
</div>
<div id="two">
<p>Lorem Ipsum</p>
</div>
JavaScript:
var clicky = document.getElementById('clicky');
clicky.onclick = function() {
console.log('clicked!');
return false;
};
function append(target,node) {
if ( typeof node === 'string' ) {
node = document.createTextNode(node); // cast string as CDATA
}
target.appendChild(node);
}
function prepend(target,node) {
if ( typeof node === 'string' ) {
node = document.createTextNode(node);
}
target.insertBefore(node,target.firstChild);
}
var twoDiv = document.getElementById('two');
append(twoDiv,clicky);
var newElement = document.createElement('p');
newElement.onclick = function() {
console.log('clicked the p');
return false;
};
newElement.appendChild(document.createTextNode('Dolor sit amet.')); // add a CDATA node to the new P element
prepend(twoDiv,newElement);
append(twoDiv,'Waddup?');

Use insertAdjacentHTML:
prepend: function(str) {
this.node.insertAdjacentHTML("beforebegin", str);
},
append: function(str) {
this.node.insertAdjacentHTML("afterend", str);
}

Okay here's what might work,
I'm not sure. How about making
a deep clone of the object in question.
And then parsing the new html you want to
to add in another variable. Next looping
recursivly over these 2 objects and adding all
their children to a new one. Finally replacing
the object in question with your new object. This
way the listeners are preserved I think. It won't be
easy to write but it might give you an idea.
Cheers.

Please user insertBefore() JS HTML DOM METHOD for add string before your element content
Check MDN
OR user appendChild() JS HTML DOM METHOD for add string after your element content
Check MDN

Related

Modifying a string of html in javascript [duplicate]

I'm trying to get the HTML of a selected object with jQuery. I am aware of the .html() function; the issue is that I need the HTML including the selected object (a table row in this case, where .html() only returns the cells inside the row).
I've searched around and found a few very ‘hackish’ type methods of cloning an object, adding it to a newly created div, etc, etc, but this seems really dirty. Is there any better way, or does the new version of jQuery (1.4.2) offer any kind of outerHtml functionality?
I believe that currently (5/1/2012), all major browsers support the outerHTML function. It seems to me that this snippet is sufficient. I personally would choose to memorize this:
// Gives you the DOM element without the outside wrapper you want
$('.classSelector').html()
// Gives you the outside wrapper as well only for the first element
$('.classSelector')[0].outerHTML
// Gives you the outer HTML for all the selected elements
var html = '';
$('.classSelector').each(function () {
html += this.outerHTML;
});
//Or if you need a one liner for the previous code
$('.classSelector').get().map(function(v){return v.outerHTML}).join('');
EDIT: Basic support stats for element.outerHTML
Firefox (Gecko): 11 ....Released 2012-03-13
Chrome: 0.2 ...............Released 2008-09-02
Internet Explorer 4.0...Released 1997
Opera 7 ......................Released 2003-01-28
Safari 1.3 ...................Released 2006-01-12
No need to generate a function for it. Just do it like this:
$('a').each(function(){
var s = $(this).clone().wrap('<p>').parent().html();
console.log(s);
});
(Your browser's console will show what is logged, by the way. Most of the latest browsers since around 2009 have this feature.)
The magic is this on the end:
.clone().wrap('<p>').parent().html();
The clone means you're not actually disturbing the DOM. Run it without it and you'll see p tags inserted before/after all hyperlinks (in this example), which is undesirable. So, yes, use .clone().
The way it works is that it takes each a tag, makes a clone of it in RAM, wraps with p tags, gets the parent of it (meaning the p tag), and then gets the innerHTML property of it.
EDIT: Took advice and changed div tags to p tags because it's less typing and works the same.
2014 Edit : The question and this reply are from 2010. At the time, no better solution was widely available. Now, many of the other replies are better : Eric Hu's, or Re Capcha's for example.
This site seems to have a solution for you :
jQuery: outerHTML | Yelotofu
jQuery.fn.outerHTML = function(s) {
return s
? this.before(s).remove()
: jQuery("<p>").append(this.eq(0).clone()).html();
};
What about: prop('outerHTML')?
var outerHTML_text = $('#item-to-be-selected').prop('outerHTML');
And to set:
$('#item-to-be-selected').prop('outerHTML', outerHTML_text);
It worked for me.
PS: This is added in jQuery 1.6.
Extend jQuery:
(function($) {
$.fn.outerHTML = function() {
return $(this).clone().wrap('<div></div>').parent().html();
};
})(jQuery);
And use it like this: $("#myTableRow").outerHTML();
I agree with Arpan (Dec 13 '10 5:59).
His way of doing it is actually a MUCH better way of doing it, as you dont use clone. The clone method is very time consuming, if you have child elements, and nobody else seemed to care that IE actually HAVE the outerHTML attribute (yes IE actually have SOME useful tricks up its sleeve).
But I would probably create his script a bit different:
$.fn.outerHTML = function() {
var $t = $(this);
if ($t[0].outerHTML !== undefined) {
return $t[0].outerHTML;
} else {
var content = $t.wrap('<div/>').parent().html();
$t.unwrap();
return content;
}
};
To be truly jQuery-esque, you might want outerHTML() to be a getter and a setter and have its behaviour as similar to html() as possible:
$.fn.outerHTML = function (arg) {
var ret;
// If no items in the collection, return
if (!this.length)
return typeof arg == "undefined" ? this : null;
// Getter overload (no argument passed)
if (!arg) {
return this[0].outerHTML ||
(ret = this.wrap('<div>').parent().html(), this.unwrap(), ret);
}
// Setter overload
$.each(this, function (i, el) {
var fnRet,
pass = el,
inOrOut = el.outerHTML ? "outerHTML" : "innerHTML";
if (!el.outerHTML)
el = $(el).wrap('<div>').parent()[0];
if (jQuery.isFunction(arg)) {
if ((fnRet = arg.call(pass, i, el[inOrOut])) !== false)
el[inOrOut] = fnRet;
}
else
el[inOrOut] = arg;
if (!el.outerHTML)
$(el).children().unwrap();
});
return this;
}
Working demo: http://jsfiddle.net/AndyE/WLKAa/
This allows us to pass an argument to outerHTML, which can be
a cancellable function — function (index, oldOuterHTML) { } — where the return value will become the new HTML for the element (unless false is returned).
a string, which will be set in place of the HTML of each element.
For more information, see the jQuery docs for html().
You can also use get (Retrieve the DOM elements matched by the jQuery object.).
e.g:
$('div').get(0).outerHTML;//return "<div></div>"
As extension method :
jQuery.fn.outerHTML = function () {
return this.get().map(function (v) {
return v.outerHTML
}).join()
};
Or
jQuery.fn.outerHTML = function () {
return $.map(this.get(), function (v) {
return v.outerHTML
}).join()
};
Multiple choice and return the outer html of all matched elements.
$('input').outerHTML()
return:
'<input id="input1" type="text"><input id="input2" type="text">'
To make a FULL jQuery plugin as .outerHTML, add the following script to any js file and include after jQuery in your header:
update New version has better control as well as a more jQuery Selector friendly service! :)
;(function($) {
$.extend({
outerHTML: function() {
var $ele = arguments[0],
args = Array.prototype.slice.call(arguments, 1)
if ($ele && !($ele instanceof jQuery) && (typeof $ele == 'string' || $ele instanceof HTMLCollection || $ele instanceof Array)) $ele = $($ele);
if ($ele.length) {
if ($ele.length == 1) return $ele[0].outerHTML;
else return $.map($("div"), function(ele,i) { return ele.outerHTML; });
}
throw new Error("Invalid Selector");
}
})
$.fn.extend({
outerHTML: function() {
var args = [this];
if (arguments.length) for (x in arguments) args.push(arguments[x]);
return $.outerHTML.apply($, args);
}
});
})(jQuery);
This will allow you to not only get the outerHTML of one element, but even get an Array return of multiple elements at once! and can be used in both jQuery standard styles as such:
$.outerHTML($("#eleID")); // will return outerHTML of that element and is
// same as
$("#eleID").outerHTML();
// or
$.outerHTML("#eleID");
// or
$.outerHTML(document.getElementById("eleID"));
For multiple elements
$("#firstEle, .someElesByClassname, tag").outerHTML();
Snippet Examples:
console.log('$.outerHTML($("#eleID"))'+"\t", $.outerHTML($("#eleID")));
console.log('$("#eleID").outerHTML()'+"\t\t", $("#eleID").outerHTML());
console.log('$("#firstEle, .someElesByClassname, tag").outerHTML()'+"\t", $("#firstEle, .someElesByClassname, tag").outerHTML());
var checkThisOut = $("div").outerHTML();
console.log('var checkThisOut = $("div").outerHTML();'+"\t\t", checkThisOut);
$.each(checkThisOut, function(i, str){ $("div").eq(i).text("My outerHTML Was: " + str); });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://rawgit.com/JDMcKinstry/ce699e82c7e07d02bae82e642fb4275f/raw/deabd0663adf0d12f389ddc03786468af4033ad2/jQuery.outerHTML.js"></script>
<div id="eleID">This will</div>
<div id="firstEle">be Replaced</div>
<div class="someElesByClassname">At RunTime</div>
<h3><tag>Open Console to see results</tag></h3>
you can also just do it this way
document.getElementById(id).outerHTML
where id is the id of the element that you are looking for
I used Jessica's solution (which was edited by Josh) to get outerHTML to work on Firefox. The problem however is that my code was breaking because her solution wrapped the element into a DIV. Adding one more line of code solved that problem.
The following code gives you the outerHTML leaving the DOM tree unchanged.
$jq.fn.outerHTML = function() {
if ($jq(this).attr('outerHTML'))
return $jq(this).attr('outerHTML');
else
{
var content = $jq(this).wrap('<div></div>').parent().html();
$jq(this).unwrap();
return content;
}
}
And use it like this: $("#myDiv").outerHTML();
Hope someone finds it useful!
// no cloning necessary
var x = $('#xxx').wrapAll('<div></div>').parent().html();
alert(x);
Fiddle here: http://jsfiddle.net/ezmilhouse/Mv76a/
If the scenario is appending a new row dynamically, you can use this:
var row = $(".myRow").last().clone();
$(".myRow").last().after(row);
.myrow is the classname of the <tr>. It makes a copy of the last row and inserts that as a new last row.
This also works in IE7, while the [0].outerHTML method does not allow assignments in ie7
node.cloneNode() hardly seems like a hack. You can clone the node and append it to any desired parent element, and also manipulate it by manipulating individual properties, rather than having to e.g. run regular expressions on it, or add it in to the DOM, then manipulate it afterwords.
That said, you could also iterate over the attributes of the element to construct an HTML string representation of it. It seems likely this is how any outerHTML function would be implemented were jQuery to add one.
I've used Volomike's solution updated by Jessica. Just added a check to see if the element exists, and made it return blank in case it doesn't.
jQuery.fn.outerHTML = function() {
return $(this).length > 0 ? $(this).clone().wrap('<div />').parent().html() : '';
};
Of course, use it like:
$('table#buttons').outerHTML();
You can find a good .outerHTML() option here https://github.com/darlesson/jquery-outerhtml.
Unlike .html() that returns only the element's HTML content, this version of .outerHTML() returns the selected element and its HTML content or replaces it as .replaceWith() method but with the difference that allows the replacing HTML to be inherit by the chaining.
Examples can also be seeing in the URL above.
This is quite simple with vanilla JavaScript...
document.querySelector('#selector')
Note that Josh's solution only works for a single element.
Arguably, "outer" HTML only really makes sense when you have a single element, but there are situations where it makes sense to take a list of HTML elements and turn them into markup.
Extending Josh's solution, this one will handle multiple elements:
(function($) {
$.fn.outerHTML = function() {
var $this = $(this);
if ($this.length>1)
return $.map($this, function(el){ return $(el).outerHTML(); }).join('');
return $this.clone().wrap('<div/>').parent().html();
}
})(jQuery);
Edit: another problem with Josh's solution fixed, see comment above.
Anothe similar solution with added remove() of the temporary DOM object.
I have made this simple test with outerHTML being tokimon solution (without clone), and outerHTML2 being jessica solution (clone)
console.time("outerHTML");
for(i=0;i<1000;i++)
{
var html = $("<span style='padding:50px; margin:50px; display:block'><input type='text' title='test' /></span>").outerHTML();
}
console.timeEnd("outerHTML");
console.time("outerHTML2");
for(i=0;i<1000;i++)
{
var html = $("<span style='padding:50px; margin:50px; display:block'><input type='text' title='test' /></span>").outerHTML2();
}
console.timeEnd("outerHTML2");
and the result in my chromium (Version 20.0.1132.57 (0)) browser was
outerHTML: 81ms
outerHTML2: 439ms
but if we use tokimon solution without the native outerHTML function (which is now supported in probably almost every browser)
we get
outerHTML: 594ms
outerHTML2: 332ms
and there are gonna be more loops and elements in real world examples, so the perfect combination would be
$.fn.outerHTML = function()
{
$t = $(this);
if( "outerHTML" in $t[0] ) return $t[0].outerHTML;
else return $t.clone().wrap('<p>').parent().html();
}
so clone method is actually faster than wrap/unwrap method
(jquery 1.7.2)
Here is a very optimized outerHTML plugin for jquery:
(http://jsperf.com/outerhtml-vs-jquery-clone-hack/5 => the 2 others fast code snippets are not compatible with some browsers like FF < 11)
(function($) {
var DIV = document.createElement("div"),
outerHTML;
if ('outerHTML' in DIV) {
outerHTML = function(node) {
return node.outerHTML;
};
} else {
outerHTML = function(node) {
var div = DIV.cloneNode();
div.appendChild(node.cloneNode(true));
return div.innerHTML;
};
}
$.fn.outerHTML = function() {
return this.length ? outerHTML(this[0]) : void(0);
};
})(jQuery);
#Andy E => I don't agree with you. outerHMTL doesn't need a getter AND a setter: jQuery already give us 'replaceWith'...
#mindplay => Why are you joining all outerHTML? jquery.html return only the HTML content of the FIRST element.
(Sorry, don't have enough reputation to write comments)
Short and sweet.
[].reduce($('.x'), function(i,v) {return i+v.outerHTML}, '')
or event more sweet with help of arrow functions
[].reduce.call($('.x'), (i,v) => i+v.outerHTML, '')
or without jQuery at all
[].reduce.call(document.querySelectorAll('.x'), (i,v) => i+v.outerHTML, '')
or if you don't like this approach, check that
$('.x').get().reduce((i,v) => i+v.outerHTML, '')
This is great for changing elements on the dom but does NOT work for ie when passing in a html string into jquery like this:
$('<div id="foo">Some <span id="blog">content</span></div>').find('#blog').outerHTML();
After some manipulation I have created a function which allows the above to work in ie for html strings:
$.fn.htmlStringOuterHTML = function() {
this.parent().find(this).wrap('<div/>');
return this.parent().html();
};
$.html = el => $("<div>"+el+"</div>").html().trim();
I came across this while looking for an answer to my issue which was that I was trying to remove a table row then add it back in at the bottom of the table (because I was dynamically creating data rows but wanted to show an 'Add New Record' type row at the bottom).
I had the same issue, in that it was returning the innerHtml so was missing the TR tags, which held the ID of that row and meant it was impossible to repeat the procedure.
The answer I found was that the jquery remove() function actually returns the element, that it removes, as an object. So, to remove and re-add a row it was as simple as this...
var a = $("#trRowToRemove").remove();
$('#tblMyTable').append(a);
If you're not removing the object but want to copy it somewhere else, use the clone() function instead.
jQuery plugin as a shorthand to directly get the whole element HTML:
jQuery.fn.outerHTML = function () {
return jQuery('<div />').append(this.eq(0).clone()).html();
};
And use it like this: $(".element").outerHTML();
Pure JavaScript:
var outerHTML = function(node) {
var div = document.createElement("div");
div.appendChild(node.cloneNode(true));
return div.innerHTML;
};
$("#myNode").parent(x).html();
Where 'x' is the node number, beginning with 0 as the first one, should get the right node you want, if you're trying to get a specific one. If you have child nodes, you should really be putting an ID on the one you want, though, to just zero in on that one. Using that methodology and no 'x' worked fine for me.
Simple solution.
var myself = $('#div').children().parent();
$("#myTable").parent().html();
Perhaps I'm not understanding your question properly, but this will get the selected element's parent element's html.
Is that what you're after?

How to access innerHTML but ignore <script> tags

I have a plugin that looks through the HTML and replaces text. However, with my current implementation text in script tags are getting caught in the search as well. This leads to broken scripts on the affected pages.
var pageText = document.body.innerHTML;
document.body.innerHTML = pageText.replace(regextgoeshere);
I tried my best to filter it out through my regex pattern but I need to figure out how to skip all tags.
Is there a way to skip all tags when getting innerHTML?
Maybe your best option is to use querySelectorAll and negate undesired elements. Then replace the textContent instead innerHTML. By using innerHTML you risk breaking document tags.
This is a cross-browser solution.
var matches = document.querySelectorAll("*:not(html):not(head):not(script):not(meta):not(link)");
console.log(matches);
[].forEach.call(matches, function(elem) {
var text = ('innerText' in elem) ? 'innerText' : 'textContent';
elem[text] = elem[text].replace("this", "works");
});
http://jsfiddle.net/m6qhuesv/
Note 1: HTML, HEAD, META and LINK tags disallow modify textContext.
Note 2: innerText is a proprietary IE thing (also works in chrome). The W3C defines textContent as the official property.
EDIT: I misunderstood your requirements
If you want something more sophisticated, try Douglas Crockford's walking the DOM function:
function walkTheDOM(node, func) {
func(node);
node = node.firstChild;
while (node) {
walkTheDOM(node, func);
node = node.nextSibling;
}
}
You can use the tagName property of node to skip <script> elements:
if(node.tagName.toLowerCase() !== 'script'){
node.innerText = node.innerText.replace(regextgoeshere);
}
I think we tend to think elements and miss nodes!
However this problem is best solved by thinking nodes.
Australian Alex has best solution
http://blog.alexanderdickson.com/javascript-replacing-text
function myRecursiveSearch(node,.....) {
var excludeElements = ['script', 'style', 'iframe', 'canvas'];
var child = node.firstChild;
if(child==null)
return;
do {
switch (child.nodeType) {
case 1:
if (excludeElements.indexOf(child.tagName.toLowerCase()) > -1) {
continue;
}
myRecursiveSearch(child,.....);
break;
case 3:
child.nodeValue=doReolace(child.nodeValue,.....);
break;
}
} while (child = child.nextSibling);
}
function doTranslit(strtext,....) {
.....
return strtext;
}
Didn't check but you can try.
var pageText = document.body.innerHTML;
mypagewithoutScriptTag = pageText.replace(<script>(.*?)</script>);

Using JavaScript & jQuery in a single function (Nodes & Stuff)

I am currently learning jQuery. I know that jQuery is a custom library for JavaScript.
I am doing some learning examples in a book that is only using JavaScript, and to further my learning experience, I am trying to make use of jQuery for anything that might be more efficient.
So, I have this code:
function addLetter(foo) {
$(foo).unbind('click');
var tileLetter = $(foo).attr('class').split(' ');
var letter = tileLetter[2].charAt(1);
if (document.getElementById('currentWord').childNodes.length > 0) {
$('#currentWord p').append(letter);
} else {
var p = document.createElement('p');
var txt = document.createTextNode(letter);
p.appendChild(txt);
$('#currentWord').append(p);
}
}
Question #1:
If I change document.getElementById('currentWord').childNodes.length to $('#currentWord').childNodes.length it doesn't work. I thought the jQuery selector was the same thing as the JS document.getElementById as that it brought me back the DOM element. If that was the case, it'd make sense to be able to use the .childNodes.length functions on it; but it doesn't work. I guess it's not the same thing?
Question #2:
The code is textbook code. I have added all the jQuery that there is in it. My jQuery knowlede is limited, is there a more efficient way to execute the function?
The function's purpose:
This function is supposed to create a p element and fill it with a Text Node if it's the first time it's run. If the p element has already been created, it simply appends characters into it.
This is a word generating game, so you click on a letter and it gets added to a 'currentWord' div. The tile's letter is embedded in the 3rd css class, hence the attr splitting.
Thanks!
document.getElementById('currentWord')
returns a DOM object whereas $('#currentWord') returns a DOM object wrapped inside a jQuery object.
To get the plain DOM object you can do
$('#currentWord').get(0)
So
$('#currentWord').get(0).childNodes.length
should work.
Question #1:
jQuery returns a jQuery object. To return it to a regular javascript object use $(object)[0] and you can then treat it as a plain javascript (or DOM) object.
Question #2:
The efficiency looks good to me. Although you might want to use spans instead of p elements.
I guess one thing you could do (even though yours looks to run very fast) is cache the dom element:
function addLetter(foo) {
$(foo).unbind('click');
var tileLetter = $(foo).attr('class').split(' ');
var letter = tileLetter[2].charAt(1);
var currentWord = document.getElementById('currentWord');
if (currentWord.childNodes.length > 0) {
$(currentWord).find('p').append(letter);
} else {
var p = document.createElement('p');
p.innerHTML = letter;
currentWord.appendChild(p);
}
}
Calls to the jQuery() function ($()) return a jQuery object containing the matching elements, not the elements themselves.
Calling $('#some-id') will, then, return a jQuery object that contains the element that would be selected by doing document.getElementById('some-id'). In order to access that element directly, you can get it out of that jQuery object, using either the .get() function or an array index syntax: $('#some-id')[0] (it's 0-indexed).
I think you can replace all of this with a call to the text function.
function addLetter(foo) {
$(foo).unbind('click');
var tileLetter = $(foo).attr('class').split(' ');
var letter = tileLetter[2].charAt(1);
var currentWordP = $('#currentWord p');
if (currentWordP.size() > 0) {
currentWordP.text(currentWordP.text() + letter);
} else {
$('#currentWord').append("<p>" + letter + "</p>");
}
}
1: Use $.get(0) or $[0] to get the DOM element. e.x. $('#currentWord')[0].childNodes.length.
2: Try this:
function addLetter(foo) {
$(foo).unbind('click');
var tileLetter = $(foo).attr('class').split(' ');
var letter = tileLetter[2].charAt(1);
if ($('#currentWord p').length > 0) {
$('#currentWord p').append(letter);
} else {
$('#currentWord').append(
$('<p />', { text: letter })
);
}
}
Question #1:
document.getElementById returns DOM object. more
childNodes.length is property of Node object which is returned by document.getElementById.
jQuery selector returns jQuery object more. You can get DOM object from jQuery object using .get
$('#IDselector').get(0) = document.getElementById('IDselector')
Question #2:
function addLetter(foo) {
$(foo).unbind('click');
var tileLetter = $(foo).attr('class').split(' ');
var letter = tileLetter[2].charAt(1);
if ($('currentWord p').length > 0) {
$('#currentWord p').append(letter);
} else {
var p = $('<p />').text(letter);
$('#currentWord').append(p);
}
}

Replace all the ocurrance of a string in an element

I want to replace a particular string in (the text of) all the descendant elements of a given element.
innerHTML cannot be used as this sequence can appear in attributes. I have tried using XPath, but it seems the interface is essentially read-only. Because this is limited to one element, functions like document.getElementsByTagName cannot be used either.
Could any suggest any way to do this? Any jQuery or pure DOM method is acceptable.
Edit:
Some of the answers are suggesting the problem I was trying to work around: modifying the text directly on an Element will cause all non-Text child nodes to be removed.
So the problem essentially comes down to how to efficiently select all the Text nodes in a tree. In XPath, you can easily do it as //text(), but the current XPath interface does not allow you to change these Text nodes it seems.
One way to do this is by recursion as shown in the answer by Bergi. Another way is to use the find('*') selector of jQuery, but this is a bit more expensive. Still waiting to see if there' are better solutions.
Just use a simple selfmade DOM-iterator, which walks recursively over all nodes:
(function iterate_node(node) {
if (node.nodeType === 3) { // Node.TEXT_NODE
var text = node.data.replace(/any regular expression/g, "any replacement");
if (text != node.data) // there's a Safari bug
node.data = text;
} else if (node.nodeType === 1) { // Node.ELEMENT_NODE
for (var i = 0; i < node.childNodes.length; i++) {
iterate_node(node.childNodes[i]); // run recursive on DOM
}
}
})(content); // any dom node
A solution might be to surf through all available nodes (TextNodes included) and apply a regexp pattern on the results. To grab TextNodes as well, you need to invoke jQuerys .contents(). For instance:
var search = "foo",
replaceWith = 'bar',
pattern = new RegExp( search, 'g' );
function searchReplace( root ) {
$( root ).contents().each(function _repl( _, node ) {
if( node.nodeType === 3 )
node.nodeValue = node.nodeValue.replace( pattern, replaceWith );
else searchReplace( node );
});
}
$('#apply').on('click', function() {
searchReplace( document.getElementById('rootNode') );
});
Example: http://jsfiddle.net/h8Rxu/3/
Reference: .contents()
Using jQuery:
$('#parent').children().each(function () {
var that = $(this);
that.text(that.text().replace('test', 'foo'));
});
If you prefer to search through all children instead of just immediate children, use .find() instead.
http://jsfiddle.net/ExwDx/
Edit: Documentation for children, each, text, and find.
Sorry, just got it myself:
$('#id').find('*').each(function(){
$.each(this.childNodes, function() {
if (this.nodeType === 3) {
this.data = this.data.toUpperCase();
}
})
})
I used toUpperCase() here to make the result more obvious, but any String operation would be valid there.

Is there any way to find an element in a documentFragment?

var oFra = document.createDocumentFragment();
// oFra.[add elements];
document.createElement("div").id="myId";
oFra.getElementById("myId"); //not in FF
How can I get "myId" before attaching fragment to document?
All of these answers are rather old, from back when querySelectorAll and querySelector were not widely available. It should be noted that these two functions which accept CSS selectors as parameters do work on DocumentFragments in modern browsers, and should be the preferred way of dealing with the situation in the question. The alternate solutions presented in some of the answers would be a good approach for legacy browsers which did not support querySelectorAll or querySelector.
Here is an example usage:
var df = document.createDocumentFragment();
var div = document.createElement('div');
div.id = 'foo';
df.appendChild(div);
var result = df.querySelector('#foo'); // result contains the div element
A good implementation should first use object detection to see if the browser supports this. For instance:
function getElementByIdInFragment(fragment, id) {
if (fragment.querySelector) {
return fragment.querySelector('#' + id);
} else {
// your custom implementation here
}
}
No. The DocumentFragment API is minimal to say the least: it defines no properties or methods, meaning that it only supports the properties and methods defined in the Node API. As methods such as getElementById are defined in the Document API, they cannot be used with a DocumentFragment.
NickFitz is right, DocumentFragment doesn't have the API you expect from Document or Element, in the standard or in browsers (which is a shame; it would be really handy to be able to set a fragment's innerHTML.
Even frameworks don't help you here, as they tend to require Nodes be in the document, or otherwise use methods on the context node that don't exist on fragments. You'd probably have to write your own, eg.:
function Node_getElementById(node, id) {
for (var i= 0; i<node.childNodes.length; i++) {
var child= node.childNodes[i];
if (child.nodeType!==1) // ELEMENT_NODE
continue;
if (child.id===id)
return child;
child= Node_getElementById(child, id);
if (child!==null)
return child;
}
return null;
}
It would almost certainly be better to keep track of references as you go along than to rely on a naïve, poorly-performing function like the above.
var frag= document.createDocumentFragment();
var mydiv= document.createElement("div");
mydiv.id= 'myId';
frag.appendChild(mydiv);
// keep reference to mydiv
What about:
var oFra = document.createDocumentFragment();
var myDiv = document.createElement("div");
myDiv.id="myId";
oFra.appendChild(myDiv);
oFra.getElementById("myId"); //not in FF
Unless you've added the the created div to your document fragment I'm not sure why getElementById would find it?
--edit
If you're willing to roll your own getElementById function then you ought to be able to get the reference you're after, because this code works:
var oFra = document.createDocumentFragment();
var myDiv = document.createElement("div");
myDiv.id = "myId";
oFra.appendChild(myDiv);
if (oFra.hasChildNodes()) {
var i=0;
var myEl;
var children = oFra.childNodes;
for (var i = 0; i < children.length; i++) {
if (children[i].id == "myId") {
myEl = children[i];
}
}
}
window.alert(myEl.id);
Using jQuery:
// Create DocumentFragment
var fragment = document.createDocumentFragment(),
container = document.createElement('div');
container.textContent = 'A div full of text!';
container.setAttribute('id', 'my-div-1');
container.setAttribute('class', 'a-div-class');
fragment.appendChild(container);
// Query container's class when given ID
var div = $('<div></div>').html(fragment);
console.log(div.find('#my-div-1').attr('class'));
jsFiddle: http://jsfiddle.net/CCkFs/
You have the overhead of creating the div with jQuery, though. A little hacky, but it works.
The best way by far to find out what you can and can't do with a DocumentFragment is to examine its prototype:
const newFrag = document.createDocumentFragment();
const protNewFrag = Object.getPrototypeOf( newFrag );
console.log( '£ protNewFrag:' );
console.log( protNewFrag );
I get
DocumentFragmentPrototype { getElementById: getElementById(),
querySelector: querySelector(), querySelectorAll: querySelectorAll(),
prepend: prepend(), append: append(), children: Getter,
firstElementChild: Getter, lastElementChild: Getter,
childElementCount: Getter, 1 more… }
... which tells me I can do things like:
const firstChild = newFrag.children[ 0 ];
PS this won't work:
const firstChild = Object.getPrototypeOf( newFrag ).children[ 0 ];
... you'll be told that "the object doesn't implement the DocumentFragment interface"
An external source, listed below, showed the following code snippet:
var textblock=document.createElement("p")
textblock.setAttribute("id", "george")
textblock.setAttribute("align", "center")
Which displays a different way of setting the object's ID parameter.
Javascript Kit - Document Object Methods
My DOM has a #document-fragment under the element tag.
This is what I am using (using jQuery) , Also I have a use case where I have the HTML DOM in a string -
var texttemplate = $(filecontents).find('template').html();
$(texttemplate).children()
<p>​Super produced One​</p>​,
<appler-one>​</appler-one>,
<p>​Super produced Two​</p>,
<appler-two>​…​</appler-two>]
$(texttemplate).html()
"<p>Super produced One</p>
<appler-one></appler-one>
<p>Super produced Two</p>
<appler-two>
<p>Super produced Three</p>
<appler-three></appler-three>
</appler-two>"
$(texttemplate).find("appler-one")
[<appler-one>​</appler-one>​]

Categories

Resources