-
-
Save patik/1456711 to your computer and use it in GitHub Desktop.
<!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 ... | |
// | |
})(); |
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)")
?
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 :)
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
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)")
.