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
class. The Array
function matches the first instance of a query. The .find()
matches all instances of a query and returns an array. What if you use .filter()
.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
variable that was in the old version of all
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!