Switch HTML elements in DOM without copying HTML - javascript

Let's say I have two <div> elements in the DOM. I can easily switch their markup between them with jQuery, like so:
function switch_html(el1, el2){
var el1_content = el1.html();
var el2_content = el2.html();
el1.html(el2_content);
el2.html(el1_content);
}
However, how do I actually switch the DOM elements, and not just copy & switch the HTML source? For example, in the app I am currently developing, I am swapping out <div> contents that include filled out <input> fields. If the source HTML of these fields is merely copied, then the values contained within the inputs will be lost.

You can move the actual DOM nodes using jQuery append / element.appendChild. Move the actual DOM nodes, don't try to make copies/clones.
Try this:
var children1 = $('#el1').children();
var children2 = $('#el2').children();
$('#el1').append(children2);
$('#el2').append(children1);

javascript has a cloneNode() function which performs a "deep" clone when true is passed.
So:
function switch_html(el1, el2){
var el1Clone = el1.cloneNode(true);
var el2Clone = el2.cloneNode(true);
el2.replaceNode(el1Clone);
el1.replaceNode(el2Clone);
}
Or for a (slightly) more performant solution:
function switch_html(el1, el2){
var el2Clone = el2.cloneNode(true);
el2.replaceNode(el1);
el1.replaceNode(el2Clone);
}
Please note, that the assumption here is you want to do this in raw JavaScript, ie: no jQuery

Related

How to return a document fragment as a string of HTML

