Re-Query Select All the Things!

Written by JRCharney | Published 2019/08/28
Tech Story Tags: queryselector | queryselectorall | javascript | qs | hoisting | filter | haqsjs | latest-tech-stories

TLDR The old version of this one-liner has been redesigned for JavaScript. The new version of the function is called apologeticqs()(). It eliminates the use of template strings like the old version, but it doesn't take advantage of templates. The function can be used to assign a value to a variable, but the assignment operator returns the value assigned to that variable. It's not the same as all other functions in the form of a query that can only be used for a single query.via the TL;DR App

Remember last week when I spoke about how to use
querySelect()
and
querySelectAll()
and how it could all be done in one command I called
qs()()
? Remember reading the part about how after some research, I've had though about redesigning the function?
Here's what the old version looks like.
var qs = parent => (query,all) => (typeof(parent) === "string") ? qs(qs()(parent))(query,all) : (parent||document)[`querySelector${(all||false)?"All":""}`](query);
And here's the new version of
qs()()
.
var qs = parent => query => {let q; return (typeof(parent) === "string") ? qs(qs()(parent))(query) : (q = (parent||document).querySelectAll(query)).length > 1 ? q : (q[0]||null);};
Try as I might, I really hoped that that
let q
(or more appropriately
var q
) would have slyly stuck itself into the
q = (parent||document).querySelectorAll(query)
part where assigning
q
there allows us to have
q.length > 1
without having to put our assignment operation (
=
) on a separate line with the
let q
statement, but I cannot do that, and here's why.
Remember that when you assign a value to a variable, the assignment operator returns the value assigned to that variable. You can try it out in the debugging console of your browser or in a Node.js console right now. If you type in
five = 5;
the console will return
5
. That
5
is stored in the variable
five
until your refresh your browser or exit the Node.js console.
I could go on about operator precedence in JavaScript, but I don't want to.
I could have gone without the
let q
statement and not used the curly braces (
{...}
) and
return
statement as JavaScript variable hoisting would have allowed me to assign a value to
q
(with
q = (parent||document).querySelectorAll(query)
) without initially declaring
q
with the
let q
statement but the scope of using the
let
keyword inside a set of parenthesis would not allow me to get
q.length
because if you use
let
,
var
, or
const
before a variable, it will return
undefined
, and
undefined.length
throws a
TypeError
. And if you are using strict mode, either by putting
"use strict";
at the top of your file or at the beginning of your function, declaring
q
without
let
,
var
, or
const
throws a
ReferenceError
.
// Let's review (assuming strict mode is not initially set in this figure)
five = 5; // => 5 (because the assignment operator returns the value that is assigned to the variable; because strict mode is not enabled, five is hoisted, and can be initialized without declaration first)
var six; // => undefined (declaration keywords (var, let, and const) return undefined even though the variable. If six was previously used, it would have been hoisted.)
var seven = 7; // => undefined (although 7 is assigned to seven; seven is declared and assigned so technically it's hoisted)
var num = x => {
 "use strict";
 let w;  // => undefined (although w is defined)
 y = x;  // => ReferenceError (because y was not defined)
 w = (var z=[x,y,3]).length; // SyntaxError (because unexpected token "var" next to z) (also, if it had worked the code inside the parenthesis would have returned undefined, of which undefined.length is a TypeError)
 return w;
};
At any rate, because our
let q
couldn't be integrated into our one-liner, we have to bring back the curly braces (
{...}
) and
return
statement, since we can't return our
q
value implicitly.
//Remember that this form, known as FUNCTIONAL FORM,...
var f = x => x;
// Means the same as this form, known as FUNCTIONAL-IMPERATIVE FORM,...
var f = x => { return x; }
// Means the same as this form known as IMPERATIVE FORM
var f = function(x){ return x; }
So here is the new
qs()()
in detail.
/* @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.
 * @returns:
 *  null if nothing is found.
 *  Node if query matches one result.
 *  NodeList query matches more than one result.
 */
var qs = function(parent){
  return function(query){
    let q;	// q is declared in the inner function
    if(typeof(parent) === "string"){
      let parent_query = qs()(parent);
      return qs(parent_query)(query);
    } else {
      // If parent is undefined, use document.
      var parent_element = parent || document;
      q = parent_element.querySelectorAll(query);
      if(q.length > 1){
        // if q match more than one instance return all matches as a NodeList.
        return q;	  
      } else {
        // Otherwise, return the first element only
        // Note: If [] is returned, and [][0] is undefined (meaning no matches), return null.
        return (q[0]||null);
      }
    }
  } // inner function
} // outer function
Before I talk about what makes this different from the old version, I want to talk about two functions in the
Array
class. The
.find()
function matches the first instance of a query. The
.filter()
matches all instances of a query and returns an array. What if you use
.filter()
and it only returns one instance? You still have to tack on
[0]
to get that one item out of the returned array. The
.find()
function, is basically the
.filter()
function where that one item is returned without needed to append
[0]
if it finds something.
There is one other thing to note. If
.filter()
finds no results, it returns an empty array,
[]
, but
.find()
returns
undefined
. Furthermore, the zeroth item in an empty array,
[][0]
, is
undefined
.
The
.querySelector()
function is basically the
.find()
function for a
NodeList
, which makes
.querySelectorAll()
the
.filter()
function. But there is one problem:
.querySelector()
doesn't return
undefined
, it returns
null
, and
null
is not the same as
undefined
.
Just about all
.getElementsBy*()
function returns an empty array, like
.querySelectorAll()
. But
.getElementById()
returns
null
like
.querySelector()
. We still need
null
any way because
typeof([]) === "object"
and
typeof(null) === "object"
too. So if
q[0]
is
undefined
,
null
will be returned.
With that, we eliminate the
all
variable that was in the old version of
qs()()
. While this function doesn't take advantage of template strings like the old version did, it finally eliminates
.querySelector()
, meaning
.querySelectorAll()
is the ultimate query function and the only one we need.
So that's the new
qs()()
. Eventually, I'd like to present a new library I'm working on called haqs which is a JavaScript library which has an emphasis on closures and functional programming.
Here at Hackernoon, I plan on describing the more simplified versions of some of these function. The haqs library version is a bit more complicated because I was a bit braggadocio with demonstrating the functional programming aspects. I would really like to see just an itty-bit of object-oriented programming to reduce the amount of repetition and static structuring.
I also want to apply functional patterns to write code with a concise behavior. Haqs isn't quite ready yet, but I integrate some of the stuff I'm talking about here into it as I would like for it to be used in my Codepen work.
We'll see how it turns out. Until next time, keep hacking!

Written by JRCharney | Web Dev Geek from St. Louis
Published by HackerNoon on 2019/08/28