microbejs
Version:
microbe.js - A modular JS library for DOM manipulation, and more
1,093 lines (952 loc) • 26.2 kB
JavaScript
/**
* pseudo.js
*
* @author Mouse Braun <mouse@knoblau.ch>
* @author Nicolas Brugneaux <nicolas.brugneaux@gmail.com>
*
* @package Microbe
*/
module.exports = function( Microbe )
{
'use strict';
/**
* ## _parseNth
*
* when supplied with a Microbe and a css style n selector (2n1), filters
* and returns the result
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var number string
* @param {Boolean} _last counting from the font or back
*
* @return _Microbe_
*/
var _parseNth = function( _el, _var, _last )
{
if ( _var === 'odd' )
{
_var = '2n';
}
else if ( _var === 'even' )
{
_var = '2n1';
}
if ( _var.indexOf( 'n' ) === -1 )
{
switch ( _last )
{
case true:
case 'last':
return new Microbe( _el[ _el.length - parseInt( _var ) ] );
}
return new Microbe( _el[ parseInt( _var ) - 1 ] );
}
else
{
_var = _var.split( 'n' );
var increment = parseInt( _var[0] ) || 1;
var offset = parseInt( _var[1] );
var top;
if ( _last === true || _last === 'last' )
{
top = _el.length - parseInt( _var[1] );
offset = top % increment;
}
var _e, resArray = [];
for ( var i = offset || 0, lenI = top || _el.length; i < lenI; )
{
_e = _el[ i ];
if ( _e )
{
resArray.push( _e );
}
i += increment;
}
return new Microbe( resArray );
}
};
/**
* ## pseudo
*
* an extension to core.__init_ to handle custom pseusoselectors
*
* @param {Microbe} self half built Microbe
* @param {String} selector pseudo-selector string
* @param {Object} _scope scope element
* @param {Function} _build build function from core
*
* @return _Microbe_
*/
var pseudo = function( self, selector, _scope, _build )
{
/**
* ## _breakUpSelector
*
* pushes each selector through the pseudo-selector engine
*
* @param {Array} _selectors split selectors
*
* @return _Microbe_
*/
function _breakUpSelector( _selectors )
{
var next, resArray = [];
for ( var i = 0, lenI = _selectors.length; i < lenI; i++ )
{
if ( i === 0 )
{
resArray = pseudo( self, _selectors[ i ], _scope, _build );
}
else
{
next = pseudo( self, _selectors[ i ], _scope, _build );
for ( var j = 0, lenJ = next.length; j < lenJ; j++ )
{
if ( Array.prototype.indexOf.call( resArray, next[ j ] ) === -1 )
{
resArray[ resArray.length ] = next[ j ];
resArray.length++;
}
}
}
}
return resArray;
}
/**
* ## _buildObject
*
* builds the Microbe ready for return
*
* @return _Microbe_
*/
function _buildObject()
{
var _pseudo = _parsePseudo( _selector );
var obj = _build( _scope.querySelectorAll( _pseudo[0] ), self );
_pseudo = _pseudo[ 1 ];
var _sel, _var;
for ( var h = 0, lenH = _pseudo.length; h < lenH; h++ )
{
_sel = _pseudo[ h ].split( '(' );
_var = _sel[ 1 ];
if ( _var )
{
_var = _var.slice( 0, _var.length - 1 );
}
_sel = _sel[ 0 ];
if ( Microbe.pseudo[ _sel ] )
{
obj = Microbe.pseudo[ _sel ]( obj, _var, selector );
}
}
return obj;
}
/**
* ## _cycleFilters
*
* filters multiple pseudo-selector selectors
*
* @param {Array} res array of results to be filtered
*
* @return _Microbe_
*/
function _cycleFilters( res )
{
var obj = Microbe.pseudo( self, res[ 0 ], _scope, _build );
var filter, connect = false;
for ( var i = 1, lenI = res.length; i < lenI; i++ )
{
filter = res[ i ].trim();
if ( filter[ 0 ] === '~' )
{
obj = obj.siblingsFlat( '~' );
connect = true;
}
else if ( filter[ 0 ] === '+' )
{
obj = obj.siblingsFlat( '+' );
connect = true;
}
else if ( connect )
{
obj = obj.filter( filter );
connect = false;
}
else
{
obj = obj.find( filter );
connect = false;
}
if ( obj.length === 0 )
{
return obj;
}
}
return obj;
}
/**
* ## _parsePseudo
*
* checks all pseudo-selectors to see if they're custom and
* otherwise it reattaches it
*
* @param {String} _sel selector string
*
* @return _String_ modified selector
*/
function _parsePseudo( _sel )
{
var _pseudoArray;
var _pseudo = _sel.split( ':' );
_sel = _pseudo[ 0 ];
_pseudo.splice( 0, 1 );
for ( var k = 0, lenK = _pseudo.length; k < lenK; k++ )
{
_pseudoArray = _pseudo[ k ].split( '(' );
if ( !Microbe.pseudo[ _pseudoArray[ 0 ] ] )
{
_sel += ':' + _pseudo[ k ];
_pseudo.splice( k, 1 );
}
}
return [ _sel, _pseudo ];
}
if ( selector.indexOf( ',' ) !== -1 )
{
selector = selector.split( /,(?![a-zA-Z0-9-#.,\s]+\))/g );
if ( selector.length > 1 )
{
return _breakUpSelector( selector );
}
else
{
selector = selector[ 0 ];
}
}
var _selector = selector;
if ( _selector[ 0 ] === ':' )
{
_selector = '*' + _selector;
}
if ( _selector.trim().indexOf( ' ' ) !== -1 )
{
var filterFunction = function( e ){ return e === ' ' ? false : e; };
var res = _selector.split( /((?:[A-Za-z0-9.#*\-_]+)?(?:\:[A-Za-z\-]+(?:\([\s\S]+\))?)?)?( )?/ );
res = res.filter( filterFunction );
if ( res.length > 1 )
{
return _cycleFilters( res );
}
else
{
_selector = res[ 0 ];
}
}
return _buildObject();
};
/**
* ## _filteredIteration
*
* special iterator that dumps all results ito one array
*
* @param {Microbe} _el elements to cycle through
* @param {Function} _cb callback
*
* @return _Microbe_ filtered microbe
*/
function _filteredIteration( _el, _cb, _recursive )
{
var _r, resArray = [], _f = 0;
for ( var i = 0, lenI = _el.length; i < lenI; i++ )
{
_r = _cb( _el[ i ], resArray, i );
if ( _r )
{
resArray[ _f ] = _r;
_f++;
}
}
if ( _recursive )
{
return resArray;
}
return _el.constructor( resArray );
}
/**
* ## any-link
*
* match elements that act as the source anchors of hyperlinks
*
* @param {Microbe} _el Microbe to be filtered
*
* @example µ( '.example:any-link' );
*
* @return _Microbe_
*/
pseudo[ 'any-link' ] = function( _el )
{
return _el.filter( 'a' );
};
/**
* ## blank
*
* matches elements that only contain content which consists of whitespace
*
* @param {Microbe} _el Microbe to be filtered
*
* @example µ( '.example:blank' );
*
* @return _Microbe_
*/
pseudo.blank = function( _el )
{
var _blank = function( _e, resArray )
{
var _t = _e.textContent;
if ( resArray.indexOf( _e ) === -1 )
{
if ( /^\s*$/.test( _t || _e.value ) )
{
return _e;
}
}
};
return _filteredIteration( _el, _blank );
};
/**
* ## column
*
* filters for columns with a suplied selector
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var string to search for
*
* @example µ( '.example:column' );
*
* @return _Microbe_
*/
pseudo.column = function( _el, _var )
{
return _el.filter( 'col' ).filter( _var );
};
/**
* ## contains
*
* Returns only elements that contain the given text. The supplied text
* is compared ignoring case
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var string to search for
*
* @example µ( '.example:contains(moon)' );
*
* @return _Microbe_
*/
pseudo.contains = function( _el, _var )
{
_var = _var.toLowerCase();
var _contains = function( _e )
{
var _getText = function( _el )
{
return document.all ? _el.innerText : _el.textContent; // ff
};
var _elText = _getText( _e );
if ( _elText.toLowerCase().indexOf( _var ) !== -1 )
{
return _e;
}
};
return _filteredIteration( _el, _contains );
};
/**
* ## default
*
* selects all inputs and select boxes that are checked by dafeult
*
* @param {Microbe} _el Microbe to be filtered
*
* @example µ( '.example:default' );
*
* @return _Microbe_
*/
pseudo.default = function( _el )
{
_el = _el.filter( 'input, option' );
var _default = function( _e )
{
if ( _e.defaultChecked === true )
{
return _e;
}
};
return _filteredIteration( _el, _default );
};
/**
* ## dir
*
* match elements by its directionality based on the document language
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var string to search for
*
* @example µ( '.example:dir(ltr)' );
*
* @return _Microbe_
*/
pseudo.dir = function( _el, _var )
{
var _dir = function( _e )
{
if ( getComputedStyle( _e ).direction === _var )
{
return _e;
}
};
return _filteredIteration( _el, _dir );
};
/**
* ## drop
*
* returns all elements that are drop targets. HTML has a dropzone
* attribute which specifies that an element is a drop target.
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var trigger string
*
* @example µ( '.example:drop' );
*
* @return _Microbe_
*/
pseudo.drop = function( _el, _var )
{
_el = _el.filter( '[dropzone]' );
if ( !_var )
{
return _el;
}
else
{
switch ( _var )
{
case 'active':
return _el.filter( ':active' );
case 'invalid':
return _el.filter();
case 'valid':
return _el.filter();
}
}
};
/**
* ## even
*
* Returns the even indexed elements of a Microbe (starting at 0)
*
* @param {Microbe} _el Microbe to be filtered
*
* @example µ( '.example:even' );
*
* @return _Microbe_
*/
pseudo.even = function( _el )
{
var _even = function( _e, resArray, i )
{
if ( ( i + 1 ) % 2 === 0 )
{
return _e;
}
};
return _filteredIteration( _el, _even );
};
/**
* ## first
*
* returns the first element of a Microbe
*
* @param {Microbe} _el Microbe to be filtered
*
* @example µ( '.example:first' );
*
* @return _Microbe_
*/
pseudo.first = function( _el )
{
return _el.first();
};
/**
* ## gt
*
* returns the last {_var} element
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var number of elements to return
*
* @example µ( '.example:gt(4)' );
*
* @return _Microbe_
*/
pseudo.gt = function( _el, _var )
{
return _el.splice( _var, _el.length );
};
/**
* ## has
*
* returns elements that have the passed selector as a child
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var selector string
*
* @example µ( '.example:has(span)' );
*
* @return _Microbe_
*/
pseudo.has = function( _el, _var )
{
var _has = function( _e )
{
if ( _e.querySelector( _var ) )
{
return _e;
}
};
return _filteredIteration( _el, _has );
};
/**
* ## in-range
*
* select the elements with a value inside the specified range
*
* @param {Microbe} _el Microbe to be filtered
*
* @example µ( '.example:in-range' );
*
* @return _Microbe_
*/
pseudo[ 'in-range' ] = function( _el )
{
_el = _el.filter( '[max],[min]' );
var _inRange = function( _e )
{
var min = _e.getAttribute( 'min' );
var max = _e.getAttribute( 'max' );
var _v = parseInt( _e.value );
if ( _v )
{
if ( min && max )
{
if ( _v > min && _v < max )
{
return _e;
}
}
else if ( min && _v > min || max && _v < max )
{
return _e;
}
}
};
return _filteredIteration( _el, _inRange );
};
/**
* ## lang
*
* match the elements based on the document language
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var specified language (accepts wildcards as *)
*
* @example µ( '.example:lang(gb-en)' );
* @example µ( '.example:lang(*-en)' );
*
* @return _Microbe_
*/
pseudo.lang = function( _el, _var )
{
if ( _var )
{
_el = _el.filter( '[lang]' );
_var = _var.replace( '*', '' );
var _lang = function( _e )
{
if ( _e.getAttribute( 'lang' ).indexOf( _var ) !== -1 )
{
return _e;
}
};
return _filteredIteration( _el, _lang );
}
else
{
return _el.constructor( [] );
}
};
/**
* ## last
*
* returns the last element of a Microbe
*
* @param {Microbe} _el Microbe to be filtered
*
* @example µ( '.example:last' );
*
* @return _Microbe_
*/
pseudo.last = function( _el )
{
return _el.last();
};
/**
* ## local-link
*
* returns all link tags that go to local links. If specified a depth
* filter can be added
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var specified depth
*
* @example µ( '.example:local-link' );
* @example µ( '.example:local-link(2)' );
*
* @return _Microbe_
*/
pseudo[ 'local-link' ] = function( _el, _var )
{
_el = _el.filter( 'a' );
var here = document.location;
var _localLink = function( _e )
{
var url = _e.href;
var urlShort = url.replace( here.protocol + '//', '' ).replace( here.host, '' );
urlShort = urlShort[ 0 ] === '/' ? urlShort.slice( 1 ) : urlShort;
var depth = urlShort.split( '/' ).length - 1;
if ( !/^https?:\/\//.test( urlShort ) &&
( !_var || parseInt( _var ) === depth ) )
{
return _e;
}
};
return _filteredIteration( _el, _localLink );
};
/**
* ## lt
*
* returns the first [_var] elements
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var number of elements to return
*
* @example µ( '.example:lt(2)' );
*
* @return _Microbe_
*/
pseudo.lt = function( _el, _var )
{
return _el.splice( 0, _var );
};
/**
* ## matches
*
* returns elements that match either selector
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var selector filter
* @param {String} _selector full original selector
*
* @example µ( '.example:matches(div)' );
*
* @return _Microbe_
*/
pseudo.matches = function( _el, _var, _selector )
{
var _constructor = _el.constructor;
var text = ':matches(' + _var + ')';
_var = _var.split( ',' );
_selector = _selector.replace( text, '' );
_selector = _selector === '*' ? '' : _selector;
var res = _constructor( _selector + _var[ 0 ].trim() );
for ( var i = 1, lenI = _var.length; i < lenI; i++ )
{
res.merge( _constructor( _selector + _var[ i ].trim() ), true );
}
return res;
};
/**
* ## not
*
* returns all elements that do not match the given selector. As per
* CSS4 spec, this accepts complex selectors seperated with a comma
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var null selector
* @param {String} _recursive an indicator that it is calling itself. defines output
*
* @example µ( '.example:not(div)' );
* @example µ( '.example:not(div,#an--id)' );
*
* @return _Microbe_
*/
pseudo.not = function( _el, _var, _selector, _recursive )
{
if ( _var.indexOf( ',' ) !== -1 )
{
var _constructor = _el.constructor;
_var = _var.split( ',' );
for ( var i = 0, lenI = _var.length; i < lenI; i++ )
{
_el = this.not( _el, _var[ i ].trim(), _selector, true );
}
return _constructor( _el );
}
else
{
var _not = function( _e )
{
if ( ! Microbe.matches( _e, _var ) )
{
return _e;
}
};
return _filteredIteration( _el, _not, _recursive );
}
};
/**
* ## nth-column
*
* returns the nth column of the current Microbe
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var column number(s) return
*
* @example µ( '.example:nth-column(1)' );
* @example µ( '.example:nth-column(2n1)' );
* @example µ( '.example:nth-column(even)' );
* @example µ( '.example:nth-column(odd)' );
*
* @return _Microbe_
*/
pseudo[ 'nth-column' ] = function( _el, _var )
{
_el = _el.filter( 'col' );
return _parseNth( _el, _var );
};
/**
* ## nth-last-column
*
* returns the nth column of the current Microbe starting from the back
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var column number(s) return
*
* @example µ( '.example:nth-last-column(1)' );
* @example µ( '.example:nth-last-column(2n1)' );
* @example µ( '.example:nth-last-column(even)' );
* @example µ( '.example:nth-last-column(odd)' );
*
* @return _Microbe_
*/
pseudo[ 'nth-last-column' ] = function( _el, _var )
{
_el = _el.filter( 'col' );
return _parseNth( _el, _var, true );
};
/**
* ## nth-last-match
*
* returns the nth match(es) of the current Microbe starting from the back
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var match number(s) return
*
* @example µ( '.example:nth-last-match(1)' );
* @example µ( '.example:nth-last-match(2n1)' );
* @example µ( '.example:nth-last-match(even)' );
* @example µ( '.example:nth-last-match(odd)' );
*
* @return _Microbe_
*/
pseudo[ 'nth-last-match' ] = function( _el, _var )
{
return _parseNth( _el, _var, true );
};
/**
* ## nth-match
*
* returns the nth match(es) of the current Microbe
*
* @param {Microbe} _el Microbe to be filtered
* @param {String} _var match number(s) return
*
* @example µ( '.example:nth-match(1)' );
* @example µ( '.example:nth-match(2n1)' );
* @example µ( '.example:nth-match(even)' );
* @example µ( '.example:nth-match(odd)' );
*
* @return _Microbe_
*/
pseudo[ 'nth-match' ] = function( _el, _var )
{
return _parseNth( _el, _var );
};
/**
* ## odd
*
* returns the odd indexed elements of a Microbe
*
* @param {Microbe} _el Microbe to be filtered
*
* @example µ( '.example:odd' );
*
* @return _Microbe_
*/
pseudo.odd = function( _el )
{
var _odd = function( _e, resArray, i )
{
if ( ( i + 1 ) % 2 !== 0 )
{
return _e;
}
};
return _filteredIteration( _el, _odd );
};
/**
* ## optional
*
* returns all optional elements
*
* @param {Microbe} _el base elements set
*
* @example µ( '.example:optional' );
*
* @return _Microbe_
*/
pseudo.optional = function( _el )
{
return _el.filter( 'input:not([required=required]), textfield:not([required=required]), [required=optional], [optional]' );
};
/**
* ## out-of-range
*
* select the elements with a value inside the specified range
*
* @param {Microbe} _el Microbe to be filtered
*
* @example µ( '.example:out-of-range' );
*
* @return _Microbe_
*/
pseudo[ 'out-of-range' ] = function( _el )
{
_el = _el.filter( '[max],[min]' );
var _outOfRange = function( _e )
{
var min = _e.getAttribute( 'min' );
var max = _e.getAttribute( 'max' );
var _v = parseInt( _e.value );
if ( _v )
{
if ( min && max )
{
if ( _v < min || _v > max )
{
return _e;
}
}
else if ( min && _v < min || max && _v > max )
{
return _e;
}
}
};
return _filteredIteration( _el, _outOfRange );
};
/**
* ## parent
*
* returns the parents of an _el match.
* normally triggered using the ! selector
*
* @param {Microbe} _el Microbe to be filtered
*
* @example µ( '.example!' );
* @example µ( '.example:parent' );
*
* @return _Microbe_
*/
pseudo.parent = function( _el )
{
_el = _el.parent();
var _parent = function( _e, resArray, i )
{
if ( resArray.indexOf( _e ) === -1 )
{
return _e;
}
};
return _filteredIteration( _el, _parent );
};
/**
* ## read-only
*
* user-non-alterable content
*
* @param {Microbe} _el Microbe to be filtered
*
* @example µ( '.example:read-only' );
*
* @return _Microbe_
*/
pseudo[ 'read-only' ] = function( _el )
{
return _el.filter( ':not(input,textfield,[contenteditable=false])' );
};
/**
* ## read-write
*
* input elements which are user-alterable or contenteditable
*
* @param {Microbe} _el Microbe to be filtered
*
* @example µ( '.example:read-write' );
*
* @return _Microbe_
*/
pseudo[ 'read-write' ] = function( _el )
{
return _el.filter( 'input,textfield,[contenteditable=true]' );
};
/**
* ## required
*
* returns all required elements
*
* @param {Microbe} _el Microbe to be filtered
*
* @example µ( '.example:required' );
*
* @return _Microbe_
*/
pseudo.required = function( _el )
{
return _el.filter( '[required=required]' );
};
/**
* ## root
*
* returns the root elements of the document
*
* @param {Microbe} _el Microbe to be filtered
*
* @example µ( '.example:root );
*
* @return _Microbe_
*/
pseudo.root = function( _el )
{
return _el.constructor( document.body.parentNode );
};
Microbe.pseudo = pseudo;
};