I'm creating an array of HTML elements.
var elements = [];
Most of these elements are being added to the array as simple strings of HTML, like this:
function myHtml() {
return '<div>Hi there, here\'s some HTML</div>';
}
elements.push(myHtml());
However, one of these elements is being generated as a document fragment, like this:
function myFragment() {
var fragment = document.createDocumentFragment();
var div = document.createElement('div');
div.appendChild(document.createTextNode('Hi there, here\'s some HTML');
fragment.appendChild(div);
return fragment;
}
elements.push(myFragment());
After creating the array, I'm inserting all of the elements into the DOM like this:
function myHtml() {...}
function myFragment() {...}
var elements = [];
elements.push(myHtml(), myFragment());
$('.my-selector').html(elements);
The problem, of course, is that the document fragment doesn't work like that, so it doesn't get inserted into the DOM the way I need it to. The reason I can't just append the fragment to .my-selector the normal way is because I'm generating different arrays based on different conditions, like:
if (condition1) {
elements.push(myHtml1(), myHtml2(), myFragment());
else {
elements.push(myHtml3(), myFragment(), myHtml4());
}
$('.my-selector').html(elements);
The order in which the elements appear matters.
I've found a ton of solutions for converting a string of HTML into a document fragment, but no way to convert a document fragment into a string of HTML so that I can use it in this manner. Is it possible? Is there something really simple that I'm missing?
Append it to an element and get the element's innerHTML
let div=document.createElement("div");
div.appendChild(fragment);
div.innerHTML;

If I have a list of DOM elements that need to be appended, is there a way to combine them first before appending?

Given the following code:
li = random_object["li_element"]
other = other_object["p_element"]
...
// I have a bunch of objects above, all of which were initially created and stored as jQuery objects, e.g.,
// random_object["li_element"] = $("<li>test</li>")
$("#target").append(li)
$("#target").append(other)
I understand that .append() is an expensive method, so I'm wondering is there a way to first combine the elements above, li and other, in that order, and then append them all at once?
Yes, both in jQuery and when using the DOM directly.
Using jQuery
You can create a jQuery object using the signature accepting an array wrapping those two elements and then append it:
$("#target").append($([li[0], other[0]]));
The [0] there are to put the raw elements, not the jQuery objects, in the array. $() accepts an array of DOM elements, but not jQuery objects. If those jQuery objects may have multiple elements in them, you need to do a bit more work. Using ES2015+:
$("#target").append($([...li.get(), ...other.get()]));
Using ES5 only:
var array = li.get();
array.push.apply(array, other.get());
$("#target").append($(array));
Live Example:
var li = $("<li>li</li>");
var other = $("<li>other</li>");
$("#target").append($([li[0], other[0]]));
<ul id="target"></ul>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Using the DOM directly
With the DOM, you can create a document fragment and append it:
var fragment = document.createDocumentFragment();
fragment.appendChild(li[0]);
fragment.appendChild(other[0]);
document.getElementById("target").appendChild(fragment);
The contents of the fragment are appended, not the fragment itself.
Again the [0] are so we access the raw DOM elements instead of the jQuery objects. And again, if there are multiple elements in li and/or other, you need to handle that:
var fragment = document.createDocumentFragment();
li.each(function() {
fragment.appendChild(this);
});
other.each(function() {
fragment.appendChild(this);
});
document.getElementById("target").appendChild(fragment);
Live Example:
var li = $("<li>li</li>");
var other = $("<li>other</li>");
var fragment = document.createDocumentFragment();
fragment.appendChild(li[0]);
fragment.appendChild(other[0]);
document.getElementById("target").appendChild(fragment);
<ul id="target"></ul>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I understand that .append() is an expensive method
Not especially, but if you had some seriously-performance-critical code and a lot of things to append, building them up in a fragment and then appending the fragment does reduce the overhead.

Delete elements in a javascript string

I have a string containing html elements, now I need to select some elements and remove them from the string.
In JQuery I tried the following:
html_string = "<ul><li data-delete>A<li><li>B</li></ul>";
html_clean_string = $(html_string).remove('[data-delete]').html();
This is what I expected:
"<ul><li>B</li></ul>"
But I got the same original string. So how can I use CSS selectors to remove html elements from a string?
You can do it like this:
var html_string = "<ul><li data-delete>A</li><li>B</li></ul>";
var elems = $(html_string);
elems.find('[data-delete]').remove();
var html_clean_string = elems[0].outerHTML;
You had a couple of issues:
.remove() only operates on the elements in the jQuery object, not on child object so you have to .find() the appropriate child elements before you can remove them.
Since you want the host top level HTML too, you will need the .outerHTML.
You had mistakes in your html_string.
Working jsFiddle: http://jsfiddle.net/jfriend00/x8ra6efz/
You can also save a little jQuery with more chaining like this:
var html_string = "<ul><li data-delete>A</li><li>B</li></ul>";
var html_clean_string = $(html_string).find('[data-delete]').remove().end()[0].outerHTML;
Working jsFiddle:http://jsfiddle.net/jfriend00/wmtascxf/

Does jQuery use create document fragment inside each loops?

So I've read that jQuery uses document fragments internally to make rendering faster. But I am wondering if anyone knows if jQuery would use createDocumentFragment in this situation where I'm appending img elements to the DOM using the each loop?
var displayArray = []; // Lots of img elements
$.each(displayArray, function()
{
$('#imgSection').append(this);
});
Or would I need to use this code in order to reduce the number of browser reflows?
var displayArray = []; // Lots of img elements
var imgHolder = $('<div/>');
$.each(displayArray, function()
{
imgHolder.append(this);
});
$('#imgSection').append(imgHolder);
Also, the displayArray is populated by other code, not shown here, that creates img elements based off of paths in a JSON file.
Thank you for any advice.
Why all the looping to add elements?
$('#imgSection').append("<div>" + displayArray .join("") + "</div>");
Okay so it is elements.
The quickest way is going to be using append with the array itself.
$("#out").append(elems);
other option using one div to append is
var div = $("<div/>").append(elems);
$("#out").append(div);
BUT appending a lot of images at once is going to be bad unless they are preloaded. That will be a bunch of http requests being queued up.
jsPerf test cases
No, if you use $.each() then jQuery won't use a DocumentFragment - jQuery has no way of knowing what you're going to do inside the loop and each iteration is independent.
The point of the document fragment is that you don't have to wrap all your new elements up in a wrapper element as you've done in your second example to limit the reflows.
jQuery apparently will use a document fragment if you pass an array of elements directly to .append() instead of iterating over them yourself.
If you really care about reflows (and have noticed the displaying to be slow), you can hide and show the image-holding element:
var displayArray = […]; // Lots of img elements
var holder = $('#imgSection').hide();
for (var i=0; i<displayArray.length; i++)
holder.append(displayArray[i]);
holder.show();

Loop through children of current node in TinyMCE

Suppose I have a specific table selected in TinyMCE, like this:
var ed = tinyMCE.activeEditor;
var selection = ed.selection.getContent();
var element = ed.dom.getParent(ed.selection.getNode(), 'table');
How do I loop through the tr elements inside this?
I suspect one of these methods might be relevant, but I'm so new to classes, I'm having trouble understanding how to apply them:
TinyMCE select(): http://www.tinymce.com/wiki.php/API3:method.tinymce.dom.DOMUtils.select
TinyMCE getAll(): http://www.tinymce.com/wiki.php/API3:method.tinymce.html.Node.getAll
You may loop through any node in tinymce like a regular html node because they are in fact regular html nodes.
So this will suffice:
var ed = tinyMCE.activeEditor;
var element = ed.dom.getParent(ed.selection.getNode(), 'table');
var child = element.firstChild;
while(child){
if(child.nodeName.toLowerCase() == 'tr'){
//do your stuff here
}
child = child.nextSibling;
}
Doesn't var element has property childNodes? It's an array of immediate child elements. Each of those will further have properties, where you would be interested in nodeName. Make a recursive function to search (each node further has childNodes), until you find that nodeName=="TR".
BTW, this would be a lot easier with jQuery, if you're interested.
http://www.w3schools.com/htmldom/dom_methods.asp
http://www.w3schools.com/htmldom/dom_nodes_info.asp

Categories

Resources