Almost all the Codepens I've written this year have had some form of a JavaScript function that I call
qs()()
. That's not a typo! There are two sets of parenthesis following it, and it is good practice as this function uses closures. In just one line, I create a Swiss Army knife of several functions.Here's what it looks like.
var qs = parent => (query,all) => (typeof(parent) === "string") ? qs(qs()(parent))(query,all) : (parent||document)[`querySelector${(all||false)?"All":""}`](query);
That's a lot going on in just one line of code, let's break it down.
/* @func: qs
* @desc: querySelector[All] in one function
* @param parent : (@default is document) A string or Element to find the query element(s)
* @param query : (required) A CSS string matching an id, class, or attribute of elements inside the parent.
* @param all : (@default is false) A Boolean which will look for one element (false) or more than one element (true).
* @returns:
* null if nothing is found and all is false.
* Node if query is found and all is false.
* NodeList if all is true even if no Nodes are found.
*/
var qs = function(parent){
return function(query){
if(typeof(parent) === "string"){
// If parent is a string, do a queryString on the parent.
let parent_query = qs()(parent);
return qs(parent_query)(query,all);
} else {
// If parent is NOT a string, do the following
// If parent is undefined, use document instead. (This is common)
var parent_element = (parent||document);
// If select_all is not defined, use false instead.
var select_all = (all||false);
if(select_all){
// Return the NodeList of all Nodes that match the query inside the parent element.
// Returns an empty NodeList if there are no matches.
return parent_element.querySelectorAll(query);
} else {
// Return the Node that matches the query inside the parent element
// Return null if no match
return parent_element.querySelector(query);
}
}
} // inner function
} // outer function
You may have noticed there is a little bit of a recursion if
parent
is a string. This is so that if you choose to modify elements inside of an element, you can do so without having to call another qs()()
command.In older version of this function, much of what goes in in
qs()()
was in separate functions. While it may also be faster to use other functions such as element
.getElementById()
or element
.getElementsByClassName()
(which often element
will be document
), which return a HTMLCollection
, I find using element
.querySelector()
and element
.querySelectorAll()
to be the most ideal functions to use primarily because query searches are CSS Selectors. If the parent
argument is a string it can also be a CSS selector, acting as a document-wide filter.If there is no
argument, the default value parent
document
will be used. If using an extra pair of parenthesis doesn't work well for you, you could define a new function.var dqs = qs(document);
"Wait? Where's the second set of parenthesis?" Closures are rather interesting. I used to do a lot of programming in C++ back in college, so you could probably say that our parent function
qs(
parent
)
is a template for what the child function can do. Templates in C++ could be though of as closures that used data types to define what kind of argument you could use before using a function. Maybe the ECMA committee will eventually move toward using data types in ECMAScript eventually, especially since TypeScript uses data types.So what can we use our
dqs()
function for, and how would it work differently if we used qs()()
? Basically it work the same way.// This
var dqsDiv = dqs("#div1");
// does the same thing as this
var qsdDiv = qs(document)("#div1");
// And so does this because `document` is the default `parent` value.
var qsDiv = qs()("#div1");
All three of those statements will be assigned the same element that has the id
div1
.The other feature of
qs()()
is the last argument in the second tuple, the all
variable. This variable is and optional Boolean value (meaning it can be either true
or false
). The default value for all
is false
unless all
is defined to be true
. When it is true
, element
.querySelectorAll()
is used instead of element
.querySelector()
.Let's break down the latter part of
qs()()
:var select_all = (all||false);
var callback = (select_all) ? "querySelectorAll" : "querySelector";
var parent_callback = parent[callback];
parent_callback(query);
Our first line we already stated means that
is the value of select_all
unless is not defined then it will be set to all
false
. The second line defines the string of the name of callback function which will be used. The callback name must be the name of a function that is part of the class or interface of the data type that parent
is. We've already stated that the parent
argument is either an Element
or Document
type.Because
members, which are key-value pairs, can be called using the dot method (Object
object.member
) or the array method (object["member"]
), we can take advantage of the array method, because the key "member"
is a String
, which will return the value which can be any type including the Function
type which we are looking for. So on the third line, the variable parent_callback
represents a function. We just need to add the arguments outside the variable to the right, which is what happens on the last line. So with some substitutions, we can contract all all this information.var select_all = (all||false);
// Let's rephrase callback
var callback = "querySelector" + ((select_all) ? "All" : "");
// combine the third and fourth lines
parent[callback](query);
Now let's use a template string on the second line.
var select_all = (all||false);
// Template string instead of concatenation
var callback = `querySelector${(select_all)?"All":""}`;
// combine the third and fourth lines
parent[callback](query);
Finally some variable substitution. Goodbye,
and select_all
. callback
// combine the all our lines
parent[`querySelector${(all||false)?"All":""}`](query);
Look how much is done. Now remember
is optional, so we need to make sure that parent
document
can be used when parent
is not.(parent||document)[`querySelector${(all||false)?"All":""}`](query);
That's the false half of
qs()()
.So why use this instead of some long named command?
If you've used jQuery, which you should really stop doing because more than likely you don't need it and is a terrible excuse not to learn "vanilla" JavaScript, and by "vanilla" I mean no-framework JavaScript not the framework Vanilla JS, you may be familiar with the
or jQuery()
$()
function. The problem with using that, is that it returns another jQuery
object. qs()()
will return ether a Node
or NodeList
because it uses querySelector()
and querySelectorAll()
.querySelector()
replaces the need to use getElementById()
. querySelectorAll()
replaces the need to use getElementsByTagName()
, getElementsByClassName()
, and getElementsByName()
.But that's with the simple stuff. The
querySelector()
and querySelectorAll()
functions have more control than the classic getElement[s]By*()
functions, which includes the CSS Selectors. By combining querySelector()
and querySelectorAll()
into a single function we can simplify object calls to just one function instead of four or two.While researching this information, I do recognize that some improvements should still be made. Like, it might just be more logical only to use
querySelectorAll()
, test the quantity of items returned, and if there is only one item, just return that item instead of needing to tack on a [0]
like jQuery's $()
is known for, which would eliminate the all
variable from qs()()
. However, I think what I have right now is fine enough as it demonstrates several important features of JavaScript.It's also important to remember, that when you select more than one item and want to map a function to each item, any
querySelectorAll()
will return a NodeList
NOT an Array
. To map, you must convert the NodeList
to an Array
, which can be done using Array.from()
. And although Array.from()
does have another argument to execute a .map()
function, I recommend doing that separately, especially if you want to use other function like .reduce()
or .filter()
.I hope you've enjoyed this article as much as I've enjoyed writing it. If you'd like me describe some of the other functions I've been using in my projects, such as one ones I've been using to reduce and in some cases eliminate the use of HTML in my projects, let me know in the community.