From fef5995995325d0b6799965bf534106e8a5faf67 Mon Sep 17 00:00:00 2001 From: Glen Cheney Date: Tue, 18 Sep 2012 12:45:48 -0700 Subject: [PATCH] Added sorting ability, made plugin responsive, added advanced filtering method. Updated to Modernizr 2.6.2 --- README.md | 143 ++++++++++++---- jquery.shuffle.js | 368 +++++++++++++++++++++++++++++++----------- jquery.shuffle.min.js | 26 +-- 3 files changed, 406 insertions(+), 131 deletions(-) diff --git a/README.md b/README.md index 0f4612f..bfbf48e 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,7 @@ Settings you can change (these are the defaults) ```js var options = { - itemWidth : 230, // Width of the grid item - marginTop : 20, // Top margin - marginRight: 20, // Right margin - key : 'all' // Which category to show + group : 'all' // Which category to show speed : 800, // Speed of the transition (in milliseconds). 800 = .8 seconds easing : 'ease-out' // css easing function to use }; @@ -25,30 +22,30 @@ $('#grid').shuffle(options); The easing function is one of `default`, `linear`, `ease-in`, `ease-out`, `ease-in-out`, or `cubic-bezier`. ### The HTML -The html structure. The only real important thing here is the 'data-key' attribute. It has to be a [valid JSON](http://jsonlint.com/) array of strings. +The html structure. The only real important thing here is the 'data-groups' attribute. It has to be a [valid JSON](http://jsonlint.com/) array of strings. ```html
-
- + -
- + -
- + -
- +
+ @@ -56,17 +53,26 @@ The html structure. The only real important thing here is the 'data-key' attribu
``` +Shuffle takes the width, margin-top, and marigin-right from the `.item`. +```css +#grid .item { + width: 230px; + margin-top: 20px; + margin-right: 20px; +} +``` + + ## How to "Shuffle" -Say you have this markup +Say you have this markup for your options ```html
    -
  • Most Recent
  • -
  • Wallpapers
  • -
  • Graphic Design
  • -
  • Photography
  • -
  • 3D Renders
  • -
  • Motion Graphics
  • +
  • Most Recent
  • +
  • Wallpapers
  • +
  • Graphic Design
  • +
  • Photography
  • +
  • 3D Renders
