Files
Disco/Disco.Web/ClientSource/Scripts/Modules/jQuery-Isotope.js
T
Gary Sharp c846fa053a Dialog height reduced & remove js minify maps
Provide better support for lower resolution devices (buttons became
hidden in tall dialogs). Remove references to JavaScript minification
source maps which aren't deployed and caused confusion.
2014-09-08 14:51:51 +10:00

1408 lines
48 KiB
JavaScript

/**
* Isotope v1.5.25
* An exquisite jQuery plugin for magical layouts
* http://isotope.metafizzy.co
*
* Commercial use requires one-time license fee
* http://metafizzy.co/#licenses
*
* Copyright 2012 David DeSandro / Metafizzy
*/
/*jshint asi: true, browser: true, curly: true, eqeqeq: true, forin: false, immed: false, newcap: true, noempty: true, strict: true, undef: true */
/*global jQuery: false */
(function (window, $, undefined) {
'use strict';
// get global vars
var document = window.document;
var Modernizr = window.Modernizr;
// helper function
var capitalize = function (str) {
return str.charAt(0).toUpperCase() + str.slice(1);
};
// ========================= getStyleProperty by kangax ===============================
// http://perfectionkills.com/feature-testing-css-properties/
var prefixes = 'Moz Webkit O Ms'.split(' ');
var getStyleProperty = function (propName) {
var style = document.documentElement.style,
prefixed;
// test standard property first
if (typeof style[propName] === 'string') {
return propName;
}
// capitalize
propName = capitalize(propName);
// test vendor specific properties
for (var i = 0, len = prefixes.length; i < len; i++) {
prefixed = prefixes[i] + propName;
if (typeof style[prefixed] === 'string') {
return prefixed;
}
}
};
var transformProp = getStyleProperty('transform'),
transitionProp = getStyleProperty('transitionProperty');
// ========================= miniModernizr ===============================
// <3<3<3 and thanks to Faruk and Paul for doing the heavy lifting
/*!
* Modernizr v1.6ish: miniModernizr for Isotope
* http://www.modernizr.com
*
* Developed by:
* - Faruk Ates http://farukat.es/
* - Paul Irish http://paulirish.com/
*
* Copyright (c) 2009-2010
* Dual-licensed under the BSD or MIT licenses.
* http://www.modernizr.com/license/
*/
/*
* This version whittles down the script just to check support for
* CSS transitions, transforms, and 3D transforms.
*/
var tests = {
csstransforms: function () {
return !!transformProp;
},
csstransforms3d: function () {
var test = !!getStyleProperty('perspective');
// double check for Chrome's false positive
if (test) {
var vendorCSSPrefixes = ' -o- -moz- -ms- -webkit- -khtml- '.split(' '),
mediaQuery = '@media (' + vendorCSSPrefixes.join('transform-3d),(') + 'modernizr)',
$style = $('<style>' + mediaQuery + '{#modernizr{height:3px}}' + '</style>')
.appendTo('head'),
$div = $('<div id="modernizr" />').appendTo('html');
test = $div.height() === 3;
$div.remove();
$style.remove();
}
return test;
},
csstransitions: function () {
return !!transitionProp;
}
};
var testName;
if (Modernizr) {
// if there's a previous Modernzir, check if there are necessary tests
for (testName in tests) {
if (!Modernizr.hasOwnProperty(testName)) {
// if test hasn't been run, use addTest to run it
Modernizr.addTest(testName, tests[testName]);
}
}
} else {
// or create new mini Modernizr that just has the 3 tests
Modernizr = window.Modernizr = {
_version: '1.6ish: miniModernizr for Isotope'
};
var classes = ' ';
var result;
// Run through tests
for (testName in tests) {
result = tests[testName]();
Modernizr[testName] = result;
classes += ' ' + (result ? '' : 'no-') + testName;
}
// Add the new classes to the <html> element.
$('html').addClass(classes);
}
// ========================= isoTransform ===============================
/**
* provides hooks for .css({ scale: value, translate: [x, y] })
* Progressively enhanced CSS transforms
* Uses hardware accelerated 3D transforms for Safari
* or falls back to 2D transforms.
*/
if (Modernizr.csstransforms) {
// i.e. transformFnNotations.scale(0.5) >> 'scale3d( 0.5, 0.5, 1)'
var transformFnNotations = Modernizr.csstransforms3d ?
{ // 3D transform functions
translate: function (position) {
return 'translate3d(' + position[0] + 'px, ' + position[1] + 'px, 0) ';
},
scale: function (scale) {
return 'scale3d(' + scale + ', ' + scale + ', 1) ';
}
} :
{ // 2D transform functions
translate: function (position) {
return 'translate(' + position[0] + 'px, ' + position[1] + 'px) ';
},
scale: function (scale) {
return 'scale(' + scale + ') ';
}
}
;
var setIsoTransform = function (elem, name, value) {
// unpack current transform data
var data = $.data(elem, 'isoTransform') || {},
newData = {},
fnName,
transformObj = {},
transformValue;
// i.e. newData.scale = 0.5
newData[name] = value;
// extend new value over current data
$.extend(data, newData);
for (fnName in data) {
transformValue = data[fnName];
transformObj[fnName] = transformFnNotations[fnName](transformValue);
}
// get proper order
// ideally, we could loop through this give an array, but since we only have
// a couple transforms we're keeping track of, we'll do it like so
var translateFn = transformObj.translate || '',
scaleFn = transformObj.scale || '',
// sorting so translate always comes first
valueFns = translateFn + scaleFn;
// set data back in elem
$.data(elem, 'isoTransform', data);
// set name to vendor specific property
elem.style[transformProp] = valueFns;
};
// ==================== scale ===================
$.cssNumber.scale = true;
$.cssHooks.scale = {
set: function (elem, value) {
// uncomment this bit if you want to properly parse strings
// if ( typeof value === 'string' ) {
// value = parseFloat( value );
// }
setIsoTransform(elem, 'scale', value);
},
get: function (elem, computed) {
var transform = $.data(elem, 'isoTransform');
return transform && transform.scale ? transform.scale : 1;
}
};
$.fx.step.scale = function (fx) {
$.cssHooks.scale.set(fx.elem, fx.now + fx.unit);
};
// ==================== translate ===================
$.cssNumber.translate = true;
$.cssHooks.translate = {
set: function (elem, value) {
// uncomment this bit if you want to properly parse strings
// if ( typeof value === 'string' ) {
// value = value.split(' ');
// }
//
// var i, val;
// for ( i = 0; i < 2; i++ ) {
// val = value[i];
// if ( typeof val === 'string' ) {
// val = parseInt( val );
// }
// }
setIsoTransform(elem, 'translate', value);
},
get: function (elem, computed) {
var transform = $.data(elem, 'isoTransform');
return transform && transform.translate ? transform.translate : [0, 0];
}
};
}
// ========================= get transition-end event ===============================
var transitionEndEvent, transitionDurProp;
if (Modernizr.csstransitions) {
transitionEndEvent = {
WebkitTransitionProperty: 'webkitTransitionEnd', // webkit
MozTransitionProperty: 'transitionend',
OTransitionProperty: 'oTransitionEnd otransitionend',
transitionProperty: 'transitionend'
}[transitionProp];
transitionDurProp = getStyleProperty('transitionDuration');
}
// ========================= smartresize ===============================
/*
* smartresize: debounced resize event for jQuery
*
* latest version and complete README available on Github:
* https://github.com/louisremi/jquery.smartresize.js
*
* Copyright 2011 @louis_remi
* Licensed under the MIT license.
*/
var $event = $.event,
dispatchMethod = $.event.handle ? 'handle' : 'dispatch',
resizeTimeout;
$event.special.smartresize = {
setup: function () {
$(this).bind("resize", $event.special.smartresize.handler);
},
teardown: function () {
$(this).unbind("resize", $event.special.smartresize.handler);
},
handler: function (event, execAsap) {
// Save the context
var context = this,
args = arguments;
// set correct event type
event.type = "smartresize";
if (resizeTimeout) { clearTimeout(resizeTimeout); }
resizeTimeout = setTimeout(function () {
$event[dispatchMethod].apply(context, args);
}, execAsap === "execAsap" ? 0 : 100);
}
};
$.fn.smartresize = function (fn) {
return fn ? this.bind("smartresize", fn) : this.trigger("smartresize", ["execAsap"]);
};
// ========================= Isotope ===============================
// our "Widget" object constructor
$.Isotope = function (options, element, callback) {
this.element = $(element);
this._create(options);
this._init(callback);
};
// styles of container element we want to keep track of
var isoContainerStyles = ['width', 'height'];
var $window = $(window);
$.Isotope.settings = {
resizable: true,
layoutMode: 'masonry',
containerClass: 'isotope',
itemClass: 'isotope-item',
hiddenClass: 'isotope-hidden',
hiddenStyle: { opacity: 0, scale: 0.001 },
visibleStyle: { opacity: 1, scale: 1 },
containerStyle: {
position: 'relative',
overflow: 'hidden'
},
animationEngine: 'best-available',
animationOptions: {
queue: false,
duration: 800
},
sortBy: 'original-order',
sortAscending: true,
resizesContainer: true,
transformsEnabled: true,
itemPositionDataEnabled: false
};
$.Isotope.prototype = {
// sets up widget
_create: function (options) {
this.options = $.extend({}, $.Isotope.settings, options);
this.styleQueue = [];
this.elemCount = 0;
// get original styles in case we re-apply them in .destroy()
var elemStyle = this.element[0].style;
this.originalStyle = {};
// keep track of container styles
var containerStyles = isoContainerStyles.slice(0);
for (var prop in this.options.containerStyle) {
containerStyles.push(prop);
}
for (var i = 0, len = containerStyles.length; i < len; i++) {
prop = containerStyles[i];
this.originalStyle[prop] = elemStyle[prop] || '';
}
// apply container style from options
this.element.css(this.options.containerStyle);
this._updateAnimationEngine();
this._updateUsingTransforms();
// sorting
var originalOrderSorter = {
'original-order': function ($elem, instance) {
instance.elemCount++;
return instance.elemCount;
},
random: function () {
return Math.random();
}
};
this.options.getSortData = $.extend(this.options.getSortData, originalOrderSorter);
// need to get atoms
this.reloadItems();
// get top left position of where the bricks should be
this.offset = {
left: parseInt((this.element.css('padding-left') || 0), 10),
top: parseInt((this.element.css('padding-top') || 0), 10)
};
// add isotope class first time around
var instance = this;
setTimeout(function () {
instance.element.addClass(instance.options.containerClass);
}, 0);
// bind resize method
if (this.options.resizable) {
$window.bind('smartresize.isotope', function () {
instance.resize();
});
}
// dismiss all click events from hidden events
this.element.delegate('.' + this.options.hiddenClass, 'click', function () {
return false;
});
},
_getAtoms: function ($elems) {
var selector = this.options.itemSelector,
// filter & find
$atoms = selector ? $elems.filter(selector).add($elems.find(selector)) : $elems,
// base style for atoms
atomStyle = { position: 'absolute' };
// filter out text nodes
$atoms = $atoms.filter(function (i, atom) {
return atom.nodeType === 1;
});
if (this.usingTransforms) {
atomStyle.left = 0;
atomStyle.top = 0;
}
$atoms.css(atomStyle).addClass(this.options.itemClass);
this.updateSortData($atoms, true);
return $atoms;
},
// _init fires when your instance is first created
// (from the constructor above), and when you
// attempt to initialize the widget again (by the bridge)
// after it has already been initialized.
_init: function (callback) {
this.$filteredAtoms = this._filter(this.$allAtoms);
this._sort();
this.reLayout(callback);
},
option: function (opts) {
// change options AFTER initialization:
// signature: $('#foo').bar({ cool:false });
if ($.isPlainObject(opts)) {
this.options = $.extend(true, this.options, opts);
// trigger _updateOptionName if it exists
var updateOptionFn;
for (var optionName in opts) {
updateOptionFn = '_update' + capitalize(optionName);
if (this[updateOptionFn]) {
this[updateOptionFn]();
}
}
}
},
// ====================== updaters ====================== //
// kind of like setters
_updateAnimationEngine: function () {
var animationEngine = this.options.animationEngine.toLowerCase().replace(/[ _\-]/g, '');
var isUsingJQueryAnimation;
// set applyStyleFnName
switch (animationEngine) {
case 'css':
case 'none':
isUsingJQueryAnimation = false;
break;
case 'jquery':
isUsingJQueryAnimation = true;
break;
default: // best available
isUsingJQueryAnimation = !Modernizr.csstransitions;
}
this.isUsingJQueryAnimation = isUsingJQueryAnimation;
this._updateUsingTransforms();
},
_updateTransformsEnabled: function () {
this._updateUsingTransforms();
},
_updateUsingTransforms: function () {
var usingTransforms = this.usingTransforms = this.options.transformsEnabled &&
Modernizr.csstransforms && Modernizr.csstransitions && !this.isUsingJQueryAnimation;
// prevent scales when transforms are disabled
if (!usingTransforms) {
delete this.options.hiddenStyle.scale;
delete this.options.visibleStyle.scale;
}
this.getPositionStyles = usingTransforms ? this._translate : this._positionAbs;
},
// ====================== Filtering ======================
_filter: function ($atoms) {
var filter = this.options.filter === '' ? '*' : this.options.filter;
if (!filter) {
return $atoms;
}
var hiddenClass = this.options.hiddenClass,
hiddenSelector = '.' + hiddenClass,
$hiddenAtoms = $atoms.filter(hiddenSelector),
$atomsToShow = $hiddenAtoms;
if (filter !== '*') {
$atomsToShow = $hiddenAtoms.filter(filter);
var $atomsToHide = $atoms.not(hiddenSelector).not(filter).addClass(hiddenClass);
this.styleQueue.push({ $el: $atomsToHide, style: this.options.hiddenStyle });
}
this.styleQueue.push({ $el: $atomsToShow, style: this.options.visibleStyle });
$atomsToShow.removeClass(hiddenClass);
return $atoms.filter(filter);
},
// ====================== Sorting ======================
updateSortData: function ($atoms, isIncrementingElemCount) {
var instance = this,
getSortData = this.options.getSortData,
$this, sortData;
$atoms.each(function () {
$this = $(this);
sortData = {};
// get value for sort data based on fn( $elem ) passed in
for (var key in getSortData) {
if (!isIncrementingElemCount && key === 'original-order') {
// keep original order original
sortData[key] = $.data(this, 'isotope-sort-data')[key];
} else {
sortData[key] = getSortData[key]($this, instance);
}
}
// apply sort data to element
$.data(this, 'isotope-sort-data', sortData);
});
},
// used on all the filtered atoms
_sort: function () {
var sortBy = this.options.sortBy,
getSorter = this._getSorter,
sortDir = this.options.sortAscending ? 1 : -1,
sortFn = function (alpha, beta) {
var a = getSorter(alpha, sortBy),
b = getSorter(beta, sortBy);
// fall back to original order if data matches
if (a === b && sortBy !== 'original-order') {
a = getSorter(alpha, 'original-order');
b = getSorter(beta, 'original-order');
}
return ((a > b) ? 1 : (a < b) ? -1 : 0) * sortDir;
};
this.$filteredAtoms.sort(sortFn);
},
_getSorter: function (elem, sortBy) {
return $.data(elem, 'isotope-sort-data')[sortBy];
},
// ====================== Layout Helpers ======================
_translate: function (x, y) {
return { translate: [x, y] };
},
_positionAbs: function (x, y) {
return { left: x, top: y };
},
_pushPosition: function ($elem, x, y) {
x = Math.round(x + this.offset.left);
y = Math.round(y + this.offset.top);
var position = this.getPositionStyles(x, y);
this.styleQueue.push({ $el: $elem, style: position });
if (this.options.itemPositionDataEnabled) {
$elem.data('isotope-item-position', { x: x, y: y });
}
},
// ====================== General Layout ======================
// used on collection of atoms (should be filtered, and sorted before )
// accepts atoms-to-be-laid-out to start with
layout: function ($elems, callback) {
var layoutMode = this.options.layoutMode;
// layout logic
this['_' + layoutMode + 'Layout']($elems);
// set the size of the container
if (this.options.resizesContainer) {
var containerStyle = this['_' + layoutMode + 'GetContainerSize']();
this.styleQueue.push({ $el: this.element, style: containerStyle });
}
this._processStyleQueue($elems, callback);
this.isLaidOut = true;
},
_processStyleQueue: function ($elems, callback) {
// are we animating the layout arrangement?
// use plugin-ish syntax for css or animate
var styleFn = !this.isLaidOut ? 'css' : (
this.isUsingJQueryAnimation ? 'animate' : 'css'
),
animOpts = this.options.animationOptions,
onLayout = this.options.onLayout,
objStyleFn, processor,
triggerCallbackNow, callbackFn;
// default styleQueue processor, may be overwritten down below
processor = function (i, obj) {
obj.$el[styleFn](obj.style, animOpts);
};
if (this._isInserting && this.isUsingJQueryAnimation) {
// if using styleQueue to insert items
processor = function (i, obj) {
// only animate if it not being inserted
objStyleFn = obj.$el.hasClass('no-transition') ? 'css' : styleFn;
obj.$el[objStyleFn](obj.style, animOpts);
};
} else if (callback || onLayout || animOpts.complete) {
// has callback
var isCallbackTriggered = false,
// array of possible callbacks to trigger
callbacks = [callback, onLayout, animOpts.complete],
instance = this;
triggerCallbackNow = true;
// trigger callback only once
callbackFn = function () {
if (isCallbackTriggered) {
return;
}
var hollaback;
for (var i = 0, len = callbacks.length; i < len; i++) {
hollaback = callbacks[i];
if (typeof hollaback === 'function') {
hollaback.call(instance.element, $elems, instance);
}
}
isCallbackTriggered = true;
};
if (this.isUsingJQueryAnimation && styleFn === 'animate') {
// add callback to animation options
animOpts.complete = callbackFn;
triggerCallbackNow = false;
} else if (Modernizr.csstransitions) {
// detect if first item has transition
var i = 0,
firstItem = this.styleQueue[0],
testElem = firstItem && firstItem.$el,
styleObj;
// get first non-empty jQ object
while (!testElem || !testElem.length) {
styleObj = this.styleQueue[i++];
// HACK: sometimes styleQueue[i] is undefined
if (!styleObj) {
return;
}
testElem = styleObj.$el;
}
// get transition duration of the first element in that object
// yeah, this is inexact
var duration = parseFloat(getComputedStyle(testElem[0])[transitionDurProp]);
if (duration > 0) {
processor = function (i, obj) {
obj.$el[styleFn](obj.style, animOpts)
// trigger callback at transition end
.one(transitionEndEvent, callbackFn);
};
triggerCallbackNow = false;
}
}
}
// process styleQueue
$.each(this.styleQueue, processor);
if (triggerCallbackNow) {
callbackFn();
}
// clear out queue for next time
this.styleQueue = [];
},
resize: function () {
if (this['_' + this.options.layoutMode + 'ResizeChanged']()) {
this.reLayout();
}
},
reLayout: function (callback) {
this['_' + this.options.layoutMode + 'Reset']();
this.layout(this.$filteredAtoms, callback);
},
// ====================== Convenience methods ======================
// ====================== Adding items ======================
// adds a jQuery object of items to a isotope container
addItems: function ($content, callback) {
var $newAtoms = this._getAtoms($content);
// add new atoms to atoms pools
this.$allAtoms = this.$allAtoms.add($newAtoms);
if (callback) {
callback($newAtoms);
}
},
// convienence method for adding elements properly to any layout
// positions items, hides them, then animates them back in <--- very sezzy
insert: function ($content, callback) {
// position items
this.element.append($content);
var instance = this;
this.addItems($content, function ($newAtoms) {
var $newFilteredAtoms = instance._filter($newAtoms);
instance._addHideAppended($newFilteredAtoms);
instance._sort();
instance.reLayout();
instance._revealAppended($newFilteredAtoms, callback);
});
},
// convienence method for working with Infinite Scroll
appended: function ($content, callback) {
var instance = this;
this.addItems($content, function ($newAtoms) {
instance._addHideAppended($newAtoms);
instance.layout($newAtoms);
instance._revealAppended($newAtoms, callback);
});
},
// adds new atoms, then hides them before positioning
_addHideAppended: function ($newAtoms) {
this.$filteredAtoms = this.$filteredAtoms.add($newAtoms);
$newAtoms.addClass('no-transition');
this._isInserting = true;
// apply hidden styles
this.styleQueue.push({ $el: $newAtoms, style: this.options.hiddenStyle });
},
// sets visible style on new atoms
_revealAppended: function ($newAtoms, callback) {
var instance = this;
// apply visible style after a sec
setTimeout(function () {
// enable animation
$newAtoms.removeClass('no-transition');
// reveal newly inserted filtered elements
instance.styleQueue.push({ $el: $newAtoms, style: instance.options.visibleStyle });
instance._isInserting = false;
instance._processStyleQueue($newAtoms, callback);
}, 10);
},
// gathers all atoms
reloadItems: function () {
this.$allAtoms = this._getAtoms(this.element.children());
},
// removes elements from Isotope widget
remove: function ($content, callback) {
// remove elements immediately from Isotope instance
this.$allAtoms = this.$allAtoms.not($content);
this.$filteredAtoms = this.$filteredAtoms.not($content);
// remove() as a callback, for after transition / animation
var instance = this;
var removeContent = function () {
$content.remove();
if (callback) {
callback.call(instance.element);
}
};
if ($content.filter(':not(.' + this.options.hiddenClass + ')').length) {
// if any non-hidden content needs to be removed
this.styleQueue.push({ $el: $content, style: this.options.hiddenStyle });
this._sort();
this.reLayout(removeContent);
} else {
// remove it now
removeContent();
}
},
shuffle: function (callback) {
this.updateSortData(this.$allAtoms);
this.options.sortBy = 'random';
this._sort();
this.reLayout(callback);
},
// destroys widget, returns elements and container back (close) to original style
destroy: function () {
var usingTransforms = this.usingTransforms;
var options = this.options;
this.$allAtoms
.removeClass(options.hiddenClass + ' ' + options.itemClass)
.each(function () {
var style = this.style;
style.position = '';
style.top = '';
style.left = '';
style.opacity = '';
if (usingTransforms) {
style[transformProp] = '';
}
});
// re-apply saved container styles
var elemStyle = this.element[0].style;
for (var prop in this.originalStyle) {
elemStyle[prop] = this.originalStyle[prop];
}
this.element
.unbind('.isotope')
.undelegate('.' + options.hiddenClass, 'click')
.removeClass(options.containerClass)
.removeData('isotope');
$window.unbind('.isotope');
},
// ====================== LAYOUTS ======================
// calculates number of rows or columns
// requires columnWidth or rowHeight to be set on namespaced object
// i.e. this.masonry.columnWidth = 200
_getSegments: function (isRows) {
var namespace = this.options.layoutMode,
measure = isRows ? 'rowHeight' : 'columnWidth',
size = isRows ? 'height' : 'width',
segmentsName = isRows ? 'rows' : 'cols',
containerSize = this.element[size](),
segments,
// i.e. options.masonry && options.masonry.columnWidth
segmentSize = this.options[namespace] && this.options[namespace][measure] ||
// or use the size of the first item, i.e. outerWidth
this.$filteredAtoms['outer' + capitalize(size)](true) ||
// if there's no items, use size of container
containerSize;
segments = Math.floor(containerSize / segmentSize);
segments = Math.max(segments, 1);
// i.e. this.masonry.cols = ....
this[namespace][segmentsName] = segments;
// i.e. this.masonry.columnWidth = ...
this[namespace][measure] = segmentSize;
},
_checkIfSegmentsChanged: function (isRows) {
var namespace = this.options.layoutMode,
segmentsName = isRows ? 'rows' : 'cols',
prevSegments = this[namespace][segmentsName];
// update cols/rows
this._getSegments(isRows);
// return if updated cols/rows is not equal to previous
return (this[namespace][segmentsName] !== prevSegments);
},
// ====================== Masonry ======================
_masonryReset: function () {
// layout-specific props
this.masonry = {};
// FIXME shouldn't have to call this again
this._getSegments();
var i = this.masonry.cols;
this.masonry.colYs = [];
while (i--) {
this.masonry.colYs.push(0);
}
},
_masonryLayout: function ($elems) {
var instance = this,
props = instance.masonry;
$elems.each(function () {
var $this = $(this),
//how many columns does this brick span
colSpan = Math.ceil($this.outerWidth(true) / props.columnWidth);
colSpan = Math.min(colSpan, props.cols);
if (colSpan === 1) {
// if brick spans only one column, just like singleMode
instance._masonryPlaceBrick($this, props.colYs);
} else {
// brick spans more than one column
// how many different places could this brick fit horizontally
var groupCount = props.cols + 1 - colSpan,
groupY = [],
groupColY,
i;
// for each group potential horizontal position
for (i = 0; i < groupCount; i++) {
// make an array of colY values for that one group
groupColY = props.colYs.slice(i, i + colSpan);
// and get the max value of the array
groupY[i] = Math.max.apply(Math, groupColY);
}
instance._masonryPlaceBrick($this, groupY);
}
});
},
// worker method that places brick in the columnSet
// with the the minY
_masonryPlaceBrick: function ($brick, setY) {
// get the minimum Y value from the columns
var minimumY = Math.min.apply(Math, setY),
shortCol = 0;
// Find index of short column, the first from the left
for (var i = 0, len = setY.length; i < len; i++) {
if (setY[i] === minimumY) {
shortCol = i;
break;
}
}
// position the brick
var x = this.masonry.columnWidth * shortCol,
y = minimumY;
this._pushPosition($brick, x, y);
// apply setHeight to necessary columns
var setHeight = minimumY + $brick.outerHeight(true),
setSpan = this.masonry.cols + 1 - len;
for (i = 0; i < setSpan; i++) {
this.masonry.colYs[shortCol + i] = setHeight;
}
},
_masonryGetContainerSize: function () {
var containerHeight = Math.max.apply(Math, this.masonry.colYs);
return { height: containerHeight };
},
_masonryResizeChanged: function () {
return this._checkIfSegmentsChanged();
},
// ====================== fitRows ======================
_fitRowsReset: function () {
this.fitRows = {
x: 0,
y: 0,
height: 0
};
},
_fitRowsLayout: function ($elems) {
var instance = this,
containerWidth = this.element.width(),
props = this.fitRows;
$elems.each(function () {
var $this = $(this),
atomW = $this.outerWidth(true),
atomH = $this.outerHeight(true);
if (props.x !== 0 && atomW + props.x > containerWidth) {
// if this element cannot fit in the current row
props.x = 0;
props.y = props.height;
}
// position the atom
instance._pushPosition($this, props.x, props.y);
props.height = Math.max(props.y + atomH, props.height);
props.x += atomW;
});
},
_fitRowsGetContainerSize: function () {
return { height: this.fitRows.height };
},
_fitRowsResizeChanged: function () {
return true;
},
// ====================== cellsByRow ======================
_cellsByRowReset: function () {
this.cellsByRow = {
index: 0
};
// get this.cellsByRow.columnWidth
this._getSegments();
// get this.cellsByRow.rowHeight
this._getSegments(true);
},
_cellsByRowLayout: function ($elems) {
var instance = this,
props = this.cellsByRow;
$elems.each(function () {
var $this = $(this),
col = props.index % props.cols,
row = Math.floor(props.index / props.cols),
x = (col + 0.5) * props.columnWidth - $this.outerWidth(true) / 2,
y = (row + 0.5) * props.rowHeight - $this.outerHeight(true) / 2;
instance._pushPosition($this, x, y);
props.index++;
});
},
_cellsByRowGetContainerSize: function () {
return { height: Math.ceil(this.$filteredAtoms.length / this.cellsByRow.cols) * this.cellsByRow.rowHeight + this.offset.top };
},
_cellsByRowResizeChanged: function () {
return this._checkIfSegmentsChanged();
},
// ====================== straightDown ======================
_straightDownReset: function () {
this.straightDown = {
y: 0
};
},
_straightDownLayout: function ($elems) {
var instance = this;
$elems.each(function (i) {
var $this = $(this);
instance._pushPosition($this, 0, instance.straightDown.y);
instance.straightDown.y += $this.outerHeight(true);
});
},
_straightDownGetContainerSize: function () {
return { height: this.straightDown.y };
},
_straightDownResizeChanged: function () {
return true;
},
// ====================== masonryHorizontal ======================
_masonryHorizontalReset: function () {
// layout-specific props
this.masonryHorizontal = {};
// FIXME shouldn't have to call this again
this._getSegments(true);
var i = this.masonryHorizontal.rows;
this.masonryHorizontal.rowXs = [];
while (i--) {
this.masonryHorizontal.rowXs.push(0);
}
},
_masonryHorizontalLayout: function ($elems) {
var instance = this,
props = instance.masonryHorizontal;
$elems.each(function () {
var $this = $(this),
//how many rows does this brick span
rowSpan = Math.ceil($this.outerHeight(true) / props.rowHeight);
rowSpan = Math.min(rowSpan, props.rows);
if (rowSpan === 1) {
// if brick spans only one column, just like singleMode
instance._masonryHorizontalPlaceBrick($this, props.rowXs);
} else {
// brick spans more than one row
// how many different places could this brick fit horizontally
var groupCount = props.rows + 1 - rowSpan,
groupX = [],
groupRowX, i;
// for each group potential horizontal position
for (i = 0; i < groupCount; i++) {
// make an array of colY values for that one group
groupRowX = props.rowXs.slice(i, i + rowSpan);
// and get the max value of the array
groupX[i] = Math.max.apply(Math, groupRowX);
}
instance._masonryHorizontalPlaceBrick($this, groupX);
}
});
},
_masonryHorizontalPlaceBrick: function ($brick, setX) {
// get the minimum Y value from the columns
var minimumX = Math.min.apply(Math, setX),
smallRow = 0;
// Find index of smallest row, the first from the top
for (var i = 0, len = setX.length; i < len; i++) {
if (setX[i] === minimumX) {
smallRow = i;
break;
}
}
// position the brick
var x = minimumX,
y = this.masonryHorizontal.rowHeight * smallRow;
this._pushPosition($brick, x, y);
// apply setHeight to necessary columns
var setWidth = minimumX + $brick.outerWidth(true),
setSpan = this.masonryHorizontal.rows + 1 - len;
for (i = 0; i < setSpan; i++) {
this.masonryHorizontal.rowXs[smallRow + i] = setWidth;
}
},
_masonryHorizontalGetContainerSize: function () {
var containerWidth = Math.max.apply(Math, this.masonryHorizontal.rowXs);
return { width: containerWidth };
},
_masonryHorizontalResizeChanged: function () {
return this._checkIfSegmentsChanged(true);
},
// ====================== fitColumns ======================
_fitColumnsReset: function () {
this.fitColumns = {
x: 0,
y: 0,
width: 0
};
},
_fitColumnsLayout: function ($elems) {
var instance = this,
containerHeight = this.element.height(),
props = this.fitColumns;
$elems.each(function () {
var $this = $(this),
atomW = $this.outerWidth(true),
atomH = $this.outerHeight(true);
if (props.y !== 0 && atomH + props.y > containerHeight) {
// if this element cannot fit in the current column
props.x = props.width;
props.y = 0;
}
// position the atom
instance._pushPosition($this, props.x, props.y);
props.width = Math.max(props.x + atomW, props.width);
props.y += atomH;
});
},
_fitColumnsGetContainerSize: function () {
return { width: this.fitColumns.width };
},
_fitColumnsResizeChanged: function () {
return true;
},
// ====================== cellsByColumn ======================
_cellsByColumnReset: function () {
this.cellsByColumn = {
index: 0
};
// get this.cellsByColumn.columnWidth
this._getSegments();
// get this.cellsByColumn.rowHeight
this._getSegments(true);
},
_cellsByColumnLayout: function ($elems) {
var instance = this,
props = this.cellsByColumn;
$elems.each(function () {
var $this = $(this),
col = Math.floor(props.index / props.rows),
row = props.index % props.rows,
x = (col + 0.5) * props.columnWidth - $this.outerWidth(true) / 2,
y = (row + 0.5) * props.rowHeight - $this.outerHeight(true) / 2;
instance._pushPosition($this, x, y);
props.index++;
});
},
_cellsByColumnGetContainerSize: function () {
return { width: Math.ceil(this.$filteredAtoms.length / this.cellsByColumn.rows) * this.cellsByColumn.columnWidth };
},
_cellsByColumnResizeChanged: function () {
return this._checkIfSegmentsChanged(true);
},
// ====================== straightAcross ======================
_straightAcrossReset: function () {
this.straightAcross = {
x: 0
};
},
_straightAcrossLayout: function ($elems) {
var instance = this;
$elems.each(function (i) {
var $this = $(this);
instance._pushPosition($this, instance.straightAcross.x, 0);
instance.straightAcross.x += $this.outerWidth(true);
});
},
_straightAcrossGetContainerSize: function () {
return { width: this.straightAcross.x };
},
_straightAcrossResizeChanged: function () {
return true;
}
};
// ======================= imagesLoaded Plugin ===============================
/*!
* jQuery imagesLoaded plugin v1.1.0
* http://github.com/desandro/imagesloaded
*
* MIT License. by Paul Irish et al.
*/
// $('#my-container').imagesLoaded(myFunction)
// or
// $('img').imagesLoaded(myFunction)
// execute a callback when all images have loaded.
// needed because .load() doesn't work on cached images
// callback function gets image collection as argument
// `this` is the container
$.fn.imagesLoaded = function (callback) {
var $this = this,
$images = $this.find('img').add($this.filter('img')),
len = $images.length,
blank = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==',
loaded = [];
function triggerCallback() {
callback.call($this, $images);
}
function imgLoaded(event) {
var img = event.target;
if (img.src !== blank && $.inArray(img, loaded) === -1) {
loaded.push(img);
if (--len <= 0) {
setTimeout(triggerCallback);
$images.unbind('.imagesLoaded', imgLoaded);
}
}
}
// if no images, trigger immediately
if (!len) {
triggerCallback();
}
$images.bind('load.imagesLoaded error.imagesLoaded', imgLoaded).each(function () {
// cached images don't fire load sometimes, so we reset src.
var src = this.src;
// webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
// data uri bypasses webkit log warning (thx doug jones)
this.src = blank;
this.src = src;
});
return $this;
};
// helper function for logging errors
// $.error breaks jQuery chaining
var logError = function (message) {
if (window.console) {
window.console.error(message);
}
};
// ======================= Plugin bridge ===============================
// leverages data method to either create or return $.Isotope constructor
// A bit from jQuery UI
// https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js
// A bit from jcarousel
// https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarousel.js
$.fn.isotope = function (options, callback) {
if (typeof options === 'string') {
// call method
var args = Array.prototype.slice.call(arguments, 1);
this.each(function () {
var instance = $.data(this, 'isotope');
if (!instance) {
logError("cannot call methods on isotope prior to initialization; " +
"attempted to call method '" + options + "'");
return;
}
if (!$.isFunction(instance[options]) || options.charAt(0) === "_") {
logError("no such method '" + options + "' for isotope instance");
return;
}
// apply method
instance[options].apply(instance, args);
});
} else {
this.each(function () {
var instance = $.data(this, 'isotope');
if (instance) {
// apply options & init
instance.option(options);
instance._init(callback);
} else {
// initialize new instance
$.data(this, 'isotope', new $.Isotope(options, this, callback));
}
});
}
// return jQuery object
// so plugin methods do not have to
return this;
};
})(window, jQuery);