I'm running document.querySelectorAll() frequently, and would like a short alias for it.
var queryAll = document.querySelectorAll
queryAll('body')
TypeError: Illegal invocation
Doesn't work. Whereas:
document.querySelectorAll('body')
Still does. How can I make the alias work?
This seems to work:
const queryAll = document.querySelectorAll.bind(document);
bind returns a new function which works identically to the querySelectorAll function, where the value of this inside the querySelectorAll method is bound to the document object.
The bind function is only supported in IE9+ (and all the other browsers) - https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
Update: In fact you could create shortcuts to a whole range of document methods like this:
const query = document.querySelector.bind(document);
const queryAll = document.querySelectorAll.bind(document);
const fromId = document.getElementById.bind(document);
const fromClass = document.getElementsByClassName.bind(document);
const fromTag = document.getElementsByTagName.bind(document);
A common answer is to use $ and $$ for querySelector and querySelectorAll. This alias mimics jQuery's one.
Example:
const $ = document.querySelector.bind(document)
const $$ = document.querySelectorAll.bind(document)
$('div').style.color = 'blue'
$$('div').forEach(div => div.style.background = 'orange')
div {
margin: 2px;
}
<div>
test
</div>
<section>
<div>
hello
</div>
<div>
foo
</div>
</section>
The JavaScript interpreter throws an error because querySelectorAll() should be invoked in document context.
The same error is thrown when you are trying to call console.log() aliased.
So you need to wrap it like this:
function x(selector) {
return document.querySelectorAll(selector);
}
My solution covers the four following use cases:
document.querySelector(...)
document.querySelectorAll(...)
element.querySelector(...)
element.querySelectorAll(...)
The code:
let doc=document,
qsa=(s,o=doc)=>o.querySelectorAll(s),
qs=(s,o=doc)=>o.querySelector(s);
In terms of parameters, the selector s is required, but the container element object o is optional.
Usage:
qs("div"): Queries the whole document for the first div, returns that element
qsa("div"): Queries the whole document for all divs, returns a nodeList of all those elements
qs("div", myContainer): Queries just within the myContainer element for the first div, returns that element
qsa("div", myContainer): Queries just within the myContainer element for all divs, returns a nodeList of all those elements
To make the code slightly shorter (but not quite as efficient), the qs code could be written as follows:
let qs=(s,o=doc)=>qsa(s,o)[0];
The code above uses ES6 features (let, arrow functions and default parameter values). An ES5 equivalent is:
var doc=document,
qsa=function(s,o){return(o||doc).querySelectorAll(s);},
qs=function(s,o){return(o||doc).querySelector(s);};
or the equivalent shorter but less efficient ES5 version of qs:
var qs=function(s,o){return qsa(s,o)[0];};
Below is a working demo. To ensure it works on all browsers, it uses the ES5 version, but if you're going to use this idea, remember that the ES6 version is shorter:
var doc = document;
var qs=function(s,o){return(o||doc).querySelector(s);},
qsa=function(s,o){return(o||doc).querySelectorAll(s);}
var show=function(s){doc.body.appendChild(doc.createElement("p")).innerHTML=s;}
// ____demo____ _____long equivalent______ __check return___ _expect__
// | | | | | | | |
let one = qs("div"); /* doc.querySelector ("#one") */ show(one .id ); // "one"
let oneN = qs("div",one); /* one.querySelector ("div") */ show(oneN .id ); // "oneNested"
let many = qsa("div"); /* doc.querySelectorAll("div") */ show(many .length); // 3
let manyN = qsa("div",one); /* one.querySelectorAll("div") */ show(manyN.length); // 2
<h3>Expecting "one", "oneNested", 3 and 2...</h3>
<div id="one">
<div id="oneNested"></div>
<div></div>
</div>
This would work, you need to invoke the alias using call() or apply() with the appropriate context.
func.call(context, arg1, arg2, ...)
func.apply(context, [args])
var x = document.querySelectorAll;
x.call(document, 'body');
x.apply(document, ['body']);
I took #David Muller's approach and one-lined it using a lambda
let $ = (selector) => document.querySelector(selector);
let $all = (selector) => document.querySelectorAll(selector);
Example:
$('body');
// <body>...</body>
function x(expr)
{
return document.querySelectorAll(expr);
}
If you don't care about supporting ancient, awful browsers that nobody should be using anymore, then you can just do this:
const $ = (sel, parent = document) => parent.querySelector(sel);
const $$ = (sel, parent = document) => Array.from(parent.querySelectorAll(sel));
Here's some examples of usage:
// find specific element by id
console.log($("#someid"));
// find every element by class, within other element
// NOTE: This is a contrived example to demonstrate the parent search feature.
// If you don't already have the parent in a JavaScript variable, you should
// query via $$("#someparent .someclass") for better performance instead.
console.log($$(".someclass", $("#someparent")));
// find every h1 element
console.log($$("h1"));
// find every h1 element, within other element
console.log($$("h1", $("#exampleparent")));
// alternative way of finding every h1 element within other element
console.log($$("#exampleparent h1"));
// example of finding an element and then checking if it contains another element
console.log($("#exampleparent").contains($("#otherelement")));
// example of finding a bunch of elements and then further filtering them by criteria
// that aren't supported by pure CSS, such as their text content
// NOTE: There WAS a ":contains(text)" selector in CSS3 but it was deprecated from the
// spec because it violated the separation between stylesheets and text content, and you
// can't rely on that CSS selector, which is why you should never use it and should
// instead re-implement it yourself like we do in this example.
// ALSO NOTE: This is just a demonstration of .filter(). I don't recommend using
// "textContent" as a filter. If you need to find specific elements, use their way
// more reliable id/class to find them instead of some arbitrary text content search.
console.log($$("#exampleparent h1").filter(el => el.textContent === "Hello World!"));
The functions I provided use a ton of modern JS features:
const to make sure the function variable can't be overwritten.
functions defined as an arrow function aka lambda: (args) => code with implied return statement
default parameters (not supported by browsers before the year 2016)
no {} or return, since those can be skipped if there's just 1 statement in the function body.
The modern function Array.from() is used, which converts the querySelectorAll result (which is always a NodeList, or empty NodeList), into an Array, which is basically what every developer wants. Because that's how you get access to .filter() and other Array functions that allow you to process the discovered nodes further, using clean, short code. And Array.from() creates a shallow copy of all elements which means that it's blazingly fast (it just copies the memory references/pointers to each Node DOM element from the original NodeList). It's a major API enhancer.
If you care about ancient browsers, you can still use my functions but use Babel to convert the modern JS to old ES5 when you release your website.
My suggestion would be to write your entire site in ES6 or higher and then use Babel if you care about visitors from Windows XP and other dead operating systems, or just random people who haven't updated their browsers in 5+ years.
But I wouldn't recommend using Babel. Stop worrying about people who have old browsers. It is their problem, not yours.
The modern "app" world is incredibly deeply based on web browsers and JavaScript and modern CSS, and most of your visitors these days have modern, auto-updated browsers. You basically can't live modern life without a modern browser, since so many websites demand it now.
In my opinion, the days of expecting web designers to waste their time and sanity trying to make a site work on browsers from 1993 are over. It's time the laziest customers/visitors update their old browsers instead. It's not difficult. Even old and dead operating systems usually have ways to install new versions of browsers on them. And people who don't have an updated browser are only a tiny fraction of a percent these days.
For example, the Bootstrap framework, the world's most popular framework for mobile/responsive sites, only cares about supporting the 2 most recent major versions of all major browsers (at least 0.5% market share). Here's their list as of this moment:
0.5% market share or higher
last 2 major versions only
not a dead browser (not discontinued)
Chrome >= 60
Firefox >= 60
Firefox ESR
iOS >= 12
Safari >= 12
not Explorer <= 11 (meaning no versions of Internet Explorer at all)
And I completely agree with this. I was a web developer in the early 2000s and it was absolute hell. Being expected to make it work on some random, stupid user's ancient browser was hell. It took all the fun out of web development. It made me hate and quit web development. Because 90% of my time was wasted on browser compatibility. That's not how life should be. It's not your fault that some customers/visitors are lazy. And these days, visitors have no more excuses to stay lazy.
Instead, you should only target users who have modern browsers. Which is basically everybody these days. There is no excuse for anyone to use an old browser. And if they use an old browser, your site should show a big, fat banner saying "Please, join the modern world for your own sake. Download a new browser. How are you even able to live your normal life with such an old browser? Are you a time traveler from caveman times?".
People have no excuses to have old browsers anymore:
Linux: Ships with the latest versions of Firefox by default.
Mac: Ships with Safari, which is a modern browser. But some older macOS versions won't get newer versions of Safari, and older machines often can't install the latest macOS version either. Well, tough luck for those visitors. They are gonna have trouble on more than just your website. It's up to them to install a modern browser (such as Chrome), which they are able to do even on old versions of macOS. So they have no excuses. Don't waste your life catering to people on very old, buggy Safari versions.
Windows: Windows 10 ships with Edge, which is based on Chromium and is as compatible with websites as Chrome is. People have no excuse to have an old browser. And most Windows users use Chrome. As for very old, discontinued versions of Windows (XP, Vista, 7, 8), well, we yet again arrive at the same question as before: Do you care about 0.0000001% stupid visitors who use a dead OS and an old Internet Explorer version? The whole freaking web will be broken for them anyway, so who cares if your site is broken for them too? They should stop being lazy and just upgrade their OS to Windows 10, or at least install Chrome or Firefox on their current OS. They have no excuses.
iOS: If you're stuck on a super old iOS device, then you can't use the modern web. Tough luck. A lot of the web is gonna be broken for you. Get a new device. Even frameworks like Bootstrap, the world's #1 mobile web framework, doesn't support iOS 11 or earlier. It's not our problem. It's the cheapskate visitor's problem if they still hang onto such an old device. They can literally get a newer iOS device second-hand for almost no money at all and fix all of their problems with visiting the modern web. And they'll need to buy that anyway since most apps (even banking/important apps) require modern iOS versions.
Android: The browser is independently updated from the OS, and can even be sideloaded, so even if you're stuck on old Android versions, you have access to modern browsers. So you have no excuses.
Most people these days have browsers that are completely up-to-date and auto-updated. That's the fact.
So yeah... the days of website designers suffering through hell just for catering to old browsers are over. Therefore I suggest that people use ES6 and CSS3 for their websites, to make web designing a joy for the first time.
Hope you enjoy the ES6 functions I provided!
Here is my take on it. If the selector has multiple matches, return like querySelectorAll. If ony one match is found return like querySelector.
function $(selector) {
let all = document.querySelectorAll(selector);
if(all.length == 1) return all[0];
return all;
}
let one = $('#my-single-element');
let many = $('#multiple-elements li');
2019 update
Today I made a new take on the problem. In this version you can also use a base like this:
let base = document.querySelectorAll('ul');
$$('li'); // All li
$$('li', base); // All li within ul
Functions
function $(selector, base = null) {
base = (base === null) ? document : base;
return base.querySelector(selector);
}
function $$(selector, base = null) {
base = (base === null) ? document : base;
return base.querySelectorAll(selector);
}
The highly invasive version:
<script>
for(const c of [HTMLDocument, Element, DocumentFragment]) {
c.prototype.$ = c.prototype.querySelector;
c.prototype.$$ = c.prototype.querySelectorAll;
}
const $ = document.$.bind(document); // For inline events
const $$ = document.$$.bind(document);
window.$ = $; // For JS files
window.$$ = $$;
</script>
So you can chain $('nav').$$('a') like jQuery allows (I think). Since ShadowRoot extends DocumentFragment you can even inspect (open) Shadow DOMs:
const styles = $('x-button').shadowRoot.$$('style');
function $(selector, base = null) {
base = (base === null) ? document : base;
return base.querySelector(selector);
}
function $$(selector, base = null) {
base = (base === null) ? document : base;
return base.querySelectorAll(selector);
}
Why not simplier ??? :
let $ = (selector, base = document) => {
let elements = base.querySelectorAll(selector);
return (elements.length == 1) ? elements[0] : elements;
}
you dont need to add any dot before class name you can modify this also by using getEle by class or id tag etc
function $(selector){
var dot = ".";
var newSelector = dot.concat(selector);
var Check = document.querySelectorAll(newSelector);
if(Check && Check.length>0 && Check.length < 2){
return document.querySelector(newSelector);
}
else {
return document.querySelectorAll(newSelector);
}
}
$("answercell")[0].remove();
I have this Javascript here:
function getTxt(obj) {
var first = obj.innerHTML.substring(0, obj.innerHTML.indexOf('<span class=\"item2\">'));
var second = obj.innerHTML.substring(obj.innerHTML.indexOf('<span class=\"item2\">'));
var f = first.replace(/(<([^>]+)>)/ig,'');
var s = second.replace(/(<([^>]+)>)/ig,'');
alert(first + "\n" + second + "\n" + f + "\n" + s);
}
and the HTML:
<span class="item" onclick="getTxt(this)"><span class="item1">MyName</span><span class="item2">555-555-5555</span></span>
In most browsers (FireFox, Chrome, Safari, Opera) it will alert:
<span class="item1">MyName</span>
<span class="item2">555-555-5555</span>
MyName
555-555-5555
as expected. However, in IE9 it alerts:
<span class="item1">MyName</span><span class="item2">555-555-5555</span>
MyName555-555-5555
So it puts the vars "first" and "second" together into var "first", and puts "f" and "s" together into var "f".
I would like to know if there is anyway to correct this for IE9 (and probably other version of IE also) to work as it does in the other browsers.
Pattern matching innerHTML is particularly a problem in IE and is generally a bad idea. IE often does NOT return to you the same HTML that was originally in the page. It often requotes or removes quotes, changes the order of attributes, changes case, etc... IE is clearly reconstituting the HTML rather than give you back what was originally in the page. As such, you cannot reliably pattern match innerHTML in IE. There are some specific things you can probably match (the start of tags), but you can't expect attributes to be in a specific spot or to have a specific format.
If you console.log(obj.innerHTML) in IE, you will likely see what I'm talking about. It will look different.
A more robust solution is to use the DOM functions to navigate the specific elements or CSS selectors to find specific objects and then change attributes or innerHTML on a single specific element. Let the DOM navigation find the right element for you rather than parsing the HTML yourself.
If you provide a desired before and after sample of the HTML and describe what you're trying to accomplish, folks here can probably help you get the job done with DOM manipulation rather than HTML parsing.
I don't know which selector libraries you have available to you or which browsers you're targeting, but in jQuery, you could do this like this:
function getText(obj) {
return $(obj).find(".item1").text();
}
In plain javascript, in IE8 and above and all other modern browsers, you can use this:
function getText(obj) {
return obj.querySelectorAll(".item1").innerHTML;
}
If you had to support back to IE6 or IE7, I'd suggest getting the Sizzle library and use that for your queries:
function getText(obj) {
return Sizzle(".item1", obj)[0].innerHTML;
}
It happens because of "Quirky mode" in Internet Explorer. It's a huge pain in the ass, but you can disable it in IE DevTools, or by adding this metatag to your page:
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
The Problem
I am converting a relatively large piece of Javascript that currently only works on Internet Explorer in order to make it work on the other browsers as well. Since the code uses XPath extensively we made a little compatibility function to make things easier
function selectNodes(xmlDoc, xpath){
if('selectNodes' in xmlDoc){
//use IE logic
}else{
//use W3C's document.evaluate
}
}
This is mostly working fine but we just came across the limitation that positions in IE are zero-based but in the W3C model used by the other browsers they are one-based. This means that to get the first element we need to do //books[0] in IE and //books[1] in the other browsers.
My proposed solution
The first thought was using a regex to add one to all indexes that appear in the queries if we are using the document.evaluate version:
function addOne(n){ return 1 + parseInt(nStr, 10); }
xpath = xpath.replace(
/\[\s*(\d+)\s*\]/g,
function(_, nStr){ return '[' + addOne(nStr) + ']'; }
);
My question
Is this regex based solution reasonably safe?
Are there any places it will convert something it should not?
Are there any places where it will not convert something it should?
For example, it would fail to replace the index in //books[position()=1] but since IE doesn't appear to support position() and our code is not using that I think this particular case would not be a problem.
Considerations
I downloaded Sarissa to see if they have a way to solve this but after looking at the source code apparently they don't?
I want to add one to the W3C version instead of subtracting one in the IE version to ease my conversion effort.
In the end
We decided to rewrite the code to use proper XPath in IE too by setting the selection language
xmlDoc.setProperty("SelectionLanguage", "XPath");
we just came across the limitation that positions in IE are zero-based
but in the W3C model used by the other browsers they are one-based.
This means that to get the first element we need to do //books[0] in
IE and //books[1] in the other browsers.
Before doing any XPath selection, specify:
xmlDoc.setProperty("SelectionLanguage", "XPath");
MSXML3 uses a dialect of XSLT/XPath that was in use before XSLT and XPath became W3C Recommendations. The default is "XSLPattern" and this is what you see as behavior.
Read more on this topic here:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms754679(v=vs.85).aspx
Why not modify the original expressions, so that this:
var expr = "books[1]";
...becomes:
var expr = "books[" + index(1) + "]";
...where index is defined as (pseudocode):
function index(i) {
return isIE ? (i - 1) : i;
}
Is there a better way then using jQuery.browser, or equivalents, for determining css 3 prefixes (-moz, -webkit, etc), as it is disencouraged? Since the css is dynamic (the user can do anything with it on runtime), css hacks and style tag hacks can't be considered.
I don't see the issue with using the navigator.userAgent to determine if you need to cater for Webkit / Gecko CSS3 prefixes. Or better yet, just stick with CSS2 until CSS3 becomes a W3C Recommendation.
The reason use of the navigator object is discouraged is because it was used over Object detection when (java)scripting for different browsers, your situation is one where it is fine to use user agent detection, because your'e specifically targeting certain quirks with different rendering engines.
Edit:
Picking up from where cy left off, you can use javascript object detection to detect whether a prefix is used, I made some quick code to do so:
window.onload = function ()
{
CSS3 = {
supported: false,
prefix: ""
};
if (typeof(document.body.style.borderRadius) != 'undefined') {
CSS3.supported = true;
CSS3.prefix = "";
} else if (typeof(document.body.style.MozBorderRadius) != 'undefined') {
CSS3.supported = true;
CSS3.prefix = "-moz-";
} else if (typeof(document.body.style.webkitBorderRadius) != 'undefined') {
CSS3.supported = true;
CSS3.prefix = "-webkit-";
}
if (CSS3.supported)
if (CSS3.prefix == "")
alert("CSS3 is supported in this browser with no prefix required.");
else
alert("CSS3 is supported in this browser with the prefix: '"+CSS3.prefix+"'.");
else
alert("CSS3 is NOT supported in this browser.");
};
Remember to watch out for strange quirks such as -moz-opacity which is only supported in older versions of Firefox but has now been deprecated in favour of opacity, while it still uses the -moz- prefix for other new CSS3 styles.
Array.prototype.slice.call(
document.defaultView.getComputedStyle(document.body, "")
)
.join("")
.match(/(?:-(moz|webkit|ms|khtml)-)/);
Will return an array with two elements. One with dashes and one without dashes, both lowercase, for your convenience.
Array.prototype.slice.call(
document.defaultView.getComputedStyle(document.body, "")
);
Without the browser check will return an array of nearly all the css properties the browser understands. Since it's computed style it won't display shorthand versions, but otherwise I think it gets all of them. It's a quick hop skip and a jump to auto detect whatever you need as only vendor prefixed stuff starts with a dash.
IE9, Chrome, Safari, FF. Opera won't let you slice CSSStyleDeclaration for you can still use the same getComputedStyle code and loop through the properties or test for a specific one. Opera also wanted to be the odd man out and not report the vendor prefix dasherized. Thanks Opera.
Object.keys(CSSStyleDeclaration.prototype)
Works in IE9 and FF and reports the TitleCased (JavaScript) version of the vendor property names. Doesn't work in WebKit as the prototype only reports the methods.
Here's an interesting and very dangerous function I just wrote along these lines:
(function(vp,np){
Object.keys(this).filter(function(p){return vp=vp||p.match(/^(Moz|ms)/)}).forEach(function(op){
this.__defineGetter__(np=op.replace(vp[0], ""), function() { return this[op] });
this.__defineSetter__(np, function(val) { this[op] = val.toString() });
}, this);
}).call(CSSStyleDeclaration.prototype);
I didn't test anything Konquerer.
It's adding in another library, but would Modernizr work for you? It adds CSS classes to the <html> tag that can tell you what the browser supports.
It does muddy up the code a bit, but can certainly be helpful in appropriate situations.
Speculatively: Yes. You can try adding a vendor prefix css rule (that's what they're called), and then test to see if that rule exists. Those vendor-specific rules won't be added to the DOM in browsers in which they're not supported in some cases.
For example, if you try adding a -moz rule in webkit, it won't add to the DOM, and thus jQuery won't be able to detect it.
so,
$('#blah').css('-moz-border-radius','1px');
$('#blah').css('-moz-border-radius') //null in Chrome
Conversely,
$('#blah').css('-webkit-border-radius','1px');
$('#blah').css('-webkit-border-radius'); //returns "" in Chrome
This method works in WebKit browsers; I'm testing to see if it works in others. Pending.
Edit: Sadly, this isn't working in Firefox or Opera, which just returns "" no matter compatibility. Thinking of ways to do this cross-browser...
Final Edit: Andrew Dunn's answer does this in a way that works (at least in FF and Webkit, which is better than my method).
I use ternary operator to have it only in 1 line. If it's not webkit nor gecko, I'll just use the standard property. If it has no support, who really cares then?
var prefix = ('webkitAnimation' in document.body.style) ? '-webkit-' : ('MozAnimation' in document.body.style? '-moz-' : '');
Basically I found Animation is one of the properties never changed. As soon as the browser starts supporting the Draft / Candidate Recommendation of a CSS3 property, it drops the prefix on JS side. So you will need to be careful and take in mind that, before copy-pasting.
The following code results in "undefined" for lastIndex:
var a = /cat/g;
var l = "A test sentence containing cat and dog.";
var r = a.exec(l);
document.write(r.lastIndex);
However, it works perfectly for r.index (and r.input).
I am using Firefox. Does anybody have a clue?
EDIT: OK, the above code works perfectly in IE! Further, in Firefox, it works perfectly if instead of calling r.lastIndex on line 5, a.lastIndex is called. It appears that Firefox does not return lastIndex property in the result - rather sets the property for the pattern invoking the exec() only. Interesting that IE sets both.
This is one of those places where Microsoft decided to add some stuff to the language and act as if it was supposed to be there. Thankfully, they are now cleaning up their act and documenting such nonsense.
To be clear: Firefox is correct according to the ECMAScript Language Specification 3rd Edition (PDF, 705KB).
IE is not correct; its behaviour is a proprietary extension. There is no reason to believe that this IE-specific behaviour will ever be supported by any other browser. It certainly isn't at the moment. See JScript Deviations from ES3 (PDF, 580KB, by Pratap Lakshman of Microsoft Corporation) Section 4.6 for more on this particular deviation from the spec, including tests showing no support on other browsers.
Note also that this may not even be supported in the future by IE: a number of proprietary IE CSS-related mechanisms are disabled by default on IE8-in-IE8-mode, and future implementations of JScript may find a reason to similarly disable this extension to the language.
lastIndex is a property of a RegExp object. So try this:
a.lastIndex
To avoid all the weird, try this
var a = /cat/g;
var l = "A test sentence containing cat and dog.";
var r = a.exec(l);
var lastIndex = (r!=null) ? l.indexOf(r[0])+r[0].length : 0;
It is used here: http://www.pagecolumn.com/tool/regtest.htm