I am just wondering will I get consistent results if I use document.getElementById and document.querySelectorAll within DOMContentLoaded event listener and outside of it.
I was searching the web and couldn't find a topic similar to this.
I was also reading the w3 spec and couldn't find anything mentioning this.
I am not asking about a situation where I get null or an empty NodeList, but imagine this scenario:
On a page you have this element
<div id="el"></div>
and in JS you have these
let id = document.getElementById('el');
let qs = document.querySelectorAll('#el');
Now the question is, will I get a positive match (id !== null and qs.length > 0) in every browser (except IE <=8) every time? Or is there a scenario where these could not find the element #el?
Has anyone had any problems using this in production environments?
You can search these API in the “https://caniuse.com”. you can see which browser supported these API. If you are looking for the existence of a single element, it is recommended to use "querySelector".
Related
I'm working on localhost (so would expect to not have any domain-related probs as here).
On a page I'm using a bit of JS to modify the content of a span in the opening-window. It does not work.
When checking my code to find the control, it works (using FF dev-tools calling my Increment-function or checking the console.log-output): $('#uploads_Count')returns an object of type HTMLSpanElement. However, trying to access the same control from an opened window's console with window.opener.$('#uploads_Count'), this returns an HTML-Document, seemingly the entire page. Why is this not working, what am I missing here?
Here is function that is supposed to increment the counter contained in the span whose id is given as argument:
function Increment(ctrl)
{
var gef = $("#" + ctrl);
if (!gef) // did not find control, maybe on opener?
{
gef = window.opener.$("#" + ctrl);
}
console.log(gef);
cnt = parseInt(gef.text() , 10);
cnt++;
gef.text(cnt);
}
The HTML is trivial:
<span id="uploads_Count">0</span>
If $(selector) returns an element (such as HTMLSpanElement), rather than a collection of elements (would look like [<span id="uploads_Count"></span>] in most dev tools), then you're not calling jQuery.
Dev tools in A-grade browsers tend to introduce $ as a selector function. It is available in the developer console only.
If window.jQuery exists, then it's likely that jQuery.noConflict() was called, in which case you should use window.opener.jQuery.
Found it!
The way I checked if the control was found, was wrong. Instead of if (!gef)I should have used if (!gef.length). Found the explanation here.
I have a couple of questions about the inner workings of JavaScript and how the interpreter handles certain queries
The following JQuery will correctly get all the images that contain the word "flowers" in the src
$("img[src*='flowers']");
Jquery makes this very simple but what the pure javascript version?
We have a very large DOM. I take it if I do $("*[src*='flowers']") this will greatly affect performance (wildcard element). I'm interested in what the Javascript interpreter does differently between $("img[src*='flowers']") and $("*[src*='flowers']")
Well, the clearest way to explain the difference is to show you how you'd write both DOM queries in plain JS:
jQuery's $("img[src*='flowers']"):
var images = document.getElementsByTagName('img');//gets all img tags
var result = [];
for (var i = 0; i < images.length;i++)
{
if (images[i].getAttribute('src').indexOf('flowers') !== -1)
{//if img src attribute contains flowers:
result.push(images[i]);
}
}
So as you can see, you're only searching through all img elements, and checking their src attribute. If the src attribute contains the substring "flowers", the add it to the result array.
Whereas $("[src*='flowers']") equates to:
var all = document.getElementsByTagName('*');//gets complete DOM
var result = [];
for (var i =0; i <all.length; i++)
{
if (all[i].hasAttribute('src') && all[i].getAttribute('src').indexOf('flowers') !== -1)
{//calls 2 methods, for each element in DOM ~= twice the overhead
result.push(all[i]);
}
}
So the total number of nodes will be a lot higher than just the number of img nodes. Add to that the fact that you're calling two methods (hasAttribute and getAttibute) for all img elements (thanks to short-circuit evaluation, all elements that don't have an src attribute, the getAttribute method won't be called) there's just a lot more going on behind the scenes in order for you to get the same result.
note:
I'm not saying that this is exactly how jQuery translates the DOM queries for you, it's a simplified version, but the basic principle stands. The second version (slower version) just deals with a lot more elements than the first. That's why it's a lot slower, too.
When you use *[src..] you will try to find all elements from the page, but when you use $("img[src..]") the search is restricted to img elements, like this: imgs = document.getElementsByTagName("img")
Heres a JSFiddle getting those images using pure javascript.
Edit:
turn console on so you can see the return from console.log
The direct JavaScript methods are document.querySelector or document.querySelectorAll. The problem with those is that they are not supported in all browsers, jQuery (through SizzleJS) provides a browser compatible way of doing these things. SizzleJS delegates to document.querySelectorAll if it is available, and it falls back on other mechanisms when it is not available. So unless you want to write the fall back code yourself, it's probably best to stick with something like SizzleJS, which provides the selector functionality without the overhead of jQuery.
How could I test CSS1-3 selectors to check that they get the correct elements, e.g. with JavaScript (maybe jQuery)?
The simplest traditional way by far is to not use JavaScript at all, and just set up a test page by hand where you can test selectors to your heart's content. The test cases you see on the Web (like the well-known CSS3.info Selectors Test) are really just souped-up versions hosted online.
But if you're looking for a JavaScript method, you can try the Selectors API. It's available in modern DOM implementations (IE8+ and others) and it provides a JavaScript frontend for querying the DOM for element nodes using CSS selectors, as well as testing CSS selectors natively supported by a given browser.
(For browsers that don't implement the Selectors API, you'll have to rely on jQuery, but remember that it provides support for a different set of selectors than what a browser supports as well as its own non-standard extensions which aren't found in the Selectors spec. An example of using jQuery with Chrome's JavaScript console to test a selector can be found here.)
Call querySelector() or querySelectorAll() depending on what you want to test, and check the return value (preferably in your browser's developer tools since you're just testing):
If matches are found, the former method returns the first Element matched while the latter returns all elements matched as a NodeList.
If nothing is found, the former returns null while the latter returns an empty NodeList.
If the selector is invalid, an exception will be thrown which you can catch.
Here are some examples with the command editor (multiline) in Firebug's console on Firefox 10, tested on this very question:
Finding the first h1 in body:
var h1 = document.body.querySelector('h1');
console.log(h1);
<h1 itemprop="name">
Querying descendants of that h1 element we just found:
var subnodes = h1.querySelectorAll('*');
console.log(subnodes[0]);
<a class="question-hyperlink" href="/questions/9165859/how-do-i-test-css-selectors-in-javascript">
Testing the :-moz-any() pseudo-class in Firefox (:-webkit-any() in Safari/Chrome):
// This selector works identically to h1 > a, li > a
var hyperlinks = document.querySelectorAll(':-moz-any(h1, li) > a');
console.log(hyperlinks);
[a#nav-questions /questions, a#nav-tags /tags, a#nav-users /users, a#nav-badges /badges, a#nav-unanswered /unanswered, a#nav-askquestion /questions/ask, a.question-hyperlink /questio...vascript]
Testing a nonexistent selector (that perhaps many of us wish did exist):
// :first-of-class doesn't exist!
var selector = 'div.answer:first-of-class';
try {
var firstAnswer = document.querySelector(selector);
console.log(firstAnswer);
} catch (e) {
console.log('Invalid selector: ' + selector);
}
Invalid selector: div.answer:first-of-class
http://selectorgadget.com is quite nice to test and build CSS selectors. Just drag and drop a piece of JavaScript they provide into your bookmarks bar and click it whenever you need.
because of some problems with joomla "in-content javascript" I have to give all my js logic to one file, but there are problems with inconsistence of dom elements across my site (it is ajax driven, so there is only one script and various DOMs).
What is the best solution to make some conditionals solving this problem..
Is it checking $(selector).length, or is there any better solution..
And in case of the $(selector).length , is there a way to save this selector to variable (performance issues)
for example some kind of
var selector = ($(selector).length !== 0) ? this : false ;
if(selector) { makeSomething; }
The this is actually pointing to Window object..So is there any way to make it like this without need of reselection?
Thanks
var $obj = $('selector');
if ($obj.length) { makeSomething(); }
Actually, this is only meaningful if you are searching for the existence of a certain element (that might identify a whole page) and running several operations based on that.
If you just want to do something on the elements like
$('selector').append('x');
the condition might be useless, because if the jQuery collection is empty, the methods won't run anyways (as pointed out by #Gary Green).
Hi I would like to do dom selection and manipulation out of the dom.
The goal is to build my widget out of the dom and to insert it in the dom only once it is ready.
My issue is that getElementById is not supported on a document fragment. I also tried createElement and cloneNode, but it does not work either.
I am trying to do that in plain js. I am used to do this with jQuery which handles it nicely. I tried to find the trick in jQuery source, but no success so far...
Olivier
I have done something similar, but not sure if it will meet your needs.
Create a "holding area" such as a plain <span id="spanReserve"></span> or <td id="cellReserve"></td>. Then you can do something like this in JS function:
var holdingArea = document.getElementById('spanReserve');
holdingArea.innerHTML = widgetHTMLValue;
jQuery will try to use getElementById first, and if that doesn't work, it'll then search all the DOM elements using getAttribute("id") until it finds the one you need.
For instance, if you built the following DOM structure that isn't attached to the document and it was assigned to the javascript var widget:
<div id="widget">
<p><strong id="target">Hello</strong>, world!</p>
</div>
You could then do the following:
var target;
// Flatten all child elements in the div
all_elements = widget.getElementsByTagName("*");
for(i=0; i < all_elements.length; i++){
if(all_widget_elements[i].getAttribute("id") === "target"){
target = all_widget_elements[i];
break;
}
}
target.innerHTML = "Goodbye";
If you need more than just searching by ID, I'd suggest installing Sizzle rather than duplicating the Sizzle functionality. Assuming you have the ability to install another library.
Hope this helps!
EDIT:
what about something simple along these lines:
DocumentFragment.prototype.getElementById = function(id) {
for(n in this.childNodes){
if(id == n.id){
return n;
}
}
return null;
}
Why not just use jQuery or the selection API in whatever other lib youre using? AFAIK all the major libs support selection on fragments.
If you wan tto skip a larger lib like jQ/Prototype/Dojo/etc.. then you could jsut use Sizzle - its the selector engine that powers jQ and Dojo and its offered as a standalone. If thats out of the question as well then i suppose you could dive in to the Sizzle source and see whats going on. All in all though it seems like alot of effort to avoid a few 100k with the added probaility that the code you come up with is going to be slower runtime wise than all the work pulled into Sizzle or another open source library.
http://sizzlejs.com/
Oh also... i think (guessing) jQ's trick is that elements are not out of the DOM. I could be wrong but i think when you do something like:
$('<div></div>');
Its actually in the DOM document its just not part of the body/head nodes. Could be totally wrong about that though, its just a guess.
So you got me curious haha. I took a look at sizzle.. than answer is - its not using DOM methods. It seems using an algorithm that compares the various DOMNode properties mapped to types of selectors - unless im missing something... which is entirely possible :-)
However as noted below in comments it seems Sizzle DOES NOT work on DocumentFragments... So back to square one :-)
Modern browsers ( read: not IE ) have the querySelector method in Element API. You can use that to get and element by id within a DocumentFragment.
jQuery uses sizzle.js
What it does on DocumentFragments is: deeply loop through all the elements in the fragment checking if an element's attribute( in your case 'id' ) is the one you're looking for. To my knowledge, sizzle.js uses querySelector too, if available, to speed things up.
If you're looking for cross browser compatibility, which you probably are, you will need to write your own method, or check for the querySelector method.
It sounds like you are doing to right things. Not sure why it is not working out.
// if it is an existing element
var node = document.getElementById("footer").cloneNode(true);
// or if it is a new element use
// document.createElement("div");
// Here you would do manipulation of the element, setAttribute, add children, etc.
node.childNodes[1].childNodes[1].setAttribute("style", "color:#F00; font-size:128px");
document.documentElement.appendChild(node)
You really have two tools to work with, html() and using the normal jQuery manipulation operators on an XML document and then insert it in the DOM.
To create a widget, you can use html():
$('#target').html('<div><span>arbitrarily complex JS</span><input type="text" /></div>');
I assume that's not what you want. Therefore, look at the additional behaviors of the jQuery selector: when passed a second parameter, it can be its own XML fragment, and manipulation can happen on those documents. eg.
$('<div />').append('<span>').find('span').text('arbitrarily complex JS'). etc.
All the operators like append, appendTo, wrap, etc. can work on fragments like this, and then they can be inserted into the DOM.
A word of caution, though: jQuery uses the browser's native functions to manipulate this (as far as I can tell), so you do get different behaviors on different browsers. Make sure to well formed XML. I've even had it reject improperly formed HTML fragments. Worst case, though, go back and use string concatenation and the html() method.