Range object: differences between Webkit and Mozilla based browsers - javascript

at the moment I have some troubles writing an abstraction layer for Mozilla and Webkit based browsers for using the DOM-range object (getting and processing user selections).
I have also tried to have a look at frameworks like Rangy but this seems far to complex for my task (I have no idea where exactly in the code to find the information I need. If someone could give me a hint, I would be grateful!).
What I want is simply this:
get back the reference to the text node the selection starts in and its offset
get back the reference to the text node the selection ends in and its offset
So far my layer looks like this:
var SEL_ABSTR = {
get_selection: function(window_object) {
return window_object.getSelection();
},
get_range: function(selection) {
return (selection.getRangeAt) ? selection.getRangeAt(0) : selection.createRange();
},
get_range_info: function(range, div_ele) {
var first_node, start_offset;
var last_node, end_offset;
if (range.startContainer == div_ele) {
// selections affects the containing div
first_node = div_ele.childNodes[0];
last_node = first_node;
start_offset = 0;
end_offset = first_node.nodeValue.length;
} else if (range.startOffset == range.startContainer.nodeValue.length && range.endOffset == 0) {
// known bug in Firefox
alert('firefox bug');
first_node = range.startContainer.nextSibling.childNodes[0];
last_node = first_node;
start_offset = 0;
end_offset = first_node.nodeValue.length;
} else {
first_node = range.startContainer;
last_node = range.endContainer;
start_offset = range.startOffset;
end_offset = range.endOffset;
}
return {
first_node: first_node,
start_offset: start_offset,
last_node: last_node,
end_offset: end_offset,
orig_diff: end_offset - start_offset
};
},
};
I have identified two Mozilla bugs for now:
Sometimes when the whole (if its the only one) text node is selected within the containing div I get back a reference to this div instead of a reference to the text node. Now I can handle it and give back a reference to the child of the div which is the text node
Sometimes I get back a reference to the previous sibling with offset == prevSibling.length and and a reference to nextSibling with offset == 0. But the correct reference would be in the middle. I can also handle this.
So what more is there for Mozilla? Webkit works fine!
One should assume that the DOM-range object is standard (and I am not talking of IE, this is another task ...)
greets!
more specific details here:
It was't meant as a critique on Rangy. So I am sorry if it sounded like that. I can imagine that handling these different APIs is not easy per se.
You are right, I wasn't specific regarding the task I am trying to fulfill. My structure is rather simple: I have a div (with attribute contenteditable=true) and text within that div (one text node at the beginning).
Starting from this structure, it is now possible to select text with the mouse and add a property to it; this property is then expressed by a span embracing the selected text and a class assigned to that span representing the selected property. Now it is possible to select again some text and a property. If it is the same text and another property, another class will be assigned to that span or removed if the property already exists. If text is selected which embraces several spans, they will be split in order to express that structure (perhaps you remember my post of July).
<div contenteditable="true">
hello I am
<span class="red">text but I am also <span class="underline">underlined</span></span>
<span class="underline"> also without color</span>
</div>
The algorithm works fine now for "symmetrical" cases: I can build a complex structure and then undo it backwards. It works fine for Safari and Chrome. Now I have of course to develop the algorithm further.
But for now I have problems with Mozilla because I do not understand the system for DOM range objects: startContainer, endContainer, startOffset, endOffset
In my perception regarding my specific case with a div only containing textnodes and spans I assume:
that startContainer and endContainer always point to a textnode affected by the mouse selection (there are no empty spans, they always contain either other spans or a textnode), marking the beginning and the end of the whole selection
that startOffset and endOffset indicate the position of the selection within the textnode at the beginning and at the end
In the posted code above I have identified two cases in which Mozilla acts differently from webkit.
So if I knew the rules of Mozilla DOM-range I could inegrate that in my layer so that the behaviour would be the same for webkit and Mozilla.
Thank you very much for your answer.

There is no rule that says selection boundaries must be expressed in terms of text nodes. Consider a selection inside an element that contains only <img> elements, for example. So, what you're calling bugs in Mozilla are not bugs at all; in fact, WebKit's selection handling is much buggier than Mozilla's. However, your observation that browsers vary in precisely where they consider a selection boundary to lie when it is at the end of a text node is valid and does complicate things. The best way to deal with it really depends on what you're trying to do, which isn't clear from your question.
If you want selection boundaries purely in terms of character offsets within the text content of an element, you can do this (although I'd generally recommend against it for reasons laid out in the linked answer).
Finally, as author of Rangy, I'd like to point out that it's based on the same APIs (DOM Range and Selection) that browsers implement, so I'd say it's no more or less complicated than those APIs. References:
Selection (work in progress)
DOM 2 Range (implemented by current versions of all major browsers)
DOM4 Range (successor to DOM 2 Range, work in progress)

You are noticing a bug where Gecko/Firefox and Presto/Opera incorrectly select the node in which the mouse cursor clicked though did not select. Wait, what? What happens is that if the click registers less than HALF of a character (though greater than 0 pixels) it is not VISUALLY selected HOWEVER Firefox and Opera still select the node itself! Trident/IE and WebKit (Chrome/Safari) do not do this.
I have been battling against this bug for some time, filed a bug at Mozilla (can't remember if I did so with Opera since this was last year) and today finally wrote directly to the editors of the DOM4 specification asking them to implement an EXPLICIT clarification for browser vendors on how to define the startContainer.
It IS possible to adapt the code to handle this however I do not have the time to write out all the code for you.
Here is the list of methods we'll use...
window.getSelection().getRangeAt(0).startContainer;
window.getSelection().anchorNode
window.getSelection().focusNode
It's VERY important to remember that the anchorNode is not EXPLICITLY the left starting position though the INITIAL CLICK. If the user click on the right side of text and drags the mouse to the left then the anchor ends up on the RIGHT side of the range. If the user click on the left side of text and then drags the mouse to the right the anchor is then on the left side of the range.
Essentially what you can try to do is see if neither the anchorNode nor the focusNode EXPLICITLY match the startContainer.
var sc = window.getSelection().getRangeAt(0).startContainer;
var an = window.getSelection().anchorNode
var fn = window.getSelection().focusNode
if (sc!==an && sc!==fn) {alert('startContainer bug encountered!');}
Even if you don't need the startContainer in ALL situations you're still going to eventually reference it SO it's best to use an object to represent the startContainer be it if the browser gets it right the first time (Trident/WebKit) or you have to correct it (Gecko/Presto).
This is where it gets a bit tricky especially because different people will have different goals and approaches so I will try to keep the following as generic as possible.
Either you can determine the correct startContainer using anchorNode or focusNode methods OR you can use object detection and W3C compliant methods. Those other methods include....
window.getSelection().getRangeAt(0).startContainer
window.getSelection().getRangeAt(0).startContainer.parentNode
window.getSelection().getRangeAt(0).startContainer.previousSibling
window.getSelection().getRangeAt(0).startContainer.nextSibling
window.getSelection().getRangeAt(0).startContainer.childNodes[]
When dealing with style elements such as s (strike), strong, em (emphasis) and so on you may access the textNode using the firstChild unless you have multiple style elements enclosed around the text.
.nextSibling.firstChild
.nextSibling.firstChild.nodeValue
<em>textNode here</em>
If you're having difficulty with determining what methods are available at what parts I recommending using the in operator. In example...
for (i in window.getSelection())
{
document.getElementById('textarea_example').value = document.getElementById('textarea_example').value+'\n'+i;
}
...keep in mind that if you're inside of a loop that it may repeat the options in your textarea element so CTRL+f for the first method and erase from it's second instance down to retain only relevant methods.
Remember to use alert and I often use multiple lines to show multiple pieces of information simultaneously to help me determine what I have. In example...
var e1 = scp.nodeName;
if (scp.nextSibling) {var e2 = scp.nextSibling.nodeName;} else {var e2 = 'null';}
var e3 = sc.nodeName;
if (sc.nextSibling) {var e4 = sc.nextSibling.nodeName;} else {var e4 = 'null';}
alert(
'startContainer = '+window.getSelection().getRangeAt(0).startContainer.nodeName
+'\n\n'+
'startContainer = '+window.getSelection().getRangeAt(0).startContainer.nodeValue
+'\n\n'+
e1
+'\n\n'+
e2
+'\n\n'+
e3
+'\n\n'+
e4
+'\n\nanchorNode = '+
window.getSelection().anchorNode.nodeName
+'\n\n'+
window.getSelection().anchorNode.nodeValue
+'\n\nfocusNode = '+
window.getSelection().focusNode.nodeName
+'\n\n'+
window.getSelection().focusNode.nodeValue
);
if (e2=='#text') {alert('e2 = '+scp.nextSibling.nodeValue);}
if (e4=='#text') {alert('e4 = '+scp.nextSibling.nodeValue);}

Related

Chrome extension - The new >>Manifest_version: 3<< Problem [duplicate]

Can the JavaScript command .replace replace text in any webpage? I want to create a Chrome extension that replaces specific words in any webpage to say something else (example cake instead of pie).
The .replace method is a string operation, so it's not immediately simple to run the operation on HTML documents, which are composed of DOM Node objects.
Use TreeWalker API
The best way to go through every node in a DOM and replace text in it is to use the document.createTreeWalker method to create a TreeWalker object. This is a practice that is used in a number of Chrome extensions!
// create a TreeWalker of all text nodes
var allTextNodes = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT),
// some temp references for performance
tmptxt,
tmpnode,
// compile the RE and cache the replace string, for performance
cakeRE = /cake/g,
replaceValue = "pie";
// iterate through all text nodes
while (allTextNodes.nextNode()) {
tmpnode = allTextNodes.currentNode;
tmptxt = tmpnode.nodeValue;
tmpnode.nodeValue = tmptxt.replace(cakeRE, replaceValue);
}
To replace parts of text with another element or to add an element in the middle of text, use DOM splitText, createElement, and insertBefore methods, example.
See also how to replace multiple strings with multiple other strings.
Don't use innerHTML or innerText or jQuery .html()
// the innerHTML property of any DOM node is a string
document.body.innerHTML = document.body.innerHTML.replace(/cake/g,'pie')
It's generally slower (especially on mobile devices).
It effectively removes and replaces the entire DOM, which is not awesome and could have some side effects: it destroys all event listeners attached in JavaScript code (via addEventListener or .onxxxx properties) thus breaking the functionality partially/completely.
This is, however, a common, quick, and very dirty way to do it.
Ok, so the createTreeWalker method is the RIGHT way of doing this and it's a good way. I unfortunately needed to do this to support IE8 which does not support document.createTreeWalker. Sad Ian is sad.
If you want to do this with a .replace on the page text using a non-standard innerHTML call like a naughty child, you need to be careful because it WILL replace text inside a tag, leading to XSS vulnerabilities and general destruction of your page.
What you need to do is only replace text OUTSIDE of tag, which I matched with:
var search_re = new RegExp("(?:>[^<]*)(" + stringToReplace + ")(?:[^>]*<)", "gi");
gross, isn't it. you may want to mitigate any slowness by replacing some results and then sticking the rest in a setTimeout call like so:
// replace some chunk of stuff, the first section of your page works nicely
// if you happen to have that organization
//
setTimeout(function() { /* replace the rest */ }, 10);
which will return immediately after replacing the first chunk, letting your page continue with its happy life. for your replace calls, you're also going to want to replace large chunks in a temp string
var tmp = element.innerHTML.replace(search_re, whatever);
/* more replace calls, maybe this is in a for loop, i don't know what you're doing */
element.innerHTML = tmp;
so as to minimize reflows (when the page recalculates positioning and re-renders everything). for large pages, this can be slow unless you're careful, hence the optimization pointers. again, don't do this unless you absolutely need to. use the createTreeWalker method zetlen has kindly posted above..
have you tryed something like that?
$('body').html($('body').html().replace('pie','cake'));

Setting a BBEdit insertion point with JXA

I am trying to translate a few Applescript (AS) into Javascript (JXA) for BBedit.
It's been a fun little thing to knock some rust off, but I'm stumped.
With AS I can set an insertion point into a BBEdit document thusly;
tell application "BBEdit"
activate
tell text 1 of window 1
select insertion point before line 40
end tell
end tell
I'm totally stumped when it comes to JXA. I've been poking around in the line objects but I can't find a insertsionPoint property.
You can access selection properties like so;
currentLine = bbedit.selection().startline().
But it is read only. So I think you need access to that select method if you want to set a selection or insertion point. I have no clue how, or if you can with JXA.
Anyone know how to set a BBEdit insertion point and/or selection with JXA?
Thanks.
JXA doesn't implement insertion reference forms (before/after/beginning/end). Relative (previous…/next…) and range (from…to…) specifiers are also borked, and filter (whose…) clauses are horrible too. Lots of non-trivial stuff that works fine in AS breaks in JXA: like Apple's earlier Scripting Bridge API, JXA was half-baked when shipped and immediately abandoned to rot.
Crap like this is why I recommend sticking to AppleScript. The language may stink, but it's the only [marginally] supported option that actually implements Apple events right. Normally I'd recommend calling into AppleScript from other languages via the AppleScript-ObjC bridge as the least sucky solution, but Apple have managed to break that too in 10.13.
(If you enjoy living dangerously, NodeAutomation provides non-broken Apple event support for Node.js, but with Apple abandoning AppleScript automation I don't want to waste anyone's time promoting or supporting it, so caveat emptor.)
Below is an example of using Javascript (JXA) for BBedit insertion point object and select method. BBEdit does seem to work with JXA but there is absolutely no documentation for JXA from BBEdit and very little information on the web. Below is some code that I came up with after spending hours of trial and error. So I hope it helps.
(() => {
/* Example to show using insertionPoints with BBEdit
the script adds new text to the last line of a BBEdit text document
*/
const strPath = '/Users/bartsimpson/Library/Mobile Documents/com~apple~ScriptEditor2/Documents/insertionPointsExample.txt';
const BBEdit = Application('BBEdit');
const docs = BBEdit.textDocuments;
//use select in case there are multiple BBEdit documents open
BBEdit.open(Path(strPath)).select();
let doc = docs[0];
let insertionPoints = doc.characters.insertionPoints;
let lines = doc.characters.lines;
let indexLastChar = doc.characters.length;
let indexLastLine = lines.length-1;
//last line in doc is blank
if (lines[indexLastLine].length() == 0){
insertionPoints[indexLastChar].contents = 'some new text5';
indexLastChar = doc.characters.length; //update after adding text
insertionPoints[indexLastChar].select(); //puts cursor end of last line
}
//last line in doc has text so add a new line first
else {
insertionPoints[indexLastChar].contents = '\n';
insertionPoints[indexLastChar+1].contents = 'some new text6';
indexLastChar = doc.characters.length; //update after adding text
insertionPoints[indexLastChar].select(); //puts cursor end of last line
}
})()

What is causing this JavaScript associative array to go out of whack?

I have an associative array that I've verified (via console.log) is originally
this.RegionsChecked = {"US":true,"APAC":true,"Canada":true,"France":true,"Germany":true,"India":true,"Japan":true,"LATAM":true,"MEA":true,"UK":true,"WE":true};
and I have an event handler that attempts to toggle the value of a corresponding item when it is checked/unchecked in the HTML:
Calculator.prototype.UpdateGraphs = function ( $elem )
{
// $elem : input[type="checkbox"] element that was clicked, as a jQuery object
var $parent = $elem.parent(); // Parent of input that was clicked.
// Will either be a th.region or a td#guiderow-[metric_name]
if ( $parent.hasClass('region') )
{
var region_name = $parent.text();
this.RegionsChecked[region_name] = !this.RegionsChecked[region_name]; // toggle region name
console.log(JSON.stringify(this.RegionsChecked)); // TEST
}
// ...
What is strange is that when I change the check value of Canada, for example, the array sometimes turns to
this.RegionsChecked = {"US":true,"APAC":true,"Canada":true,"France":true,"Germany":true,"India":true,"Japan":true,"LATAM":true,"MEA":true,"UK":true,"WE":true,"\n\t\t\t\t\tCanada":true};
(look at the last key, what previously was "Unknown")
instead of the expected
this.RegionsChecked = {"US":true,"APAC":true,"Canada":false,"France":true,"Germany":true,"India":true,"Japan":true,"LATAM":true,"MEA":true,"UK":true,"WE":true};
which is does some of the time, I think (but still have to verify). I'm still trying to figure out how consistently it is happening, but you have any ideas on why?
EDIT: Weird ... It just did it correctly. I can't find any discernable pattern in when it works and doesn't .. I am using Microsoft Sharepoint Designer, which can do strange things ...
In general, it's not a very good idea to depend on the actual text of a DOM node as a key for information, especially if that DOM is dynamically generated by a large unwieldy CMS-like like SharePoint that performs various transforms on your text before it ends up in the DOM (using the Designer adds another fun step to that chain). That is what constructs like data attributes are for.
That said, if you have to do it, you are probably better off trimming your text before using it. I.e.,
var region_name = $.trim($parent.text());

Return Sentence That A Clicked Word Appears In

Follow up to a previous question: Use Javascript to get the Sentence of a Clicked Word
I have been fumbling around with this question for some time now. However, I woke up this morning and started reading this: http://branch.com/b/nba-playoffs-round-1
Voila! Branch allow for users to select a sentence and then share it, save it, etc...That's exactly what I want to do. It looks like they're wrapping each sentence in <span> tags.
Previously, people have suggested to find each <p> tag and then break each sentence up within the tag. However, I am making a chrome extension and this needs to work on virtually any website, so a word could appear outside a <p> tag, maybe in an <h1> type tag or even in a <div>.
Any insight into how Branch did it?
They appear to be wrapping everything in <span>s, and also adding meta-data about the character counts. From their source:
<p><span class="highlight js-highlight-this" data-end-char="23"
data-highlight-count="0" data-start-char="0" id="highlight-86552-0">No
doubt they can lose.</span> <span class="highlight js-highlight-this"
data-end-char="132" data-highlight-count="0" data-start-char="24" id=
"highlight-86552-24">As Adi says, I don't think they will, but OKC - in
particular - still looms as a legit threat to the throne.</span>
<span class="highlight js-highlight-this" data-end-char="336"
data-highlight-count="0" data-start-char="133" id="highlight-86552-133">The
Thunder are better on both ends this year than last, have the experience of
having been there before, and you know Durant doesn't want to spend the
rest of his career playing second fiddle to LeBron.</span> <span class=
"highlight js-highlight-this" data-end-char="588" data-highlight-count="0"
data-start-char="337" id="highlight-86552-337">The problem, and I think the
reason so many assume the Heat will repeat, is that we haven't seen this
version of the Thunder (with Kevin Martin rather than James Harden in the
6th man role) in the playoffs before so the mystery factor comes into
play.</span></p>
However, another more flexible approach would be simply to use regular expression matching to extract sentences from the text of any element, whether it be a span, p, h1, etc.
In this scenario, you would find the sentences via regular expression matching, and then surround each one with a <span> element dynamically using javascript. Then you could attach your event listeners to those dynamically created tags to do the highlighting and whatever else you desired to do on hover, on click, etc.
You can do something like this, not quite the same as what you are after, I think? But may give you a start to further ideas.
<div>In cryptography, a keyed-hash message authentication code (HMAC) is a specific construction for calculating a message authentication code (MAC) involving a cryptographic hash function in combination with a secret cryptographic key. As with any MAC, it may be used to simultaneously verify both the data integrity and the authentication of a message. Any cryptographic hash function, such as MD5 or SHA-1, may be used in the calculation of an HMAC; the resulting MAC algorithm is termed HMAC-MD5 or HMAC-SHA1 accordingly. The cryptographic strength of the HMAC depends upon the cryptographic strength of the underlying hash function, the size of its hash output, and on the size and quality of the key.</div>
<button id="get">Get Selected</button>
function getText() {
var selectedText
if (typeof window.getSelection === "function") {
selectedText = window.getSelection();
} else if (typeof document.getSelection === "function") {
selectedText = document.getSelection();
} else if (document.selection && typeof document.selection.createRange() === "function") {
selectedText = document.selection.createRange().text;
} else {
selectedText = "";
alert("No method to get selected text");
}
if (!selectedText || selectedText === "") {
if (document.activeElement.selectionStart) {
selectedText = document.activeElement.value.substring(
document.activeElement.selectionStart.document.activeElement.selectionEnd);
}
}
alert(selectedText);
}
document.getElementById("get").addEventListener("click", getText, false);
on jsfiddle
you can also see a further answer where I have expanded on this idea here on SO.
the author pulled the other question but here is the other jsfiddle
window.getSelection
Summary
Returns a selection object representing the range of text selected by
the user.
Specification
DOM Level 0. Not part of any standard.
It is expected to be specified in a new DOM Range spec
There is also a library called Rangy that is supposed to handle this kind of thin cross-browser, never tried it but you may want to take a look.
A cross-browser JavaScript range and selection library. It provides a
simple standards-based API for performing common DOM Range and
Selection tasks in all major browsers, abstracting away the wildly
different implementations of this functionality between Internet
Explorer up to and including version 8 and DOM-compliant browsers.

Why should y.innerHTML = x.innerHTML; be avoided?

Let's say that we have a DIV x on the page and we want to duplicate ("copy-paste") the contents of that DIV into another DIV y. We could do this like so:
y.innerHTML = x.innerHTML;
or with jQuery:
$(y).html( $(x).html() );
However, it appears that this method is not a good idea, and that it should be avoided.
(1) Why should this method be avoided?
(2) How should this be done instead?
Update:
For the sake of this question let's assume that there are no elements with ID's inside the DIV x.
(Sorry I forgot to cover this case in my original question.)
Conclusion:
I have posted my own answer to this question below (as I originally intended). Now, I also planed to accept my own answer :P, but lonesomeday's answer is so amazing that I have to accept it instead.
This method of "copying" HTML elements from one place to another is the result of a misapprehension of what a browser does. Browsers don't keep an HTML document in memory somewhere and repeatedly modify the HTML based on commands from JavaScript.
When a browser first loads a page, it parses the HTML document and turns it into a DOM structure. This is a relationship of objects following a W3C standard (well, mostly...). The original HTML is from then on completely redundant. The browser doesn't care what the original HTML structure was; its understanding of the web page is the DOM structure that was created from it. If your HTML markup was incorrect/invalid, it will be corrected in some way by the web browser; the DOM structure will not contain the invalid code in any way.
Basically, HTML should be treated as a way of serialising a DOM structure to be passed over the internet or stored in a file locally.
It should not, therefore, be used for modifying an existing web page. The DOM (Document Object Model) has a system for changing the content of a page. This is based on the relationship of nodes, not on the HTML serialisation. So when you add an li to a ul, you have these two options (assuming ul is the list element):
// option 1: innerHTML
ul.innerHTML += '<li>foobar</li>';
// option 2: DOM manipulation
var li = document.createElement('li');
li.appendChild(document.createTextNode('foobar'));
ul.appendChild(li);
Now, the first option looks a lot simpler, but this is only because the browser has abstracted a lot away for you: internally, the browser has to convert the element's children to a string, then append some content, then convert the string back to a DOM structure. The second option corresponds to the browser's native understanding of what's going on.
The second major consideration is to think about the limitations of HTML. When you think about a webpage, not everything relevant to the element can be serialised to HTML. For instance, event handlers bound with x.onclick = function(); or x.addEventListener(...) won't be replicated in innerHTML, so they won't be copied across. So the new elements in y won't have the event listeners. This probably isn't what you want.
So the way around this is to work with the native DOM methods:
for (var i = 0; i < x.childNodes.length; i++) {
y.appendChild(x.childNodes[i].cloneNode(true));
}
Reading the MDN documentation will probably help to understand this way of doing things:
appendChild
cloneNode
childNodes
Now the problem with this (as with option 2 in the code example above) is that it is very verbose, far longer than the innerHTML option would be. This is when you appreciate having a JavaScript library that does this kind of thing for you. For example, in jQuery:
$('#y').html($('#x').clone(true, true).contents());
This is a lot more explicit about what you want to happen. As well as having various performance benefits and preserving event handlers, for example, it also helps you to understand what your code is doing. This is good for your soul as a JavaScript programmer and makes bizarre errors significantly less likely!
You can duplicate IDs which need to be unique.
jQuery's clone method call like, $(element).clone(true); will clone data and event listeners, but ID's will still also be cloned. So to avoid duplicate IDs, don't use IDs for items that need to be cloned.
It should be avoided because then you lose any handlers that may have been on that
DOM element.
You can try to get around that by appending clones of the DOM elements instead of completely overwriting them.
First, let's define the task that has to be accomplished here:
All child nodes of DIV x have to be "copied" (together with all its descendants = deep copy) and "pasted" into the DIV y. If any of the descendants of x has one or more event handlers bound to it, we would presumably want those handlers to continue working on the copies (once they have been placed inside y).
Now, this is not a trivial task. Luckily, the jQuery library (and all the other popular libraries as well I assume) offers a convenient method to accomplish this task: .clone(). Using this method, the solution could be written like so:
$( x ).contents().clone( true ).appendTo( y );
The above solution is the answer to question (2). Now, let's tackle question (1):
This
y.innerHTML = x.innerHTML;
is not just a bad idea - it's an awful one. Let me explain...
The above statement can be broken down into two steps.
The expression x.innerHTML is evaluated,
That return value of that expression (which is a string) is assigned to y.innerHTML.
The nodes that we want to copy (the child nodes of x) are DOM nodes. They are objects that exist in the browser's memory. When evaluating x.innerHTML, the browser serializes (stringifies) those DOM nodes into a string (HTML source code string).
Now, if we needed such a string (to store it in a database, for instance), then this serialization would be understandable. However, we do not need such a string (at least not as an end-product).
In step 2, we are assigning this string to y.innerHTML. The browser evaluates this by parsing the string which results in a set of DOM nodes which are then inserted into DIV y (as child nodes).
So, to sum up:
Child nodes of x --> stringifying --> HTML source code string --> parsing --> Nodes (copies)
So, what's the problem with this approach? Well, DOM nodes may contain properties and functionality which cannot and therefore won't be serialized. The most important such functionality are event handlers that are bound to descendants of x - the copies of those elements won't have any event handlers bound to them. The handlers got lost in the process.
An interesting analogy can be made here:
Digital signal --> D/A conversion --> Analog signal --> A/D conversion --> Digital signal
As you probably know, the resulting digital signal is not an exact copy of the original digital signal - some information got lost in the process.
I hope you understand now why y.innerHTML = x.innerHTML should be avoided.
I wouldn't do it simply because you're asking the browser to re-parse HTML markup that has already been parsed.
I'd be more inclined to use the native cloneNode(true) to duplicate the existing DOM elements.
var node, i=0;
while( node = x.childNodes[ i++ ] ) {
y.appendChild( node.cloneNode( true ) );
}
Well it really depends. There is a possibility of creating duplicate elements with the same ID, which is never a good thing.
jQuery also has methods that can do this for you.

Categories

Resources