beyond the final frontier of jquery selectors
DESCRIPTION
How to extend jQuery, Sizzle selector engineTRANSCRIPT
By day: Software Engineer at CiscoBy night: OSS contributorCoordinator of Bulgarian Gnome TP Contacts:
E-mail: [email protected] Jabber: [email protected] LinkedIn: http://www.linkedin.com/in/alshopov SlideShare: http://www.slideshare.net/al_shopovWeb: Just search “al_shopov”
[ash@edge ~]$ whoami
Contents
● The Bold And the Beautiful● Guiding Light● As The World Turns
The Bold And the Beautiful
Bold!
<style>.bold {font-weight: bold}</style><div id="d" class="bold">This is bold</div><script>jQuery(function (){ console.log(jQuery('#d').css('font-weight'));});</script>
Bold!!
<style>.bold {font-weight: bold}</style><div id="d" class="bold">This is bold</div><script>jQuery(function (){ console.log(jQuery('#d').css('font-weight'));});</script>
Chrome/Safari/Opera bold
Bold??
<style>.bold {font-weight: bold}</style><div id="d" class="bold">This is bold</div><script>jQuery(function (){ console.log(jQuery('#d').css('font-weight'));});</script>
Chrome/Safari/Opera bold
IE11/Firefox700
Thus Spoke The Standards
Value: normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900Initial: normalApplies to: all elementsInherited: yesPercentage values: N/ACSS1 [CSS Level 1/CSS Level 2 (Revision 1)/CSS Transitions/CSS Fonts Module Level 3]
100 - Thin 200 - Extra Light (Ultra Light) 300 - Light 400 - Normal 500 - Medium 600 - Semi Bold (Demi Bold) 700 - Bold 800 - Extra Bold (Ultra Bold) 900 - Black (Heavy)
100 ≤ 200 ≤ 300 ≤ 400 ≤ 500 ≤ 600 ≤ 700 ≤ 800 ≤ 900
Beautiful
● There is difference between Italic and Oblique font shapes
● Do you know it?
Beautiful
● Italics are stylized cursive writings – as if we write by hand. They have different shapes than roman/regular
● Obliques look like slanted roman/regular – poor man's italics
The Three Graces – Roman/regular, Italic, Oblique
How Can We Use Them With A Single Button?
How Can We Use Them? Web Fonts!<style type="text/css">@font-face { font-family: lmr; src: url(lmrtest-regular.otf);}@font-face { font-family: lmr; font-style: italic; src: url(lmrtest-italic.otf);}@font-face { font-family: lmr; font-style: oblique; src: url(lmrtest-oblique.otf);}</style>
<div style="font-family:lmr"><p style="font-style:normal"> Roman</p><p style="font-style:italic"> Italic</p><p style="font-style:oblique"> Oblique</p></div>
How Can We Find The Bold And The Beautiful?
function isBold(el){ var w = jQuery(el).css('font-weight'); return ('bold' === w) || (400 < (+w));}
function isSlanted(el){ var s = jQuery(el).css('font-style'); return ('italic' === s) || ('oblique' === s) ;}
Sorry Mario, Our Princess is in Another Castle
● We did not find the elements● We can just check whether a particular element is what we are
interested in● So we use it like this and create functions time and again:
jQuery('selector …').filter(function(i){ return isBold(i);});
A Slight Improvement
function bold4jQ(){ var w = jQuery(this).css('font-weight'); return ('bold' === w) || (400 < (+w));}jQuery('selector …').filter(bold4jQ);
function slanted4jQ(){ var s = jQuery(this).css('font-style'); return ('italic' === s) || ('oblique' === s);}jQuery('selector …').filter(slanted4jQ);
(You Gotta) Fight For Your Right (to jQuery)
● Why do this imperatively and explicitly call time and agang the same functions?
● jQuery has its own selector engine, why not use it to accomplish our needs
● Let's be declarative – which is more readable?
jQuery('selector …').filter(bold4jQ);jQuery('selector …').filter(slanted4jQ);
jQuery('selector:bold');jQuery('selector:slanted');
Guiding Light
This Is How We Do ItjQuery.extend(jQuery.expr[':'], { bold: function(el) { var w = jQuery(el).css('font-weight'); return ('bold' === w) || (400 < (+w)); }});jQuery('div:bold');
jQuery.extend(jQuery.expr[':'], { slanted: function(el) { var s = jQuery(el).css('font-style'); return ('italic' === s) || ('oblique' === s); }});jQuery('#editor span.usr:slanted');
Homework Asignments
● Find external links● Find links that lead to ASPX or JSP pages directly● Find divs with sizes above 42px● Find spans that are block elements or divs that are inline● The sky is the limit
Is This All There Is?
● We are not limited to no args functions. The function we provide is called with 3 arguments. We can dispatch behaviour based on all of them
/* returns boolean - whether given DOM * * element matches */matcher( /* single DOMElement from currenlty * * matched set to check */ DOMElement, /* index of the current element in * * in the matched set */ index, /* array of the split matching * * sequence */ matches);
Positional Parameters Example Usage
jQuery('selector…:font(14,400,italic)');
m = ['font', 'font', '', '14,400,italic'];
Positional Parameters Example Usage
jQuery('selector…:font(14,400,italic)');
m = ['font', 'font', '', '14,400,italic'];
m[3] === '14,400,italic';
Positional Parameters Example Usage
jQuery('selector…:font(14,400,italic)');
m = ['font', 'font', '', '14,400,italic'];
m[3] === '14,400,italic'; // split & coerce
m[3] === '14,400,italic';
Positional Parameters Example Usage
jQuery.extend(jQuery.expr[':'], { font: function(el, i, m){ var a = m[3].split(','), // dispatch l = a.length, // dispatch j = jQuery(el), // cache p = ['size', 'weight', 'style'], z = 0; // index if(0 === l){return true;} if(3 < l){throw {name:'Funky', message:'Nuts?'};} // or return false and fail silently for(;z<l;++z){if(j.css('font-'+p[z])!=a[z]){return false;}} return true; }});
Named Parameters Example Usage
jQuery('selector…:font' + '(size>14,400<=weight,italic!=style,family!=Sans)'
m = ['font', 'font', '', 'size>14,400<=weight,italic!=style,family!=Sans'
Named Parameters Example Usage
jQuery('selector…:font' + '(size>14,400<=weight,italic!=style,family!=Sans)'
m[3] === // what to do, what to do? 'size>14,400<=weight,italic!=style,family!=Sans'
m = ['font', 'font', '', 'size>14,400<=weight,italic!=style,family!=Sans'
Named Parameters Example Usage
'size>14,400<=weight,italic!=style,family!=Sans'
Named Parameters Example Usage // split, split, juggle & check'size>14,400<=weight,italic!=style,family!=Sans'
Named Parameters Example Usage // split, split, juggle & check'size>14,400<=weight,italic!=style,family!=Sans'
// split, juggle & check['size>14','400<=weight','italic!=style','family!=Sans']
Named Parameters Example Usage // split, split, juggle & check'size>14,400<=weight,italic!=style,family!=Sans'
// split, juggle & check['size>14','400<=weight','italic!=style','family!=Sans']
[['size','>','14' ], // juggle & check ['400','<=','weight' ], ['italic','!=','style'], ['family','!=','Sans' ]]
Named Parameters Example Usage // split, split, juggle & check'size>14,400<=weight,italic!=style,family!=Sans'
// split, juggle & check['size>14','400<=weight','italic!=style','family!=Sans']
[['size','>','14' ], // juggle & check ['400','<=','weight' ], ['italic','!=','style'], ['family','!=','Sans' ]]
[['family','!=','Sans' ], // check left as an ['size' ,'>' ,'14' ], // excercise for the ['style' ,'!=','italic' ], // reader ['weight','<=','400' ]]
Named Parameters Example UsageGetting Jiggy With It
m[3] === // be creative 'size>14,400<=weight,italic!=style,family!=Sans'
'size>14,400<=weight,italic!=style,family!=Sans'
Named Parameters Example UsageGetting Jiggy With It
m[3] === // be creative 'size>14,400<=weight,italic!=style,family!=Sans'
'size>14,400<=weight,italic!=style,family!=Sans'
'size>14,400<=weight,italic!=style,family!=Sans'. replace(/,/g,'&&') === ???
Named Parameters Example UsageGetting Jiggy With It
m[3] === // be creative 'size>14,400<=weight,italic!=style,family!=Sans'
'size>14,400<=weight,italic!=style,family!=Sans'
'size>14,400<=weight,italic!=style,family!=Sans'. replace(/,/g,'&&') === ???
"size>14&&400<=weight&&italic!=style&&family!=Sans"
Looks familiar, doesnt' it?
var expression ="size>14&&400<=weight&&italic!=style&&family!=Sans";
● But what are size, weight, style, family?● What about italic and Sans?
But We Already Know That
size: j.css('font-size') weight: j.css('font-weight') style: j.css('font-style') family: j.css('font-family') italic: 'italic', Sans: 'Sans'
Why Didn't You Say So?
var scope = { size: j.css('font-size'), weight: j.css('font-weight'), style: j.css('font-style'), family: j.css('font-family'), italic: 'italic', Sans: 'Sans'};
Mojo!!!
var r;with (scope){ r = eval (expression);}// PROFIT!!!
Now That You Know How
● DON'T!!!● Use to explore, you brave explorers.● Use in production is misuse – will void your warranty, kill kittens
and cause plague.
So What Is The Catch?
● Quick to implement, experiment, POC;● Piggyback on JavaScript as a host for the DSL;● Hard to debug, with weird error messages in strange places – still
we are in control of the scope template and this can be mitigated;● Blocks browser's JS optimization engine (but we are not the only
ones);● Jslint will complain – we are treading in one of the the darkest
places in the language.
How Do You Say De-Groovie?How Do You Say De-Bug?
jQuery('selector…:debug');
jQuery.extend(jQuery.expr[':'], { debug: function(el, i, m) { console.log("args: %o", arguments); console.log("m: %o", m); return true; }});
The Power Of Their Source? The Crystal!
define(["./core", "sizzle"], function( jQuery, Sizzle ) { jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; jQuery.expr[":"] = jQuery.expr.pseudos; jQuery.unique = Sizzle.uniqueSort; jQuery.text = Sizzle.getText; jQuery.isXMLDoc = Sizzle.isXML; jQuery.contains = Sizzle.contains;});
https://github.com/jquery/jquery/blob/master/src/selector-sizzle.js
As the World Turns
Sizzle, Who the (BLEEP) is Sizzle?
● Sizzle: A pure-JavaScript CSS selector engine designed to be easily dropped in to a host library.
● Embedded in jQuery, MooTools, Dojo
Version Supported browsers LOC Minified API
jQuery 1.10.2 IE6+, FF(Current-1)+, Chrome(Current-1)+, Safari 5.1+, Opera 12.1x, (Current-1)+
≈10k ≈93kB Vast
jQuery 2.0.3 IE9+, FF(Current-1)+, Chrome(Current-1)+, Safari 5.1+, Opera 12.1x, (Current-1)+
≈9k ≈84kB Vast
Sizzle 1.10.1x IE6+, FF3.0+, Chrome 5+, Safari 3+, Opera 9+
≈2k ≈18kB Tiny
Sizzle API – part 1
/* returns boolean - whether given DOM * * element matches selector * * If available uses native matchesSelector */Sizzle.matchesSelector( /* DOMElement to check */ DOMElement, /* String – CSS Selector */ selector);
Sizzle API – part 2
/* returns Array of the elements that match * * the selector */Sizzle.matches( /* String – CSS Selector */ selector, /* Array of DOMElements */ elements);
Sizzle API – part 3/* Return nodes that match selector * [starting from the context node] * as an array [or added to the provided * array-like object] */Sizzle( /* String – CSS Selector */ selector, /* Optional: DOMElement|DOMDocument * * Default: Current document * * Node to start the search from */ context, /* Optional: Array-like object to * * add elements to * * eg.: jQuery() obj * * Default: Create new array */ results);
That's All Folks!
This was the whole public API!
Do It Very Slowly (True Lies)
● document.querySelectorAll is very fast and meddles with our meddling
● Get rid of it!
document.querySelectorAll = null; // or any falsy// why not?delete document.querySelectorAll;
Alien
● querySelectorAll (as well as querySelector) comes from elsewhere – from the prototype world
● Prototype world is wilder that the West!● Object.getPrototypeOf() – IE, Chrome, Firefox● {}.__proto__ – Firefox, Chome
Alien 2 – IE 10
document
DocumentProtoype
NodeProtoype
window
Window
Object
nullquerySelectorAllWORKS: = null;
FAILS: delete
Alien 4 – Firefox
document
HTMLDocumentPrototype
DocumentPrototype
NodePrototype
EventTarget
Prototype
window
[WindowPrototype]
Object
nullquerySelectorAll
WORKS: = null;FAILS: delete
Alien 3 – Chrome
document
HTMLDocument
Document
Node
EventTarget
window
Window
Object
nullquerySelectorAll
WORKS: = null;FAILS: delete
Alien 4 – Firefox
document
HTMLDocumentPrototype
DocumentPrototype
NodePrototype
EventTarget
Prototype
window
[WindowPrototype]
Object
nullquerySelectorAll
WORKS: = null;FAILS: delete
What We Used Thus Far
// Thus far we actually extendedSizzle.selectors.pseudos;
//jQuery does some magic to ease using Sizzle.selectors.createPseudo(function(){});
Where Next// Matches parts of selector (as string)// divides in partsSizzle.selectors.match.NAME = new RegExp('...');
Sizzle.selectors.find.NAME = function( // what was matched above match, // element to start search from context, // whether doc is XML/HTML IsXML ) {};
// regex of orders, add |NAME to itSizzle.selectors.order;
Where Next
// quick prefilter, eg: remove all attributes or // elements based on a quick decisionSizzle.selectors.preFilter.NAME = function( match ) {};// slow filter invoked after the one above (if )∃// real evaluationSizzle.selectors.filter.NAME = function(element, m1, m2, m3 ) {};
Sizzle is extensible
● No extension tests in test harness;● Dragons around the corers, YMMV;● Looooooooooooooooo + ooooooooooooo + oooo + oooooo +
oooooooo + oooooo + ooooooooooooooooooooooooooo + oooooooo + ooooooooooo + ooooong regular expressions;
● Actual regular expressions are longer.
TO BE IMPROVED...