``` And when you click on a li, you want the plugin to shuffle. Here's an example: @@ -84,29 +90,107 @@ $(document).ready(function(){ $this.addClass('active'); // Filter elements - $grid.shuffle($this.attr('data-key')); + $grid.shuffle($this.data('group')); }); // instantiate the plugin $('#grid').shuffle({ - itemWidth : 230, - marginTop : 20, - marginRight: 20, - key : 'all', + group : 'all', speed : 800, easing : 'ease-out' }); }); ``` -Events that get triggered: `shrink.shuffle`, `shrunk.shuffle`, `filter.shuffle`, and `filtered.shuffle`. +These events will be triggered at their respective times: `shrink.shuffle`, `shrunk.shuffle`, `filter.shuffle`, `filtered.shuffle`, and `sorted.shuffle`. + +## Sorting + +You can order the elements based off a function you supply. In the example above, each item has a `data-date-created` and `data-title` attribute. The filter buttons have a `data-sort` attribute with the value of the item’s attribute. Then, with some JavaScript, we can get the correct attribute and provide a function to sort by. + +```html +
  • Title
  • +``` + +```html +
    +``` + +```javascript +// Sorting options +$('.sort-options li').on('click', function() { + var $this = $(this), + $grid = $('#grid'), + sort = $this.data('sort'), + opts = {}; + + // Hide current label, show current label in title + $('.sort-options .active').removeClass('active'); + $this.addClass('active'); + + // We're given the element wrapped in jQuery + if (sort === 'date-created') { + opts = { + by: function($el) { + return $el.data('date-created'); + } + } + } else if (sort === 'title') { + opts = { + by: function($el) { + return $el.data('title').toLowerCase(); + } + } + } + + + // Filter elements + $grid.shuffle('sort', opts); +}); +``` + +The `opts` parameter can contain two properties. `reverse`, a boolean which will reverse the array. `by` is a function that is passed the element wrapped in jQuery. In the case above, we’re returning the value of the data-date-created or data-title attributes. + +Calling sort with an empty object will reset the elements to DOM order. + +## Advanced Filtering + +By passing a function to shuffle, you can customize the filtering to your hearts content. Shuffle will iterate over each item in the container and give your function the element wrapped in jQuery and the shuffle instance. Return `true` to keep the element or `false` to hide it. + +### Example + +```javascript +// Filters elements with a data-title attribute with less than 10 characters +$('#grid').shuffle(function($el, shuffle) { + return $el.data('title').length < 10; +}); +``` + +### Searching + +```javascript +// Advanced filtering +$('.filter .search').on('keyup change', function() { + var val = this.value.toLowerCase(); + $('#grid').shuffle(function($el, shuffle) { + + // Only search elements in the current group + if (shuffle.group !== 'all' && $.inArray(shuffle.group, $el.data('groups')) === -1) { + return false; + } + + // Get the text inside our element and search for the value in the input + var text = $.trim($el.text()).toLowerCase(); + return text.indexOf(val) != -1; + }); +}); +``` ## Dependencies * jQuery * Modernizr -** A custom Modernizr build has been included with the plugin. -** If you already have Modernizr on your site, you may delete it. -** If you don't know what Modernizr is, leave it! + +A custom Modernizr build has been included with the plugin. If you already have Modernizr on your site, you may delete it. If you don't know what Modernizr is, leave it! ## Supported Browsers @@ -120,6 +204,7 @@ _Browsers that don't support CSS transitions and transforms *cough* IE <= 9 *cou ## Changes +* 9.17.12 - Added sorting ability, made plugin responsive, added advanced filtering method. Updated to Modernizr 2.6.2 * 7.21.12 - Rewrote plugin in more object oriented structure. Added custom events. Updated to Modernizr 2.6.1. * 7.3.12 - Removed dependency on the css file and now apply the css with javascript. Updated Modernizr to 2.5.3. diff --git a/jquery.shuffle.js b/jquery.shuffle.js index a3c3963..9a2282a 100644 --- a/jquery.shuffle.js +++ b/jquery.shuffle.js @@ -1,10 +1,10 @@ // IMPORTANT! // If you're already using Modernizr, delete it from this file. If you don't know what Modernizr is, leave it :) -/* Modernizr 2.6.1 (Custom Build) | MIT & BSD +/* Modernizr 2.6.2 (Custom Build) | MIT & BSD * Build: http://modernizr.com/download/#-csstransforms-csstransforms3d-csstransitions-prefixed-teststyles-testprop-testallprops-prefixes-domprefixes */ -;window.Modernizr=function(a,b,c){function y(a){i.cssText=a}function z(a,b){return y(l.join(a+";")+(b||""))}function A(a,b){return typeof a===b}function B(a,b){return!!~(""+a).indexOf(b)}function C(a,b){for(var d in a){var e=a[d];if(!B(e,"-")&&i[e]!==c)return b=="pfx"?e:!0}return!1}function D(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:A(f,"function")?f.bind(d||b):f}return!1}function E(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+n.join(d+" ")+d).split(" ");return A(b,"string")||A(b,"undefined")?C(e,b):(e=(a+" "+o.join(d+" ")+d).split(" "),D(e,b,c))}var d="2.6.1",e={},f=b.documentElement,g="modernizr",h=b.createElement(g),i=h.style,j,k={}.toString,l=" -webkit- -moz- -o- -ms- ".split(" "),m="Webkit Moz O ms",n=m.split(" "),o=m.toLowerCase().split(" "),p={},q={},r={},s=[],t=s.slice,u,v=function(a,c,d,e){var h,i,j,k=b.createElement("div"),l=b.body,m=l?l:b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:g+(d+1),k.appendChild(j);return h=["­",'"].join(""),k.id=g,(l?k:m).innerHTML+=h,m.appendChild(k),l||(m.style.background="",f.appendChild(m)),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},w={}.hasOwnProperty,x;!A(w,"undefined")&&!A(w.call,"undefined")?x=function(a,b){return w.call(a,b)}:x=function(a,b){return b in a&&A(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=t.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(t.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(t.call(arguments)))};return e}),p.csstransforms=function(){return!!E("transform")},p.csstransforms3d=function(){var a=!!E("perspective");return a&&"webkitPerspective"in f.style&&v("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},p.csstransitions=function(){return E("transition")};for(var F in p)x(p,F)&&(u=F.toLowerCase(),e[u]=p[F](),s.push((e[u]?"":"no-")+u));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)x(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,enableClasses&&(f.className+=" "+(b?"":"no-")+a),e[a]=b}return e},y(""),h=j=null,e._version=d,e._prefixes=l,e._domPrefixes=o,e._cssomPrefixes=n,e.testProp=function(a){return C([a])},e.testAllProps=E,e.testStyles=v,e.prefixed=function(a,b,c){return b?E(a,b,c):E(a,"pfx")},e}(this,this.document); +;window.Modernizr=function(a,b,c){function y(a){i.cssText=a}function z(a,b){return y(l.join(a+";")+(b||""))}function A(a,b){return typeof a===b}function B(a,b){return!!~(""+a).indexOf(b)}function C(a,b){for(var d in a){var e=a[d];if(!B(e,"-")&&i[e]!==c)return b=="pfx"?e:!0}return!1}function D(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:A(f,"function")?f.bind(d||b):f}return!1}function E(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+n.join(d+" ")+d).split(" ");return A(b,"string")||A(b,"undefined")?C(e,b):(e=(a+" "+o.join(d+" ")+d).split(" "),D(e,b,c))}var d="2.6.2",e={},f=b.documentElement,g="modernizr",h=b.createElement(g),i=h.style,j,k={}.toString,l=" -webkit- -moz- -o- -ms- ".split(" "),m="Webkit Moz O ms",n=m.split(" "),o=m.toLowerCase().split(" "),p={},q={},r={},s=[],t=s.slice,u,v=function(a,c,d,e){var h,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:g+(d+1),l.appendChild(j);return h=["­",'"].join(""),l.id=g,(m?l:n).innerHTML+=h,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=f.style.overflow,f.style.overflow="hidden",f.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),f.style.overflow=k),!!i},w={}.hasOwnProperty,x;!A(w,"undefined")&&!A(w.call,"undefined")?x=function(a,b){return w.call(a,b)}:x=function(a,b){return b in a&&A(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=t.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(t.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(t.call(arguments)))};return e}),p.csstransforms=function(){return!!E("transform")},p.csstransforms3d=function(){var a=!!E("perspective");return a&&"webkitPerspective"in f.style&&v("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},p.csstransitions=function(){return E("transition")};for(var F in p)x(p,F)&&(u=F.toLowerCase(),e[u]=p[F](),s.push((e[u]?"":"no-")+u));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)x(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof enableClasses!="undefined"&&enableClasses&&(f.className+=" "+(b?"":"no-")+a),e[a]=b}return e},y(""),h=j=null,e._version=d,e._prefixes=l,e._domPrefixes=o,e._cssomPrefixes=n,e.testProp=function(a){return C([a])},e.testAllProps=E,e.testStyles=v,e.prefixed=function(a,b,c){return b?E(a,b,c):E(a,"pfx")},e}(this,this.document); /** * jQuery Shuffle Plugin @@ -12,65 +12,104 @@ * Inspired by Isotope http://isotope.metafizzy.co/ * Use it for whatever you want! * @author Glen Cheney (http://glencheney.com) - * @version 1.4 - * @date 7/21/12 + * @version 1.5 + * @date 9/18/12 */ ;(function($, Modernizr) { - "use strict"; + 'use strict'; + + + $.fn.sorted = function(options) { + var opts = $.extend({}, $.fn.sorted.defaults, options), + arr = this.get(); + + // Sort the elements by the opts.by function. + // If we don't have opts.by, default to DOM order + if (opts.by !== $.noop && opts.by !== null && opts.by !== undefined) { + arr.sort(function(a, b) { + var valA = opts.by($(a)); + var valB = opts.by($(b)); + return (valA < valB) ? -1 : (valA > valB) ? 1 : 0; + }); + } + + if (opts.reverse) { + arr.reverse(); + } + return arr; + + }; + + $.fn.sorted.defaults = { + reverse: false, + by: null + }; var Shuffle = function($container, options) { - var self = this, - $this = $(this); - - $.extend(self, { - itemWidth : 230, - marginTop : 20, - marginRight: 20, - key : 'all', - speed : 800, - easing : 'ease-out' - }, options, { - $container: $container, - supported: Modernizr.csstransforms && Modernizr.csstransitions - }); + var self = this; + + $.extend(self, $.fn.shuffle.options, options, $.fn.shuffle.settings); + + self.$container = $container; + self.$items = self.$container.children(); + self.$item = self.$items.first(); + self.itemWidth = self.$item.outerWidth(); + self.itemHeight = self.$item.outerHeight(); + self.marginTop = parseInt(self.$item.css('marginTop'), 10); + self.marginRight = parseInt(self.$item.css('marginRight'), 10); + self.itemsPerRow = self.getItemsPerRow(); + self.transitionName = self.prefixed('transition'), + self.transform = self.getPrefixed('transform'); + + // Get transitionend event name + var transEndEventNames = { + 'WebkitTransition' : 'webkitTransitionEnd', + 'MozTransition' : 'transitionend', + 'OTransition' : 'oTransitionEnd', + 'msTransition' : 'MSTransitionEnd', + 'transition' : 'transitionend' + }; + self.transitionEndName = transEndEventNames[ self.transitionName ]; // CSS for each item self.itemCss = { position: 'absolute', - opacity: 1, // Everything after this is for jQuery fallback top: 0, left: 0, - marginTop: self.marginTop, - marginRight: self.marginRight, - 'float': 'left' + opacity: 1 }; - - self.$items = self.$container.children(); - self.itemsPerRow = Math.floor(self.$container.width() / self.itemWidth); - self.itemHeight = self.$items.first().outerHeight(); - self.transitionName = self.prefixed('transition'), - self.transform = self.getPrefixed('transform'); // Set up css for transitions self.$container.css('position', 'relative').get(0).style[self.transitionName] = 'height ' + self.speed + 'ms ' + self.easing; - self.$items.each(function(index) { - var defaults = self.itemCss; + self.$items.each(function() { + $(this).css(self.itemCss); // Set CSS transition for transforms and opacity if (self.supported) { this.style[self.transitionName] = self.transform + ' ' + self.speed + 'ms ' + self.easing + ', opacity ' + self.speed + 'ms ' + self.easing; } - - // Set the margin-right to zero for the last item in the row - if ((index + 1) % self.itemsPerRow === 0) { - defaults.marginRight = 0; - } - $(this).css(self.itemCss); + // Remove margins, we don't need them anymore + this.style.marginTop = 0; + this.style.marginRight = 0; }); + // http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer + self.windowHeight = $(window).height(); + self.windowWidth = $(window).width(); + $(window).on('resize.shuffle', function () { + var height = $(window).height(), + width = $(window).width(); + + if (width !== self.windowWidth || height !== self.windowHeight) { + self.resized(); + self.windowHeight = height; + self.windowWidth = width; + } + }); + // Do it - self.shuffle('all'); + self.shuffle(self.group); }; Shuffle.prototype = { @@ -81,58 +120,102 @@ * The magic. This is what makes the plugin 'shuffle' */ shuffle : function(category) { - var self = this, - numElements, - gridHeight; + var self = this; - if (!category) category = 'all'; + if (!category) { + category = 'all'; + } - // Hide/show appropriate items - if (category === 'all') { - self.$items.removeClass('concealed'); - } else { - self.$items.removeClass('concealed filtered').each(function() { - var keys = $(this).attr('data-key'), - kArray = $.parseJSON(keys); - if ($.inArray(category, kArray) === -1) { - $(this).addClass('concealed'); - return; - } + // Default is to show all items + self.$items.removeClass('concealed filtered'); + + // Loop through each item and use provided function to determine + // whether to hide it or not. + if ($.isFunction(category)) { + self.$items.each(function() { + var $item = $(this); + $item.addClass(category($item, self) ? 'filtered' : 'concealed'); }); } + + // Otherwise we've been passed a category to filter by + else { + self.group = category; + if (category !== 'all') { + self.$items.each(function() { + var keys = $(this).data('groups'); + if ($.inArray(category, keys) === -1) { + $(this).addClass('concealed'); + return; + } else { + $(this).addClass('filtered'); + } + }); + } + + // category === all, add filtered class to everything + else { + self.$items.addClass('filtered'); + } + } // How many filtered elements? - numElements = self.$items.not('.concealed').addClass('filtered').length; + self.visibleItems = self.$items.filter('.filtered').length; // Shrink each concealed item - self.$container.trigger('shrink.shuffle', self); + self.fire('shrink'); self.shrink(); - setTimeout(function() { - self.$container.trigger('shrunk.shuffle', self); - }, self.speed); // Update transforms on .filtered elements so they will animate to their new positions - self.$container.trigger('filter.shuffle', self); + self.fire('filter'); self.filter(); - setTimeout(function() { - self.$container.trigger('filtered.shuffle', self); - }, self.speed); - // Adjust the height of the grid - gridHeight = (Math.ceil(numElements / self.itemsPerRow) * (self.itemHeight + self.marginTop)) - self.marginTop; - self.$container.css('height', gridHeight + 'px'); + // Adjust the height of the container + self.resizeContainer(); + }, + + getItemsPerRow : function() { + var self = this, + totalWidth = self.$container.width(), + num = Math.floor(totalWidth / self.itemWidth); + + // Make sure the items will fit with margins too + if (num * (self.itemWidth + self.marginRight) - self.marginRight > totalWidth) { + num -= 1; + } + + return num; }, + /** + * Adjust the height of the grid + */ + resizeContainer : function() { + var self = this, + gridHeight = (Math.ceil(self.visibleItems / self.itemsPerRow) * (self.itemHeight + self.marginTop)) - self.marginTop; + self.$container.css('height', gridHeight + 'px'); + }, + + /** + * Fire events with .shuffle namespace + */ + fire : function(name) { + this.$container.trigger(name + '.shuffle', [this]); + }, /** * Hides the elements that don't match our filter */ shrink : function() { var self = this, - $concealed = self.$container.find('.concealed'); + $concealed = self.$items.filter('.concealed'); + + // Abort if no items if ($concealed.length === 0) { return; } + + self.shrinkTransitionEnded = false; $concealed.each(function() { var $this = $(this), x = parseInt($this.attr('data-x'), 10), @@ -142,6 +225,7 @@ if (!y) y = 0; self.transition({ + from: 'shrink', $this: $this, x: x, y: y, @@ -150,7 +234,8 @@ scale : 0.001, opacity: 0, height: '0px', - width: '0px' + width: '0px', + callback: self.shrinkEnd }); }); }, @@ -158,15 +243,23 @@ /** * Loops through each item that should be shown - * Calculates the x and y position and then tranitions it + * Calculates the x and y position and then transitions it + * @param {array} items - array of items that will be shown/layed out in order in their array. + * Because jQuery collection are always ordered in DOM order, we can't pass a jq collection + * @param {function} complete callback function */ - filter : function() { + layout: function(items, fn) { var self = this, - y = 0, - $filtered = self.$container.find('.filtered'); + y = 0; + + // Abort if no items + if (items.length === 0) { + return; + } - $filtered.each(function(index) { - var $this = $(this), + self.layoutTransitionEnded = false; + $.each(items, function(index) { + var $this = $(items[index]), x = (index % self.itemsPerRow) * (self.itemWidth + self.marginRight), row = Math.floor(index / self.itemsPerRow); @@ -178,6 +271,7 @@ $this.attr({'data-x' : x, 'data-y' : y}); self.transition({ + from: 'layout', $this: $this, x: x, y: y, @@ -186,32 +280,65 @@ scale : 1, opacity: 1, height: self.itemHeight + 'px', - width: self.itemWidth + 'px' + width: self.itemWidth + 'px', + callback: fn }); }); }, + + /** + * Grabs the .filtered elements and passes them to layout + */ + filter : function() { + var self = this; + // If we've already sorted the elements, keep them sorted + if (self.keepSorted && self.lastSort) { + self.sort(self.lastSort, true); + } else { + var items = self.$items.filter('.filtered').get(); + self.layout(items, self.filterEnd); + } + }, + + /** + * Gets the .filtered elements, sorts them, and passes them to layout + * + * @param {object} opts the options object for the sorted plugin + * @param {bool} [fromFilter] was called from Shuffle.filter method. + */ + sort: function(opts, fromFilter) { + var self = this, + items = self.$items.filter('.filtered').sorted(opts); + self.layout(items, function() { + if (fromFilter) { + self.filterEnd(); + } + self.sortEnd(); + }); + self.lastSort = opts; + }, /** * Uses Modernizr's prefixed() to get the correct vendor property name and sets it using jQuery .css() + * * @param {jq} $el the jquery object to set the css on * @param {string} prop the property to set (e.g. 'transition') * @param {string} value the value of the prop */ setPrefixedCss : function($el, prop, value) { - $el.css(Modernizr.prefixed(prop), value); + $el.css(this.prefixed(prop), value); }, - prefixed : function(prop) { - return Modernizr.prefixed(prop); - }, /** * Returns things like -webkit-transition or -moz-box-sizing + * * @param {string} property to be prefixed. * @return {string} the prefixed css property */ getPrefixed : function(prop) { - return Modernizr.prefixed(prop).replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-'); + var styleName = this.prefixed(prop); + return styleName ? styleName.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-') : styleName; }, /** @@ -227,14 +354,28 @@ * @param {float} opts.opacity opacity of the item * @param {String} opts.height the height of the item (used when no transforms available) * @param {String} opts.width the width of the item (used when no transforms available) + * @param {function} opts.callback complete function for the animation */ transition: function(opts) { var self = this, - transform; + transform, + + // Only fire callback once per collection's transition + complete = function() { + if (!self.layoutTransitionEnded && opts.from === 'layout') { + opts.callback.call(self); + self.layoutTransitionEnded = true; + } else if (!self.shrinkTransitionEnded && opts.from === 'shrink') { + opts.callback.call(self); + self.shrinkTransitionEnded = true; + } + }; + + // Use CSS Transforms if we have them if (self.supported) { - if (Modernizr.csstransforms3d) { - transform = 'translate3d(' + opts.x + 'px, ' + opts.y + 'px, 0px) scale3d(' + opts.scale + ', ' + opts.scale + ', ' + opts.scale + ')'; + if (self.threeD) { + transform = 'translate3d(' + opts.x + 'px, ' + opts.y + 'px, 0) scale3d(' + opts.scale + ', ' + opts.scale + ', 1)'; } else { transform = 'translate(' + opts.x + 'px, ' + opts.y + 'px) scale(' + opts.scale + ', ' + opts.scale + ')'; } @@ -242,37 +383,82 @@ // Update css to trigger CSS Animation opts.$this.css('opacity' , opts.opacity); self.setPrefixedCss(opts.$this, 'transform', transform); + opts.$this.one(self.transitionEndName, complete); } else { // Use jQuery to animate left/top - opts.$this.animate({ + opts.$this.stop().animate({ left: opts.left, top: opts.top, opacity: opts.opacity, height: opts.height, width: opts.width - }, self.speed); + }, self.speed, 'swing', complete); } + }, + + /** + * On window resize, recalculate the width, height, and items per row. + */ + resized: function() { + var self = this; + self.itemWidth = self.$items.filter('.filtered').outerWidth(); + self.itemHeight = self.$items.filter('.filtered').outerHeight(); + self.itemsPerRow = self.getItemsPerRow(); + self.filter(); + self.resizeContainer(); + }, + + shrinkEnd: function() { + this.fire('shrunk'); + }, + + filterEnd: function() { + this.fire('filtered'); + }, + + sortEnd: function() { + this.fire('sorted'); } + }; + // Plugin definition - $.fn.shuffle = function(opts, key) { + $.fn.shuffle = function(opts, sortObj) { return this.each(function() { var $this = $(this), shuffle = $this.data('shuffle'); + // If we don't have a stored shuffle, make a new one and save it if (!shuffle) { shuffle = new Shuffle($this, opts); $this.data('shuffle', shuffle); } - // Execute a function - if (typeof opts === 'string') { - if (opts !== 'shuffle') { - key = opts; - } - shuffle.shuffle(key); + // If passed a string, lets decide what to do with it. Or they've provided a function to filter by + if ($.isFunction(opts) || (typeof opts === 'string' && opts !== 'sort')) { + shuffle.shuffle(opts); + + // Key should be an object with propreties reversed and by. + } else if (typeof opts === 'string' && opts === 'sort') { + shuffle.sort(sortObj); } }); }; + + // Overrideable options + $.fn.shuffle.options = { + group : 'all', + speed : 800, + easing : 'ease-out', + keepSorted: true + }; + + // Not overrideable + $.fn.shuffle.settings = { + supported: Modernizr.csstransforms && Modernizr.csstransitions, + prefixed: Modernizr.prefixed, + threeD: Modernizr.csstransforms3d + }; + })(jQuery, Modernizr); \ No newline at end of file diff --git a/jquery.shuffle.min.js b/jquery.shuffle.min.js index 52af071..1b8fe01 100644 --- a/jquery.shuffle.min.js +++ b/jquery.shuffle.min.js @@ -1,10 +1,10 @@ // IMPORTANT! // If you're already using Modernizr, delete it from this file. If you don't know what Modernizr is, leave it :) -/* Modernizr 2.6.1 (Custom Build) | MIT & BSD +/* Modernizr 2.6.2 (Custom Build) | MIT & BSD * Build: http://modernizr.com/download/#-csstransforms-csstransforms3d-csstransitions-prefixed-teststyles-testprop-testallprops-prefixes-domprefixes */ -;window.Modernizr=function(a,b,c){function y(a){i.cssText=a}function z(a,b){return y(l.join(a+";")+(b||""))}function A(a,b){return typeof a===b}function B(a,b){return!!~(""+a).indexOf(b)}function C(a,b){for(var d in a){var e=a[d];if(!B(e,"-")&&i[e]!==c)return b=="pfx"?e:!0}return!1}function D(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:A(f,"function")?f.bind(d||b):f}return!1}function E(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+n.join(d+" ")+d).split(" ");return A(b,"string")||A(b,"undefined")?C(e,b):(e=(a+" "+o.join(d+" ")+d).split(" "),D(e,b,c))}var d="2.6.1",e={},f=b.documentElement,g="modernizr",h=b.createElement(g),i=h.style,j,k={}.toString,l=" -webkit- -moz- -o- -ms- ".split(" "),m="Webkit Moz O ms",n=m.split(" "),o=m.toLowerCase().split(" "),p={},q={},r={},s=[],t=s.slice,u,v=function(a,c,d,e){var h,i,j,k=b.createElement("div"),l=b.body,m=l?l:b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:g+(d+1),k.appendChild(j);return h=["­",'"].join(""),k.id=g,(l?k:m).innerHTML+=h,m.appendChild(k),l||(m.style.background="",f.appendChild(m)),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},w={}.hasOwnProperty,x;!A(w,"undefined")&&!A(w.call,"undefined")?x=function(a,b){return w.call(a,b)}:x=function(a,b){return b in a&&A(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=t.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(t.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(t.call(arguments)))};return e}),p.csstransforms=function(){return!!E("transform")},p.csstransforms3d=function(){var a=!!E("perspective");return a&&"webkitPerspective"in f.style&&v("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},p.csstransitions=function(){return E("transition")};for(var F in p)x(p,F)&&(u=F.toLowerCase(),e[u]=p[F](),s.push((e[u]?"":"no-")+u));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)x(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,enableClasses&&(f.className+=" "+(b?"":"no-")+a),e[a]=b}return e},y(""),h=j=null,e._version=d,e._prefixes=l,e._domPrefixes=o,e._cssomPrefixes=n,e.testProp=function(a){return C([a])},e.testAllProps=E,e.testStyles=v,e.prefixed=function(a,b,c){return b?E(a,b,c):E(a,"pfx")},e}(this,this.document); +;window.Modernizr=function(a,b,c){function y(a){i.cssText=a}function z(a,b){return y(l.join(a+";")+(b||""))}function A(a,b){return typeof a===b}function B(a,b){return!!~(""+a).indexOf(b)}function C(a,b){for(var d in a){var e=a[d];if(!B(e,"-")&&i[e]!==c)return b=="pfx"?e:!0}return!1}function D(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:A(f,"function")?f.bind(d||b):f}return!1}function E(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+n.join(d+" ")+d).split(" ");return A(b,"string")||A(b,"undefined")?C(e,b):(e=(a+" "+o.join(d+" ")+d).split(" "),D(e,b,c))}var d="2.6.2",e={},f=b.documentElement,g="modernizr",h=b.createElement(g),i=h.style,j,k={}.toString,l=" -webkit- -moz- -o- -ms- ".split(" "),m="Webkit Moz O ms",n=m.split(" "),o=m.toLowerCase().split(" "),p={},q={},r={},s=[],t=s.slice,u,v=function(a,c,d,e){var h,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:g+(d+1),l.appendChild(j);return h=["­",'"].join(""),l.id=g,(m?l:n).innerHTML+=h,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=f.style.overflow,f.style.overflow="hidden",f.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),f.style.overflow=k),!!i},w={}.hasOwnProperty,x;!A(w,"undefined")&&!A(w.call,"undefined")?x=function(a,b){return w.call(a,b)}:x=function(a,b){return b in a&&A(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=t.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(t.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(t.call(arguments)))};return e}),p.csstransforms=function(){return!!E("transform")},p.csstransforms3d=function(){var a=!!E("perspective");return a&&"webkitPerspective"in f.style&&v("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},p.csstransitions=function(){return E("transition")};for(var F in p)x(p,F)&&(u=F.toLowerCase(),e[u]=p[F](),s.push((e[u]?"":"no-")+u));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)x(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof enableClasses!="undefined"&&enableClasses&&(f.className+=" "+(b?"":"no-")+a),e[a]=b}return e},y(""),h=j=null,e._version=d,e._prefixes=l,e._domPrefixes=o,e._cssomPrefixes=n,e.testProp=function(a){return C([a])},e.testAllProps=E,e.testStyles=v,e.prefixed=function(a,b,c){return b?E(a,b,c):E(a,"pfx")},e}(this,this.document); /** * jQuery Shuffle Plugin @@ -12,13 +12,17 @@ * Inspired by Isotope http://isotope.metafizzy.co/ * Use it for whatever you want! * @author Glen Cheney (http://glencheney.com) - * @version 1.4 - * @date 7/21/12 + * @version 1.5 + * @date 9/18/12 */ -;(function(e,d){var g=function(a,c){var b=this;e(this);e.extend(b,{itemWidth:230,marginTop:20,marginRight:20,key:"all",speed:800,easing:"ease-out"},c,{$container:a,supported:d.csstransforms&&d.csstransitions});b.itemCss={position:"absolute",opacity:1,top:0,left:0,marginTop:b.marginTop,marginRight:b.marginRight,"float":"left"};b.$items=b.$container.children();b.itemsPerRow=Math.floor(b.$container.width()/b.itemWidth);b.itemHeight=b.$items.first().outerHeight();b.transitionName=b.prefixed("transition"); -b.transform=b.getPrefixed("transform");b.$container.css("position","relative").get(0).style[b.transitionName]="height "+b.speed+"ms "+b.easing;b.$items.each(function(a){var c=b.itemCss;b.supported&&(this.style[b.transitionName]=b.transform+" "+b.speed+"ms "+b.easing+", opacity "+b.speed+"ms "+b.easing);0===(a+1)%b.itemsPerRow&&(c.marginRight=0);e(this).css(b.itemCss)});b.shuffle("all")};g.prototype={constructor:g,shuffle:function(a){var c=this,b;a||(a="all");"all"===a?c.$items.removeClass("concealed"): -c.$items.removeClass("concealed filtered").each(function(){var b=e(this).attr("data-key"),b=e.parseJSON(b);-1===e.inArray(a,b)&&e(this).addClass("concealed")});b=c.$items.not(".concealed").addClass("filtered").length;c.$container.trigger("shrink.shuffle",c);c.shrink();setTimeout(function(){c.$container.trigger("shrunk.shuffle",c)},c.speed);c.$container.trigger("filter.shuffle",c);c.filter();setTimeout(function(){c.$container.trigger("filtered.shuffle",c)},c.speed);b=Math.ceil(b/c.itemsPerRow)*(c.itemHeight+ -c.marginTop)-c.marginTop;c.$container.css("height",b+"px")},shrink:function(){var a=this,c=a.$container.find(".concealed");0!==c.length&&c.each(function(){var b=e(this),c=parseInt(b.attr("data-x"),10),f=parseInt(b.attr("data-y"),10);c||(c=0);f||(f=0);a.transition({$this:b,x:c,y:f,left:c+a.itemWidth/2+"px",top:f+a.itemHeight/2+"px",scale:0.001,opacity:0,height:"0px",width:"0px"})})},filter:function(){var a=this,c=0;a.$container.find(".filtered").each(function(b){var h=e(this),f=b%a.itemsPerRow*(a.itemWidth+ -a.marginRight),d=Math.floor(b/a.itemsPerRow);0===b%a.itemsPerRow&&(c=d*(a.itemHeight+a.marginTop));h.attr({"data-x":f,"data-y":c});a.transition({$this:h,x:f,y:c,left:f+"px",top:c+"px",scale:1,opacity:1,height:a.itemHeight+"px",width:a.itemWidth+"px"})})},setPrefixedCss:function(a,c,b){a.css(d.prefixed(c),b)},prefixed:function(a){return d.prefixed(a)},getPrefixed:function(a){return d.prefixed(a).replace(/([A-Z])/g,function(a,b){return"-"+b.toLowerCase()}).replace(/^ms-/,"-ms-")},transition:function(a){var c; -this.supported?(c=d.csstransforms3d?"translate3d("+a.x+"px, "+a.y+"px, 0px) scale3d("+a.scale+", "+a.scale+", "+a.scale+")":"translate("+a.x+"px, "+a.y+"px) scale("+a.scale+", "+a.scale+")",a.$this.css("opacity",a.opacity),this.setPrefixedCss(a.$this,"transform",c)):a.$this.animate({left:a.left,top:a.top,opacity:a.opacity,height:a.height,width:a.width},this.speed)}};e.fn.shuffle=function(a,c){return this.each(function(){var b=e(this),d=b.data("shuffle");d||(d=new g(b,a),b.data("shuffle",d));"string"=== -typeof a&&("shuffle"!==a&&(c=a),d.shuffle(c))})}})(jQuery,Modernizr); \ No newline at end of file +(function(d,g){d.fn.sorted=function(b){var c=d.extend({},d.fn.sorted.defaults,b),b=this.get();c.by!==d.noop&&(null!==c.by&&void 0!==c.by)&&b.sort(function(a,b){var e=c.by(d(a)),h=c.by(d(b));return eh?1:0});c.reverse&&b.reverse();return b};d.fn.sorted.defaults={reverse:!1,by:null};var i=function(b,c){var a=this;d.extend(a,d.fn.shuffle.options,c,d.fn.shuffle.settings);a.$container=b;a.$items=a.$container.children();a.$item=a.$items.first();a.itemWidth=a.$item.outerWidth();a.itemHeight=a.$item.outerHeight(); +a.marginTop=parseInt(a.$item.css("marginTop"),10);a.marginRight=parseInt(a.$item.css("marginRight"),10);a.itemsPerRow=a.getItemsPerRow();a.transitionName=a.prefixed("transition");a.transform=a.getPrefixed("transform");a.transitionEndName={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",msTransition:"MSTransitionEnd",transition:"transitionend"}[a.transitionName];a.itemCss={position:"absolute",top:0,left:0,opacity:1};a.$container.css("position","relative").get(0).style[a.transitionName]= +"height "+a.speed+"ms "+a.easing;a.$items.each(function(){d(this).css(a.itemCss);a.supported&&(this.style[a.transitionName]=a.transform+" "+a.speed+"ms "+a.easing+", opacity "+a.speed+"ms "+a.easing);this.style.marginTop=0;this.style.marginRight=0});a.windowHeight=d(window).height();a.windowWidth=d(window).width();d(window).on("resize.shuffle",function(){var b=d(window).height(),c=d(window).width();if(c!==a.windowWidth||b!==a.windowHeight)a.resized(),a.windowHeight=b,a.windowWidth=c});a.shuffle(a.group)}; +i.prototype={constructor:i,shuffle:function(b){var c=this;b||(b="all");c.$items.removeClass("concealed filtered");d.isFunction(b)?c.$items.each(function(){var a=d(this);a.addClass(b(a,c)?"filtered":"concealed")}):(c.group=b,"all"!==b?c.$items.each(function(){var a=d(this).data("groups");-1===d.inArray(b,a)?d(this).addClass("concealed"):d(this).addClass("filtered")}):c.$items.addClass("filtered"));c.visibleItems=c.$items.filter(".filtered").length;c.fire("shrink");c.shrink();c.fire("filter");c.filter(); +c.resizeContainer()},getItemsPerRow:function(){var b=this.$container.width(),c=Math.floor(b/this.itemWidth);c*(this.itemWidth+this.marginRight)-this.marginRight>b&&(c-=1);return c},resizeContainer:function(){var b=Math.ceil(this.visibleItems/this.itemsPerRow)*(this.itemHeight+this.marginTop)-this.marginTop;this.$container.css("height",b+"px")},fire:function(b){this.$container.trigger(b+".shuffle",[this])},shrink:function(){var b=this,c=b.$items.filter(".concealed");0!==c.length&&(b.shrinkTransitionEnded= +!1,c.each(function(){var a=d(this),c=parseInt(a.attr("data-x"),10),e=parseInt(a.attr("data-y"),10);c||(c=0);e||(e=0);b.transition({from:"shrink",$this:a,x:c,y:e,left:c+b.itemWidth/2+"px",top:e+b.itemHeight/2+"px",scale:0.001,opacity:0,height:"0px",width:"0px",callback:b.shrinkEnd})}))},layout:function(b,c){var a=this,f=0;0!==b.length&&(a.layoutTransitionEnded=!1,d.each(b,function(e){var h=d(b[e]),g=e%a.itemsPerRow*(a.itemWidth+a.marginRight),i=Math.floor(e/a.itemsPerRow);0===e%a.itemsPerRow&&(f=i* +(a.itemHeight+a.marginTop));h.attr({"data-x":g,"data-y":f});a.transition({from:"layout",$this:h,x:g,y:f,left:g+"px",top:f+"px",scale:1,opacity:1,height:a.itemHeight+"px",width:a.itemWidth+"px",callback:c})}))},filter:function(){if(this.keepSorted&&this.lastSort)this.sort(this.lastSort,!0);else{var b=this.$items.filter(".filtered").get();this.layout(b,this.filterEnd)}},sort:function(b,c){var a=this,d=a.$items.filter(".filtered").sorted(b);a.layout(d,function(){c&&a.filterEnd();a.sortEnd()});a.lastSort= +b},setPrefixedCss:function(b,c,a){b.css(this.prefixed(c),a)},getPrefixed:function(b){return(b=this.prefixed(b))?b.replace(/([A-Z])/g,function(b,a){return"-"+a.toLowerCase()}).replace(/^ms-/,"-ms-"):b},transition:function(b){var c=this,a,d=function(){!c.layoutTransitionEnded&&"layout"===b.from?(b.callback.call(c),c.layoutTransitionEnded=!0):!c.shrinkTransitionEnded&&"shrink"===b.from&&(b.callback.call(c),c.shrinkTransitionEnded=!0)};c.supported?(a=c.threeD?"translate3d("+b.x+"px, "+b.y+"px, 0) scale3d("+ +b.scale+", "+b.scale+", 1)":"translate("+b.x+"px, "+b.y+"px) scale("+b.scale+", "+b.scale+")",b.$this.css("opacity",b.opacity),c.setPrefixedCss(b.$this,"transform",a),b.$this.one(c.transitionEndName,d)):b.$this.stop().animate({left:b.left,top:b.top,opacity:b.opacity,height:b.height,width:b.width},c.speed,"swing",d)},resized:function(){this.itemWidth=this.$items.filter(".filtered").outerWidth();this.itemHeight=this.$items.filter(".filtered").outerHeight();this.itemsPerRow=this.getItemsPerRow();this.filter(); +this.resizeContainer()},shrinkEnd:function(){this.fire("shrunk")},filterEnd:function(){this.fire("filtered")},sortEnd:function(){this.fire("sorted")}};d.fn.shuffle=function(b,c){return this.each(function(){var a=d(this),f=a.data("shuffle");f||(f=new i(a,b),a.data("shuffle",f));d.isFunction(b)||"string"===typeof b&&"sort"!==b?f.shuffle(b):"string"===typeof b&&"sort"===b&&f.sort(c)})};d.fn.shuffle.options={group:"all",speed:800,easing:"ease-out",keepSorted:!0};d.fn.shuffle.settings={supported:g.csstransforms&& +g.csstransitions,prefixed:g.prefixed,threeD:g.csstransforms3d}})(jQuery,Modernizr); \ No newline at end of file