Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Customized Zepto for fake selectors
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<style> .hidden { display: none; } </style>
<script src="zepto.custom.js"></script>
<script>
$(document).ready(function(e) {
// This should print 2 results: #div3 and #div4
console.log("visible:", $("div:visible"));
// This should print 3 results: #div1, #div2, and #div5
console.log("hidden:", $("div:hidden"));
});
</script>
</head>
<body>
<div class="hidden" id="div1">
<div id="div2"></div>
</div>
<div id="div3"></div>
<div id="div4">
<div style="display:none;" id="div5"></div>
</div>
</body>
</html>
var Zepto = (function() {
//
// ... snipped the Zepto code for brevity ...
//
// Custom selectors
// Each method takes an array/list and returns an array of matching elements
// Method names match the custom selector name (eg, hidden() is for :hidden)
$.customSelectors = {
hidden: function(elems) {
var matches = [], len = elems.length, i = 0;
while (i < len) {
if ($.customSelectors.test.hidden(elems[i])) {
matches.push(elems[i]);
}
i++;
}
return matches;
},
visible: function(elems) {
var matches = [], len = elems.length, i = 0;
while (i < len) {
if ($.customSelectors.test.visible(elems[i])) {
matches.push(elems[i]);
}
i++;
}
return matches;
},
// This contains the Boolean functions which test a single element
// Which means you can't create a fake selector div:test unless you rename this...
test: {
// Pulled from jQuery and trimmed down to the basics
hidden: function(elem) {
var width = elem.offsetWidth,
height = elem.offsetHeight;
return (width === 0 && height === 0) || ((elem.style.display || $(elem).css("display")) === "none");
},
visible: function(elem) {
return !$.customSelectors.test.hidden(elem);
}
}
}
$.qsa = $$ = function(element, selector) {
var found, standard = "", custom = "";
// Look for a colon to indicate the presence of a custom selector
if (selector && ~selector.indexOf(":")) {
standard = selector.split(":")[0];
custom = selector.split(":")[1];
}
if (custom.length && custom in $.customSelectors) {
// First find all elements matching the non-custom selector(s)
found = standard.length ? $$(element, standard) : $$(element);
// Then filter that result by testing against the custom selector
return $.customSelectors[custom](found);
}
else {
// No function exists for this selector, so we can't perform a query
return emptyArray;
}
}
else {
// This is the original, untouched code from $.qsa
return (element === document && idSelectorRE.test(selector)) ?
( (found = element.getElementById(RegExp.$1)) ? [found] : emptyArray ) :
slice.call(
classSelectorRE.test(selector) ? element.getElementsByClassName(RegExp.$1) :
tagSelectorRE.test(selector) ? element.getElementsByTagName(selector) :
element.querySelectorAll(selector)
);
}
}
// ... futher down ...
$.fn = {
// ... functions snipped for brevity ...
filter: function(selector){
return $([].filter.call(this, function(element){
var custom = "";
// Look for a colon to indicate the presence of a custom selector
if (selector && ~selector.indexOf(":")) {
custom = selector.split(":")[1];
}
if (custom.length && custom in $.customSelectors.test) {
return $.customSelectors.test[custom](element);
}
else {
return element.parentNode && $$(element.parentNode, selector).indexOf(element) >= 0;
}
}));
},
// ... the rest of $.fn
};
//
// ... the rest of the Zepto code snipped for brevity ...
//
})();
Owner

patik commented Dec 10, 2011

TL;DR: Don't use this, it's not the most efficient way to do it and it probably doesn't work on complex selectors that the native qsa() would handle. This is in response to this issue.

Basically, I created an object $.customSelectors which contains methods for 1) testing an element against each custom selector, and 2) filtering a given array of elements down to just the ones that match. Included here are methods for testing element visibility, but you can add anything else you'd like.

Then I added a block of code to $.qsa to look for a colon in the selector. If there is one, it first queries the right side of the colon (the "regular" selector, i.e. tag and class names) then filters that list by the left side of the colon (the custom/fake selector). Otherwise, if there's no colon, the original $.qsa code is run.

This isn't very efficient — ideally the custom selector matching should be done elsewhere (maybe $.filter; I'm new to Zepto so I'm not sure) so that you're not looping through the elements twice. Don't use it if you're using custom selectors frequently or on large numbers of elements.

This has only been tested with very simple, singular selectors like $("div.myClass:hidden"). I haven't tried things like $("div:hidden span") or $(".myClass:not(p:hidden)").

arextar commented Dec 10, 2011

I'm not sure you should return an empty array if the custom selector doesn't exist, what if you run $("div:nth-child(2)")?

Owner

patik commented Dec 10, 2011

Actually I wanted to avoid that initial if() clause entirely unless the part after the colon was a defined custom selector. (That's why I threw in the check for :not, the only pseudo-class that came to mind at the moment.)

I updated the code so it will use the native qsa for :nth-child, :not, and any other real pseudo-class. I also added code for modifying $.fn.filter so that you can do things like $("div").is(":hidden").

BTW, the fact that I am repeating myself in the $.qsa and $.fn.filter functions is another BIG RED FLAG that this code is inefficient and not for production use :)

arextar commented Dec 10, 2011

I have a pull open in Zepto that creates a function that checks whether or not an element matches a selector(using the speedy native matchesSelector), if that gets accepted that would probably be a good spot to put code like this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment