Javascript library implementing emulation of CSS selectors lookup, as document.querySelector.
View the Project on GitHub jeremiah-shaulov/joyquery
The API consists of:
Iterator function joyquery(string selector, DOMElement context, Object functions, Object override_settings);
The joyquery() function returns another function, which is iterator.
DOMElement function iterator();
Each call to the iterator function causes to search for next element. The found element will be returned, or if there are no more results, null is returned. Subsequent calls will do nothing.
for (var elem, it=joyquery("a.cls"); elem=it();)
{ console.log(elem);
}
The iterator function (it()) has method get().
Array function get(); // [1]
DOMElement function get(Number index); // [2]
Array function get(index, count); // [3]
The [1]st form iterates over all results and returns the results as Array of DOMElement. Even if built-in querySelectorAll() was used, which returns non-array collection (in Chrome it is NodeList), get() always returns Array object.
Example:
var all_results = joyquery("a.cls").get();
The [2]nd form returns single result whoose number is provided. Can return null (or undefined).
Example:
var elem = joyquery("a.cls").get(0);
The [3]rd form returns slice Array from the result set.
Example:
var results = joyquery("a.cls").get(10, 3);
If browser is equiped with built-in element.querySelector and element.querySelectorAll functions, they will get a try to handle the supplied selector.
var all_results = joyquery("a.cls").get(); // querySelectorAll is tried
var some_results = joyquery("a.cls").get(10, 3); // querySelectorAll is tried
var elem = joyquery("a.cls").get(0); // querySelector is tried
var results = joyquery("a.cls").get(0, 1); // querySelector is tried
Though there is option not to utilize built-in functions. See joyquery.SETTINGS.
When iterating through result of a selector that contains alternatives (commas), the same element can be returned several times.
for (var elem, it=joyquery("div a, div > span > a"); elem=it();)
{ console.log(elem);
}
This can happen only when result is not stored to array. When you use get() to store the result to an array, only unique elements are returned.
var all_results = joyquery("div a, div > span > a").get();
If selector contains a pseudo-class (like :contains-word-Hello) which is not implemented by joyquery library, joyquery will look for Javascript function to handle it. Dashes in the name are substituted with underscores.
First, if "functions" argument (the 3rd argument) was provided for joyquery(), and it is an object that has such function (method), this function is called.
If there was no function provided, the function will be looked in global joyquery.FUNCTIONS object.
Adding methods to this global object will extend joyquery functionality.
The extension functions are called in context of some object that has the following properties:
Extension function must return true if it considers the current element (this.node) to be matching.
var elements = joyquery
( "div > p:contains-word-Hello",
null,
{ contains_word_Hello: function()
{ return (this.node.textContent || this.node.innerText || '').indexOf('Hello') != -1;
}
}
).get();
Extension functions can recieve arguments. If a selector passed as argument (like in :has() or :not() functions), then the extension function will recieve object that has two methods: evaluate(node) and evaluate_one(node).
var elements = joyquery
( "div > p:contains-word('Hello')",
null,
{ contains_word: function(word)
{ return (this.node.textContent || this.node.innerText || '').indexOf(word) != -1;
}
}
).get();
If a CSS-escaped string literal was passed as argument, the extension function will receive String object. If expression like 3n+1 was passed (e.g. :my-custom-nth-child(3n+1)), the function receives object with 2 fields: "imag" (3 in example) and "real" (1 in example).
Functions like :has(), :not() and :any() expect to receive CSS selectors as arguments. If a selector passed as argument, then the extension function will recieve object that has two methods: evaluate(node) and evaluate_one(node).
This is custom implementation of :has(selector) handler:
var elements = joyquery
( "div:my-custom-has(* p)",
null,
{ my_custom_has: function(selector)
{ return selector.evaluate(this.node).get(0);
}
}
).get();
Method evaluate_one(node) is optimized version of evaluate(node) that returns first found node.
var elements = joyquery
( "div:my-custom-has(* p)", null,
{ my_custom_has: function(selector)
{ return selector.evaluate_one(this.node);
}
}
).get();
Some functions like :lang(), expect to receive raw identifier, not quoted, e.g. :lang(br). In this case joyquery needs to know that this identifier is not a selector that selects tag name. To tell joyquery to interpret first argument as raw string, provide func_name+'.raw_argument'
in the same object that contains the extension function (either functions or joyquery.FUNCTIONS). For example, this is how to do this for :lang() extension:
joyquery.FUNCTIONS['lang.raw_argument'] = true;
joyquery.FUNCTIONS.lang = function(lang_name)
{ // ...
};
This global object contains settings that affect joyquery operation. There are 2 settings:
compiler_cache_max specifies for how many selectors to store compiler result. Each time you call joyquery() with selectors that built-in document.querySelector() can't handle, joyquery() needs to parse the selector and convert it to internal commands. These commands are stored in compiler cache for further reuse. So next time joyquery() will not need to reparse the same selector, if you query it several times (maybe in loop).
The default value 4 means there will be up to 4 compiler results stored in memory permanently.
jQuery also has compiler cache.
Example:
joyquery.SETTINGS.compiler_cache_max = 1;
var subelems = joyquery('child::*', elem);
The joyquery() function also has 4th argument that overrides global settings during current call.
Example:
var subelems = joyquery('child::*', elem, null, {compiler_cache_max: 1});
emulate - if set to true, joyquery() will not try to utilize built-in document.querySelector() and document.querySelectorAll() functions.
This is rarely needed. One of possible reasons is to avoid calling document.querySelector() for selectors where it will fail for sure.
Another reason is to test joyquery emulation performance.
Example:
joyquery.SETTINGS.emulate = true;
var subelems = joyquery('child::*', elem);
Example:
var subelems = joyquery('child::*', elem, null, {emulate: true});
Though there can be one more theoretic reason. Utilization of built-in function means that all the results will be found at once and stored in array. You may not want this. For example in case that there are much more results than you are planning to iterate over. Or maybe if you are willing to modify DOM tree between the calls to iterator, and thus affecting the further search.