/*!
* pbjs JavaScript Framework v0.6.2
* http://saartje87.github.com/pbjs
*
* Includes Qwery
* https://github.com/ded/qwery
*
* Copyright 2014 Niek Saarberg
* Licensed MIT
*
* Build date 2014-10-01 12:11
*/
(function ( name, context, definition ) {
this[name] = definition( context );
})('PB', this, function ( context ) {
'use strict';
var PB = {},
// Previous PB
OLD_PB = context.PB,
// Unique id, fetch from previous PB or start from 0
uid = OLD_PB ? OLD_PB.id() : 0,
// References
slice = Array.prototype.slice,
toString = Object.prototype.toString;
// Define version
PB.VERSION = '0.6.0';
/**
* Get unique id inside PB
*
* @return {Number}
*/
PB.id = function () {
return ++uid;
};
/**
* Get currect timestamp in milliseconds
*
* @return {Number}
*/
PB.now = Date.now || function () { return new Date().getTime(); };
/**
* Overwrite properties or methods in target object
*/
PB.overwrite = function ( target, source ) {
var key;
for( key in source ) {
if( source.hasOwnProperty(key) ) {
target[key] = source[key];
}
}
return target;
};
/**
* Extend object
*
* Existing values will not be overwritten
*/
PB.extend = function ( target, source ) {
var key;
for( key in source ) {
if( source.hasOwnProperty(key) && target[key] === undefined ) {
target[key] = source[key];
}
}
return target;
};
/**
* Return a deep clone of the given object
*
* @return {Object} clone
*/
PB.clone = function ( source ) {
var clone,
key;
if( source === null || typeof source !== 'object' ) {
return source;
}
clone = PB.type(source) === 'object' ? {} : [];
for( key in source ) {
if( source.hasOwnProperty(key) ) {
clone[key] = PB.clone(source[key]);
}
}
return clone;
};
/**
* Walk trough object
*
* When returning true in the callback method, the crawling stops
*
* fn arguments: key, value
*
* @param {Object}
* @param {Function}
* @param {Object}
* @return {Void}
*/
PB.each = function ( collection, fn, context ) {
var prop;
if ( !collection || typeof fn !== 'function' ) {
return;
}
for( prop in collection ) {
if( collection.hasOwnProperty(prop) && fn.call(context, prop, collection[prop], collection) === true ) {
return;
}
}
};
/**
* Create array of array like object
*
* @return {Array}
*/
PB.toArray = function ( arr ) {
var i = 0,
result = [],
length = arr.length;
for( ; i < length; i++ ) {
result[i] = arr[i];
}
return result;
};
/**
* Returns te primitive type of the given variable
*
* PB.type([]) -> array
* PB.type('') -> string
* PB.type({}) -> object
*
* @param {mixed}
* @return {String}
*/
PB.type = function ( mixed ) {
var type = toString.call(mixed);
return type.substr(8, type.length - 9).toLowerCase();
};
/**
* Log given arguments in the browser console if existing, otherwise dispose the arguments
*/
PB.log = function () {
if( typeof console !== 'undefined' && typeof console.log === 'function' ) {
var args = PB.toArray(arguments),
error = Error.apply(this, arguments),
stackLines,
stackFirstLine,
lineNumber,
fileNameArray,
filename;
// TODO rewrite this to a regex solution
if( error && error.stack ) {
// Firefox leaves a trailing endline
stackLines = error.stack.replace(/\n+$/g, '').split('\n');
stackFirstLine = stackLines[stackLines.length-2];
lineNumber = stackFirstLine.split(':')[2];
fileNameArray = stackFirstLine.split(':')[1].split('/');
filename = fileNameArray[fileNameArray.length-1];
args.unshift('pbjs: '+filename+' (line '+lineNumber+'): ');
} else {
args.unshift('pbjs: ');
}
console.log.apply(console, args);
}
};
/**
* Put back previous value of PB global and returns current PB (pbjs) object
*
* @return {Object}
*/
PB.noConflict = function () {
if( window.PB === PB ) {
window.PB = OLD_PB;
}
return PB;
};
/**
* Create a wrapper function that makes it possible to call the parent method
* trough 'this.parent()'
*/
function createClassResponder ( method, parentMethod ) {
return function () {
this.parent = parentMethod;
return method.apply( this, arguments );
};
}
PB.Class = function ( parentClass, baseProto ) {
var child, childProto, constructor,
name, prop, parentProp, parentProto, parentConstructor;
if( !baseProto ) {
baseProto = parentClass;
parentClass = null;
}
if( baseProto.construct || baseProto.constructor.toString().indexOf('Function()') > -1 ) {
constructor = baseProto.construct || baseProto.constructor;
}
if( parentClass ) {
parentProto = parentClass.prototype;
if( constructor ) {
parentConstructor = parentClass;
} else {
constructor = parentClass;
}
}
child = constructor
? function () { if( parentConstructor ) { this.parent = parentConstructor; } return constructor.apply(this, arguments); }
: function () {};
childProto = child.prototype;
// Fill our prototype
for( name in baseProto ) {
if( baseProto.hasOwnProperty(name) && name !== 'construct' ) {
prop = baseProto[name];
parentProp = parentClass ? parentProto[name] : null;
if( parentProp && typeof prop === 'function' && typeof parentProp === 'function' ) {
prop = createClassResponder(prop, parentProp);
}
childProto[name] = prop;
}
}
PB.extend(childProto, parentProto);
return child;
};
PB.Observer = PB.Class({
/**
* Use constructor to declare class properties
*
* Child with own contruct method should call the parent method `this.parent()`
*/
construct: function () {
this.listeners = {};
},
/**
* Attach listener to object
*
* @param {String} type
* @param {Function} callback
* @param {Object} context
*/
on: function ( type, fn, context ) {
var types = type.split(' '),
i = types.length;
if( typeof fn !== 'function' ) {
throw new TypeError('PB.Observer error, fn is not a function');
}
while( i-- ) {
type = types[i];
if( !this.listeners[type] ) {
this.listeners[type] = [];
}
this.listeners[type].push({
fn: fn,
context: context
});
}
return this;
},
/**
* Detach listener from object
*
* @param {String} type
* @param {Function} callback
*/
off: function ( type, fn ) {
var listeners = this.listeners[type],
i;
// Remove all listeners
if( !type ) {
this.listeners = {};
return this;
}
// No listeners attached
if( !listeners ) {
return this;
}
// Remove all listening to `type`
if( !fn ) {
listeners.length = 0;
return this;
}
i = listeners.length;
while( i-- ) {
if( listeners[i].fn === fn ) {
listeners.splice(i, 1);
}
}
if( !listeners.length ) {
delete this.listeners[type];
}
return this;
},
/**
* Invoke listeners of given type
*
* @param {String}
* @return this
*/
emit: function ( type ) {
var listeners = this.listeners[type],
args = slice.call( arguments, 1 ),
i = 0;
if( !listeners ) {
return this;
}
for( ; i < listeners.length; i++ ) {
listeners[i].fn.apply(listeners[i].context, args);
}
return this;
}
});
PB.Queue = PB.Class(PB.Observer, {
construct: function () {
var self = this;
// Constuct Observer
this.parent();
//
this._queue = [];
// Wrap run for next callback
this.next = function () {
self.run();
};
return this;
},
queue: function ( fn, context ) {
this._queue.push({
fn: fn,
context: context
});
return this;
},
run: function () {
var item = this._queue.shift();
if( !item ) {
return;
}
item.fn.call(item.fn.context, this.next);
return this;
},
stop: function () {
this._queue.unshift(undefined);
return this;
},
clear: function () {
this._queue.length = 0;
return this;
}
});
var window = context,
doc = window.document,
docElement = doc.documentElement,
$doc = new Dom(document);
/**
* Create PB.$ global
*/
PB.$ = function ( selector ) {
// Handle false argument
if( !selector ) {
return null;
}
// Already extended
if( selector instanceof Dom ) {
return selector;
}
// If already a node, return new Dom instance
// element and document nodes are valid
if( selector.nodeType === 1 || selector.nodeType === 9 || selector === window ) {
return new Dom(selector);
}
// Handle string argument
if( typeof selector === 'string' ) {
// Element id given
if( selector.charAt(0) === '#' && selector.indexOf(' ') === -1 ) {
// Select element
selector = doc.getElementById(selector.substr(1));
// Element not found
if( !selector ) {
return null;
}
return new Dom( selector );
}
// Create element
else if ( selector.charAt(0) === '<' && selector.charAt(selector.length - 1) === '>' ) {
// Create element
return PB.$.buildFragment(selector);
}
// user querySelector
else {
return $doc.find(selector, true);
}
}
return null;
};
/**
* Return collection by css selector
*/
PB.$$ = function ( selector ) {
PB.log('Usage of PB.$$ is deprecated');
// Already PB Dom object
if( selector instanceof Dom ) {
return selector;
}
return $doc.find(selector);
};
/**
* Dom constructor
*/
function Dom ( collection ) {
var i = 0;
if( collection.nodeName || collection === window ) {
this[0] = collection;
i = 1;
} else if ( 'length' in collection ) {
for( i = 0; i < collection.length; i++ ) {
this[i] = collection[i];
}
}
this.length = i;
};
Dom.prototype.constructor = Dom;
// For extending PB.$ methods
PB.$.fn = Dom.prototype;
// Element cache
PB.$.cache = {};
/**
* Get cache entry by element
*
* Will create new cache entry if not existing
*/
function domGetStorage ( element ) {
var id = element.__PBID__ || (element.__PBID__ = PB.id());
return PB.$.cache[id] || (PB.$.cache[id] = {});
}
/**
* Merges first 2 values to a single object
*
* @param {Object} arguments
* @return {Object}
*/
function argsToObject ( args ) {
var obj;
// Force arguments to object
if( args.length === 2 ) {
obj = {};
obj[args[0]] = args[1];
}
return obj || args[0];
}
// Hook storage
PB.$.hooks = {};
/**
* Register new hook
*
* Note that hooks are not stacking, hook with same name will
* be overwriten
*/
PB.$.hook = function ( name, fn ) {
if( typeof fn !== 'function' ) {
throw new TypeError('fn must be a function');
}
PB.$.hooks[name] = fn;
};
PB.overwrite(PB.$.fn, {
/**
* Set the given attribute(s) for every element in de set.
*/
setAttr: function ( data ) {
var i = 0,
key;
data = argsToObject(arguments);
for( ; i < this.length; i++ ) {
for( key in data ) {
if( data.hasOwnProperty(key) ) {
this[i].setAttribute(key, data[key]);
}
}
}
return this;
},
/**
* Get the attribute value from the first element in the set.
*/
getAttr: function ( key ) {
return this[0].getAttribute(key);
},
/**
* Remove the given attribute(s) for every element in de set.
*/
removeAttr: function ( key ) {
var i = 0;
for( ; i < this.length; i++ ) {
this[i].removeAttribute(key);
}
return this;
},
/**
* Set the given value for every element in the set.
*/
setValue: function ( value ) {
var i = 0;
for( ; i < this.length; i++ ) {
this[i].value = value;
}
return this;
},
/**
* Get the value from the first element in the set.
*/
getValue: function () {
return this[0].value;
},
/**
* Set data for every element in the set.
*/
setData: function ( data ) {
var i = 0,
storage;
data = argsToObject(arguments);
for( ; i < this.length; i++ ) {
storage = domGetStorage(this[i]);
storage.data = storage.data || {};
PB.overwrite(storage.data, data);
}
return this;
},
/**
* Get data from first element in the set.
*
* @todo if key is not given, return all data? Merge memory data with data- attibute?
*/
getData: function ( key ) {
// Read 'data-' attribute
var storage = domGetStorage(this[0]),
data;
// Read from memory if set
if( storage.data ) {
data = storage.data[key];
}
// No data found yet, try from 'data-' attribute
if( data === undefined ) {
data = this[0].getAttribute('data-'+key);
}
return data;
},
/**
* Remove data from every element in the set.
*/
removeData: function ( key ) {
var i = 0,
id;
for( ; i < this.length; i++ ) {
this[i].removeAttribute('data-'+key);
id = this[i].__PBID__;
if( !id || !PB.$.cache[id] || !PB.$.cache[id].data ) {
continue;
}
delete PB.$.cache[id].data[key];
}
return this;
},
/**
* Checks every element in the set.
*/
check: function () {
var i = 0,
elem;
for( ; i < this.length; i++ ) {
elem = this[i];
if( elem.checked === undefined ) {
continue;
}
elem.checked = true;
}
},
/**
* Unchecks every element in the set.
*/
uncheck: function () {
var i = 0,
elem;
for( ; i < this.length; i++ ) {
elem = this[i];
if( elem.checked === undefined ) {
continue;
}
elem.checked = false;
}
},
/**
* Serialize form data
*
* Will throw an error if no form is found in set
*
* @param {Object} formData
*/
serializeForm: function () {
var i = 0,
j = 0,
data = {},
formElements,
element,
type,
isGroup = /radio|checkbox/i,
exclude = /file|undefined|reset|button|submit|fieldset/i;
for( ; i < this.length; i++ ) {
if( this[i].nodeName === 'FORM' ) {
formElements = this[i].elements;
for( ; j < formElements.length; j++ ) {
element = formElements[j];
type = element.type;
if( element.name && !exclude.test(type) && !(isGroup.test(type) && !element.checked) ) {
data[element.name] = new this.constructor(element).getValue();
}
}
return data;
}
}
// No form element given
throw new Error('Form not found'); // A tought, where selector is css expression 'Form not found ['+this.selector+']'
}
});
// Used for tests
var div = document.createElement('div'),
//
rpixel = /^-?[\d.]+px$/i,
// Vendor prefixes
// We could probably drop ms :) http://www.impressivewebs.com/dropping-ms-vendor-prefixes-ie10/
vendorPrefixes = 'O ms Moz Webkit'.split(' '),
// Styles that could require a vendor prefix
stylesUsingPrefix = 'animationName animationDuration transform transition transitionProperty transitionDuration transitionTimingFunction boxSizing backgroundSize boxReflect'.split(' '),
// All styles that require a prefix are stored in here
prefixStyles = {
// Crossbrowser float property
'float': (div.style.cssFloat !== undefined) ? 'cssFloat' : 'styleFloat'
},
// Do not add units to the given styles
skipUnits = {
zIndex: true,
zoom: true,
fontWeight: true,
opacity: true
};
/**
* Map all styles that need a prefix in the browsers its executed to styles with prefix.
*
* Example result:
* prefixStyles = {
* boxSizing: 'MozBoxSizing'
* }
*/
PB.each(stylesUsingPrefix, function ( i, prop ) {
var translateProp;
// Browser support property without prefix
if( prop in div.style ) {
// Add normal property to prefixStyles, so we know the browers supports the css property
prefixStyles[prop] = prop;
return;
}
translateProp = prop.charAt(0).toUpperCase()+prop.substr(1);
i = vendorPrefixes.length;
while ( i-- ) {
if( vendorPrefixes[i]+translateProp in div.style ) {
// Prefix found
prefixStyles[prop] = vendorPrefixes[i]+translateProp;
return;
}
}
});
// Free memory
div = null;
PB.overwrite(PB.$.fn, {
/**
* Set inline css style(s) for every element in the set.
*
* @return {Object} this
*/
setStyle: function ( styles ) {
var i = 0,
prop,
value;
styles = argsToObject(arguments);
for( ; i < this.length; i++ ) {
for( prop in styles ) {
if( styles.hasOwnProperty(prop) ) {
value = styles[prop];
// Add px when value is a number and property is a px value
if( typeof value === 'number' && !skipUnits[prop] ) {
value += 'px';
}
// Use hook
if( PB.$.hooks['setStyle.'+prop] ) {
PB.$.hooks['setStyle.'+prop](this[i], value);
}
// Use normal setter
else {
// IE throws error when setting a non valid value
try {
// Make sure we use the correct style name
this[i].style[prefixStyles[prop] || prop] = value;
} catch (e) {}
}
}
}
}
return this;
},
/**
* Get css style from the first element in the set.
*
* @todo build for currentStyle
*/
getStyle: function ( styleName, calculated ) {
var value,
// Get prefixed style name or current style name
prefixStyleName = prefixStyles[styleName] || styleName,
hook = PB.$.hooks['getStyle.'+styleName];
// Store inline value
value = this[0].style[prefixStyleName];
if( calculated || !value || value === 'auto' ) {
value = window.getComputedStyle(this[0], null)[prefixStyleName];
// IE 9 sometimes return auto.. In this case we force the value to 0
if( value === 'auto' ) {
value = 0;
}
}
if( styleName === 'opacity' ) {
value = value ? parseFloat(value) : 1.0;
}
// Parse to int when value is a pixel value
else {
value = rpixel.test(value) ? parseInt(value, 10) : value;
}
// If a hook is specified use the hook
return hook ? hook(this[0], value, prefixStyleName) : value;
}
});
PB.overwrite(PB.$.fn, {
/**
* PB.$('#element').append('
Append me
');
*/
append: function ( target ) {
var i = 0;
target = PB.$(target);
for( ; i < target.length; i++ ) {
this[0].appendChild(target[i]);
}
return this;
},
/**
* PB.$('Append me
').appendTo('#element');
*/
appendTo: function ( target ) {
var i = 0;
target = PB.$(target);
for( ; i < this.length; i++ ) {
target[0].appendChild(this[i]);
}
return this;
},
/**
* PB.$('#element').prepend('Prepend me
');
*/
prepend: function ( target ) {
var i = 0,
firstChild = this[0].firstElementChild || this[0].firstChild;
target = PB.$(target);
for( ; i < target.length; i++ ) {
if( firstChild ) {
this[0].insertBefore(target[i], firstChild);
} else {
this[0].appendChild(target[i]);
}
}
return this;
},
/**
* PB.$('Prepend me
').prependTo('#element');
*/
prependTo: function ( target ) {
var i = 0,
firstChild;
target = PB.$(target);
firstChild = target[0].firstElementChild || target[0].firstChild;
for( ; i < this.length; i++ ) {
if( firstChild ) {
target[0].insertBefore(this[i], firstChild);
} else {
target[0].appendChild(this[i]);
}
}
return this;
},
/**
* PB.$('Append me
').insertBefore('#element');
*/
insertBefore: function ( target ) {
var i = 0;
target = PB.$(target);
for( ; i < this.length; i++ ) {
target[0].parentNode.insertBefore(this[i], target[0]);
}
return this;
},
/**
* PB.$('Append me
').insertAfter('#element');
*/
insertAfter: function ( target ) {
var i = 0,
next;
target = PB.$(target);
next = target[0].nextElementSibling || target[0].nextSibling;
for( ; i < this.length; i++ ) {
if( next ) {
target[0].parentNode.insertBefore(this[i], next);
} else {
target[0].appendChild(this[i]);
}
}
return this;
},
/**
* PB.$('Replacement
').replace('#element');
*/
replace: function ( target ) {
target = PB.$(target);
// Insert collection
this.insertBefore(target);
// Remove target
target.remove();
return this;
},
/**
* Remove every element in the set.
*/
remove: function () {
var i = 0;
for( ; i < this.length; i++ ) {
// Remove data
delete PB.$.cache[this[i].__PBID__];
// Remove element
if( this[i].parentNode ) {
this[i].parentNode.removeChild( this[i] );
}
// Clear reference to element
delete this[i];
}
this.length = 0;
// Return null
return null;
},
empty: function () {
return this.setHtml('');
},
clone: function ( deep ) {
var ret = [],
children,
i = 0;
//
for( ; i < this.length; i++ ) {
// Clone element, and add to collection
ret[i] = this[i].cloneNode( deep );
// Remove id and __PBID__ attribute / expando
ret[i].removeAttribute('id');
ret[i].removeAttribute('__PBID__');
// When cloning children make sure all id and __PBID__ attributes / expandos are removed.
if( deep ) {
children = PB.$(ret[i]).find('*');
for ( ; i < children.length; i++) {
children[i].removeAttribute('id');
children[i].removeAttribute('__PBID__');
}
}
}
return new this.constructor(ret);
},
/**
* Set the `HTML` for every element in the set.
*/
// Should we make an option to parse script tags?
setHtml: function ( value ) {
var i = 0,
children;
for( ; i < this.length; i++ ) {
// There are some browser (IE,NokiaBrowser) that do not support innerHTML on table elements, in this case we should use
// appendChild.
try {
this[i].innerHTML = value;
} catch (e) {
// Remove all childs
children = PB.$(this[i]).children();
children && children.remove();
// Check for certain node names, in case of tbody|tr|td we have to use a 'special' approach
// in which we create the element with a wrapper.
// Should we put this code 'PB.$.buildFragment' ?
if( /^'+value+'').firstChild()
.appendTo(this[i]);
} else if ( /^
'+value+'').firstChild().children()
.appendTo(this[i]);
} else if ( /^ | '+value+'
').firstChild().firstChild().children()
.appendTo(this[i]);
} else {
PB.$(value).appendTo(this[i]);
}
}
}
return this;
},
/**
* Get the `HTML` of first element in the set.
*/
getHtml: function () {
return this[0].innerHTML;
},
/**
*
*/
setText: function ( value ) {
var i = 0;
// Append text to every element
for( ; i < this.length; i++ ) {
this[i].textContent = value;
}
return this;
},
/**
*
*/
getText: function () {
return this[0].textContent;
}
});
PB.overwrite(PB.$.fn, {
width: function () {
if( this[0] === window ) {
// Return viewport width, excluding toolbars/scrollbars
// Using docElement.clientWidth for IE7/8
return window.innerWidth || docElement.clientWidth;
} else if ( this[0] === doc ) {
// Return document size
return Math.max(docElement.clientWidth, docElement.offsetWidth, docElement.scrollWidth);
}
return this.getStyle('width', true);
},
innerWidth: function () {
return this.width() + this.getStyle('paddingLeft', true) + this.getStyle('paddingRight', true);
},
outerWidth: function ( includeMargin ) {
var outerWidth = this.innerWidth() + this.getStyle('borderLeftWidth', true) + this.getStyle('borderRightWidth', true);
if( includeMargin ) {
outerWidth += this.getStyle('marginLeft', true) + this.getStyle('marginRight', true);
}
return outerWidth;
},
scrollWidth: function () {
return this[0].scrollWidth;
},
height: function () {
if( this[0] === window ) {
// Return viewport width, excluding toolbars/scrollbars
// Using docElement.clientWidth for IE7/8
return window.innerHeight || docElement.clientHeight;
} else if ( this[0] === doc ) {
// Return document size
return Math.max(docElement.clientHeight, docElement.offsetHeight, docElement.scrollHeight);
}
return this.getStyle('height', true);
},
innerHeight: function () {
return this.height() + this.getStyle('paddingTop', true) + this.getStyle('paddingBottom', true);
},
outerHeight: function ( includeMargin ) {
var outerHeight = this.innerHeight() + this.getStyle('borderTopWidth', true) + this.getStyle('borderBottomWidth', true);
if( includeMargin ) {
outerHeight += this.getStyle('marginTop', true) + this.getStyle('marginBottom', true);
}
return outerHeight;
},
scrollHeight: function () {
return this[0].scrollHeight;
},
setScroll: function ( position ) {
var i = 0;
for( ; i < this.length; i++ ) {
if( position.top !== undefined ) {
this[i].scrollTop = position.top;
}
if( position.left !== undefined ) {
this[i].scrollLeft = position.left;
}
}
return this;
},
/**
* Get scroll position left/top from the first element in the set.
*
* When first element is not element node, this will return position of scroll in viewport
*
* @return {Object} {top: x, left: x}
*/
getScroll: function () {
return {
top: this[0].nodeType === 1 ? this[0].scrollTop : Math.max(docElement.scrollTop, doc.body.scrollTop),
left: this[0].nodeType === 1 ? this[0].scrollLeft : Math.max(docElement.scrollLeft, doc.body.scrollLeft)
};
},
// position
position: function () {
var box = this[0].getBoundingClientRect();
return {
top: box.top + (window.scrollY || window.pageYOffset || docElement.scrollTop),
left: box.left + (window.scrollX || window.pageXOffset || docElement.scrollLeft)
};
},
offset: function () {
var element = this[0],
box = {
top: 0,
left: 0
};
while( element ) {
box.top += element.offsetTop;
box.left += element.offsetLeft;
element = element.offsetParent;
if( !element || PB.$(element).getStyle('position') !== 'static' ) {
break;
}
}
return box;
},
/**
* Returns true if the first element in the set has the given class name.
*/
hasClass: function ( className ) {
return (' '+this[0].className+' ').indexOf(' '+className+' ') >= 0;
},
/**
* Add class(es) to every element in the set.
*/
addClass: function ( classNames ) {
var i = 0,
classList = classNames.split(' '),
className,
j;
for( ; i < this.length; i++ ) {
className = ' '+this[i].className+' ';
for( j = 0; j < classList.length; j++ ) {
// Skip if element already got the class
if( className.indexOf(' '+classList[j]+' ') >= 0 ) {
continue;
}
// Add class
this[i].className += (this[i].className ? ' ' : '')+classList[j];
}
}
return this;
},
/**
* Removes class(es) from every element in the set.
*/
removeClass: function ( classNames ) {
var i = 0,
classList = classNames.split(' '),
l = classList.length,
className,
j;
for( ; i < this.length; i++ ) {
className = ' '+this[i].className+' ';
for( j = 0; j < l; j++ ) {
// Already exists
if( className.indexOf(' '+classList[j]+' ') >= 0 ) {
className = className.replace(' '+classList[j]+' ', ' ');
}
}
// Trim whitespaces
className = className.replace(/^\s|\s$/g, '');
// Update class list
if( className ) {
this[i].className = className;
}
// Remove class attribute
else {
this[i].removeAttribute('class');
}
}
return this;
},
/**
* Shows every element in the set.
*/
show: function () {
var i = 0;
for( ; i < this.length; i++ ) {
this[i].style.display = domGetStorage(this[i])['css-display'] || 'block';
}
return this;
},
/**
* Hides every element in the set.
*/
hide: function () {
var display,
i = 0;
for( ; i < this.length; i++ ) {
display = PB.$(this[i]).getStyle('display');
// Store css display value
if( display !== 'none' ) {
domGetStorage(this[i])['css-display'] = display;
}
// Hide element
this[i].style.display = 'none';
}
return this;
},
/**
* Returns boolean whether the first element in the set is visible or not.
*
* - rename to shown ?
*/
isVisible: function () {
var element = PB.$(this[0]);
return element.getStyle('display') !== 'none' && element.getStyle('opacity') > 0;
}
});
PB.overwrite(PB.$.fn, {
/**
* Returns the parent node of the first element in the set.
*
* @return {Object} PB.$
*/
parent: function () {
return PB.$(this[0].parentNode);
},
/**
* Returns the children for the first element in the set.
* When element has no children it will return null
*
* @return {Object} PB.$ or null
*/
children: function () {
var node = this[0].firstElementChild || this[0].firstChild,
i = 0,
elements = [];
if( !node ) {
return null;
}
do {
// Only add element nodes
if( node.nodeType === 1 ) {
elements[i++] = node;
}
} while( node = node.nextSibling );
return elements.length ? new this.constructor(elements) : null;
},
/**
* Returns the child from the first element in the set at a specifed index.
*
* @param {Number}
* @return {Object} PB.$ or null
*/
childAt: function( index ) {
var children = this.children();
return children && children[index] ? PB.$(children[index]) : null;
},
/**
* Returns the first child from the first element in the set.
*
* @return {Object} PB.$ or null
*/
firstChild: function () {
return PB.$(this[0].firstElementChild || this[0].firstChild);
},
/**
* Returns the first child from the first element in the set.
*
* @return {Object} PB.$ or null
*/
lastChild: function () {
return PB.$(this[0].lastElementChild || this[0].lastChild);
},
/**
* Returns x element in the set.
*
* @return {Object} PB.$ or null
*/
get: function ( index ) {
return PB.$(this[index]);
},
/**
* Returns the first element in the set.
*
* @return {Object} PB.$ or null
*/
first: function () {
return PB.$(this[0]);
},
/**
* Returns the last element in the set.
*
* @return {Object} PB.$ or null
*/
last: function () {
return PB.$(this[this.length-1]);
},
/**
* Retrieve next sibling from first element in set
*
* @return {Object} PB.$ or null
*/
next: function () {
return PB.$(this[0].nextElementSibling || this[0].nextSibling);
},
/**
* Retrieve previous sibling from first element in set
*
* @return {Object} PB.$ or null
*/
prev: function () {
return PB.$(this[0].previousElementSibling || this[0].previousSibling);
},
/**
* Retrieve next sibling from first element in set
*
* @param {String} css expression
* @param {Number} number of parent to follow
* @return {Object} PB.$ or null
*/
closest: function ( expression, maxDepth ) {
var node = this[0];
maxDepth = maxDepth || 50;
do {
if( PB.$.selector.matches( node, expression ) ) {
return PB.$(node);
}
if( !--maxDepth ) {
break;
}
} while ( node = node.parentNode );
return null;
},
/**
*
*
* @return {Object} PB.$ or null
*/
indexOf: function ( element ) {
var i = 0;
element = PB.$(element);
if( !element ) {
return -1;
}
for( ; i < this.length; i++ ) {
if( this[i] === element[0] ) {
return i;
}
}
return -1;
},
/**
* Check whether first element in the set contains the given element
*
* @param {mixed} valid PB.$ argument
* @return {Boolean}
*/
contains: function ( element ) {
var node = this[0];
element = PB.$(element);
if( !node || !element ) {
return false;
}
return node.contains
? node.contains(element[0])
: !!(node.compareDocumentPosition(element[0]) & 16);
},
/**
* Returns all matched elements by CSS expression for every element in the set.
*
* @param {String} css expression
* @param {Boolean} *default false* true => find method return null if no elements matched
* @return {Object} PB.$ or null
*/
find: function ( expression, nullable ) {
var i = 0,
j, k, r,
result,
elements;
for( ; i < this.length; i++ ) {
if( i === 0 ) {
elements = PB.$.selector.find(expression, this[i]);
} else {
result = PB.$.selector.find(expression, this[i]);
for ( j = 0, k = elements.length, r = result.length; j < r; j++ ) {
// Only add unique value
if( elements.indexOf(result[j]) === -1 ) {
elements[k++] = result[j];
}
}
}
}
// we should return an unique set
return elements && elements.length || !nullable ? new this.constructor(elements) : null;
},
/**
* Check whether the given selector matches all elements in the set
*
* @param {String} css expression
* @return {Boolean}
*/
matches: function ( expression ) {
var i = 0;
for( ; i < this.length; i++ ) {
// Using qwery for selector validation
if( !PB.$.selector.matches(this[i], expression) ) {
return false;
}
}
return true;
}
});
// Does browser support mouseenter and mouseleave
var mouseenterleave = 'onmouseenter' in docElement && 'onmouseleave' in docElement,
// Contains all event that should be triggered `manual` node.focus()
rmanualevent = /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
standardEvents = 'type target defaultPrevented bubbles which'.split(' '),
mouseEvents = 'altKey ctrlKey metaKey shiftKey which pageX pageY'.split(' ');
/**
*
*/
function Event ( originalEvent, element ) {
var hooks = Event.hooks,
type = originalEvent.type,
key;
this.originalEvent = originalEvent;
this.currentTarget = element;
// Extend with standard event properties
this.extend(standardEvents);
// Any hooks for this event.type ?
for( key in Event.hooks ) {
if( hooks.hasOwnProperty(key) && hooks[key].matches.test(type) ) {
hooks[key].hook(this, originalEvent);
}
}
}
Event.prototype = {
/**
* Extend event with original event
*
* @param {Array} filled with property names that should be copied
*/
extend: function ( properties ) {
var i = 0,
l = properties.length;
// Populate
for( ; i < l; i++ ) {
this[properties[i]] = this.originalEvent[properties[i]];
}
},
/**
*
*/
preventDefault: function () {
this.defaultPrevented = true;
this.originalEvent.preventDefault();
},
/**
*
*/
stopPropagation: function () {
this.originalEvent.stopPropagation();
},
/**
* Short for preventDefault() and stopPropagation()
*/
stop: function () {
this.preventDefault();
this.stopPropagation();
},
/**
* Checks whether the event target matches the given selector
*
* @param {String} css selector
* @return {Boolean}
*/
matchesSelector: function ( selector ) {
var target = this.target;
do {
// When selector matches target set as currentTarget
if( PB.$.selector.matches(target, selector) ) {
this.currentTarget = target;
return true;
}
// No need to look further then the target that listens to the event
if( target === this.currentTarget ) {
return false;
}
} while ( target = target.parentNode );
// No match
return false;
}
};
Event.hooks = {
/**
* Extend mouse
*/
mouse: {
matches: /(!?mouse|click|drag|focusin|focusout)/,
hook: function ( event, originalEvent ) {
// Extend with standard event properties
event.extend(mouseEvents);
if( originalEvent.relatedTarget ) {
event.relatedTarget = originalEvent.relatedTarget;
}
}
},
/**
* Normalize wheelDelta crossbrowser
*/
mousewheel: {
matches: /^(?:DOMMouseScroll|mousewheel|wheel)$/,
hook: function ( event, originalEvent ) {
event.wheelDelta = originalEvent.wheelDelta
? originalEvent.wheelDelta / 120
: -(originalEvent.detail || 0) / 3;
}
}
};
// Expose
PB.$.Event = Event;
/**
* Register event
*
* @param {Object} element node
* @param {String} event name
* @param {Function} handler
* @param {Object} handler context
* @param {String} css selector
*/
function register ( element, eventName, handler, context, selector ) {
var storage = domGetStorage(element),
entries,
entry,
i;
// Store element
storage.element = element;
// Create event storage
if( !storage.eventData ) {
storage.eventData = {};
}
if( !storage.eventData[eventName] ) {
storage.eventData[eventName] = [];
}
entries = storage.eventData[eventName];
i = entries.length;
// Do not register same handler twice
while ( i-- ) {
if( entries.handler === handler ) {
return;
}
}
// Store handler and responder se we know wich event to remove when calling `off`
entry = {
handler: handler,
responder: eventResponder(element.__PBID__, eventName, handler, context, selector)
};
entries.push(entry);
// [Chrome] Map to correct event name
if( !mouseenterleave && (eventName === 'mouseenter' || eventName === 'mouseleave') ) {
eventName = (eventName === 'mouseenter') ? 'mouseover' : 'mouseout';
}
// Attach event
if( window.addEventListener ) {
element.addEventListener(eventName, entry.responder, false);
} else {
element.attachEvent('on'+eventName, entry.responder);
}
}
/**
* Unregister event
*
* @param {Object} element node
* @param {String} event name
* @param {Function} handler
*/
function unregister ( element, eventName, handler ) {
var storage = domGetStorage(element),
entries = storage.eventData && storage.eventData[eventName],
entry,
i;
if( !entries ) {
return;
}
i = entries.length;
// Find cache entry
while ( i-- ) {
if( entries[i].handler === handler ) {
entry = entries[i];
entries.splice(i, 1);
break;
}
}
// No entry in cache
if( !entry ) {
return;
}
// Remove event
if( window.removeEventListener ) {
element.removeEventListener(eventName, entry.responder, false);
} else {
element.detachEvent('on'+eventName, entry.responder);
}
}
/**
* Create a wrapper arround the original event
*
* @param {Number} element pbid
* @param {String} event name
* @param {Function} handler
* @param {Object} handler context
* @param {String} css selector
*/
function eventResponder ( pbid, eventName, handler, context, selector ) {
return function ( originalEvent ) {
var element = PB.$.cache[pbid] && PB.$.cache[pbid].element,
event,
relatedTarget;
if( !element ) {
return;
}
event = new Event(originalEvent, element);
// If selector is given, test selector
if( selector && !event.matchesSelector(selector) ) {
return;
}
// When selector is given, currentTarget is now the selected element
element = event.currentTarget;
// [Chrome] Workaround to support for mouseenter / mouseleave
// Inspired by blog.stchur.com/2007/03/15/mouseenter-and-mouseleave-events-for-firefox-and-other-non-ie-browsers/
if( !mouseenterleave && (eventName === 'mouseleave' || eventName === 'mouseenter') ) {
relatedTarget = event.relatedTarget;
if( element === relatedTarget || (element.contains && element.contains(relatedTarget)) ) {
return;
}
event.type = eventName;
}
// Execute callback, use context as scope otherwise the given element
handler.call( context || element, event );
};
}
// Export
PB.overwrite(PB.$.fn, {
/**
* Add event listener to every element in the set
*
* @param {String} event name
* @param {String} *optional css selector
* @param {Function} handler
* @param {Object} handler context
* @return
*/
on: function ( eventName, selector, handler, context ) {
var types = eventName.split(' '),
l = types.length,
i = 0,
j;
if( typeof selector === 'function' ) {
context = handler;
handler = selector;
selector = null;
}
if( typeof handler !== 'function' ) {
throw new TypeError();
}
// Loop trough every elements in set
for( ; i < this.length; i++ ) {
// For every element we get to bind the given event(s)
for( j = 0; j < l; j++ ) {
//this[i].addEventListener(types[i], callback, false);
register(this[i], types[j], handler, context, selector);
}
}
return this;
},
/**
* Remove event listener(s) for every element in the set
*
* When `handler` is undefined all handlers attached to the event name are removed.
* When `eventName` is undefined all handlers for all types are removed
*
* @param {String} event name
* @param {Function} handler
* @return {Object} this
*/
off: function ( eventName, handler ) {
var i = 0,
entries,
j;
for( ; i < this.length; i++ ) {
entries = domGetStorage(this[i]).eventData;
// No events stored
if( !entries && (eventName && !entries[eventName]) ) {
continue;
}
// When no event name is given destroy all events
if( !eventName ) {
// Remove all event listeners
for( j in entries ) {
if( entries.hasOwnProperty(j) ) {
// Remove events by event name
new Dom(this[i]).off(j);
}
}
}
// When no handler is given destoy all events attached to the event name
else if ( !handler ) {
// Remove all event listeners for given event name
for( j = 0; j < entries[eventName].length; j++ ) {
unregister( this[i], eventName, entries[eventName][j].handler );
}
// Remove property
delete entries[name];
}
// Remove a single event, must match eventName and handler to be removed
else {
// Remove event listener by event name and handler
unregister(this[i], eventName, handler);
}
}
return this;
},
/**
* Trigger hmtl event
*
* @param {String} event name
* @return {Object} this
*/
emit: function ( eventName ) {
var i = 0,
manual = rmanualevent.test(eventName),
evt;
// translate mouseenter/mouseleave if needed
for( ; i < this.length; i++ ) {
// Some events need manual trigger, like element.focus() make sure the method exsits on given element
if( (manual && eventName in this[i]) || (this[i].nodeName === 'INPUT' && eventName === 'click') ) {
this[i][eventName]();
}
// W3C
else if( doc.createEvent ) {
evt = doc.createEvent('HTMLEvents');
evt.initEvent(eventName, true, true, window, 1);
this[i].dispatchEvent(evt);
}
// IE
else {
this[i].fireEvent('on'+eventName, doc.createEventObject());
}
}
return this;
}
});
/**
* Convert string to html nodes
*
* @param {String} html
* @return {Object} PB.$
*/
PB.$.buildFragment = function ( html ) {
var fragment = doc.createElement('div'),
children;
// Table elements should be created in an table
// html: ' | ' wrap with:
fragment.innerHTML = html;
// Return the childeren
children = PB.$(fragment).children();
fragment = null;
return children;
};
PB.overwrite(PB.$.fn, {
/**
*
*/
// should be forEach
each: function ( fn ) {
var _args = slice.call( arguments, 1 ),
i = 0;
for( ; i < this.length; i++ ) {
fn.apply(this[i], _args);
}
return this;
},
/**
*
*/
filter: function ( filter ) {
var res = [],
i = 0,
filterIsString = typeof filter === 'string';
for( ; i < this.length; i++ ) {
if( filterIsString ) {
if( PB.$.selector.matches(this[i], filter) ) {
res.push(this[i]);
}
} else if ( filter(this[i]) === true ) {
res.push(this[i]);
}
}
return new this.constructor(res);
}
});
// Animation handler
PB.Animation = function Animation ( options ) {
this.running = false;
// this.startAt;
// this.endAt;
// this.timer;
this.duration = options.duration * 1000;
this.onTick = options.onTick || function () {};
this.timerFunction = PB.Animation.effects[options.effect] || PB.Animation.effects.ease;
this.data = options.data;
};
PB.overwrite(PB.Animation.prototype, {
start: function () {
this.startAt = +new Date();
this.endAt = this.startAt + this.duration;
this.running = true;
this.tick();
},
stop: function () {
clearTimeout(this.timer);
this.running = false;
},
tick: function () {
var time = +new Date(),
self = this,
// Position in animation from 0.0 - 1.0
position = this.timerFunction(1 - ((this.endAt - time) / this.duration ));
if( time >= this.endAt ) {
this.onTick(1, this.data, this);
this.stop();
return;
}
this.onTick(position, this.data, this);
this.timer = setTimeout(function () {
self.tick();
}, 1000 / 60);
}
});
//Should be reconsidered
PB.Animation.effects = {
linear: function ( t ) {
return t;
},
ease: function ( t ) {
return t;
},
'ease-in': function ( t ) {
return t*t;
},
'ease-out': function ( t ) {
return -1*t*(t-2);
},
'ease-in-out': function ( t ) {
return t;
},
bounce: function ( t ) {
if (t < (1/2.75)) {
return (7.5625*t*t);
} else if (t < (2/2.75)) {
return (7.5625*(t-=(1.5/2.75))*t + 0.75);
} else if (t < (2.5/2.75)) {
return (7.5625*(t-=(2.25/2.75))*t + 0.9375);
} else {
return (7.5625*(t-=(2.625/2.75))*t + 0.984375);
}
}
};
PB.Queue = PB.Class({
construct: function () {
this.stack = [];
this.length = 0;
this.state = PB.Queue.STATE_IDLE;
this._next = this.next.bind(this);
},
add: function ( fn, context ) {
this.length++;
this.stack.unshift(fn.bind(context || fn, this._next));
return this;
},
run: function () {
var i = 0,
queue = this.stack,
fn;
if( this.state === PB.Queue.STATE_INPROGRESS || !queue.length ) {
return this;
}
this.state = PB.Queue.STATE_INPROGRESS;
fn = this.stack.pop();
fn();
return this;
},
next: function () {
this.state = PB.Queue.STATE_IDLE;
this.run();
},
empty: function () {
this.stack.length = 0;
this.state = PB.Queue.STATE_IDLE;
}
});
PB.Queue.STATE_IDLE = 0;
PB.Queue.STATE_INPROGRESS = 1;
/**
* Convert arguments to ordered object
*/
function morphArgsToObject ( args ) {
// Default options
var i = 1,
effect,
options = {
duration: 0.4,
effect: 'ease'
};
// Loop trough args
for( ; i < args.length; i++ ) {
switch( typeof args[i] ) {
case 'function':
options.fn = args[i];
break;
case 'number':
options.duration = args[i];
break;
case 'string':
// easeInOut -> ease-in-out
effect = args[i].replace(/([A-Z])/g, '-$1').toLowerCase();
if( /^linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier\(.*?\)$/.test(effect) ) {
options.effect = effect;
}
break;
}
}
return options;
}
/**
* Css transition function
*
* Uses hooks for animation fallbacks
*
* @param {Object} Css properties to be animated
*/
PB.$.fn.transition = function ( properties ) {
// Normalize arguments
var options = morphArgsToObject(arguments);
options.properties = properties;
PB.$.hooks['transition'].call(this, options);
return this;
};
PB.$.hook('transition', function ( options ) {
return this.each(function () {
var element = PB.$(this),
queue = element.getData('pbjs-fx-queue'),
properties = options.properties;
if( !queue ) {
queue = new PB.Queue();
element.setData('pbjs-fx-queue', queue);
}
queue.add(function ( next, data ) {
var data = {},
currentStyles = {
transition: 'all '+options.duration+'s '+options.effect+' 0s'
};
// Store
data.end = properties;
data.fn = options.fn;
// Calculate current styles
PB.each(properties, function ( property ) {
currentStyles[property] = element.getStyle( property, true );
});
// Set the current styles inline
element.setStyle(currentStyles);
// Force redraw, for some browsers (atleast firefox). Otherwise there will be no animation
this.offsetHeight;
// Start transition
element.setStyle(properties);
// Our callback is handles with timeout, an easy crossbrowser solution.
// Todo: could this lead to a memory leak? Timer (closure that leads to the parent function..)
// Maybe use the correct event
data.timer = setTimeout(function () {
// Make sure the element still exists
if( !element[0].parentNode ) {
return;
}
//
data.running = false;
// Remove transition
element.setStyle({
transition: ''
});
// Trigger callback
if( data.fn ) {
data.fn( element );
}
next();
}, (options.duration*1000)+50); // Add a small delay, so the animation is realy finished
});
queue.run();
});
});
if( !prefixStyles.transition ) {
/**
* Set styles directly for every element. This is used when the
* browser does not support css transitions.
*/
PB.$.hook('transition', function ( properties, options ) {
// Normalize arguments
var i = 0;
// Set css styles
this.setStyle(properties);
// Trigger callbacks, if given
if( options.fn ) {
for( ; i < this.length; i++ ) {
options.fn( PB.$(this[i]) );
}
}
return this;
});
}
/**
* Stop animation, if any in queue, start next
*/
PB.$.fn.stop = function ( gotoEnd, clearQueue ) {
return this.each(function () {
var element = PB.$(this),
data = element.getData('morph');
if( !data || !data.running ) {
return;
}
// Assign default value
gotoEnd = (gotoEnd === undefined) ? true : !!gotoEnd;
// Not running anymore
data.running = false;
// Clear the callback
clearTimeout( data.timer );
// Clear transition
data.end.transition = 'none 0s ease 0s';
// Stop animation
if( !gotoEnd ) {
// Get current styles to 'pause' our transition
PB.each(data.end, function ( property ) {
data.end[property] = element.getStyle(property, true);
});
}
// Set ending styles
element.setStyle(data.end);
// Trigger callback
if( gotoEnd && data.fn ) {
data.fn( this );
}
});
};
PB.$.fn.morph = PB.$.fn.transition;
// Native query selector
var matches = docElement.matchesSelector || docElement.mozMatchesSelector || docElement.webkitMatchesSelector || docElement.oMatchesSelector || docElement.msMatchesSelector;
PB.$.selector = {
/**
* Native
*/
find: function ( selector, node ) {
return PB.toArray(node.querySelectorAll(selector));
},
/**
* Compare node to selector
*/
matches: function ( node, selector ) {
// #22 matchesSelector only avaible for element nodes
if( node.nodeType !== 1 ) {
return false;
}
return matches.call(node, selector);
}
};
/**
* PB.ready
*/
(function ( PB ) {
var doc = window.document,
ready = doc.readyState === 'complete',
fn,
queue = [];
// When browser supports addEventListener, DOMContentLoaded is existing
if( doc.addEventListener ) {
doc.addEventListener('DOMContentLoaded', fn = function () {
doc.removeEventListener('DOMContentLoaded', fn);
runQueue();
});
}
// For IE7/8 check readystatechange event
else {
doc.attachEvent('onreadystatechange', fn = function () {
if( doc.readyState === 'complete' ) {
doc.detachEvent('onreadystatechange', fn);
runQueue();
}
});
}
/**
* Call every function in queue
*
* @return {Void}
*/
function runQueue () {
var callback;
ready = true;
while( callback = queue.shift() ) {
callback(PB);
}
}
/**
* Handle callback, call callback imidiatily when document is ready else queue. And call
* when document is ready.
*
* @return {Void}
*/
function onDomReady ( callback ) {
if( ready ) {
callback(PB);
} else {
queue.push(callback);
}
}
// Expose
PB.ready = onDomReady;
})( PB );
var supportXMLHttpRequest = 'XMLHttpRequest' in context;
/*
PB.Request.defaultSend
PB.Request.defaultSuccess
PB.Request.defaultError
PB.Request.defaultEnd
PB.Request.defaultAbort
PB.Request.default('success', function () {
})
// Add callback for all PB.Request instances
PB.Request.add('success', function () {});
PB.Request.global('success', function () {});
PB.Request.forAll('success', function () {});
PB.Request.all('success', function () {});
*/
/**
* Request class
*
*
*/
PB.Request = PB.Class(PB.Observer, {
// Xhr, instance of XMLHttpRequest
xhr: null,
/**
* Construct new class instance
*
* Set request defaults
*
* @param {Object} options
* @return this
*/
construct: function ( options ) {
// Set default options
this.options = PB.clone(PB.Request.defaults);
// Init observer
this.parent();
// Overwrite default options
PB.overwrite(this.options, options);
},
/**
* Send request
*
* @return this
*/
send: function () {
var options = this.options,
async = options.async,
xhr = this.getTransport(),
url = options.url,
method = options.method.toUpperCase(),
// Assign query string or null/false/undefined/empty string
query = PB.type(options.data) === 'object' ? PB.Request.buildQueryString( options.data ) : options.data;
// Clear previous abort timer
clearTimeout(this.abortTimer);
// Add query string to url for GET / DELETE request types
if( query && (method === 'GET' || method === 'PUT') ) {
url += (url.indexOf('?') === -1 ? '?' : '&')+query;
query = null;
}
// Attach onreadystatechange listener
if( async ) {
xhr.onreadystatechange = this.onreadystatechange.bind(this);
}
// Open connection
xhr.open( method, url, async );
// Set post / put header
if( method === 'POST' || method === 'PUT' ) {
xhr.setRequestHeader( 'Content-type', 'application/x-www-form-urlencoded; charset='+this.charset );
}
// Set headers
PB.each(options.headers, function( name, val ){
xhr.setRequestHeader( name, val );
});
// Emit send event
this.emit( 'send', xhr, 0 );
// Send the request
xhr.send( query || null );
if( options.timeout > 0 ) {
this.abortTimer = setTimeout(this.abort.bind(this), options.timeout*1000);
}
// Handle synchrone callback
if( !async ) {
this.onreadystatechange();
}
return this;
},
/**
* Abort the request
*
* @return this
*/
abort: function () {
// Cleanup memory
this.xhr.onreadystatechange = null;
this.xhr.abort();
this.emit('abort');
return this;
},
/**
* Set option, key value
*
* @param {String}
* @param {String/Object/Array/Function/Number}
*/
set: function ( key, value ) {
// Match header and headers
if( key.substr(0, 6) === 'header' ) {
PB.overwrite(this.options.headers, value);
} else {
this.options[key] = value;
}
return this;
},
/**
* Get new transport object
*
* @return {XmlHttpRequest}
*/
getTransport: function () {
// IE < 8 has troubles with a reusable xmlHttpRequest object
// In this case we always return a new xmlHttpRequest instance
if( this.xhr && supportXMLHttpRequest ) {
return this.xhr;
}
// Abort previous request if any
if( this.xhr ) {
this.xhr.abort();
}
this.xhr = supportXMLHttpRequest ?
new XMLHttpRequest() :
new ActiveXObject('Microsoft.XMLHTTP');
return this.xhr;
},
/**
* Handle onreadystatechange event
*/
onreadystatechange: function () {
var xhr = this.xhr,
options = this.options,
type;
// Request has finished
if( xhr.readyState === 4 ) {
clearTimeout(this.abortTimer);
xhr.responseJSON = null;
switch ( xhr.status ) {
case 200:
case 201:
case 204:
case 304:
type = 'success';
// If request is a json call then decode json response
if( options.json || xhr.getResponseHeader('Content-type').indexOf( 'application/json' ) >= 0 ) {
try {
xhr.responseJSON = JSON.parse( xhr.responseText );
} catch ( e ) {}
}
break;
default:
type = 'error';
}
// Cleanup memory
this.xhr.onreadystatechange = null;
// Emit error or success and end
this.emit(type, xhr, xhr.status);
this.emit('end', xhr, xhr.status);
}
}
});
/**
* Request defaults
*/
PB.Request.defaults = {
url: '',
// Default request method
method: 'GET',
// Default async requests
async: true,
// Force datatypes, only one could be true..
json: false,
// IE10 has somehing different in this.. find out and normalize
xml: false,
// {}
data: null,
// Todo: implement auth
// {user: 'xxx', pass: 'xxx'}
auth: null,
// Default request headers
headers: {
// Note, Do not send headers when requesting crossdomain requests
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
},
encoding: 'UTF-8',
// Todo: timeout
timeout: 0,
// Is crossdomain request
crossdomain: false
};
// Declare methods, then assign to namespace
// more or less an idea to create less annanomous functions.
/**
* Translate object to query string
*
* @param {Object/Array}
* @param {String} for internal usage
* @return {String}
*/
function buildQueryString ( queryObject, prefix ) {
var query = '',
key,
value,
type = PB.type(queryObject);
// Validate
if( type !== 'array' && type !=='object' ) {
throw new TypeError(type+' given.');
}
if( type === 'array' || type ==='object' ) {
for( key in queryObject ) {
if( queryObject.hasOwnProperty(key) ) {
value = queryObject[key];
if( value !== null && typeof value === 'object' ) {
query += buildQueryString( value, prefix ? prefix+'['+key+']' : key );
} else {
query += encodeURIComponent(prefix ? prefix+(type === 'array' ? '[]' : '['+key+']') : key)+
'='+encodeURIComponent( value )+'&';
}
}
}
}
return prefix ? query : query.replace(/&$/, '');
}
/**
* Translate query string to object
*
* Can handle url or query string
*
* @param {String}
* @return {Object}
*/
function parseQueryString ( str ) {
var parts = {},
part;
str = str.indexOf('?') !== -1 ? str.substr( str.indexOf('?') + 1 ) : str;
// Remove forEach
str.split('&').forEach(function ( entry ) {
part = entry.split('=');
// utf8 fail in decoding.. Escape works fine with utf8 strings..
try {
parts[decodeURIComponent(part[0])] = decodeURIComponent(part[1]);
} catch ( e ) {
parts[unescape(part[0])] = unescape(part[1]);
}
});
return parts;
}
PB.overwrite(PB.Request, {
buildQueryString: buildQueryString,
parseQueryString: parseQueryString
});
/*
PB.each({get: 'GET', post: 'POST', put: 'PUT', del: 'DELETE'}, function ( key, value ) {
// arguments -> url, data, success, error ?
PB[key] = function ( options ) {
var request = new PB.Request(options),
success = options.onSuccess,
err = options.onError;
if( success ) {
request.on('success', success);
}
if( err ) {
request.on('error', err);
}
return request.send();
}
});*/
return PB;
});
(function ( context ) {
//'use strict';
var PB = context.PB,
// References
slice = Array.prototype.slice,
toString = Object.prototype.toString;
// Date
PB.extend(Date, {
now: function () {
return +new Date;
}
});
// Function
PB.extend(Function.prototype, {
/**
* Created a wrapper function around the `this` object
*
* @param mixed scope
* @param [mixed] additional arguments
* @return function
*/
bind: function ( scope/*, arg1, argN*/ ) {
var _args = slice.call(arguments, 1),
fn = this;
if( typeof this !== 'function' ) {
throw new TypeError();
}
return function () {
return fn.apply(scope, _args.concat(slice.call(arguments, 0)));
};
}
});
// Object
PB.extend(Object, {
/**
* Retrieve keys from object as array
*
* @param object object
* @return array
*/
keys: function ( object ) {
var result = [],
key;
if ( this === null || PB.type(object) === 'object' ) {
throw new TypeError();
}
for( key in object ) {
if( object.hasOwnProperty(key) ) {
result.push(key);
}
}
return result;
}
});
/**
* Implementation to check if object is an array
*
* @param mixed object
* @return boolean
*/
PB.extend(Array, {
isArray: function ( object) {
return PB.type(object) === 'array';
}
});
PB.extend(Array.prototype, {
/**
* Iterate trough array
*
* @param function fn
* @param mixed scope
* @param void
*/
forEach: function ( fn, scope ) {
if ( this === null || typeof fn !== 'function' ) {
throw new TypeError();
}
var length = this.length,
i = 0;
while ( i < length ) {
fn.call(scope, this[i], i, this);
i++;
}
},
/**
* Searches the given array for a value and returns the found index or -1 if none found
*
* Note! Comparsion is done with ===
*
* @param mixed searchValue
* @param integer startIndex
* @return integer
*/
indexOf: function ( searchValue, startIndex ) {
if ( this === null ) {
throw new TypeError();
}
var length = this.length;
startIndex = startIndex || 0;
if( length <= startIndex || length === 0 ) {
return -1;
}
while( startIndex < length ) {
if ( this[startIndex] === searchValue ) {
return startIndex;
}
startIndex++;
}
return -1;
},
/**
* Searches the given array reversed for a value and returns the found index or -1 if none found
*
* Note! Comparsion is done with ===
*
* @param mixed searchValue
* @param integer stopIndex
* @return integer
*/
lastIndexOf: function ( searchValue, stopIndex ) {
if ( this === null ) {
throw new TypeError();
}
var length = this.length;
stopIndex = stopIndex || 0;
if( length <= stopIndex || length === 0 ) {
return -1;
}
while( stopIndex <= length ) {
length--;
if ( this[length] === searchValue ) {
return length;
}
}
return -1;
},
/**
* Iterate trough array and return new array with filtered values
*
* @param function fn
* @param scope mixed
* @return array
*/
filter: function ( fn, scope ) {
if ( this === null || typeof fn !== "function" ) {
throw new TypeError();
}
var result = [],
i = 0,
length = this.length;
while ( i < length ) {
if( !!fn.call(scope, this[i], i, this) ) {
result.push( this[i] );
}
i++;
}
return result;
},
/**
* Return new array with modified values
*
* @param function fn
* @param mixed scope
* @return boolean
*/
map: function ( fn, scope ) {
if ( this === null || typeof fn !== "function" ) {
throw new TypeError();
}
var length = this.length,
result = new Array( length ),
i = 0;
while ( i < length ) {
if( i in this ) {
result[i] = fn.call(scope, this[i], i, this);
}
i++;
}
return result;
}
});
var doc = context.document,
docElement = doc.documentElement,
body = doc.body,
div = document.createElement('div'),
style = div.style
legacyEventModel = context.attachEvent && !context.addEventListener,
supportsTextContent = div.textContent !== undefined,
supportsOpacityProperty = style.opacity !== undefined,
supportsGetComputedStyle = !!window.getComputedStyle,
supportsCssTransition = 'transition' in style || 'MozTransition' in style || 'WebkitTransition' in style,
supportQuerySelectorAll = !!document.querySelectorAll,
supportMatchesSelector = !!(docElement.matchesSelector || docElement.mozMatchesSelector || docElement.webkitMatchesSelector || docElement.oMatchesSelector || docElement.msMatchesSelector);
// Clear memory
div = null;
PB.ready(function () {
body = doc.body;
});
var ropacity = /alpha\(opacity=(.*)\)/i,
rpixel = /^-?[\d.]+px$/i,
rnum = /^-?[\d.]/;
// IE < 9 opacity support
if( !supportsOpacityProperty ) {
/**
* Set opacity trough filter property
*
* @param {Object} node element
* @param {Float} opacity range 0.0-1.0
*/
PB.$.hook('setStyle.opacity', function ( element, value ) {
// Make sure element got layout
if( !element.currentStyle || !element.currentStyle.hasLayout ) {
element.style.zoom = 1;
}
// Set opacity
element.style.filter = 'alpha(opacity='+(value*100)+')';
});
/**
* Get opacity as float 0.0-1.0 from filter property
*
* @param {Object} node element
* @return {Float}
*/
PB.$.hook('getStyle.opacity', function ( element ) {
var filter = element.style.filter || element.currentStyle.filter,
match = filter && filter.match(ropacity);
if( match && match[1] ) {
return parseFloat(match[1]) / 100;
}
return 1.0;
});
}
//
if( !supportsGetComputedStyle ) {
/**
* Overwrite getStyle when browser does not support getComputedStyle
*
* IE's currentStyle wont return calculated values so we also calculate non
* pixel values.
*
* @param {String} style
* @param {Boolean}
* @return {String/Number}
*/
PB.$.fn.getStyle = function ( styleName, calculated ) {
var value,
div,
targetNode,
hook = PB.$.hooks['getStyle.'+styleName],
node = this[0];
// If a hook is specified use the hook
if( hook ) {
return hook(node);
}
// Get inline value
value = node.style[styleName];
// Do some magic when no value or when it should be calculated
if( calculated || !value || value === 'auto' ) {
// Substract borders from offsetWidth and offsetHeight
if( styleName === 'width' ) {
return node.offsetWidth - this.getStyle('borderLeftWidth', true) - this.getStyle('borderRightWidth', true);
}
if( styleName === 'height' ) {
return node.offsetHeight - this.getStyle('borderTopWidth', true) - this.getStyle('borderBottomWidth', true);
}
// Get current style
value = node.currentStyle[styleName];
// Awesomo trick! from http://blog.stchur.com/2006/09/20/converting-to-pixels-with-javascript/
// Calculate non pixel values
// Is not a pixel number
//if( value && !rpixel.test(value) && !rnum.test(value) ) {
if( value && (/em|%|pt/.test(value) || /border/.test(styleName)) ) {
div = document.createElement('div');
div.style.cssText = 'visbility: hidden; position: absolute; line-height: 0;';
//
if( value === 'auto' || value.lastIndexOf('%') > 0 ) {
targetNode = node.parentNode;
div.style.height = value;
} else {
div.style.borderStyle = 'solid';
div.style.borderBottomWidth = '0';
div.style.borderTopWidth = value;
}
// Make sure we got an element
targetNode = targetNode || document.body;
// Append div so we can get the offsetHeight
targetNode.appendChild(div);
value = div.offsetHeight;
targetNode.removeChild(div);
// Clear memory
div = null;
// No need to run regex
return value;
}
}
// Parse to int when value is a pixel value
return rpixel.test(value) ? parseInt(value, 10) : value;
};
}
// Create a fallback for the morph method if transition are not supported
if( !supportsCssTransition ) {
PB.$.hook('transition', function ( options ) {
this.stop(false);
return this.each(function () {
var element = PB.$(this),
queue = element.getData('pbjs-fx-queue'),
properties = options.properties;
if( !queue ) {
queue = new PB.Queue();
element.setData('pbjs-fx-queue', queue);
}
queue.add(function ( next, data ) {
var //element = PB.$(this),
currentStyles = {},
styleValueDiff = {},
animation;
// Calculate current styles
PB.each(properties, function ( property ) {
currentStyles[property] = element.getStyle(property, true);
});
// Calculate the difference between the given and current styles
PB.each(properties, function ( property ) {
var value = properties[property];
value = /^-?[\d.]+px$/i.test( value ) ? parseInt(value, 10) : value;
styleValueDiff[property] = value - currentStyles[property];
});
animation = new PB.Animation({
duration: options.duration,
effect: options.effect,
onTick: function ( pos ) {
PB.each(styleValueDiff, function ( style, value ) {
element.setStyle(style, currentStyles[style]+(value*pos));
});
if( pos === 1 ) {
options.fn && options.fn( element );
next();
}
}
}).start();
element.setData('morph', animation);
});
queue.run();
});
});
PB.$.fn.stop = function ( gotoEnd ) {
return this.each(function () {
var element = PB.$(this),
animation = element.getData('morph');
if( !animation || !animation.running ) {
return;
}
// Assign default value
gotoEnd = (gotoEnd === undefined) ? true : !!gotoEnd;
animation.stop();
// Trigger callback
if( gotoEnd && animation.fn ) {
animation.fn( this );
}
});
};
}
/**
* Event normalisation for browsers with older event model
*/
if( legacyEventModel ) {
PB.overwrite(PB.$.Event.hooks, {
fixes: {
matches: /(!?)/,
hook: function ( event, originalEvent ) {
event.target = originalEvent.srcElement || originalEvent.toElement;
// Add correct value for which
event.which = (event.keyCode === undefined) ? event.charCode : event.keyCode;
// Normalize mouse button codes..
// left: 0, middle: 1, right: 2
event.which = (event.which === 0 ? 1 : (event.which === 4 ? 2: (event.which === 2 ? 3 : event.which)));
}
},
mouseIe: {
matches: /(!?mouse|click|focus|drag)/,
hook: function ( event, originalEvent ) {
event.pageX = originalEvent.clientX + (docElement.scrollLeft || body.scrollLeft) - (docElement.clientLeft || 0);
event.pageY = originalEvent.clientY + (docElement.scrollTop || body.scrollTop) - (docElement.clientTop || 0);
if( originalEvent.fromElement ) {
event.relatedTarget = originalEvent.fromElement;
}
// Set which
event.which = originalEvent.keyCode || originalEvent.charCode;
// Normalize mousebutton codes to W3C standards
// Left: 0, Middle: 1, Right: 2
event.which = (event.which === 0 ? 1 : (event.which === 4 ? 2: (event.which === 2 ? 3 : event.which)));
}
}
});
/**
* Prevents further propagation of the current event.
*/
PB.$.Event.prototype.stopPropagation = function () {
this.originalEvent.defaultPrevented = true;
this.originalEvent.cancelBubble = true;
};
/**
* Cancels the event if it is cancelable, without stopping further propagation of the event.
*/
PB.$.Event.prototype.preventDefault = function () {
this.originalEvent.returnValue = false;
};
/**
* Destroy element cache
*
* We added element to cache entry so make sure there are no
* references that could stick
*/
function destroyCache () {
PB.$.cache = null;
context.detachEvent('onunload', destroyCache);
}
// Destroy cache in case of older IE browsers
context.attachEvent('onunload', destroyCache);
}
/**
* Add qwery to pbjs
*
* Using ready method to ensure qwery is loaded
*/
if( !supportQuerySelectorAll ) {
PB.$.selector.find = function ( expression, element ) {
return qwery(expression, element);
};
}
if( !supportMatchesSelector ) {
PB.$.selector.matches = function ( element, expression ) {
return qwery.is(element, expression);
};
}
if( !supportsTextContent ) {
/**
*
*/
PB.$.fn.setText = function ( value ) {
var i = 0;
// Empty elements
this.setHtml('');
// Append text to every element
for( ; i < this.length; i++ ) {
this[i].appendChild(doc.createTextNode(value));
}
return this;
};
/**
*
*/
PB.$.fn.getText = function () {
return this[0].innerText;
};
}
})(this);
/*!
* @preserve Qwery - A Blazing Fast query selector engine
* https://github.com/ded/qwery
* copyright Dustin Diaz 2012
* MIT License
*/
(function (name, context, definition) {
if (typeof module != 'undefined' && module.exports) module.exports = definition()
else if (typeof define == 'function' && define.amd) define(definition)
else context[name] = definition()
})('qwery', this, function () {
var doc = document
, html = doc.documentElement
, byClass = 'getElementsByClassName'
, byTag = 'getElementsByTagName'
, qSA = 'querySelectorAll'
, useNativeQSA = 'useNativeQSA'
, tagName = 'tagName'
, nodeType = 'nodeType'
, select // main select() method, assign later
, id = /#([\w\-]+)/
, clas = /\.[\w\-]+/g
, idOnly = /^#([\w\-]+)$/
, classOnly = /^\.([\w\-]+)$/
, tagOnly = /^([\w\-]+)$/
, tagAndOrClass = /^([\w]+)?\.([\w\-]+)$/
, splittable = /(^|,)\s*[>~+]/
, normalizr = /^\s+|\s*([,\s\+\~>]|$)\s*/g
, splitters = /[\s\>\+\~]/
, splittersMore = /(?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\]|[\s\w\+\-]*\))/
, specialChars = /([.*+?\^=!:${}()|\[\]\/\\])/g
, simple = /^(\*|[a-z0-9]+)?(?:([\.\#]+[\w\-\.#]+)?)/
, attr = /\[([\w\-]+)(?:([\|\^\$\*\~]?\=)['"]?([ \w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^]+)["']?)?\]/
, pseudo = /:([\w\-]+)(\(['"]?([^()]+)['"]?\))?/
, easy = new RegExp(idOnly.source + '|' + tagOnly.source + '|' + classOnly.source)
, dividers = new RegExp('(' + splitters.source + ')' + splittersMore.source, 'g')
, tokenizr = new RegExp(splitters.source + splittersMore.source)
, chunker = new RegExp(simple.source + '(' + attr.source + ')?' + '(' + pseudo.source + ')?')
var walker = {
' ': function (node) {
return node && node !== html && node.parentNode
}
, '>': function (node, contestant) {
return node && node.parentNode == contestant.parentNode && node.parentNode
}
, '~': function (node) {
return node && node.previousSibling
}
, '+': function (node, contestant, p1, p2) {
if (!node) return false
return (p1 = previous(node)) && (p2 = previous(contestant)) && p1 == p2 && p1
}
}
function cache() {
this.c = {}
}
cache.prototype = {
g: function (k) {
return this.c[k] || undefined
}
, s: function (k, v, r) {
v = r ? new RegExp(v) : v
return (this.c[k] = v)
}
}
var classCache = new cache()
, cleanCache = new cache()
, attrCache = new cache()
, tokenCache = new cache()
function classRegex(c) {
return classCache.g(c) || classCache.s(c, '(^|\\s+)' + c + '(\\s+|$)', 1)
}
// not quite as fast as inline loops in older browsers so don't use liberally
function each(a, fn) {
var i = 0, l = a.length
for (; i < l; i++) fn(a[i])
}
function flatten(ar) {
for (var r = [], i = 0, l = ar.length; i < l; ++i) arrayLike(ar[i]) ? (r = r.concat(ar[i])) : (r[r.length] = ar[i])
return r
}
function arrayify(ar) {
var i = 0, l = ar.length, r = []
for (; i < l; i++) r[i] = ar[i]
return r
}
function previous(n) {
while (n = n.previousSibling) if (n[nodeType] == 1) break;
return n
}
function q(query) {
return query.match(chunker)
}
// called using `this` as element and arguments from regex group results.
// given => div.hello[title="world"]:foo('bar')
// div.hello[title="world"]:foo('bar'), div, .hello, [title="world"], title, =, world, :foo('bar'), foo, ('bar'), bar]
function interpret(whole, tag, idsAndClasses, wholeAttribute, attribute, qualifier, value, wholePseudo, pseudo, wholePseudoVal, pseudoVal) {
var i, m, k, o, classes
if (this[nodeType] !== 1) return false
if (tag && tag !== '*' && this[tagName] && this[tagName].toLowerCase() !== tag) return false
if (idsAndClasses && (m = idsAndClasses.match(id)) && m[1] !== this.id) return false
if (idsAndClasses && (classes = idsAndClasses.match(clas))) {
for (i = classes.length; i--;) if (!classRegex(classes[i].slice(1)).test(this.className)) return false
}
if (pseudo && qwery.pseudos[pseudo] && !qwery.pseudos[pseudo](this, pseudoVal)) return false
if (wholeAttribute && !value) { // select is just for existance of attrib
o = this.attributes
for (k in o) {
if (Object.prototype.hasOwnProperty.call(o, k) && (o[k].name || k) == attribute) {
return this
}
}
}
if (wholeAttribute && !checkAttr(qualifier, getAttr(this, attribute) || '', value)) {
// select is for attrib equality
return false
}
return this
}
function clean(s) {
return cleanCache.g(s) || cleanCache.s(s, s.replace(specialChars, '\\$1'))
}
function checkAttr(qualify, actual, val) {
switch (qualify) {
case '=':
return actual == val
case '^=':
return actual.match(attrCache.g('^=' + val) || attrCache.s('^=' + val, '^' + clean(val), 1))
case '$=':
return actual.match(attrCache.g('$=' + val) || attrCache.s('$=' + val, clean(val) + '$', 1))
case '*=':
return actual.match(attrCache.g(val) || attrCache.s(val, clean(val), 1))
case '~=':
return actual.match(attrCache.g('~=' + val) || attrCache.s('~=' + val, '(?:^|\\s+)' + clean(val) + '(?:\\s+|$)', 1))
case '|=':
return actual.match(attrCache.g('|=' + val) || attrCache.s('|=' + val, '^' + clean(val) + '(-|$)', 1))
}
return 0
}
// given a selector, first check for simple cases then collect all base candidate matches and filter
function _qwery(selector, _root) {
var r = [], ret = [], i, l, m, token, tag, els, intr, item, root = _root
, tokens = tokenCache.g(selector) || tokenCache.s(selector, selector.split(tokenizr))
, dividedTokens = selector.match(dividers)
if (!tokens.length) return r
token = (tokens = tokens.slice(0)).pop() // copy cached tokens, take the last one
if (tokens.length && (m = tokens[tokens.length - 1].match(idOnly))) root = byId(_root, m[1])
if (!root) return r
intr = q(token)
// collect base candidates to filter
els = root !== _root && root[nodeType] !== 9 && dividedTokens && /^[+~]$/.test(dividedTokens[dividedTokens.length - 1]) ?
function (r) {
while (root = root.nextSibling) {
root[nodeType] == 1 && (intr[1] ? intr[1] == root[tagName].toLowerCase() : 1) && (r[r.length] = root)
}
return r
}([]) :
root[byTag](intr[1] || '*')
// filter elements according to the right-most part of the selector
for (i = 0, l = els.length; i < l; i++) {
if (item = interpret.apply(els[i], intr)) r[r.length] = item
}
if (!tokens.length) return r
// filter further according to the rest of the selector (the left side)
each(r, function (e) { if (ancestorMatch(e, tokens, dividedTokens)) ret[ret.length] = e })
return ret
}
// compare element to a selector
function is(el, selector, root) {
if (isNode(selector)) return el == selector
if (arrayLike(selector)) return !!~flatten(selector).indexOf(el) // if selector is an array, is el a member?
var selectors = selector.split(','), tokens, dividedTokens
while (selector = selectors.pop()) {
tokens = tokenCache.g(selector) || tokenCache.s(selector, selector.split(tokenizr))
dividedTokens = selector.match(dividers)
tokens = tokens.slice(0) // copy array
if (interpret.apply(el, q(tokens.pop())) && (!tokens.length || ancestorMatch(el, tokens, dividedTokens, root))) {
return true
}
}
return false
}
// given elements matching the right-most part of a selector, filter out any that don't match the rest
function ancestorMatch(el, tokens, dividedTokens, root) {
var cand
// recursively work backwards through the tokens and up the dom, covering all options
function crawl(e, i, p) {
while (p = walker[dividedTokens[i]](p, e)) {
if (isNode(p) && (interpret.apply(p, q(tokens[i])))) {
if (i) {
if (cand = crawl(p, i - 1, p)) return cand
} else return p
}
}
}
return (cand = crawl(el, tokens.length - 1, el)) && (!root || isAncestor(cand, root))
}
function isNode(el, t) {
return el && typeof el === 'object' && (t = el[nodeType]) && (t == 1 || t == 9)
}
function uniq(ar) {
var a = [], i, j;
o:
for (i = 0; i < ar.length; ++i) {
for (j = 0; j < a.length; ++j) if (a[j] == ar[i]) continue o
a[a.length] = ar[i]
}
return a
}
function arrayLike(o) {
return (typeof o === 'object' && isFinite(o.length))
}
function normalizeRoot(root) {
if (!root) return doc
if (typeof root == 'string') return qwery(root)[0]
if (!root[nodeType] && arrayLike(root)) return root[0]
return root
}
function byId(root, id, el) {
// if doc, query on it, else query the parent doc or if a detached fragment rewrite the query and run on the fragment
return root[nodeType] === 9 ? root.getElementById(id) :
root.ownerDocument &&
(((el = root.ownerDocument.getElementById(id)) && isAncestor(el, root) && el) ||
(!isAncestor(root, root.ownerDocument) && select('[id="' + id + '"]', root)[0]))
}
function qwery(selector, _root) {
var m, el, root = normalizeRoot(_root)
// easy, fast cases that we can dispatch with simple DOM calls
if (!root || !selector) return []
if (selector === window || isNode(selector)) {
return !_root || (selector !== window && isNode(root) && isAncestor(selector, root)) ? [selector] : []
}
if (selector && arrayLike(selector)) return flatten(selector)
if (m = selector.match(easy)) {
if (m[1]) return (el = byId(root, m[1])) ? [el] : []
if (m[2]) return arrayify(root[byTag](m[2]))
if (hasByClass && m[3]) return arrayify(root[byClass](m[3]))
}
return select(selector, root)
}
// where the root is not document and a relationship selector is first we have to
// do some awkward adjustments to get it to work, even with qSA
function collectSelector(root, collector) {
return function (s) {
var oid, nid
if (splittable.test(s)) {
if (root[nodeType] !== 9) {
// make sure the el has an id, rewrite the query, set root to doc and run it
if (!(nid = oid = root.getAttribute('id'))) root.setAttribute('id', nid = '__qwerymeupscotty')
s = '[id="' + nid + '"]' + s // avoid byId and allow us to match context element
collector(root.parentNode || root, s, true)
oid || root.removeAttribute('id')
}
return;
}
s.length && collector(root, s, false)
}
}
var isAncestor = 'compareDocumentPosition' in html ?
function (element, container) {
return (container.compareDocumentPosition(element) & 16) == 16
} : 'contains' in html ?
function (element, container) {
container = container[nodeType] === 9 || container == window ? html : container
return container !== element && container.contains(element)
} :
function (element, container) {
while (element = element.parentNode) if (element === container) return 1
return 0
}
, getAttr = function () {
// detect buggy IE src/href getAttribute() call
var e = doc.createElement('p')
return ((e.innerHTML = 'x') && e.firstChild.getAttribute('href') != '#x') ?
function (e, a) {
return a === 'class' ? e.className : (a === 'href' || a === 'src') ?
e.getAttribute(a, 2) : e.getAttribute(a)
} :
function (e, a) { return e.getAttribute(a) }
}()
, hasByClass = !!doc[byClass]
// has native qSA support
, hasQSA = doc.querySelector && doc[qSA]
// use native qSA
, selectQSA = function (selector, root) {
var result = [], ss, e
try {
if (root[nodeType] === 9 || !splittable.test(selector)) {
// most work is done right here, defer to qSA
return arrayify(root[qSA](selector))
}
// special case where we need the services of `collectSelector()`
each(ss = selector.split(','), collectSelector(root, function (ctx, s) {
e = ctx[qSA](s)
if (e.length == 1) result[result.length] = e.item(0)
else if (e.length) result = result.concat(arrayify(e))
}))
return ss.length > 1 && result.length > 1 ? uniq(result) : result
} catch (ex) { }
return selectNonNative(selector, root)
}
// no native selector support
, selectNonNative = function (selector, root) {
var result = [], items, m, i, l, r, ss
selector = selector.replace(normalizr, '$1')
if (m = selector.match(tagAndOrClass)) {
r = classRegex(m[2])
items = root[byTag](m[1] || '*')
for (i = 0, l = items.length; i < l; i++) {
if (r.test(items[i].className)) result[result.length] = items[i]
}
return result
}
// more complex selector, get `_qwery()` to do the work for us
each(ss = selector.split(','), collectSelector(root, function (ctx, s, rewrite) {
r = _qwery(s, ctx)
for (i = 0, l = r.length; i < l; i++) {
if (ctx[nodeType] === 9 || rewrite || isAncestor(r[i], root)) result[result.length] = r[i]
}
}))
return ss.length > 1 && result.length > 1 ? uniq(result) : result
}
, configure = function (options) {
// configNativeQSA: use fully-internal selector or native qSA where present
if (typeof options[useNativeQSA] !== 'undefined')
select = !options[useNativeQSA] ? selectNonNative : hasQSA ? selectQSA : selectNonNative
}
configure({ useNativeQSA: true })
qwery.configure = configure
qwery.uniq = uniq
qwery.is = is
qwery.pseudos = {}
return qwery
});