/************************************************* ** jQuery Mercutio version 0.1 ** Copyright David DeSandro, licensed MIT ** http://desandro.com/resources/jquery-mercutio **************************************************/ (function($){ /*! * smartresize: debounced resize event for jQuery * http://github.com/lrbabe/jquery-smartresize * * Copyright (c) 2009 Louis-Remi Babe * Licensed under the GPL license. * http://docs.jquery.com/License * */ var $event = $.event, 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() { jQuery.event.handle.apply( context, args ); }, execAsap === "execAsap"? 0 : 100 ); } }; $.fn.smartresize = function( fn ) { return fn ? this.bind( "smartresize", fn ) : this.trigger( "smartresize", ["execAsap"] ); }; // ========================= mercutio =============================== var mercutioMethods = { filter : function( $cards ) { var props = this.data('mercutio'), filter = props.opts.filter === '' ? '*' : props.opts.filter; if ( !filter ) { props.$cards.filtered = $cards; } else { var hiddenClass = props.opts.hiddenClass, hiddenSelector = '.' + hiddenClass, $visibleCards = $cards.not( hiddenSelector ), $hiddenCards = $cards.filter( hiddenSelector ), $cardsToShow = $hiddenCards; props.$cards.filtered = $cards.filter( filter ); if ( filter !== '*' ) { $cardsToShow = $hiddenCards.filter( filter ); var $cardsToHide = $visibleCards.not( filter ).toggleClass( hiddenClass ); $cardsToHide.addClass( hiddenClass ); props.styleQueue.push({ $el: $cardsToHide, style: props.opts.hiddenStyle }); } props.styleQueue.push({ $el: $cardsToShow, style: props.opts.visibleStyle }); $cardsToShow.removeClass( hiddenClass ); } return this; }, // used on all the filtered cards, $cards.filtered sort : function() { }, // will use top position : function() { }, placeCard : function( setCount, setY, props ) { // here, `this` refers to a child element or "brick" // get the minimum Y value from the columns var minimumY = Math.min.apply( Math, setY ), setHeight = minimumY + this.outerHeight(true), i = setY.length, shortCol = i, setSpan = props.colCount + 1 - i, animOpts = $.extend( {}, props.opts.animationOptions ), position; // Which column has the minY value, closest to the left while (i--) { if ( setY[i] === minimumY ) { shortCol = i; } } position = { left: props.colW * shortCol + props.posLeft, top: minimumY }; // position the brick props.styleQueue.push({ $el: this, style: position }); // this[ props.applyStyle ]( position, animOpts ); // apply setHeight to necessary columns for ( i=0; i < setSpan; i++ ) { props.colYs[ shortCol + i ] = setHeight; } return this; }, singleColumn : function( colYs, props ) { return this.each(function(){ $(this).mercutio( 'placeCard', props.colCount, colYs, props ); }); }, multiColumn : function( colYs, props ) { return this.each(function(){ var $this = $(this), //how many columns does this brick span colSpan = Math.ceil( $this.outerWidth(true) / props.colW ); colSpan = Math.min( colSpan, props.colCount ); if ( colSpan === 1 ) { // if brick spans only one column, just like singleMode $this.mercutio( 'placeCard', props.colCount, colYs, props ); } else { // brick spans more than one column // how many different places could this brick fit horizontally var groupCount = props.colCount + 1 - colSpan, groupY = []; // for each group potential horizontal position for ( i=0; i < groupCount; i++ ) { // make an array of colY values for that one group var groupColY = colYs.slice( i, i+colSpan ); // and get the max value of the array groupY[i] = Math.max.apply( Math, groupColY ); } $this.mercutio( 'placeCard', groupCount, groupY, props ); } }); }, complete : function( props ) { // are we animating the layout arrangement? // use plugin-ish syntax for css or animate var styleFn = ( props.initiated && props.opts.animate ) ? 'animate' : 'css', animOpts = props.opts.animationOptions; // process styleQueue $.each( props.styleQueue, function( i, obj ){ // have to extend animation to play nice with jQuery obj.$el[ styleFn ]( obj.style, $.extend( {}, animOpts ) ); }); // clear out queue for next time props.styleQueue = []; // provide props.bricks as context for the callback // callback = callback || function(){}; // callback.call( props.$bricks ); // set all data so we can retrieve it for appended appendedContent // or anyone else's crazy jquery fun this.data( 'mercutio', props ); return this; }, // used on collection of cards (should be filtered, and sorted before ) // accepts cards-to-be-laid-out and colYs to start with layout : function( $cards, colYs ) { var props = this.data('mercutio'); // layout logic var layoutMode = props.opts.singleMode ? 'singleColumn' : 'multiColumn'; $cards.mercutio( layoutMode, colYs, props ); // set the height of the container to the tallest column props.containerHeight = Math.max.apply( Math, props.colYs ); var containerStyle = { height: props.containerHeight - props.posTop }; props.styleQueue.push({ $el: this, style: containerStyle }); // this[ props.applyStyle ]( containerStyle, animOpts ).mercutio( 'complete', props ); this.mercutio( 'complete', props ); return this; }, resetColYs : function( props ) { var colYs = [], i = props.colCount; while (i--) { colYs.push( props.posTop ); } props.colYs = colYs; return colYs }, resize : function() { // console.log( this.data('mercutio') , this[0].id ) var props = this.data('mercutio'), prevColCount = props.colCount; props.initiated = true; // get updated colCount this.mercutio( 'getColCount', props ); if ( props.colCount !== prevColCount ) { // if column count has changed, do a new column cound var colYs = this.mercutio( 'resetColYs', props ); this.mercutio( 'layout', props.$cards.filtered, colYs ); } return this; }, append : function() { }, getColCount : function( props ) { props.colCount = Math.floor( this.width() / props.colW ) ; props.colCount = Math.max( props.colCount, 1 ); return this; }, // only run though on initial init setup : function() { var props = this.data('mercutio'); props.$cards = {}; props.styleQueue = []; // need to get cards props.$cards.all = props.opts.selector ? this.find( props.opts.selector ) : this.children(); props.colW = props.opts.columnWidth || props.$cards.all.outerWidth(true); // if colW == 0, back out before divide by zero if ( !props.colW ) { window.console && console.error('Column width calculated to be zero. Stopping Mercutio plugin before divide by zero. Check that the width of first child inside the masonry container is not zero.'); return this; } this.css('position', 'relative').mercutio( 'getColCount', props ); props.$cards.all.css( 'position', 'absolute' ); // get top left position of where the bricks should be var $cursor = $( document.createElement('div') ); this.prepend( $cursor ); props.posTop = Math.round( $cursor.position().top ); props.posLeft = Math.round( $cursor.position().left ); $cursor.remove(); // add mercutio class first time around var $container = this; setTimeout(function(){ $container.addClass('mercutio'); }, 1 ); return this; }, init : function( options ) { return this.each(function() { var $this = $(this), data = $this.data('mercutio'), props = data || {}; // checks if masonry has been called before on this object props.initiated = !!data; var previousOptions = props.initiated ? data.opts : {}; props.opts = $.extend( {}, $.fn.mercutio.defaults, previousOptions, options ); $this.data( 'mercutio', props ); if ( !props.initiated ) { $this.mercutio( 'setup' ); } var colYs = $this.mercutio( 'resetColYs', props ); $this .mercutio( 'filter', props.$cards.all ) .mercutio( 'layout', props.$cards.filtered, colYs ); // binding window resizing if ( props.opts.resizeable ) { $(window).bind('smartresize.mercutio', function() { $this.mercutio( 'resize' ); } ); } else if ( !props.opts.resizeable && !!previousOptions.resizeable ) { $(window).unbind('smartresize.mercutio'); } }); } }; // mercutio code begin $.fn.mercutio = function( firstArg ) { // Method calling logic var method = mercutioMethods[ firstArg ]; if ( method ) { // remove firstArg, which is a string of the function name, from arguments var args = Array.prototype.slice.call( arguments, 1 ); return method.apply( this, args ); } else if ( !firstArg || typeof firstArg === 'object' ) { return mercutioMethods.init.apply( this, arguments ); } }; // Default plugin options $.fn.mercutio.defaults = { // singleMode: false, // columnWidth: undefined, // itemSelector: undefined, // appendedContent: undefined, // saveOptions: true, resizeable: true, hiddenClass : 'mercutio-hidden', hiddenStyle : { opacity : 0 }, visibleStyle : { opacity : 1 }, animationOptions: { queue: false } }; })(jQuery);