Skip to content

Instantly share code, notes, and snippets.

@refactornator
Created May 23, 2013 03:23
Show Gist options
  • Save refactornator/5632574 to your computer and use it in GitHub Desktop.
Save refactornator/5632574 to your computer and use it in GitHub Desktop.
Zoomdata Dashboard Example A simple dashboard that uses the Packery JS library for draggable layout. Just create a new visualization and add a new file for each of the files in this gist. Then, preview the 'Real Time Sales' data. #Zoomdata
// ©2013 Zoomdata, Inc. All Rights Reserved.
CurrentVisualizationController = (function (){
var Datum = Backbone.Model.extend({
defaults: {
}
});
var Graph = Backbone.Collection.extend({
model: Datum
});
var Visualization = Backbone.View.extend({
el: $('#visualization')[0],
defaults: {
width: 960,
height: 500
},
initialize: function (options) {
_.bindAll(this, 'render', 'update');
this.settings = $.extend({}, this.defaults, options);
this.render();
this.$el.css('margin-top', '120px');
this.listenTo(this.collection, 'add remove change', this.update);
},
render: function () {
var width = this.settings.width,
height = this.settings.height;
this._rootDiv = d3.select(this.el).classed("packery", true);
var descriptionItem = this._rootDiv.append("div")
.classed("item", true)
.classed("stamp", true)
.style("width", "600px")
.style("height", "100px")
.style("float", "left")
.style("background", "#C90")
.style("border", "4px solid #333")
.style("border-color", "hsla(0, 0%, 0%, 0.3)");
this._descriptionSvg = descriptionItem.append('svg');
this._descriptionSvg.append('text')
.attr('class', 'label')
.attr("x", "50%")
.attr("y", "40%")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.style("font-size", "30px")
.style("font-family", '"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif')
.style("fill", "rgba(255, 255, 255, 0.7)")
.text('Income Bracket for Realtime Sales');
this._descriptionSvg.append('text')
.attr('class', 'label')
.attr("x", "50%")
.attr("y", "70%")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.style("font-size", "15px")
.style("font-family", '"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif')
.style("fill", "rgba(255, 255, 255, 0.8)")
.text('This is the number of sales by income bracket for the past hour.');
var zeroToTwentyFiveThousand = this._rootDiv.append("div").classed("item", true)
.style("width", "200px")
.style("height", "200px")
.style("float", "left")
.style("background", "#12b0c5")
.style("border", "4px solid #333")
.style("border-color", "hsla(0, 0%, 0%, 0.3)");
this._zeroToTwentyFiveThousandSvg = zeroToTwentyFiveThousand.append('svg');
this._zeroToTwentyFiveThousandSvg.append("text")
.attr('class', 'units')
.attr("x", "50%")
.attr("y", "65%")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.style("font-size", "16px")
.style("font-family", '"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif')
.style("fill", "rgba(255, 255, 255, 0.65)")
.text('sales');
this._zeroToTwentyFiveThousandSvg.append('text')
.attr('class', 'label')
.attr("x", "40%")
.attr("y", "95%")
.attr("dy", ".3em")
.text('$0 to $25,000 a year');
var twentyFiveThousandToFiftyThousandItem = this._rootDiv.append("div").classed("item", true)
.style("width", "200px")
.style("height", "200px")
.style("float", "left")
.style("background", "#12b0c5")
.style("border", "4px solid #333")
.style("border-color", "hsla(0, 0%, 0%, 0.3)");
this._twentyFiveThousandToFiftyThousandSvg = twentyFiveThousandToFiftyThousandItem.append('svg');
this._twentyFiveThousandToFiftyThousandSvg.append("text")
.attr('class', 'units')
.attr("x", "50%")
.attr("y", "65%")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.style("font-size", "16px")
.style("font-family", '"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif')
.style("fill", "rgba(255, 255, 255, 0.65)")
.text('sales');
this._twentyFiveThousandToFiftyThousandSvg.append('text')
.attr('class', 'label')
.attr("x", "25%")
.attr("y", "95%")
.attr("dy", ".3em")
.text('$25,000 to $50,000 a year');
var container = document.querySelector('#visualization');
var pckry = new Packery( container, {
// options
itemSelector: '.item',
stamped: ".stamp",
gutter: 1
});
var zeroToTwentyFiveThousandDraggie = new Draggabilly( zeroToTwentyFiveThousand[0][0] );
var twentyFiveThousandToFiftyThousandDraggie = new Draggabilly( twentyFiveThousandToFiftyThousandItem[0][0] );
// bind Draggabilly events to Packery
pckry.bindDraggabillyEvents( zeroToTwentyFiveThousandDraggie );
pckry.bindDraggabillyEvents( twentyFiveThousandToFiftyThousandDraggie );
},
update: function (model) {
var selector;
if(model.get('group') === '$0 to $25,000') {
selector = this._zeroToTwentyFiveThousandSvg;
} else if(model.get('group') === '$25,000 to $50,000') {
selector = this._twentyFiveThousandToFiftyThousandSvg;
} else {
return;
}
var text = selector.selectAll("text.count")
.data([model.toJSON()]);
text.enter().append("text")
.attr('class', 'count')
.attr("x", "50%")
.attr("y", "50%")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.style("font-size", "60px")
.style("font-family", '"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif')
.style("fill", "rgba(255, 255, 255, 0.7)");
text.transition().tween("text", function(d) {
var i = d3.interpolate(this.textContent, d.current.count);
return function(t) {
this.textContent = Math.round(i(t));
};
});
}
});
var CustomVisualizationController = function(source, eventManager) {
this.source = source;
this.state = new State();
this.state.groupBy = 'userincome';
this.state.timeWindowScale = "3600000";
this.state.comparison = false;
this.state.comparisonPeriod = "YESTERDAY";
this.state.color = new Metric({ "name": "usersentiment", "func": "avg", "label": "", "type": "NUMBER" });
this.state.volume = new Metric({ "name": "price", "func": "avg", "label": "", "type": "NUMBER" });
this.state.limit = 100;
var _graph = new Graph();
var _visualization = new Visualization({ collection: _graph });
this.processData = function(data) {
if (data instanceof Array && data.length > 0) {
var arrayToAdd = [];
data.forEach(function(dataItem) {
var datum = new Datum(dataItem);
_graph.add(datum);
});
}
};
return this;
};
_.extend(CustomVisualizationController.prototype, VisualizationController);
return CurrentVisualizationController = CustomVisualizationController;
} ());
/*!
* Draggabilly PACKAGED v1.0.2
* Make that shiz draggable
* http://draggabilly.desandro.com
*/
/*!
* 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 );
/**
* EventEmitter v4.0.5 - git.io/ee
* Oliver Caldwell
* MIT license
* @preserve
*/
;(function(exports) {
// JSHint config - http://www.jshint.com/
/*jshint laxcomma:true*/
/*global define:true*/
// Place the script in strict mode
'use strict';
/**
* Class for managing events.
* Can be extended to provide event functionality in other classes.
*
* @class Manages event registering and emitting.
*/
function EventEmitter(){}
// Shortcuts to improve speed and size
// Easy access to the prototype
var proto = EventEmitter.prototype
// Existence of a native indexOf
, nativeIndexOf = Array.prototype.indexOf ? true : false;
/**
* Finds the index of the listener for the event in it's storage array
*
* @param {Function} listener Method to look for.
* @param {Function[]} listeners Array of listeners to search through.
* @return {Number} Index of the specified listener, -1 if not found
*/
function indexOfListener(listener, listeners) {
// Return the index via the native method if possible
if(nativeIndexOf) {
return listeners.indexOf(listener);
}
// There is no native method
// Use a manual loop to find the index
var i = listeners.length;
while(i--) {
// If the listener matches, return it's index
if(listeners[i] === listener) {
return i;
}
}
// Default to returning -1
return -1;
}
/**
* Fetches the events object and creates one if required.
*
* @return {Object} The events storage object.
*/
proto._getEvents = function() {
return this._events || (this._events = {});
};
/**
* Returns the listener array for the specified event.
* Will initialise the event object and listener arrays if required.
*
* @param {String} evt Name of the event to return the listeners from.
* @return {Function[]} All listener functions for the event.
* @doc
*/
proto.getListeners = function(evt) {
// Create a shortcut to the storage object
// Initialise it if it does not exists yet
var events = this._getEvents();
// Return the listener array
// Initialise it if it does not exist
return events[evt] || (events[evt] = []);
};
/**
* 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.
*
* @param {String} 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.
* @doc
*/
proto.addListener = function(evt, listener) {
// Fetch the listeners
var listeners = this.getListeners(evt);
// Push the listener into the array if it is not already there
if(indexOfListener(listener, listeners) === -1) {
listeners.push(listener);
}
// Return the instance of EventEmitter to allow chaining
return this;
};
/**
* Alias of addListener
* @doc
*/
proto.on = proto.addListener;
/**
* Removes a listener function from the specified event.
*
* @param {String} 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.
* @doc
*/
proto.removeListener = function(evt, listener) {
// Fetch the listeners
// And get the index of the listener in the array
var listeners = this.getListeners(evt)
, index = indexOfListener(listener, listeners);
// If the listener was found then remove it
if(index !== -1) {
listeners.splice(index, 1);
// If there are no more listeners in this array then remove it
if(listeners.length === 0) {
this.removeEvent(evt);
}
}
// Return the instance of EventEmitter to allow chaining
return this;
};
/**
* Alias of removeListener
* @doc
*/
proto.off = proto.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.
*
* @param {String|Object} 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.
* @doc
*/
proto.addListeners = function(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.
*
* @param {String|Object} 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.
* @doc
*/
proto.removeListeners = function(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.
*
* @param {Boolean} remove True if you want to remove listeners, false if you want to add.
* @param {String|Object} 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.
* @doc
*/
proto.manipulateListeners = function(remove, evt, listeners) {
// Initialise any required variables
var i
, value
, single = remove ? this.removeListener : this.addListener
, 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') {
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 the instance of EventEmitter to allow chaining
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.
*
* @param {String} [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.
* @doc
*/
proto.removeEvent = function(evt) {
// Remove different things depending on the state of evt
if(evt) {
// Remove all listeners for the specified event
delete this._getEvents()[evt];
}
else {
// Remove all listeners in all events
delete this._events;
}
// Return the instance of EventEmitter to allow chaining
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.
*
* @param {String} 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.
* @doc
*/
proto.emitEvent = function(evt, args) {
// Get the listeners for the event
// Also initialise any other required variables
var listeners = this.getListeners(evt)
, i = listeners.length
, response;
// Loop over all listeners assigned to the event
// Apply the arguments array to each listener function
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
response = args ? listeners[i].apply(null, args) : listeners[i]();
if(response === true) {
this.removeListener(evt, listeners[i]);
}
}
// Return the instance of EventEmitter to allow chaining
return this;
};
/**
* Alias of emitEvent
* @doc
*/
proto.trigger = proto.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.
*
* @param {String} 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.
* @doc
*/
proto.emit = function(evt) {
var args = Array.prototype.slice.call(arguments, 1);
return this.emitEvent(evt, args);
};
// Expose the class either via AMD or the global object
if(typeof define === 'function' && define.amd) {
define(function() {
return EventEmitter;
});
}
else {
exports.EventEmitter = EventEmitter;
}
}(this));
/*!
* eventie v1.0.2
* 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 ] );
delete obj[ type + fn ];
};
}
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*/
( function( window ) {
'use strict';
var EventEmitter = window.EventEmitter;
var eventie = window.eventie;
var document = window.document;
function docReady( fn ) {
if ( docReady.isReady ) {
fn();
} else {
docReady.on( 'ready', fn );
}
}
docReady.isReady = false;
for ( var prop in EventEmitter.prototype ) {
docReady[ prop ] = EventEmitter.prototype[ prop ];
}
// 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;
docReady.emit( 'ready', event );
}
eventie.bind( document, 'DOMContentLoaded', init );
eventie.bind( document, 'readystatechange', init );
eventie.bind( window, 'load', init );
// transport
window.docReady = docReady;
})( this );
/*!
* 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.2
* 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 ) {
// do not proceed on non-objects
if ( 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' ], defineGetSize );
} else {
// browser global
window.getSize = defineGetSize( window.getStyleProperty );
}
})( window );
/*!
* Draggabilly v1.0.2
* Make that shiz draggable
* http://draggabilly.desandro.com
*/
( function( window ) {
'use strict';
// vars
var document = window.document;
// -------------------------- helpers -------------------------- //
// extend objects
function extend( a, b ) {
for ( var prop in b ) {
a[ prop ] = b[ prop ];
}
return a;
}
// ----- get style ----- //
var defView = document.defaultView;
var getStyle = defView && defView.getComputedStyle ?
function( elem ) {
return defView.getComputedStyle( elem, null );
} :
function( elem ) {
return elem.currentStyle;
};
// 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';
};
// -------------------------- requestAnimationFrame -------------------------- //
// https://gist.github.com/1866474
var lastTime = 0;
var prefixes = 'webkit moz ms o'.split(' ');
// get unprefixed rAF and cAF, if present
var requestAnimationFrame = window.requestAnimationFrame;
var cancelAnimationFrame = window.cancelAnimationFrame;
// loop through vendor prefixes and get prefixed rAF and cAF
var prefix;
for( var i = 0; i < prefixes.length; i++ ) {
if ( requestAnimationFrame && cancelAnimationFrame ) {
break;
}
prefix = prefixes[i];
requestAnimationFrame = requestAnimationFrame || window[ prefix + 'RequestAnimationFrame' ];
cancelAnimationFrame = cancelAnimationFrame || window[ prefix + 'CancelAnimationFrame' ] ||
window[ prefix + 'CancelRequestAnimationFrame' ];
}
// fallback to setTimeout and clearTimeout if either request/cancel is not supported
if ( !requestAnimationFrame || !cancelAnimationFrame ) {
requestAnimationFrame = function( callback ) {
var currTime = new Date().getTime();
var timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) );
var id = window.setTimeout( function() {
callback( currTime + timeToCall );
}, timeToCall );
lastTime = currTime + timeToCall;
return id;
};
cancelAnimationFrame = function( id ) {
window.clearTimeout( id );
};
}
// -------------------------- definition -------------------------- //
function draggabillyDefinition( classie, EventEmitter, eventie, getStyleProperty, getSize ) {
// -------------------------- support -------------------------- //
var transformProperty = getStyleProperty('transform');
// TODO fix quick & dirty check for 3D support
var is3d = !!getStyleProperty('perspective');
// -------------------------- -------------------------- //
function Draggabilly( element, options ) {
this.element = element;
this.options = extend( {}, this.options );
extend( this.options, options );
this._create();
}
// inherit EventEmitter methods
extend( Draggabilly.prototype, EventEmitter.prototype );
Draggabilly.prototype.options = {
};
Draggabilly.prototype._create = function() {
// properties
this.position = {};
this._getPosition();
this.startPoint = { x: 0, y: 0 };
this.dragPoint = { x: 0, y: 0 };
this.startPosition = extend( {}, this.position );
// set relative positioning
var style = getStyle( this.element );
if ( style.position !== 'relative' && style.position !== 'absolute' ) {
this.element.style.position = 'relative';
}
this.enable();
this.setHandles();
};
/**
* set this.handles and bind start events to 'em
*/
Draggabilly.prototype.setHandles = function() {
this.handles = this.options.handle ?
this.element.querySelectorAll( this.options.handle ) : [ this.element ];
for ( var i=0, len = this.handles.length; i < len; i++ ) {
var handle = this.handles[i];
// bind pointer start event
// listen for both, for devices like Chrome Pixel
// which has touch and mouse events
eventie.bind( handle, 'mousedown', this );
eventie.bind( handle, 'touchstart', this );
}
};
// get left/top position from style
Draggabilly.prototype._getPosition = function() {
// properties
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
this.position.x = isNaN( x ) ? 0 : x;
this.position.y = isNaN( y ) ? 0 : y;
this._addTransformPosition( style );
};
// add transform: translate( x, y ) to position
Draggabilly.prototype._addTransformPosition = function( style ) {
if ( !transformProperty ) {
return;
}
var transform = style[ transformProperty ];
// bail out if value is 'none'
if ( transform.indexOf('matrix') !== 0 ) {
return;
}
// split matrix(1, 0, 0, 1, x, y)
var matrixValues = transform.split(',');
// translate X value is in 12th or 4th position
var xIndex = transform.indexOf('matrix3d') === 0 ? 12 : 4;
var translateX = parseInt( matrixValues[ xIndex ], 10 );
// translate Y value is in 13th or 5th position
var translateY = parseInt( matrixValues[ xIndex + 1 ], 10 );
this.position.x += translateX;
this.position.y += translateY;
};
// -------------------------- events -------------------------- //
// trigger handler methods for events
Draggabilly.prototype.handleEvent = function( event ) {
var method = 'on' + event.type;
if ( this[ method ] ) {
this[ method ]( event );
}
};
// returns the touch that we're keeping track of
Draggabilly.prototype.getTouch = function( touches ) {
for ( var i=0, len = touches.length; i < len; i++ ) {
var touch = touches[i];
if ( touch.identifier === this.pointerIdentifier ) {
return touch;
}
}
};
// ----- start event ----- //
Draggabilly.prototype.onmousedown = function( event ) {
this.dragStart( event, event );
};
Draggabilly.prototype.ontouchstart = function( event ) {
// disregard additional touches
if ( this.isDragging ) {
return;
}
this.dragStart( event, event.changedTouches[0] );
};
function setPointerPoint( point, pointer ) {
point.x = pointer.pageX !== undefined ? pointer.pageX : pointer.clientX;
point.y = pointer.pageY !== undefined ? pointer.pageY : pointer.clientY;
}
/**
* drag start
* @param {Event} event
* @param {Event or Touch} pointer
*/
Draggabilly.prototype.dragStart = function( event, pointer ) {
if ( !this.isEnabled ) {
return;
}
if ( event.preventDefault ) {
event.preventDefault();
} else {
event.returnValue = false;
}
var isTouch = event.type === 'touchstart';
// save pointer identifier to match up touch events
this.pointerIdentifier = pointer.identifier;
this._getPosition();
this.measureContainment();
// point where drag began
setPointerPoint( this.startPoint, pointer );
// position _when_ drag began
this.startPosition.x = this.position.x;
this.startPosition.y = this.position.y;
// reset left/top style
this.setLeftTop();
this.dragPoint.x = 0;
this.dragPoint.y = 0;
// bind move and and events
this.pointerMoveEvent = isTouch ? 'touchmove' : 'mousemove';
this.pointerEndEvent = isTouch ? 'touchend' : 'mouseup';
// IE8 needs to be bound to document
this.boundNode = event.preventDefault ? window : document;
eventie.bind( this.boundNode, this.pointerMoveEvent, this );
eventie.bind( this.boundNode, this.pointerEndEvent, this );
classie.add( this.element, 'is-dragging' );
// reset isDragging flag
this.isDragging = true;
this.emitEvent( 'dragStart', [ this, event, pointer ] );
// start animation
this.animate();
};
Draggabilly.prototype.measureContainment = function() {
var containment = this.options.containment;
if ( !containment ) {
return;
}
this.size = getSize( this.element );
var elemRect = this.element.getBoundingClientRect();
// use element if element
var container = isElement( containment ) ? containment :
// fallback to querySelector if string
typeof containment === 'string' ? document.querySelector( containment ) :
// otherwise just `true`, use the parent
this.element.parentNode;
this.containerSize = getSize( container );
var containerRect = container.getBoundingClientRect();
this.relativeStartPosition = {
x: elemRect.left - containerRect.left,
y: elemRect.top - containerRect.top
};
// console.log( this.relativeStartPosition.x, this.relativeStartPosition.y );
};
// ----- move event ----- //
Draggabilly.prototype.onmousemove = function( event ) {
this.dragMove( event, event );
};
Draggabilly.prototype.ontouchmove = function( event ) {
var touch = this.getTouch( event.changedTouches );
if ( touch ) {
this.dragMove( event, touch );
}
};
/**
* drag move
* @param {Event} event
* @param {Event or Touch} pointer
*/
Draggabilly.prototype.dragMove = function( event, pointer ) {
setPointerPoint( this.dragPoint, pointer );
this.dragPoint.x -= this.startPoint.x;
this.dragPoint.y -= this.startPoint.y;
if ( this.options.containment ) {
var relX = this.relativeStartPosition.x;
var relY = this.relativeStartPosition.y;
this.dragPoint.x = Math.max( this.dragPoint.x, -relX );
this.dragPoint.y = Math.max( this.dragPoint.y, -relY );
this.dragPoint.x = Math.min( this.dragPoint.x, this.containerSize.width - relX - this.size.width );
this.dragPoint.y = Math.min( this.dragPoint.y, this.containerSize.height - relY - this.size.height );
}
this.position.x = this.startPosition.x + this.dragPoint.x;
this.position.y = this.startPosition.y + this.dragPoint.y;
this.emitEvent( 'dragMove', [ this, event, pointer ] );
};
// ----- end event ----- //
Draggabilly.prototype.onmouseup = function( event ) {
this.dragEnd( event, event );
};
Draggabilly.prototype.ontouchend = function( event ) {
var touch = this.getTouch( event.changedTouches );
if ( touch ) {
this.dragEnd( event, touch );
}
};
/**
* drag end
* @param {Event} event
* @param {Event or Touch} pointer
*/
Draggabilly.prototype.dragEnd = function( event, pointer ) {
this.isDragging = false;
delete this.pointerIdentifier;
// use top left position when complete
if ( transformProperty ) {
this.element.style[ transformProperty ] = '';
this.setLeftTop();
}
// remove events
eventie.unbind( this.boundNode, this.pointerMoveEvent, this );
eventie.unbind( this.boundNode, this.pointerEndEvent, this );
delete this.pointerMoveEvent;
delete this.pointerEndEvent;
classie.remove( this.element, 'is-dragging' );
this.emitEvent( 'dragEnd', [ this, event, pointer ] );
};
// ----- cancel event ----- //
// coerce to end event
Draggabilly.prototype.ontouchcancel = function( event ) {
this.dragEnd( event );
};
// -------------------------- animation -------------------------- //
Draggabilly.prototype.animate = function() {
// only render and animate if dragging
if ( !this.isDragging ) {
return;
}
this.positionDrag();
var _this = this;
requestAnimationFrame( function animateFrame() {
_this.animate();
});
};
// 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)';
};
// left/top positioning
Draggabilly.prototype.setLeftTop = function() {
this.element.style.left = this.position.x + 'px';
this.element.style.top = this.position.y + 'px';
};
Draggabilly.prototype.positionDrag = transformProperty ?
function() {
// position with transform
this.element.style[ transformProperty ] = translate( this.dragPoint.x, this.dragPoint.y );
} : Draggabilly.prototype.setLeftTop;
Draggabilly.prototype.enable = function() {
this.isEnabled = true;
};
Draggabilly.prototype.disable = function() {
this.isEnabled = false;
if ( this.isDragging ) {
this.dragEnd();
}
};
return Draggabilly;
} // end definition
// -------------------------- transport -------------------------- //
if ( typeof define === 'function' && define.amd ) {
// AMD
define( [
'classie',
'eventEmitter',
'eventie',
'get-style-property',
'get-size'
],
draggabillyDefinition );
} else {
// browser global
window.Draggabilly = draggabillyDefinition(
window.classie,
window.EventEmitter,
window.eventie,
window.getStyleProperty,
window.getSize
);
}
})( window );
/*!
* Packery PACKAGED v1.0.3
* 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
*/
/*!
* 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 );
/**
* EventEmitter v4.0.5 - git.io/ee
* Oliver Caldwell
* MIT license
* @preserve
*/
;(function(exports) {
// JSHint config - http://www.jshint.com/
/*jshint laxcomma:true*/
/*global define:true*/
// Place the script in strict mode
'use strict';
/**
* Class for managing events.
* Can be extended to provide event functionality in other classes.
*
* @class Manages event registering and emitting.
*/
function EventEmitter(){}
// Shortcuts to improve speed and size
// Easy access to the prototype
var proto = EventEmitter.prototype
// Existence of a native indexOf
, nativeIndexOf = Array.prototype.indexOf ? true : false;
/**
* Finds the index of the listener for the event in it's storage array
*
* @param {Function} listener Method to look for.
* @param {Function[]} listeners Array of listeners to search through.
* @return {Number} Index of the specified listener, -1 if not found
*/
function indexOfListener(listener, listeners) {
// Return the index via the native method if possible
if(nativeIndexOf) {
return listeners.indexOf(listener);
}
// There is no native method
// Use a manual loop to find the index
var i = listeners.length;
while(i--) {
// If the listener matches, return it's index
if(listeners[i] === listener) {
return i;
}
}
// Default to returning -1
return -1;
}
/**
* Fetches the events object and creates one if required.
*
* @return {Object} The events storage object.
*/
proto._getEvents = function() {
return this._events || (this._events = {});
};
/**
* Returns the listener array for the specified event.
* Will initialise the event object and listener arrays if required.
*
* @param {String} evt Name of the event to return the listeners from.
* @return {Function[]} All listener functions for the event.
* @doc
*/
proto.getListeners = function(evt) {
// Create a shortcut to the storage object
// Initialise it if it does not exists yet
var events = this._getEvents();
// Return the listener array
// Initialise it if it does not exist
return events[evt] || (events[evt] = []);
};
/**
* 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.
*
* @param {String} 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.
* @doc
*/
proto.addListener = function(evt, listener) {
// Fetch the listeners
var listeners = this.getListeners(evt);
// Push the listener into the array if it is not already there
if(indexOfListener(listener, listeners) === -1) {
listeners.push(listener);
}
// Return the instance of EventEmitter to allow chaining
return this;
};
/**
* Alias of addListener
* @doc
*/
proto.on = proto.addListener;
/**
* Removes a listener function from the specified event.
*
* @param {String} 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.
* @doc
*/
proto.removeListener = function(evt, listener) {
// Fetch the listeners
// And get the index of the listener in the array
var listeners = this.getListeners(evt)
, index = indexOfListener(listener, listeners);
// If the listener was found then remove it
if(index !== -1) {
listeners.splice(index, 1);
// If there are no more listeners in this array then remove it
if(listeners.length === 0) {
this.removeEvent(evt);
}
}
// Return the instance of EventEmitter to allow chaining
return this;
};
/**
* Alias of removeListener
* @doc
*/
proto.off = proto.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.
*
* @param {String|Object} 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.
* @doc
*/
proto.addListeners = function(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.
*
* @param {String|Object} 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.
* @doc
*/
proto.removeListeners = function(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.
*
* @param {Boolean} remove True if you want to remove listeners, false if you want to add.
* @param {String|Object} 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.
* @doc
*/
proto.manipulateListeners = function(remove, evt, listeners) {
// Initialise any required variables
var i
, value
, single = remove ? this.removeListener : this.addListener
, 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') {
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 the instance of EventEmitter to allow chaining
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.
*
* @param {String} [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.
* @doc
*/
proto.removeEvent = function(evt) {
// Remove different things depending on the state of evt
if(evt) {
// Remove all listeners for the specified event
delete this._getEvents()[evt];
}
else {
// Remove all listeners in all events
delete this._events;
}
// Return the instance of EventEmitter to allow chaining
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.
*
* @param {String} 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.
* @doc
*/
proto.emitEvent = function(evt, args) {
// Get the listeners for the event
// Also initialise any other required variables
var listeners = this.getListeners(evt)
, i = listeners.length
, response;
// Loop over all listeners assigned to the event
// Apply the arguments array to each listener function
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
response = args ? listeners[i].apply(null, args) : listeners[i]();
if(response === true) {
this.removeListener(evt, listeners[i]);
}
}
// Return the instance of EventEmitter to allow chaining
return this;
};
/**
* Alias of emitEvent
* @doc
*/
proto.trigger = proto.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.
*
* @param {String} 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.
* @doc
*/
proto.emit = function(evt) {
var args = Array.prototype.slice.call(arguments, 1);
return this.emitEvent(evt, args);
};
// Expose the class either via AMD or the global object
if(typeof define === 'function' && define.amd) {
define(function() {
return EventEmitter;
});
}
else {
exports.EventEmitter = EventEmitter;
}
}(this));
/*!
* eventie v1.0.2
* 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 ] );
delete obj[ type + fn ];
};
}
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*/
( function( window ) {
'use strict';
var EventEmitter = window.EventEmitter;
var eventie = window.eventie;
var document = window.document;
function docReady( fn ) {
if ( docReady.isReady ) {
fn();
} else {
docReady.on( 'ready', fn );
}
}
docReady.isReady = false;
for ( var prop in EventEmitter.prototype ) {
docReady[ prop ] = EventEmitter.prototype[ prop ];
}
// 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;
docReady.emit( 'ready', event );
}
eventie.bind( document, 'DOMContentLoaded', init );
eventie.bind( document, 'readystatechange', init );
eventie.bind( window, 'load', init );
// transport
window.docReady = docReady;
})( this );
/*!
* 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.2
* 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 ) {
// do not proceed on non-objects
if ( 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' ], defineGetSize );
} else {
// browser global
window.getSize = defineGetSize( window.getStyleProperty );
}
})( window );
/**
* Bridget makes jQuery widgets
* v0.1.2
*/
( function( window, $ ) {
'use strict';
// bail if no jQuery
if ( !$ ) {
return;
}
// -------------------------- utils -------------------------- //
var slice = Array.prototype.slice;
function noop() {}
// -------------------------- 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 );
};
})( window, window.jQuery );
/**
* matchesSelector helper v1.0.0
*
* @augments {Object} this - global object
* @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() {
var methods = [
'matchesSelector',
'webkitMatchesSelector',
'mozMatchesSelector',
'msMatchesSelector',
'oMatchesSelector'
];
for ( var i=0, len = methods.length; i < len; i++ ) {
var method = methods[i];
if ( ElemProto[ method ] ) {
return method;
}
}
})();
// ----- match ----- //
function match( elem, selector ) {
return elem[ matchesMethod ]( selector );
}
// ----- appendToFragment ----- //
function appendToFragment( elem ) {
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
if ( !elem.parentNode ) {
appendToFragment( 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 ) {
if ( !elem.parentNode ) {
appendToFragment( 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.3
* 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;
}
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 );
if ( 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