I am in search for an efficient method to replace a couple of DOM Elements - if possible in one action only.
It is needed for augmenting TextNodes. If a certain word is present, it shall be wrapped in span-Tags, to which an event handler needs to be assigned later.
so if a text Node contains the word it needs to turn from
-#textNode
to
- #textNode
- span
- #textNode
- #textNode
While leaving the rest of the document untouched.
Known/assumed issues:
- innerHTML would be fast, but the Elements would need to have a generated ID and be later retrieved later again via getElementByID; not the cleanest/fastest way.
- Cloning the node, putting it into an documentFragment, doing all operations and replacing it back again would be good – but cloning does discard event handlers as far as I know. Since this code is to be used in an Bookmarklet/Add-On this is not an option than.
Use the DOM Level 1 Core method splitText on the Text node, once on the point just after the target text then once just before. createElement a new span, replaceChild the middle Text node (containing the target text) and appendChild that Text node into the span. Add event handlers etc. to the span as necessary.
No need to start messing with innerHTML or clever Range/adjacentHTML hacks; you're not really going to get any faster than this. Don't believe the hype: DOM manipulations like splitText or a single insertion aren't slow at all. It's only when you try to manipulate a lot of contiguous nodes all at once that other methods can improve matters.
Related
This problem that I'm facing is common for me and I want to learn for about best practices.
My problem is:
I have to wait a text which has an attribute of ".title" class and the text involves the statement of: "Hello". Before triggering this element to come to surface, we have an element already have attributes of ".title" which have a text of "StatementX" as well (At the end of the process, I have 2 ".title" class items on screen).
When I tried to wait for the element "Hello", I write:
`cy.get('.title').contains('Hello').should('be.visible')
`
Since "StatementX" is already on the screen, Cypress finds ".title" class and does not check "contains" part. What is the best practice to handle such cases?
Thank you so much
If you move the class .title into command .contains() it will focus solely on the element you wish to test, i.e two criteria will be tested in one command call and it will find the specific element.
cy.contains('.title', 'Hello').should('be.visible')
If you don't have any difference at all in those elements you will have a return of an array of elements.
In that part .eq(NUMBER OF THE ARRAY) you can validate the option that you want.
Also you can use XPATH for those cases. Not recommended since it's volatile but since is old stuff that is not changed it should have an higher change of stability.
Here is my HTML snippet.
<div id='advanced'>
<a id='javscript'>testing</a>
</div>
I need to find the parent node given the innerhtml testing.
As others have said you may want to consider re-thinking whatever it is you're trying to achieve. Anyway, if you need to do just one lookup then you just need to iterate over each node in the DOM tree and test your string against the text content. If you need to do it multiple times then this will get very slow. So a possible solution is this: loop over the DOM and put the innerHTML into a hash, with the text as the key (field) and the parent node as the value. Then for any text you can test against the hash, returning the parent node if it exists. But - don't just bang in every innerHTML for every node - this will result in a large object. If it's just text nodes you're after then only stick these into your hash.
You might want to consider going with a library, specifically jQuery.
It would make this type of search very easy for you.
This should work:
http://api.jquery.com/contains-selector/
I've a jQuery object and I shall retrieve the next sibling node, which may be a text node.
For example:
<div id="test">some text<br/>other text</div>
var test = $("#test");
var br = $("br", test);
alert(br.next().length); // No next ELEMENTS
alert(br.get(0).nextSibling.nodeValue); // "other text"
alert(br.get(0).nextSibling.nextSibling); // null
The DOM level 2 allows to get the next NODE sibling, but jQuery next() works on elements (nodeType 1, I guess).
I am asking this because I'm already using jQuery and I prefer to don't touch the DOM nodes directly (also because jQuery itself may provide a layer of abstraction from DOM and may run where DOM level 2 is not supported, but this is only a thought).
If jQuery doesn't provide this, shall I use DOM like above, or there are better options?
EDIT: I forgot something: I don't want to get ONLY text elements, but any kind of node, just as nextSibling does.
I'm using .contents() to iterate over the content, but this is pretty annoying (and slow, and many other bad things) when you just need the next node and DOM provides a solution.
EDIT2: Looking jQuery source code, it seems it relies on nextSibling.
Use the DOM. Don't be scared of it; it's easy and you already seem to know what to use. jQuery is built on top of the DOM and for this kind of thing, using the DOM will in fact work in more browsers than the jQuery version.
To my knowledge, there is no method in jquery like nextSibling in javaScript which also returns text elements.But you can go to the parent element and use contents() as it will also consider text elements.
I have HTML page with some HTML element with ID="logo". I need to create JS script (with no external libs calls) that will overwrite that html element with other HTML element like "<div id=logo> stuff inside </div>".
Most of the time, it's just the content you want to replace, not the element itself. If you actually replace the element, you'll find that event handlers attached to it are no longer attached (because they were attached to the old one).
Replacing its content
Replacing the element's content is easy:
var element;
element = document.getElementById("logo");
if (element) {
element.innerHTML = "-new content-";
}
The innerHTML property has only recently been standardized, but is supported by all major browsers (probably most minor ones, too). (See notes below about innerHTML and alternatives.)
Replacing the element iself
Actually replacing the element itself is a little harder, but not much:
var element, newElement, parent;
// Get the original element
element = document.getElementById("logo");
// Assuming it exists...
if (element) {
// Get its parent
parent = element.parentNode;
// Create the new element
newElement = document.createElement('div');
// Set its ID and content
newElement.id = "logo";
newElement.innerHTML = "-new content here-";
// Insert the new one in front of the old one (this temporarily
// creates an invalid DOM tree [two elements with the same ID],
// but that's harmless because we're about to fix that).
parent.insertBefore(newElement, element);
// Remove the original
parent.removeChild(element);
}
Notes on innerHTML and other DOM manipulation techiques
There are a number of wrinkles around using innerHTML in certain browsers, mostly around tables and forms. If you can possibly use a library like jQuery, Prototype, etc., I'd do so, as they've got workarounds for those issues built-in.
Alternatively, you can use the various other DOM methods rather than innerHTML (the same ones I used for creating the div and adding/removing, above). Note that in most browsers, doing any significant amount of markup by doing a bunch of createElement, appendChild, etc., calls rather than using innerHTML will be dramatically slower. Parsing HTML into their internal structures and displaying it is fundamentally what browsers do, and so they're highly optimized to do that. When you go through the DOM interface, you're going through a layer built on top of their internal structures and not getting the advantage of their optimizations. Sometimes you have to do it that way, but mostly, innerHTML is your friend.
Do you really need to 'replace' the element or can you just toggle its visibility? This is a technique that's much simpler and will be more efficient. Most importantly it keeps the content (html) separated from the behavior (javascript).
function toggle() {
document.getElementById("logo").style.display="none";
document.getElementById("element_to_show").style.display="block";
}
see T.J.'s answer if you actually want to replace the element.
Do you really need to 'replace' the element or can you just toggle its visibility? This is a technique that's much simpler and will be more efficient. Most importantly it keeps the content (html) separated from the behavior (javascript).
I'm performance-tuning my code, and am surprised to find that the bottleneck is not dom node insert, but selection.
This is fast:
var row = jquery(rowHTML).appendTo(oThis.parentTable);
but the subsequent getting of an element inside "row" is slow:
var checkbox = jquery(".checkbox input", row);
I need to get the checkbox in every row so I can attach an event handler to it. Selecting the checkbox is ALMOST 10X AS SLOW as inserting the entire parent row.
What am I doing wrong here?
DOM manipulation uses native functions to perform simple operations. Browser vendors optimize these. You are building the row from HTML. Internally jQuery is using .innerHTML to build the collection which then patches into the browser's mega-fast parser.
Selection is slow in comparison because JS code needs to loop through the DOM repeatedly. Newer browsers have native selection handling which provides dramatic speedups to selector based JS. As time moves on this will be less of a problem.
Here is how the query in question, $(".checkbox input", row), breaks down:
row.getElementsByTagName('*');
for-loop through every element returned (all elements within the row) and test elements[i].className with /(\s|^)checkbox(\s|$)/.
for-loop every element still remaining and collect matched[i].getElementsByTagName('input');
unique the final collection.
This is different for jQuery 1.3 as it's engine moves through the selector the other way around, beginning with getting all input elements and then testing the parent elements.
Rremember that the JS selector engines implement a lot more of the CSS selector spec than is actually usable with CSS (or implemented by current browsers). Exploiting this, and knowledge of the engines, we can optimize selector can be optimized in a few different ways:
If you know what element type the .checkbox is:
$("td.checkbox input", row);
It is faster for filter first for type and then for the class for only those matches. This doesn't apply for a very small subset of elements, but that is almost never the case in praxis.
The single class test is the slowest of the common selectors people actually use.
Simpler selection:
$("input[type=checkbox]", row);
One loop is faster than two loops. This only finds input elements and then directly filters them by type attribute. Since sub/child-elements are never used, unique may also be skipped (and smart engines will try to do this because unique is slow).
A more direct selector:
$("td:first.checkbox input", row);
A more complex selector may actually be faster if it is more direct (YMMV).
If possible, move the search context up to the table level:
By this I mean that instead of looping through the rows, and searching for the checkbox in every one, leave them alone until after the loop and then select them all at a time:
$("tr td:first.checkbox input", table);
The point of this is to eliminate the overhead of firing the selector engine up repeatedly, but instead do everything in one haul. This is presented here for completeness rather than something that I think would return massive speedups.
Don't select:
Build the row from bits, assigning events as you go.
var row = $( '<tr></tr>' );
var cell = $( '<td class="checkbox"></td>' ).appendTo( row );
$( '<input type="checkbox" name="..."/>' ).appendTo( cell ).click(/* ... */);
This may be impossible for reasons of Ajax or other templates out of your control. Additionally, the speed may not be worth turning your code into this sort of mess, but sometimes this may make sense.
Or, if none of these work for you, or return too performance gain, it may be time to rethink the method entirely. You can assign an event listener higher up the tree and grab the events there, instead of per-element instance:
$('table').change(function(e){
// you may want a faster check...
if ( $(e.target).is('input[type=checkbox]') ) {
// do some stuff ...
}
});
This way you don't do anything unless, and until, the user actually requests it. Fastest. :-)
var checkbox = jquery(".checkbox input", row);
This is traversing the entire dom tree to find the checkbox. You could possibly speed it up by changing the selector to an ID which can use the browsers native getElementById functionality.
var checkbox = jquery("#checkbox input", row);
You could also use your row as a starting point for the DOM search like the following example. Now your not parsing through the entire DOM tree again to find the matched element.
var row = jquery(rowHTML).appendTo(oThis.parentTable);
row.children(".checkbox input");
Use event delegation and add a single handler to a parent element and not the checkboxes themselves.
jQuery supports this via the live() function.
Try putting a class name on the input field itself. That may prove to be faster.
The reason for that is your code goes through all .checkbox classes, tries to find the input child of that element and returns that. I think that action might be your culprit.
By simply just looking for all elements with the class the input field has, you might see some speedup.
Try using Sly, it has an emphasis on performance.
If you're looking for performance, jQuery selectors are very slow. In the example there, it has to scan the full DOM tree and check CSS classes and so on to find the relevant nodes.
It is significantly faster to use native DOM methods. There are some interesting library performance comparisons here:
http://ajaxian.com/archives/taskspeed-more-benchmarks-for-the-libraries-and-browsers
The fastest way to get the DOM element is to use pure JavaScript and calling it by ID.
var element = document.getElementById('element);