Skip to content

Instantly share code, notes, and snippets.

@desandro
Created August 9, 2013 19:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save desandro/6196642 to your computer and use it in GitHub Desktop.
Save desandro/6196642 to your computer and use it in GitHub Desktop.
/*!
* Packery PACKAGED v1.0.6
* bin-packing layout library
* http://packery.metafizzy.co
*
* Commercial use requires one-time purchase of a commercial license
* http://packery.metafizzy.co/license.html
*
* Copyright 2013 Metafizzy
*/
/*!
* classie - class helper functions
* from bonzo https://github.com/ded/bonzo
*
* classie.has( elem, 'my-class' ) -> true/false
* classie.add( elem, 'my-new-class' )
* classie.remove( elem, 'my-unwanted-class' )
* classie.toggle( elem, 'my-class' )
*/
/*jshint browser: true, strict: true, undef: true */
/*global define: false */
( function( window ) {
'use strict';
// class helper functions from bonzo https://github.com/ded/bonzo
function classReg( className ) {
return new RegExp("(^|\\s+)" + className + "(\\s+|$)");
}
// classList support for class management
// altho to be fair, the api sucks because it won't accept multiple classes at once
var hasClass, addClass, removeClass;
if ( 'classList' in document.documentElement ) {
hasClass = function( elem, c ) {
return elem.classList.contains( c );
};
addClass = function( elem, c ) {
elem.classList.add( c );
};
removeClass = function( elem, c ) {
elem.classList.remove( c );
};
}
else {
hasClass = function( elem, c ) {
return classReg( c ).test( elem.className );
};
addClass = function( elem, c ) {
if ( !hasClass( elem, c ) ) {
elem.className = elem.className + ' ' + c;
}
};
removeClass = function( elem, c ) {
elem.className = elem.className.replace( classReg( c ), ' ' );
};
}
function toggleClass( elem, c ) {
var fn = hasClass( elem, c ) ? removeClass : addClass;
fn( elem, c );
}
var classie = {
// full names
hasClass: hasClass,
addClass: addClass,
removeClass: removeClass,
toggleClass: toggleClass,
// short names
has: hasClass,
add: addClass,
remove: removeClass,
toggle: toggleClass
};
// transport
if ( typeof define === 'function' && define.amd ) {
// AMD
define( classie );
} else {
// browser global
window.classie = classie;
}
})( window );
/*!
* getStyleProperty by kangax
* http://perfectionkills.com/feature-testing-css-properties/
*/
/*jshint browser: true, strict: true, undef: true */
/*globals define: false */
( function( window ) {
'use strict';
var prefixes = 'Webkit Moz ms Ms O'.split(' ');
var docElemStyle = document.documentElement.style;
function getStyleProperty( propName ) {
if ( !propName ) {
return;
}
// test standard property first
if ( typeof docElemStyle[ propName ] === 'string' ) {
return propName;
}
// capitalize
propName = propName.charAt(0).toUpperCase() + propName.slice(1);
// test vendor specific properties
var prefixed;
for ( var i=0, len = prefixes.length; i < len; i++ ) {
prefixed = prefixes[i] + propName;
if ( typeof docElemStyle[ prefixed ] === 'string' ) {
return prefixed;
}
}
}
// transport
if ( typeof define === 'function' && define.amd ) {
// AMD
define( function() {
return getStyleProperty;
});
} else {
// browser global
window.getStyleProperty = getStyleProperty;
}
})( window );
/**
* getSize v1.1.4
* measure size of elements
*/
/*jshint browser: true, strict: true, undef: true, unused: true */
/*global define: false */
( function( window, undefined ) {
'use strict';
// -------------------------- helpers -------------------------- //
var defView = document.defaultView;
var getStyle = defView && defView.getComputedStyle ?
function( elem ) {
return defView.getComputedStyle( elem, null );
} :
function( elem ) {
return elem.currentStyle;
};
// get a number from a string, not a percentage
function getStyleSize( value ) {
var num = parseFloat( value );
// not a percent like '100%', and a number
var isValid = value.indexOf('%') === -1 && !isNaN( num );
return isValid && num;
}
// -------------------------- measurements -------------------------- //
var measurements = [
'paddingLeft',
'paddingRight',
'paddingTop',
'paddingBottom',
'marginLeft',
'marginRight',
'marginTop',
'marginBottom',
'borderLeftWidth',
'borderRightWidth',
'borderTopWidth',
'borderBottomWidth'
];
function getZeroSize() {
var size = {
width: 0,
height: 0,
innerWidth: 0,
innerHeight: 0,
outerWidth: 0,
outerHeight: 0
};
for ( var i=0, len = measurements.length; i < len; i++ ) {
var measurement = measurements[i];
size[ measurement ] = 0;
}
return size;
}
function defineGetSize( getStyleProperty ) {
// -------------------------- box sizing -------------------------- //
var boxSizingProp = getStyleProperty('boxSizing');
var isBoxSizeOuter;
/**
* WebKit measures the outer-width on style.width on border-box elems
* IE & Firefox measures the inner-width
*/
( function() {
if ( !boxSizingProp ) {
return;
}
var div = document.createElement('div');
div.style.width = '200px';
div.style.padding = '1px 2px 3px 4px';
div.style.borderStyle = 'solid';
div.style.borderWidth = '1px 2px 3px 4px';
div.style[ boxSizingProp ] = 'border-box';
var body = document.body || document.documentElement;
body.appendChild( div );
var style = getStyle( div );
isBoxSizeOuter = getStyleSize( style.width ) === 200;
body.removeChild( div );
})();
// -------------------------- getSize -------------------------- //
function getSize( elem ) {
// use querySeletor if elem is string
if ( typeof elem === 'string' ) {
elem = document.querySelector( elem );
}
// do not proceed on non-objects
if ( !elem || typeof elem !== 'object' || !elem.nodeType ) {
return;
}
var style = getStyle( elem );
// if hidden, everything is 0
if ( style.display === 'none' ) {
return getZeroSize();
}
var size = {};
size.width = elem.offsetWidth;
size.height = elem.offsetHeight;
var isBorderBox = size.isBorderBox = !!( boxSizingProp &&
style[ boxSizingProp ] && style[ boxSizingProp ] === 'border-box' );
// get all measurements
for ( var i=0, len = measurements.length; i < len; i++ ) {
var measurement = measurements[i];
var value = style[ measurement ];
var num = parseFloat( value );
// any 'auto', 'medium' value will be 0
size[ measurement ] = !isNaN( num ) ? num : 0;
}
var paddingWidth = size.paddingLeft + size.paddingRight;
var paddingHeight = size.paddingTop + size.paddingBottom;
var marginWidth = size.marginLeft + size.marginRight;
var marginHeight = size.marginTop + size.marginBottom;
var borderWidth = size.borderLeftWidth + size.borderRightWidth;
var borderHeight = size.borderTopWidth + size.borderBottomWidth;
var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter;
// overwrite width and height if we can get it from style
var styleWidth = getStyleSize( style.width );
if ( styleWidth !== false ) {
size.width = styleWidth +
// add padding and border unless it's already including it
( isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth );
}
var styleHeight = getStyleSize( style.height );
if ( styleHeight !== false ) {
size.height = styleHeight +
// add padding and border unless it's already including it
( isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight );
}
size.innerWidth = size.width - ( paddingWidth + borderWidth );
size.innerHeight = size.height - ( paddingHeight + borderHeight );
size.outerWidth = size.width + marginWidth;
size.outerHeight = size.height + marginHeight;
return size;
}
return getSize;
}
// transport
if ( typeof define === 'function' && define.amd ) {
// AMD
define( [ 'get-style-property/get-style-property' ], defineGetSize );
} else {
// browser global
window.getSize = defineGetSize( window.getStyleProperty );
}
})( window );
/*!
* eventie v1.0.3
* event binding helper
* eventie.bind( elem, 'click', myFn )
* eventie.unbind( elem, 'click', myFn )
*/
/*jshint browser: true, undef: true, unused: true */
/*global define: false */
( function( window ) {
'use strict';
var docElem = document.documentElement;
var bind = function() {};
if ( docElem.addEventListener ) {
bind = function( obj, type, fn ) {
obj.addEventListener( type, fn, false );
};
} else if ( docElem.attachEvent ) {
bind = function( obj, type, fn ) {
obj[ type + fn ] = fn.handleEvent ?
function() {
var event = window.event;
// add event.target
event.target = event.target || event.srcElement;
fn.handleEvent.call( fn, event );
} :
function() {
var event = window.event;
// add event.target
event.target = event.target || event.srcElement;
fn.call( obj, event );
};
obj.attachEvent( "on" + type, obj[ type + fn ] );
};
}
var unbind = function() {};
if ( docElem.removeEventListener ) {
unbind = function( obj, type, fn ) {
obj.removeEventListener( type, fn, false );
};
} else if ( docElem.detachEvent ) {
unbind = function( obj, type, fn ) {
obj.detachEvent( "on" + type, obj[ type + fn ] );
try {
delete obj[ type + fn ];
} catch ( err ) {
// can't delete window object properties
obj[ type + fn ] = undefined;
}
};
}
var eventie = {
bind: bind,
unbind: unbind
};
// transport
if ( typeof define === 'function' && define.amd ) {
// AMD
define( eventie );
} else {
// browser global
window.eventie = eventie;
}
})( this );
/*!
* docReady
* Cross browser DOMContentLoaded event emitter
*/
/*jshint browser: true, strict: true, undef: true, unused: true*/
/*global define: false */
( function( window ) {
'use strict';
var document = window.document;
// collection of functions to be triggered on ready
var queue = [];
function docReady( fn ) {
// throw out non-functions
if ( typeof fn !== 'function' ) {
return;
}
if ( docReady.isReady ) {
// ready now, hit it
fn();
} else {
// queue function when ready
queue.push( fn );
}
}
docReady.isReady = false;
// triggered on various doc ready events
function init( event ) {
// bail if IE8 document is not ready just yet
var isIE8NotReady = event.type === 'readystatechange' && document.readyState !== 'complete';
if ( docReady.isReady || isIE8NotReady ) {
return;
}
docReady.isReady = true;
// process queue
for ( var i=0, len = queue.length; i < len; i++ ) {
var fn = queue[i];
fn();
}
}
function defineDocReady( eventie ) {
eventie.bind( document, 'DOMContentLoaded', init );
eventie.bind( document, 'readystatechange', init );
eventie.bind( window, 'load', init );
return docReady;
}
// transport
if ( typeof define === 'function' && define.amd ) {
// AMD
// if RequireJS, then doc is already ready
docReady.isReady = typeof requirejs === 'function';
define( [ 'eventie/eventie' ], defineDocReady );
} else {
// browser global
window.docReady = defineDocReady( window.eventie );
}
})( this );
/*!
* EventEmitter v4.2.3 - git.io/ee
* Oliver Caldwell
* MIT license
* @preserve
*/
(function () {
'use strict';
/**
* Class for managing events.
* Can be extended to provide event functionality in other classes.
*
* @class EventEmitter Manages event registering and emitting.
*/
function EventEmitter() {}
// Shortcuts to improve speed and size
// Easy access to the prototype
var proto = EventEmitter.prototype;
/**
* Finds the index of the listener for the event in it's storage array.
*
* @param {Function[]} listeners Array of listeners to search through.
* @param {Function} listener Method to look for.
* @return {Number} Index of the specified listener, -1 if not found
* @api private
*/
function indexOfListener(listeners, listener) {
var i = listeners.length;
while (i--) {
if (listeners[i].listener === listener) {
return i;
}
}
return -1;
}
/**
* Alias a method while keeping the context correct, to allow for overwriting of target method.
*
* @param {String} name The name of the target method.
* @return {Function} The aliased method
* @api private
*/
function alias(name) {
return function aliasClosure() {
return this[name].apply(this, arguments);
};
}
/**
* Returns the listener array for the specified event.
* Will initialise the event object and listener arrays if required.
* Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them.
* Each property in the object response is an array of listener functions.
*
* @param {String|RegExp} evt Name of the event to return the listeners from.
* @return {Function[]|Object} All listener functions for the event.
*/
proto.getListeners = function getListeners(evt) {
var events = this._getEvents();
var response;
var key;
// Return a concatenated array of all matching events if
// the selector is a regular expression.
if (typeof evt === 'object') {
response = {};
for (key in events) {
if (events.hasOwnProperty(key) && evt.test(key)) {
response[key] = events[key];
}
}
}
else {
response = events[evt] || (events[evt] = []);
}
return response;
};
/**
* Takes a list of listener objects and flattens it into a list of listener functions.
*
* @param {Object[]} listeners Raw listener objects.
* @return {Function[]} Just the listener functions.
*/
proto.flattenListeners = function flattenListeners(listeners) {
var flatListeners = [];
var i;
for (i = 0; i < listeners.length; i += 1) {
flatListeners.push(listeners[i].listener);
}
return flatListeners;
};
/**
* Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful.
*
* @param {String|RegExp} evt Name of the event to return the listeners from.
* @return {Object} All listener functions for an event in an object.
*/
proto.getListenersAsObject = function getListenersAsObject(evt) {
var listeners = this.getListeners(evt);
var response;
if (listeners instanceof Array) {
response = {};
response[evt] = listeners;
}
return response || listeners;
};
/**
* Adds a listener function to the specified event.
* The listener will not be added if it is a duplicate.
* If the listener returns true then it will be removed after it is called.
* If you pass a regular expression as the event name then the listener will be added to all events that match it.
*
* @param {String|RegExp} evt Name of the event to attach the listener to.
* @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.addListener = function addListener(evt, listener) {
var listeners = this.getListenersAsObject(evt);
var listenerIsWrapped = typeof listener === 'object';
var key;
for (key in listeners) {
if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
listeners[key].push(listenerIsWrapped ? listener : {
listener: listener,
once: false
});
}
}
return this;
};
/**
* Alias of addListener
*/
proto.on = alias('addListener');
/**
* Semi-alias of addListener. It will add a listener that will be
* automatically removed after it's first execution.
*
* @param {String|RegExp} evt Name of the event to attach the listener to.
* @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.addOnceListener = function addOnceListener(evt, listener) {
return this.addListener(evt, {
listener: listener,
once: true
});
};
/**
* Alias of addOnceListener.
*/
proto.once = alias('addOnceListener');
/**
* Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad.
* You need to tell it what event names should be matched by a regex.
*
* @param {String} evt Name of the event to create.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.defineEvent = function defineEvent(evt) {
this.getListeners(evt);
return this;
};
/**
* Uses defineEvent to define multiple events.
*
* @param {String[]} evts An array of event names to define.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.defineEvents = function defineEvents(evts) {
for (var i = 0; i < evts.length; i += 1) {
this.defineEvent(evts[i]);
}
return this;
};
/**
* Removes a listener function from the specified event.
* When passed a regular expression as the event name, it will remove the listener from all events that match it.
*
* @param {String|RegExp} evt Name of the event to remove the listener from.
* @param {Function} listener Method to remove from the event.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.removeListener = function removeListener(evt, listener) {
var listeners = this.getListenersAsObject(evt);
var index;
var key;
for (key in listeners) {
if (listeners.hasOwnProperty(key)) {
index = indexOfListener(listeners[key], listener);
if (index !== -1) {
listeners[key].splice(index, 1);
}
}
}
return this;
};
/**
* Alias of removeListener
*/
proto.off = alias('removeListener');
/**
* Adds listeners in bulk using the manipulateListeners method.
* If you pass an object as the second argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added.
* You can also pass it a regular expression to add the array of listeners to all events that match it.
* Yeah, this function does quite a bit. That's probably a bad thing.
*
* @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once.
* @param {Function[]} [listeners] An optional array of listener functions to add.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.addListeners = function addListeners(evt, listeners) {
// Pass through to manipulateListeners
return this.manipulateListeners(false, evt, listeners);
};
/**
* Removes listeners in bulk using the manipulateListeners method.
* If you pass an object as the second argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
* You can also pass it an event name and an array of listeners to be removed.
* You can also pass it a regular expression to remove the listeners from all events that match it.
*
* @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once.
* @param {Function[]} [listeners] An optional array of listener functions to remove.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.removeListeners = function removeListeners(evt, listeners) {
// Pass through to manipulateListeners
return this.manipulateListeners(true, evt, listeners);
};
/**
* Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level.
* The first argument will determine if the listeners are removed (true) or added (false).
* If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
* You can also pass it an event name and an array of listeners to be added/removed.
* You can also pass it a regular expression to manipulate the listeners of all events that match it.
*
* @param {Boolean} remove True if you want to remove listeners, false if you want to add.
* @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once.
* @param {Function[]} [listeners] An optional array of listener functions to add/remove.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) {
var i;
var value;
var single = remove ? this.removeListener : this.addListener;
var multiple = remove ? this.removeListeners : this.addListeners;
// If evt is an object then pass each of it's properties to this method
if (typeof evt === 'object' && !(evt instanceof RegExp)) {
for (i in evt) {
if (evt.hasOwnProperty(i) && (value = evt[i])) {
// Pass the single listener straight through to the singular method
if (typeof value === 'function') {
single.call(this, i, value);
}
else {
// Otherwise pass back to the multiple function
multiple.call(this, i, value);
}
}
}
}
else {
// So evt must be a string
// And listeners must be an array of listeners
// Loop over it and pass each one to the multiple method
i = listeners.length;
while (i--) {
single.call(this, evt, listeners[i]);
}
}
return this;
};
/**
* Removes all listeners from a specified event.
* If you do not specify an event then all listeners will be removed.
* That means every event will be emptied.
* You can also pass a regex to remove all events that match it.
*
* @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.removeEvent = function removeEvent(evt) {
var type = typeof evt;
var events = this._getEvents();
var key;
// Remove different things depending on the state of evt
if (type === 'string') {
// Remove all listeners for the specified event
delete events[evt];
}
else if (type === 'object') {
// Remove all events matching the regex.
for (key in events) {
if (events.hasOwnProperty(key) && evt.test(key)) {
delete events[key];
}
}
}
else {
// Remove all listeners in all events
delete this._events;
}
return this;
};
/**
* Emits an event of your choice.
* When emitted, every listener attached to that event will be executed.
* If you pass the optional argument array then those arguments will be passed to every listener upon execution.
* Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
* So they will not arrive within the array on the other side, they will be separate.
* You can also pass a regular expression to emit to all events that match it.
*
* @param {String|RegExp} evt Name of the event to emit and execute listeners for.
* @param {Array} [args] Optional array of arguments to be passed to each listener.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.emitEvent = function emitEvent(evt, args) {
var listeners = this.getListenersAsObject(evt);
var listener;
var i;
var key;
var response;
for (key in listeners) {
if (listeners.hasOwnProperty(key)) {
i = listeners[key].length;
while (i--) {
// If the listener returns true then it shall be removed from the event
// The function is executed either with a basic call or an apply if there is an args array
listener = listeners[key][i];
if (listener.once === true) {
this.removeListener(evt, listener.listener);
}
response = listener.listener.apply(this, args || []);
if (response === this._getOnceReturnValue()) {
this.removeListener(evt, listener.listener);
}
}
}
}
return this;
};
/**
* Alias of emitEvent
*/
proto.trigger = alias('emitEvent');
/**
* Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on.
* As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
*
* @param {String|RegExp} evt Name of the event to emit and execute listeners for.
* @param {...*} Optional additional arguments to be passed to each listener.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.emit = function emit(evt) {
var args = Array.prototype.slice.call(arguments, 1);
return this.emitEvent(evt, args);
};
/**
* Sets the current value to check against when executing listeners. If a
* listeners return value matches the one set here then it will be removed
* after execution. This value defaults to true.
*
* @param {*} value The new value to check for when executing listeners.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.setOnceReturnValue = function setOnceReturnValue(value) {
this._onceReturnValue = value;
return this;
};
/**
* Fetches the current value to check against when executing listeners. If
* the listeners return value matches this one then it should be removed
* automatically. It will return true by default.
*
* @return {*|Boolean} The current value to check for or the default, true.
* @api private
*/
proto._getOnceReturnValue = function _getOnceReturnValue() {
if (this.hasOwnProperty('_onceReturnValue')) {
return this._onceReturnValue;
}
else {
return true;
}
};
/**
* Fetches the events object and creates one if required.
*
* @return {Object} The events storage object.
* @api private
*/
proto._getEvents = function _getEvents() {
return this._events || (this._events = {});
};
// Expose the class either via AMD, CommonJS or the global object
if (typeof define === 'function' && define.amd) {
define(function () {
return EventEmitter;
});
}
else if (typeof module === 'object' && module.exports){
module.exports = EventEmitter;
}
else {
this.EventEmitter = EventEmitter;
}
}.call(this));
/**
* Bridget makes jQuery widgets
* v1.0.0
*/
( function( window ) {
'use strict';
// -------------------------- utils -------------------------- //
var slice = Array.prototype.slice;
function noop() {}
// -------------------------- definition -------------------------- //
function defineBridget( $ ) {
// bail if no jQuery
if ( !$ ) {
return;
}
// -------------------------- addOptionMethod -------------------------- //
/**
* adds option method -> $().plugin('option', {...})
* @param {Function} PluginClass - constructor class
*/
function addOptionMethod( PluginClass ) {
// don't overwrite original option method
if ( PluginClass.prototype.option ) {
return;
}
// option setter
PluginClass.prototype.option = function( opts ) {
// bail out if not an object
if ( !$.isPlainObject( opts ) ){
return;
}
this.options = $.extend( true, this.options, opts );
};
}
// -------------------------- plugin bridge -------------------------- //
// helper function for logging errors
// $.error breaks jQuery chaining
var logError = typeof console === 'undefined' ? noop :
function( message ) {
console.error( message );
};
/**
* jQuery plugin bridge, access methods like $elem.plugin('method')
* @param {String} namespace - plugin name
* @param {Function} PluginClass - constructor class
*/
function bridge( namespace, PluginClass ) {
// add to jQuery fn namespace
$.fn[ namespace ] = function( options ) {
if ( typeof options === 'string' ) {
// call plugin method when first argument is a string
// get arguments for method
var args = slice.call( arguments, 1 );
for ( var i=0, len = this.length; i < len; i++ ) {
var elem = this[i];
var instance = $.data( elem, namespace );
if ( !instance ) {
logError( "cannot call methods on " + namespace + " prior to initialization; " +
"attempted to call '" + options + "'" );
continue;
}
if ( !$.isFunction( instance[options] ) || options.charAt(0) === '_' ) {
logError( "no such method '" + options + "' for " + namespace + " instance" );
continue;
}
// trigger method with arguments
var returnValue = instance[ options ].apply( instance, args );
// break look and return first value if provided
if ( returnValue !== undefined ) {
return returnValue;
}
}
// return this if no return value
return this;
} else {
return this.each( function() {
var instance = $.data( this, namespace );
if ( instance ) {
// apply options & init
instance.option( options );
instance._init();
} else {
// initialize new instance
instance = new PluginClass( this, options );
$.data( this, namespace, instance );
}
});
}
};
}
// -------------------------- bridget -------------------------- //
/**
* converts a Prototypical class into a proper jQuery plugin
* the class must have a ._init method
* @param {String} namespace - plugin name, used in $().pluginName
* @param {Function} PluginClass - constructor class
*/
$.bridget = function( namespace, PluginClass ) {
addOptionMethod( PluginClass );
bridge( namespace, PluginClass );
};
}
// transport
if ( typeof define === 'function' && define.amd ) {
// AMD
define( [ 'jquery' ], defineBridget );
} else {
// get jquery from browser global
defineBridget( window.jQuery );
}
})( window );
/**
* matchesSelector helper v1.0.1
*
* @name matchesSelector
* @param {Element} elem
* @param {String} selector
*/
/*jshint browser: true, strict: true, undef: true, unused: true */
/*global define: false */
( function( global, ElemProto ) {
'use strict';
var matchesMethod = ( function() {
// check un-prefixed
if ( ElemProto.matchesSelector ) {
return 'matchesSelector';
}
// check vendor prefixes
var prefixes = [ 'webkit', 'moz', 'ms', 'o' ];
for ( var i=0, len = prefixes.length; i < len; i++ ) {
var prefix = prefixes[i];
var method = prefix + 'MatchesSelector';
if ( ElemProto[ method ] ) {
return method;
}
}
})();
// ----- match ----- //
function match( elem, selector ) {
return elem[ matchesMethod ]( selector );
}
// ----- appendToFragment ----- //
function checkParent( elem ) {
// not needed if already has parent
if ( elem.parentNode ) {
return;
}
var fragment = document.createDocumentFragment();
fragment.appendChild( elem );
}
// ----- query ----- //
// fall back to using QSA
// thx @jonathantneal https://gist.github.com/3062955
function query( elem, selector ) {
// append to fragment if no parent
checkParent( elem );
// match elem with all selected elems of parent
var elems = elem.parentNode.querySelectorAll( selector );
for ( var i=0, len = elems.length; i < len; i++ ) {
// return true if match
if ( elems[i] === elem ) {
return true;
}
}
// otherwise return false
return false;
}
// ----- matchChild ----- //
function matchChild( elem, selector ) {
checkParent( elem );
return match( elem, selector );
}
// ----- matchesSelector ----- //
var matchesSelector;
if ( matchesMethod ) {
// IE9 supports matchesSelector, but doesn't work on orphaned elems
// check for that
var div = document.createElement('div');
var supportsOrphans = match( div, 'div' );
matchesSelector = supportsOrphans ? match : matchChild;
} else {
matchesSelector = query;
}
// transport
if ( typeof define === 'function' && define.amd ) {
// AMD
define( function() {
return matchesSelector;
});
} else {
// browser global
window.matchesSelector = matchesSelector;
}
})( this, Element.prototype );
/**
* Rect
* low-level utility class for basic geometry
*/
( function( window ) {
"use strict";
// -------------------------- Packery -------------------------- //
// global namespace
var Packery = window.Packery = function() {};
// -------------------------- Rect -------------------------- //
function Rect( props ) {
// extend properties from defaults
for ( var prop in Rect.defaults ) {
this[ prop ] = Rect.defaults[ prop ];
}
for ( prop in props ) {
this[ prop ] = props[ prop ];
}
}
// make available
Packery.Rect = Rect;
Rect.defaults = {
x: 0,
y: 0,
width: 0,
height: 0
};
/**
* Determines whether or not this rectangle wholly encloses another rectangle or point.
* @param {Rect} rect
* @returns {Boolean}
**/
Rect.prototype.contains = function( rect ) {
// points don't have width or height
var otherWidth = rect.width || 0;
var otherHeight = rect.height || 0;
return this.x <= rect.x &&
this.y <= rect.y &&
this.x + this.width >= rect.x + otherWidth &&
this.y + this.height >= rect.y + otherHeight;
};
/**
* Determines whether or not the rectangle intersects with another.
* @param {Rect} rect
* @returns {Boolean}
**/
Rect.prototype.overlaps = function( rect ) {
var thisRight = this.x + this.width;
var thisBottom = this.y + this.height;
var rectRight = rect.x + rect.width;
var rectBottom = rect.y + rect.height;
// http://stackoverflow.com/a/306332
return this.x < rectRight &&
thisRight > rect.x &&
this.y < rectBottom &&
thisBottom > rect.y;
};
/**
* @param {Rect} rect - the overlapping rect
* @returns {Array} freeRects - rects representing the area around the rect
**/
Rect.prototype.getMaximalFreeRects = function( rect ) {
// if no intersection, return false
if ( !this.overlaps( rect ) ) {
return false;
}
var freeRects = [];
var freeRect;
var thisRight = this.x + this.width;
var thisBottom = this.y + this.height;
var rectRight = rect.x + rect.width;
var rectBottom = rect.y + rect.height;
// top
if ( this.y < rect.y ) {
freeRect = new Rect({
x: this.x,
y: this.y,
width: this.width,
height: rect.y - this.y
});
freeRects.push( freeRect );
}
// right
if ( thisRight > rectRight ) {
freeRect = new Rect({
x: rectRight,
y: this.y,
width: thisRight - rectRight,
height: this.height
});
freeRects.push( freeRect );
}
// bottom
if ( thisBottom > rectBottom ) {
freeRect = new Rect({
x: this.x,
y: rectBottom,
width: this.width,
height: thisBottom - rectBottom
});
freeRects.push( freeRect );
}
// left
if ( this.x < rect.x ) {
freeRect = new Rect({
x: this.x,
y: this.y,
width: rect.x - this.x,
height: this.height
});
freeRects.push( freeRect );
}
return freeRects;
};
Rect.prototype.canFit = function( rect ) {
return this.width >= rect.width && this.height >= rect.height;
};
})( window );
( function( window ) {
'use strict';
var Packery = window.Packery;
var Rect = Packery.Rect;
// -------------------------- Packer -------------------------- //
function Packer( width, height ) {
this.width = width || 0;
this.height = height || 0;
this.reset();
}
Packer.prototype.reset = function() {
this.spaces = [];
this.newSpaces = [];
var initialSpace = new Rect({
x: 0,
y: 0,
width: this.width,
height: this.height
});
this.spaces.push( initialSpace );
};
// change x and y of rect to fit with in Packer's available spaces
Packer.prototype.pack = function( rect ) {
for ( var i=0, len = this.spaces.length; i < len; i++ ) {
var space = this.spaces[i];
if ( space.canFit( rect ) ) {
this.placeInSpace( rect, space );
break;
}
}
};
Packer.prototype.placeInSpace = function( rect, space ) {
// place rect in space
rect.x = space.x;
rect.y = space.y;
this.placed( rect );
};
// update spaces with placed rect
Packer.prototype.placed = function( rect ) {
// update spaces
var revisedSpaces = [];
for ( var i=0, len = this.spaces.length; i < len; i++ ) {
var space = this.spaces[i];
var newSpaces = space.getMaximalFreeRects( rect );
// add either the original space or the new spaces to the revised spaces
if ( newSpaces ) {
revisedSpaces.push.apply( revisedSpaces, newSpaces );
} else {
revisedSpaces.push( space );
}
}
this.spaces = revisedSpaces;
// remove redundant spaces
Packer.mergeRects( this.spaces );
this.spaces.sort( Packer.spaceSorterTopLeft );
};
// -------------------------- utility functions -------------------------- //
/**
* Remove redundant rectangle from array of rectangles
* @param {Array} rects: an array of Rects
* @returns {Array} rects: an array of Rects
**/
Packer.mergeRects = function( rects ) {
for ( var i=0, len = rects.length; i < len; i++ ) {
var rect = rects[i];
// skip over this rect if it was already removed
if ( !rect ) {
continue;
}
// clone rects we're testing, remove this rect
var compareRects = rects.slice(0);
// do not compare with self
compareRects.splice( i, 1 );
// compare this rect with others
var removedCount = 0;
for ( var j=0, jLen = compareRects.length; j < jLen; j++ ) {
var compareRect = compareRects[j];
// if this rect contains another,
// remove that rect from test collection
var indexAdjust = i > j ? 0 : 1;
if ( rect.contains( compareRect ) ) {
// console.log( 'current test rects:' + testRects.length, testRects );
// console.log( i, j, indexAdjust, rect, compareRect );
rects.splice( j + indexAdjust - removedCount, 1 );
removedCount++;
}
}
}
return rects;
};
// top down, then left to right
Packer.spaceSorterTopLeft = function( a, b ) {
return a.y - b.y || a.x - b.x;
};
// left to right, then top down
Packer.spaceSorterLeftTop = function( a, b ) {
return a.x - b.x || a.y - b.y;
};
// ----- ----- //
Packery.Packer = Packer;
})( window );
/**
* Packery Item Element
**/
( function( window ) {
'use strict';
// dependencies
var Packery = window.Packery;
var Rect = Packery.Rect;
var getSize = window.getSize;
var getStyleProperty = window.getStyleProperty;
var EventEmitter = window.EventEmitter;
// ----- get style ----- //
var defView = document.defaultView;
var getStyle = defView && defView.getComputedStyle ?
function( elem ) {
return defView.getComputedStyle( elem, null );
} :
function( elem ) {
return elem.currentStyle;
};
// extend objects
function extend( a, b ) {
for ( var prop in b ) {
a[ prop ] = b[ prop ];
}
return a;
}
// -------------------------- CSS3 support -------------------------- //
var transitionProperty = getStyleProperty('transition');
var transformProperty = getStyleProperty('transform');
var supportsCSS3 = transitionProperty && transformProperty;
var is3d = !!getStyleProperty('perspective');
var transitionEndEvent = {
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'otransitionend',
transition: 'transitionend'
}[ transitionProperty ];
var transformCSSProperty = {
WebkitTransform: '-webkit-transform',
MozTransform: '-moz-transform',
OTransform: '-o-transform',
transform: 'transform'
}[ transformProperty ];
// -------------------------- Item -------------------------- //
function Item( element, packery ) {
this.element = element;
this.packery = packery;
this.position = {
x: 0,
y: 0
};
this.rect = new Rect();
// rect used for placing, in drag or Packery.fit()
this.placeRect = new Rect();
// style initial style
this.element.style.position = 'absolute';
}
// inherit EventEmitter
extend( Item.prototype, EventEmitter.prototype );
// trigger specified handler for event type
Item.prototype.handleEvent = function( event ) {
var method = 'on' + event.type;
if ( this[ method ] ) {
this[ method ]( event );
}
};
Item.prototype.getSize = function() {
this.size = getSize( this.element );
};
/**
* apply CSS styles to element
* @param {Object} style
*/
Item.prototype.css = function( style ) {
var elemStyle = this.element.style;
for ( var prop in style ) {
elemStyle[ prop ] = style[ prop ];
}
};
// measure position, and sets it
Item.prototype.getPosition = function() {
var style = getStyle( this.element );
var x = parseInt( style.left, 10 );
var y = parseInt( style.top, 10 );
// clean up 'auto' or other non-integer values
x = isNaN( x ) ? 0 : x;
y = isNaN( y ) ? 0 : y;
// remove padding from measurement
var packerySize = this.packery.elementSize;
x -= packerySize.paddingLeft;
y -= packerySize.paddingTop;
this.position.x = x;
this.position.y = y;
};
// transform translate function
var translate = is3d ?
function( x, y ) {
return 'translate3d( ' + x + 'px, ' + y + 'px, 0)';
} :
function( x, y ) {
return 'translate( ' + x + 'px, ' + y + 'px)';
};
Item.prototype._transitionTo = function( x, y ) {
this.getPosition();
// get current x & y from top/left
var curX = this.position.x;
var curY = this.position.y;
var compareX = parseInt( x, 10 );
var compareY = parseInt( y, 10 );
var didNotMove = compareX === this.position.x && compareY === this.position.y;
// save end position
this.setPosition( x, y );
// if did not move and not transitioning, just go to layout
if ( didNotMove && !this.isTransitioning ) {
this.layoutPosition();
return;
}
var transX = x - curX;
var transY = y - curY;
var transitionStyle = {};
transitionStyle[ transformCSSProperty ] = translate( transX, transY );
this.transition( transitionStyle, this.layoutPosition );
};
// non transition + transform support
Item.prototype.goTo = function( x, y ) {
this.setPosition( x, y );
this.layoutPosition();
};
// use transition and transforms if supported
Item.prototype.moveTo = supportsCSS3 ?
Item.prototype._transitionTo : Item.prototype.goTo;
Item.prototype.setPosition = function( x, y ) {
this.position.x = parseInt( x, 10 );
this.position.y = parseInt( y, 10 );
};
Item.prototype.layoutPosition = function() {
var packerySize = this.packery.elementSize;
this.css({
// set settled position, apply padding
left: ( this.position.x + packerySize.paddingLeft ) + 'px',
top : ( this.position.y + packerySize.paddingTop ) + 'px'
});
this.emitEvent( 'layout', [ this ] );
};
/**
* @param {Object} style - CSS
* @param {Function} onTransitionEnd
*/
// non transition, just trigger callback
Item.prototype._nonTransition = function( style, onTransitionEnd ) {
this.css( style );
if ( onTransitionEnd ) {
onTransitionEnd.call( this );
}
};
// proper transition
Item.prototype._transition = function( style, onTransitionEnd ) {
this.transitionStyle = style;
var transitionValue = [];
for ( var prop in style ) {
transitionValue.push( prop );
}
// enable transition
var transitionStyle = {};
transitionStyle[ transitionProperty + 'Property' ] = transitionValue.join(',');
transitionStyle[ transitionProperty + 'Duration' ] = this.packery.options.transitionDuration;
this.element.addEventListener( transitionEndEvent, this, false );
// bind callback to transition end
if ( onTransitionEnd ) {
this.on( 'transitionEnd', function( _this ) {
onTransitionEnd.call( _this );
return true; // bind once
});
}
// set transition styles
this.css( transitionStyle );
// set styles that are transitioning
this.css( style );
this.isTransitioning = true;
};
Item.prototype.transition = Item.prototype[ transitionProperty ? '_transition' : '_nonTransition' ];
Item.prototype.onwebkitTransitionEnd = function( event ) {
this.ontransitionend( event );
};
Item.prototype.onotransitionend = function( event ) {
this.ontransitionend( event );
};
Item.prototype.ontransitionend = function( event ) {
// console.log('transition end');
// disregard bubbled events from children
if ( event.target !== this.element ) {
return;
}
// trigger callback
if ( this.onTransitionEnd ) {
this.onTransitionEnd();
delete this.onTransitionEnd;
}
this.removeTransitionStyles();
// clean up transition styles
var cleanStyle = {};
for ( var prop in this.transitionStyle ) {
cleanStyle[ prop ] = '';
}
this.css( cleanStyle );
this.element.removeEventListener( transitionEndEvent, this, false );
delete this.transitionStyle;
this.isTransitioning = false;
this.emitEvent( 'transitionEnd', [ this ] );
};
Item.prototype.removeTransitionStyles = function() {
var noTransStyle = {};
// remove transition
noTransStyle[ transitionProperty + 'Property' ] = '';
noTransStyle[ transitionProperty + 'Duration' ] = '';
this.css( noTransStyle );
};
Item.prototype.remove = function() {
// start transition
var hiddenStyle = {
opacity: 0
};
hiddenStyle[ transformCSSProperty ] = 'scale(0.001)';
this.transition( hiddenStyle, this.removeElem );
};
// remove element from DOM
Item.prototype.removeElem = function() {
this.element.parentNode.removeChild( this.element );
this.emitEvent( 'remove', [ this ] );
};
Item.prototype.reveal = !transitionProperty ? function() {} : function() {
// hide item
var hiddenStyle = {
opacity: 0
};
hiddenStyle[ transformCSSProperty ] = 'scale(0.001)';
this.css( hiddenStyle );
// force redraw. http://blog.alexmaccaw.com/css-transitions
var h = this.element.offsetHeight;
// transition to revealed
var visibleStyle = {
opacity: 1
};
visibleStyle[ transformCSSProperty ] = 'scale(1)';
this.transition( visibleStyle );
// hack for JSHint to hush about unused var
h = null;
};
Item.prototype.destroy = function() {
this.css({
position: '',
left: '',
top: ''
});
};
// -------------------------- drag -------------------------- //
Item.prototype.dragStart = function() {
this.getPosition();
this.removeTransitionStyles();
// remove transform property from transition
if ( this.isTransitioning && transformProperty ) {
this.element.style[ transformProperty ] = 'none';
}
this.getSize();
// create place rect, used for position when dragged then dropped
// or when positioning
this.isPlacing = true;
this.needsPositioning = false;
this.positionPlaceRect( this.position.x, this.position.y );
this.isTransitioning = false;
this.didDrag = false;
};
/**
* handle item when it is dragged
* @param {Number} x - horizontal position of dragged item
* @param {Number} y - vertical position of dragged item
*/
Item.prototype.dragMove = function( x, y ) {
this.didDrag = true;
var packerySize = this.packery.elementSize;
x -= packerySize.paddingLeft;
y -= packerySize.paddingTop;
this.positionPlaceRect( x, y );
};
Item.prototype.dragStop = function() {
this.getPosition();
var isDiffX = this.position.x !== this.placeRect.x;
var isDiffY = this.position.y !== this.placeRect.y;
// set post-drag positioning flag
this.needsPositioning = isDiffX || isDiffY;
// reset flag
this.didDrag = false;
};
// -------------------------- placing -------------------------- //
/**
* position a rect that will occupy space in the packer
* @param {Number} x
* @param {Number} y
* @param {Boolean} isMaxYContained
*/
Item.prototype.positionPlaceRect = function( x, y, isMaxYOpen ) {
this.placeRect.x = this.getPlaceRectCoord( x, true );
this.placeRect.y = this.getPlaceRectCoord( y, false, isMaxYOpen );
};
/**
* get x/y coordinate for place rect
* @param {Number} coord - x or y
* @param {Boolean} isX
* @param {Boolean} isMaxOpen - does not limit value to outer bound
* @returns {Number} coord - processed x or y
*/
Item.prototype.getPlaceRectCoord = function( coord, isX, isMaxOpen ) {
var measure = isX ? 'Width' : 'Height';
var size = this.size[ 'outer' + measure ];
var segment = this.packery[ isX ? 'columnWidth' : 'rowHeight' ];
var parentSize = this.packery.elementSize[ 'inner' + measure ];
// additional parentSize calculations for Y
if ( !isX ) {
parentSize = Math.max( parentSize, this.packery.maxY );
// prevent gutter from bumping up height when non-vertical grid
if ( !this.packery.rowHeight ) {
parentSize -= this.packery.gutter;
}
}
var max;
if ( segment ) {
segment += this.packery.gutter;
// allow for last column to reach the edge
parentSize += isX ? this.packery.gutter : 0;
// snap to closest segment
coord = Math.round( coord / segment );
// contain to outer bound
// x values must be contained, y values can grow box by 1
var maxSegments = Math[ isX ? 'floor' : 'ceil' ]( parentSize / segment );
maxSegments -= Math.ceil( size / segment );
max = maxSegments;
} else {
max = parentSize - size;
}
coord = isMaxOpen ? coord : Math.min( coord, max );
coord *= segment || 1;
return Math.max( 0, coord );
};
Item.prototype.copyPlaceRectPosition = function() {
this.rect.x = this.placeRect.x;
this.rect.y = this.placeRect.y;
};
// -------------------------- -------------------------- //
// publicize
Packery.Item = Item;
})( window );
/*!
* Packery v1.0.6
* bin-packing layout library
* http://packery.metafizzy.co
*
* Commercial use requires one-time purchase of a commercial license
* http://packery.metafizzy.co/license.html
*
* Non-commercial use is licensed under the MIT License
*
* Copyright 2013 Metafizzy
*/
( function( window ) {
'use strict';
// Packery classes
var _Packery = window.Packery;
var Rect = _Packery.Rect;
var Packer = _Packery.Packer;
var Item = _Packery.Item;
// dependencies
var classie = window.classie;
var docReady = window.docReady;
var EventEmitter = window.EventEmitter;
var eventie = window.eventie;
var getSize = window.getSize;
var matchesSelector = window.matchesSelector;
// ----- vars ----- //
var document = window.document;
var console = window.console;
var jQuery = window.jQuery;
// -------------------------- helpers -------------------------- //
// extend objects
function extend( a, b ) {
for ( var prop in b ) {
a[ prop ] = b[ prop ];
}
return a;
}
// turn element or nodeList into an array
function makeArray( obj ) {
var ary = [];
if ( typeof obj.length === 'number' ) {
// convert nodeList to array
for ( var i=0, len = obj.length; i < len; i++ ) {
ary.push( obj[i] );
}
} else {
// array of single index
ary.push( obj );
}
return ary;
}
// http://stackoverflow.com/a/384380/182183
var isElement = ( typeof HTMLElement === 'object' ) ?
function isElementDOM2( obj ) {
return obj instanceof HTMLElement;
} :
function isElementQuirky( obj ) {
return obj && typeof obj === 'object' &&
obj.nodeType === 1 && typeof obj.nodeName === 'string';
};
// index of helper cause IE8
var indexOf = Array.prototype.indexOf ? function( ary, obj ) {
return ary.indexOf( obj );
} : function( ary, obj ) {
for ( var i=0, len = ary.length; i < len; i++ ) {
if ( ary[i] === obj ) {
return i;
}
}
return -1;
};
// -------------------------- Packery -------------------------- //
// globally unique identifiers
var GUID = 0;
// internal store of all Packery intances
var packeries = {};
function Packery( element, options ) {
// bail out if not proper element
if ( !element || !isElement( element ) ) {
if ( console ) {
console.error( 'bad Packery element: ' + element );
}
return;
}
this.element = element;
// options
this.options = extend( {}, this.options );
extend( this.options, options );
// add id for Packery.getFromElement
var id = ++GUID;
this.element.packeryGUID = id; // expando
packeries[ id ] = this; // associate via id
// kick it off
this._create();
if ( this.options.isInitLayout ) {
this.layout();
}
}
// inherit EventEmitter
extend( Packery.prototype, EventEmitter.prototype );
// default options
Packery.prototype.options = {
containerStyle: {
position: 'relative'
},
isInitLayout: true,
isResizeBound: true,
transitionDuration: '0.4s'
};
Packery.prototype._create = function() {
// initial properties
this.packer = new Packer();
// get items from children
this.reloadItems();
// collection of element that don't get laid out
this.stampedElements = [];
this.stamp( this.options.stamped );
var containerStyle = this.options.containerStyle;
extend( this.element.style, containerStyle );
// bind resize method
if ( this.options.isResizeBound ) {
this.bindResize();
}
// create drag handlers
var _this = this;
this.handleDraggabilly = {
dragStart: function( draggie ) {
_this.itemDragStart( draggie.element );
},
dragMove: function( draggie ) {
_this.itemDragMove( draggie.element, draggie.position.x, draggie.position.y );
},
dragEnd: function( draggie ) {
_this.itemDragEnd( draggie.element );
}
};
this.handleUIDraggable = {
start: function handleUIDraggableStart( event ) {
_this.itemDragStart( event.currentTarget );
},
drag: function handleUIDraggableDrag( event, ui ) {
_this.itemDragMove( event.currentTarget, ui.position.left, ui.position.top );
},
stop: function handleUIDraggableStop( event ) {
_this.itemDragEnd( event.currentTarget );
}
};
};
// goes through all children again and gets bricks in proper order
Packery.prototype.reloadItems = function() {
// collection of item elements
this.items = this._getItems( this.element.children );
};
/**
* get item elements to be used in layout
* @param {Array or NodeList or HTMLElement} elems
* @returns {Array} items - collection of new Packery Items
*/
Packery.prototype._getItems = function( elems ) {
var itemElems = this._filterFindItemElements( elems );
// create new Packery Items for collection
var items = [];
for ( var i=0, len = itemElems.length; i < len; i++ ) {
var elem = itemElems[i];
var item = new Item( elem, this );
items.push( item );
}
return items;
};
/**
* get item elements to be used in layout
* @param {Array or NodeList or HTMLElement} elems
* @returns {Array} items - item elements
*/
Packery.prototype._filterFindItemElements = function( elems ) {
// make array of elems
elems = makeArray( elems );
var itemSelector = this.options.itemSelector;
if ( !itemSelector ) {
return elems;
}
var itemElems = [];
// filter & find items if we have an item selector
for ( var i=0, len = elems.length; i < len; i++ ) {
var elem = elems[i];
// filter siblings
if ( matchesSelector( elem, itemSelector ) ) {
itemElems.push( elem );
}
// find children
var childElems = elem.querySelectorAll( itemSelector );
// concat childElems to filterFound array
for ( var j=0, jLen = childElems.length; j < jLen; j++ ) {
itemElems.push( childElems[j] );
}
}
return itemElems;
};
/**
* getter method for getting item elements
* @returns {Array} elems - collection of item elements
*/
Packery.prototype.getItemElements = function() {
var elems = [];
for ( var i=0, len = this.items.length; i < len; i++ ) {
elems.push( this.items[i].element );
}
return elems;
};
// ----- init & layout ----- //
/**
* lays out all items
*/
Packery.prototype.layout = function() {
this._prelayout();
// don't animate first layout
var isInstant = this.options.isLayoutInstant !== undefined ?
this.options.isLayoutInstant : !this._isLayoutInited;
this.layoutItems( this.items, isInstant );
// flag for initalized
this._isLayoutInited = true;
};
// _init is alias for layout
Packery.prototype._init = Packery.prototype.layout;
/**
* logic before any new layout
*/
Packery.prototype._prelayout = function() {
// reset packer
this.elementSize = getSize( this.element );
this._getMeasurements();
this.packer.width = this.elementSize.innerWidth + this.gutter;
this.packer.height = Number.POSITIVE_INFINITY;
this.packer.reset();
// layout
this.maxY = 0;
this.placeStampedElements();
};
/**
* update columnWidth, rowHeight, & gutter
* @private
*/
Packery.prototype._getMeasurements = function() {
this._getMeasurement( 'columnWidth', 'width' );
this._getMeasurement( 'rowHeight', 'height' );
this._getMeasurement( 'gutter', 'width' );
};
/**
* get measurement from option, for columnWidth, rowHeight, gutter
* if option is String -> get element from selector string, & get size of element
* if option is Element -> get size of element
* else use option as a number
*
* @param {String} measurement
* @param {String} size - width or height
* @private
*/
Packery.prototype._getMeasurement = function( measurement, size ) {
var option = this.options[ measurement ];
var elem;
if ( !option ) {
// default to 0
this[ measurement ] = 0;
} else {
if ( typeof option === 'string' ) {
elem = this.element.querySelector( option );
} else if ( isElement( option ) ) {
elem = option;
}
// use size of element, if element
this[ measurement ] = elem ? getSize( elem )[ size ] : option;
}
};
/**
* layout a collection of item elements
* @param {Array} items - array of Packery.Items
* @param {Boolean} isInstant - disable transitions for setting item position
*/
Packery.prototype.layoutItems = function( items, isInstant ) {
// console.log('layout Items');
var layoutItems = this._getLayoutItems( items );
if ( !layoutItems || !layoutItems.length ) {
// no items, just emit layout complete with empty array
this.emitEvent( 'layoutComplete', [ this, [] ] );
} else {
this._itemsOn( layoutItems, 'layout', function onItemsLayout() {
this.emitEvent( 'layoutComplete', [ this, layoutItems ] );
});
for ( var i=0, len = layoutItems.length; i < len; i++ ) {
var item = layoutItems[i];
// listen to layout events for callback
this._packItem( item );
this._layoutItem( item, isInstant );
}
}
// set container size
var elemSize = this.elementSize;
var elemH = this.maxY - this.gutter;
// add padding and border width if border box
if ( elemSize.isBorderBox ) {
elemH += elemSize.paddingBottom + elemSize.paddingTop +
elemSize.borderTopWidth + elemSize.borderBottomWidth;
}
// prevent negative size, which causes error in IE
elemH = Math.max( elemH, 0 );
this.element.style.height = elemH + 'px';
};
/**
* filters items for non-ignored items
* @param {Array} items
* @returns {Array} layoutItems
*/
Packery.prototype._getLayoutItems = function( items ) {
var layoutItems = [];
for ( var i=0, len = items.length; i < len; i++ ) {
var item = items[i];
if ( !item.isIgnored ) {
layoutItems.push( item );
}
}
return layoutItems;
};
/**
* layout item in packer
* @param {Packery.Item} item
*/
Packery.prototype._packItem = function( item ) {
this._setRectSize( item.element, item.rect );
// pack the rect in the packer
this.packer.pack( item.rect );
this._setMaxY( item.rect );
};
/**
* set max Y value, for height of container
* @param {Packery.Rect} rect
* @private
*/
Packery.prototype._setMaxY = function( rect ) {
this.maxY = Math.max( rect.y + rect.height, this.maxY );
};
/**
* set the width and height of a rect, applying columnWidth and rowHeight
* @param {Element} elem
* @param {Packery.Rect} rect
*/
Packery.prototype._setRectSize = function( elem, rect ) {
var size = getSize( elem );
var w = size.outerWidth;
var h = size.outerHeight;
// size for columnWidth and rowHeight, if available
var colW = this.columnWidth + this.gutter;
var rowH = this.rowHeight + this.gutter;
w = this.columnWidth ? Math.ceil( w / colW ) * colW : w + this.gutter;
h = this.rowHeight ? Math.ceil( h / rowH ) * rowH : h + this.gutter;
// rect must fit in packer
rect.width = Math.min( w, this.packer.width );
rect.height = h;
};
/**
* Sets position of item in DOM
* @param {Packery.Item} item
* @param {Boolean} isInstant - disables transitions
*/
Packery.prototype._layoutItem = function( item, isInstant ) {
// copy over position of packed rect to item element
var rect = item.rect;
if ( isInstant ) {
// if not transition, just set CSS
item.goTo( rect.x, rect.y );
} else {
item.moveTo( rect.x, rect.y );
}
};
/**
* trigger a callback for a collection of items events
* @param {Array} items - Packery.Items
* @param {String} eventName
* @param {Function} callback
*/
Packery.prototype._itemsOn = function( items, eventName, callback ) {
var doneCount = 0;
var count = items.length;
// event callback
var _this = this;
function tick() {
doneCount++;
if ( doneCount === count ) {
callback.call( _this );
}
return true; // bind once
}
// bind callback
for ( var i=0, len = items.length; i < len; i++ ) {
var item = items[i];
item.on( eventName, tick );
}
};
// -------------------------- stamp -------------------------- //
/**
* adds elements to stampedElements
* @param {NodeList, Array, Element, or String} elems
*/
Packery.prototype.stamp = function( elems ) {
if ( !elems ) {
return;
}
// if string, use argument as selector string
if ( typeof elems === 'string' ) {
elems = this.element.querySelectorAll( elems );
}
elems = makeArray( elems );
this.stampedElements.push.apply( this.stampedElements, elems );
// ignore
for ( var i=0, len = elems.length; i < len; i++ ) {
var elem = elems[i];
this.ignore( elem );
}
};
/**
* removes elements to stampedElements
* @param {NodeList, Array, or Element} elems
*/
Packery.prototype.unstamp = function( elems ) {
if ( !elems ){
return;
}
elems = makeArray( elems );
for ( var i=0, len = elems.length; i < len; i++ ) {
var elem = elems[i];
// filter out removed stamp elements
var index = indexOf( this.stampedElements, elem );
if ( index !== -1 ) {
this.stampedElements.splice( index, 1 );
}
this.unignore( elem );
}
};
// make spaces for stamped elements
Packery.prototype.placeStampedElements = function() {
if ( !this.stampedElements || !this.stampedElements.length ) {
return;
}
this._getBounds();
for ( var i=0, len = this.stampedElements.length; i < len; i++ ) {
var elem = this.stampedElements[i];
this.placeStamp( elem );
}
};
// update boundingLeft / Top
Packery.prototype._getBounds = function() {
// get bounding rect for container element
var elementBoundingRect = this.element.getBoundingClientRect();
this._boundingLeft = elementBoundingRect.left + this.elementSize.paddingLeft;
this._boundingTop = elementBoundingRect.top + this.elementSize.paddingTop;
};
/**
* makes space for element
* @param {Element} elem
*/
Packery.prototype.placeStamp = function( elem ) {
var item = this.getItem( elem );
var rect;
if ( item && item.isPlacing ) {
rect = item.placeRect;
} else {
rect = this._getElementOffsetRect( elem );
}
this._setRectSize( elem, rect );
// save its space in the packer
this.packer.placed( rect );
this._setMaxY( rect );
};
/**
* get x/y position of element relative to container element
* @param {Element} elem
* @returns {Rect} rect
*/
Packery.prototype._getElementOffsetRect = function( elem ) {
var boundingRect = elem.getBoundingClientRect();
var rect = new Rect({
x: boundingRect.left - this._boundingLeft,
y: boundingRect.top - this._boundingTop
});
rect.x -= this.elementSize.borderLeftWidth;
rect.y -= this.elementSize.borderTopWidth;
return rect;
};
// -------------------------- resize -------------------------- //
// enable event handlers for listeners
// i.e. resize -> onresize
Packery.prototype.handleEvent = function( event ) {
var method = 'on' + event.type;
if ( this[ method ] ) {
this[ method ]( event );
}
};
/**
* Bind layout to window resizing
*/
Packery.prototype.bindResize = function() {
// bind just one listener
if ( this.isResizeBound ) {
return;
}
eventie.bind( window, 'resize', this );
this.isResizeBound = true;
};
/**
* Unbind layout to window resizing
*/
Packery.prototype.unbindResize = function() {
eventie.unbind( window, 'resize', this );
this.isResizeBound = false;
};
// original debounce by John Hann
// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
// this fires every resize
Packery.prototype.onresize = function() {
if ( this.resizeTimeout ) {
clearTimeout( this.resizeTimeout );
}
var _this = this;
function delayed() {
_this.resize();
}
this.resizeTimeout = setTimeout( delayed, 100 );
};
// debounced, layout on resize
Packery.prototype.resize = function() {
// don't trigger if size did not change
var size = getSize( this.element );
// check that elementSize and size are there
// IE8 triggers resize on body size change, so they might not be
var hasSizes = this.elementSize && size;
if ( hasSizes && size.innerWidth === this.elementSize.innerWidth ) {
return;
}
this.layout();
delete this.resizeTimeout;
};
// -------------------------- methods -------------------------- //
/**
* add items to Packery instance
* @param {Array or NodeList or Element} elems
* @returns {Array} items - Packery.Items
**/
Packery.prototype.addItems = function( elems ) {
var items = this._getItems( elems );
if ( !items.length ) {
return;
}
// add items to collection
this.items.push.apply( this.items, items );
return items;
};
/**
* Layout newly-appended item elements
* @param {Array or NodeList or Element} elems
*/
Packery.prototype.appended = function( elems ) {
var items = this.addItems( elems );
if ( !items.length ) {
return;
}
// layout and reveal just the new items
this.layoutItems( items, true );
this.reveal( items );
};
/**
* Layout prepended elements
* @param {Array or NodeList or Element} elems
*/
Packery.prototype.prepended = function( elems ) {
var items = this._getItems( elems );
if ( !items.length ) {
return;
}
// add items to beginning of collection
var previousItems = this.items.slice(0);
this.items = items.concat( previousItems );
// start new layout
this._prelayout();
// layout new stuff without transition
this.layoutItems( items, true );
this.reveal( items );
// layout previous items
this.layoutItems( previousItems );
};
// reveal a collection of items
Packery.prototype.reveal = function( items ) {
if ( !items || !items.length ) {
return;
}
for ( var i=0, len = items.length; i < len; i++ ) {
var item = items[i];
item.reveal();
}
};
/**
* get Packery.Item, given an Element
* @param {Element} elem
* @param {Function} callback
* @returns {Packery.Item} item
*/
Packery.prototype.getItem = function( elem ) {
// loop through items to get the one that matches
for ( var i=0, len = this.items.length; i < len; i++ ) {
var item = this.items[i];
if ( item.element === elem ) {
// return item
return item;
}
}
};
/**
* get collection of Packery.Items, given Elements
* @param {Array} elems
* @returns {Array} items - Packery.Items
*/
Packery.prototype.getItems = function( elems ) {
if ( !elems || !elems.length ) {
return;
}
var items = [];
for ( var i=0, len = elems.length; i < len; i++ ) {
var elem = elems[i];
var item = this.getItem( elem );
if ( item ) {
items.push( item );
}
}
return items;
};
/**
* remove element(s) from instance and DOM
* @param {Array or NodeList or Element} elems
*/
Packery.prototype.remove = function( elems ) {
elems = makeArray( elems );
var removeItems = this.getItems( elems );
this._itemsOn( removeItems, 'remove', function() {
this.emitEvent( 'removeComplete', [ this, removeItems ] );
});
for ( var i=0, len = removeItems.length; i < len; i++ ) {
var item = removeItems[i];
item.remove();
// remove item from collection
var index = indexOf( this.items, item );
this.items.splice( index, 1 );
}
};
/**
* keep item in collection, but do not lay it out
* @param {Element} elem
*/
Packery.prototype.ignore = function( elem ) {
var item = this.getItem( elem );
if ( item ) {
item.isIgnored = true;
}
};
/**
* return item to layout collection
* @param {Element} elem
*/
Packery.prototype.unignore = function( elem ) {
var item = this.getItem( elem );
if ( item ) {
delete item.isIgnored;
}
};
Packery.prototype.sortItemsByPosition = function() {
// console.log('sortItemsByPosition');
this.items.sort( function( a, b ) {
return a.position.y - b.position.y || a.position.x - b.position.x;
});
};
/**
* Fit item element in its current position
* Packery will position elements around it
* useful for expanding elements
*
* @param {Element} elem
* @param {Number} x - horizontal destination position, optional
* @param {Number} y - vertical destination position, optional
*/
Packery.prototype.fit = function( elem, x, y ) {
var item = this.getItem( elem );
if ( !item ) {
return;
}
// prepare internal properties
this._getMeasurements();
// stamp item to get it out of layout
this.stamp( item.element );
// required for positionPlaceRect
item.getSize();
// set placing flag
item.isPlacing = true;
// fall back to current position for fitting
x = x === undefined ? item.rect.x: x;
y = y === undefined ? item.rect.y: y;
// position it best at its destination
item.positionPlaceRect( x, y, true );
// emit event when item is fit and other items are laid out
var _this = this;
var ticks = 0;
function tick() {
ticks++;
if ( ticks !== 2 ) {
return;
}
_this.emitEvent( 'fitComplete', [ _this, item ] );
}
item.on( 'layout', function() {
tick();
return true;
});
this.on( 'layoutComplete', function() {
tick();
return true;
});
item.moveTo( item.placeRect.x, item.placeRect.y );
// layout everything else
this.layout();
// return back to regularly scheduled programming
this.unstamp( item.element );
this.sortItemsByPosition();
// un set placing flag, back to normal
item.isPlacing = false;
// copy place rect position
item.copyPlaceRectPosition();
};
// -------------------------- drag -------------------------- //
/**
* handle an item drag start event
* @param {Element} elem
*/
Packery.prototype.itemDragStart = function( elem ) {
this.stamp( elem );
var item = this.getItem( elem );
if ( item ) {
item.dragStart();
}
};
/**
* handle an item drag move event
* @param {Element} elem
* @param {Number} x - horizontal change in position
* @param {Number} y - vertical change in position
*/
Packery.prototype.itemDragMove = function( elem, x, y ) {
var item = this.getItem( elem );
if ( item ) {
item.dragMove( x, y );
}
// debounce
var _this = this;
// debounce triggering layout
function delayed() {
_this.layout();
delete _this.dragTimeout;
}
this.clearDragTimeout();
this.dragTimeout = setTimeout( delayed, 40 );
};
Packery.prototype.clearDragTimeout = function() {
if ( this.dragTimeout ) {
clearTimeout( this.dragTimeout );
}
};
/**
* handle an item drag end event
* @param {Element} elem
*/
Packery.prototype.itemDragEnd = function( elem ) {
var item = this.getItem( elem );
var itemDidDrag;
if ( item ) {
itemDidDrag = item.didDrag;
item.dragStop();
}
// if elem didn't move, or if it doesn't need positioning
// unignore and unstamp and call it a day
if ( !item || ( !itemDidDrag && !item.needsPositioning ) ) {
this.unstamp( elem );
return;
}
// procced with dragged item
classie.add( item.element, 'is-positioning-post-drag' );
// save this var, as it could get reset in dragStart
var itemNeedsPositioning = item.needsPositioning;
var asyncCount = itemNeedsPositioning ? 2 : 1;
var completeCount = 0;
var _this = this;
function onLayoutComplete() {
completeCount++;
// don't proceed if not complete
if ( completeCount !== asyncCount ) {
return true;
}
// reset item
if ( item ) {
classie.remove( item.element, 'is-positioning-post-drag' );
item.isPlacing = false;
item.copyPlaceRectPosition();
}
_this.unstamp( elem );
// only sort when item moved
_this.sortItemsByPosition();
// emit item drag event now that everything is done
if ( item && itemNeedsPositioning ) {
_this.emitEvent( 'dragItemPositioned', [ _this, item ] );
}
// listen once
return true;
}
if ( itemNeedsPositioning ) {
item.on( 'layout', onLayoutComplete );
item.moveTo( item.placeRect.x, item.placeRect.y );
} else if ( item ) {
// item didn't need placement
item.copyPlaceRectPosition();
}
this.clearDragTimeout();
this.on( 'layoutComplete', onLayoutComplete );
this.layout();
};
/**
* binds Draggabilly events
* @param {Draggabilly} draggie
*/
Packery.prototype.bindDraggabillyEvents = function( draggie ) {
draggie.on( 'dragStart', this.handleDraggabilly.dragStart );
draggie.on( 'dragMove', this.handleDraggabilly.dragMove );
draggie.on( 'dragEnd', this.handleDraggabilly.dragEnd );
};
/**
* binds jQuery UI Draggable events
* @param {jQuery} $elems
*/
Packery.prototype.bindUIDraggableEvents = function( $elems ) {
$elems
.on( 'dragstart', this.handleUIDraggable.start )
.on( 'drag', this.handleUIDraggable.drag )
.on( 'dragstop', this.handleUIDraggable.stop );
};
// ----- destroy ----- //
// remove and disable Packery instance
Packery.prototype.destroy = function() {
// reset element styles
this.element.style.position = '';
this.element.style.height = '';
delete this.element.packeryGUID;
// destroy items
for ( var i=0, len = this.items.length; i < len; i++ ) {
var item = this.items[i];
item.destroy();
}
this.unbindResize();
};
// -------------------------- data -------------------------- //
/**
* get Packery instance from element
* @param {Element} elem
* @returns {Packery}
*/
Packery.data = function( elem ) {
var id = elem.packeryGUID;
return id && packeries[ id ];
};
// -------------------------- declarative -------------------------- //
/**
* allow user to initialize Packery via .js-packery class
* options are parsed from data-packery-option attribute
*/
docReady( function() {
var elems = document.querySelectorAll('.js-packery');
for ( var i=0, len = elems.length; i < len; i++ ) {
var elem = elems[i];
var attr = elem.getAttribute('data-packery-options');
var options;
try {
options = attr && JSON.parse( attr );
} catch ( error ) {
// log error, do not initialize
if ( console ) {
console.error( 'Error parsing data-packery-options on ' +
elem.nodeName.toLowerCase() + ( elem.id ? '#' + elem.id : '' ) + ': ' +
error );
}
continue;
}
// initialize
var pckry = new Packery( elem, options );
// make available via $().data('packery')
if ( jQuery ) {
jQuery.data( elem, 'packery', pckry );
}
}
});
// -------------------------- jQuery bridge -------------------------- //
// make into jQuery plugin
if ( jQuery && jQuery.bridget ) {
jQuery.bridget( 'packery', Packery );
}
// -------------------------- transport -------------------------- //
// back in global
Packery.Rect = Rect;
Packery.Packer = Packer;
Packery.Item = Item;
window.Packery = Packery;
})( window );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment