[25981] | 1 | /** |
---|
| 2 | * jQuery Masonry v2.0.110517 beta |
---|
| 3 | * The flip-side of CSS Floats. |
---|
| 4 | * jQuery plugin that rearranges item elements to a grid. |
---|
| 5 | * http://masonry.desandro.com |
---|
| 6 | * |
---|
| 7 | * Licensed under the MIT license. |
---|
| 8 | * Copyright 2011 David DeSandro |
---|
| 9 | */ |
---|
| 10 | |
---|
| 11 | (function( window, $, undefined ){ |
---|
| 12 | |
---|
| 13 | /* |
---|
| 14 | * smartresize: debounced resize event for jQuery |
---|
| 15 | * |
---|
| 16 | * latest version and complete README available on Github: |
---|
| 17 | * https://github.com/louisremi/jquery.smartresize.js |
---|
| 18 | * |
---|
| 19 | * Copyright 2011 @louis_remi |
---|
| 20 | * Licensed under the MIT license. |
---|
| 21 | */ |
---|
| 22 | |
---|
| 23 | var $event = $.event, |
---|
| 24 | resizeTimeout; |
---|
| 25 | |
---|
| 26 | $event.special.smartresize = { |
---|
| 27 | setup: function() { |
---|
| 28 | $(this).bind( "resize", $event.special.smartresize.handler ); |
---|
| 29 | }, |
---|
| 30 | teardown: function() { |
---|
| 31 | $(this).unbind( "resize", $event.special.smartresize.handler ); |
---|
| 32 | }, |
---|
| 33 | handler: function( event, execAsap ) { |
---|
| 34 | // Save the context |
---|
| 35 | var context = this, |
---|
| 36 | args = arguments; |
---|
| 37 | |
---|
| 38 | // set correct event type |
---|
| 39 | event.type = "smartresize"; |
---|
| 40 | |
---|
| 41 | if ( resizeTimeout ) { clearTimeout( resizeTimeout ); } |
---|
| 42 | resizeTimeout = setTimeout(function() { |
---|
| 43 | jQuery.event.handle.apply( context, args ); |
---|
| 44 | }, execAsap === "execAsap"? 0 : 100 ); |
---|
| 45 | } |
---|
| 46 | }; |
---|
| 47 | |
---|
| 48 | $.fn.smartresize = function( fn ) { |
---|
| 49 | return fn ? this.bind( "smartresize", fn ) : this.trigger( "smartresize", ["execAsap"] ); |
---|
| 50 | }; |
---|
| 51 | |
---|
| 52 | |
---|
| 53 | |
---|
| 54 | // ========================= Masonry =============================== |
---|
| 55 | |
---|
| 56 | |
---|
| 57 | // our "Widget" object constructor |
---|
| 58 | $.Mason = function( options, element ){ |
---|
| 59 | this.element = $( element ); |
---|
| 60 | |
---|
| 61 | this._create( options ); |
---|
| 62 | this._init(); |
---|
| 63 | }; |
---|
| 64 | |
---|
| 65 | // styles of container element we want to keep track of |
---|
| 66 | var masonryContainerStyles = [ 'position', 'height' ]; |
---|
| 67 | |
---|
| 68 | $.Mason.settings = { |
---|
| 69 | isResizable: true, |
---|
| 70 | isAnimated: false, |
---|
| 71 | animationOptions: { |
---|
| 72 | queue: false, |
---|
| 73 | duration: 500 |
---|
| 74 | }, |
---|
| 75 | gutterWidth: 0, |
---|
| 76 | isRTL: false, |
---|
| 77 | isFitWidth: false |
---|
| 78 | }; |
---|
| 79 | |
---|
| 80 | $.Mason.prototype = { |
---|
| 81 | |
---|
| 82 | _getBricks: function( $elems ) { |
---|
| 83 | var selector = this.options.itemSelector, |
---|
| 84 | // if there is a selector |
---|
| 85 | // filter/find appropriate item elements |
---|
| 86 | $bricks = !selector ? $elems : |
---|
| 87 | $elems.filter( selector ).add( $elems.find( selector ) ); |
---|
| 88 | $bricks |
---|
| 89 | .css({ position: 'absolute' }) |
---|
| 90 | .addClass('masonry-brick'); |
---|
| 91 | return $bricks; |
---|
| 92 | }, |
---|
| 93 | |
---|
| 94 | // sets up widget |
---|
| 95 | _create : function( options ) { |
---|
| 96 | |
---|
| 97 | this.options = $.extend( true, {}, $.Mason.settings, options ); |
---|
| 98 | |
---|
| 99 | this.styleQueue = []; |
---|
| 100 | // need to get bricks |
---|
| 101 | this.reloadItems(); |
---|
| 102 | |
---|
| 103 | |
---|
| 104 | // get original styles in case we re-apply them in .destroy() |
---|
| 105 | var elemStyle = this.element[0].style; |
---|
| 106 | this.originalStyle = {}; |
---|
| 107 | for ( var i=0, len = masonryContainerStyles.length; i < len; i++ ) { |
---|
| 108 | var prop = masonryContainerStyles[i]; |
---|
| 109 | this.originalStyle[ prop ] = elemStyle[ prop ] || null; |
---|
| 110 | } |
---|
| 111 | |
---|
| 112 | this.element.css({ |
---|
| 113 | position : 'relative' |
---|
| 114 | }); |
---|
| 115 | |
---|
| 116 | this.horizontalDirection = this.options.isRTL ? 'right' : 'left'; |
---|
| 117 | this.offset = {}; |
---|
| 118 | |
---|
| 119 | // get top left position of where the bricks should be |
---|
| 120 | var $cursor = $( document.createElement('div') ); |
---|
| 121 | this.element.prepend( $cursor ); |
---|
| 122 | this.offset.y = Math.round( $cursor.position().top ); |
---|
| 123 | // get horizontal offset |
---|
| 124 | if ( this.options.isRTL ) { |
---|
| 125 | this.offset.x = Math.round( $cursor.position().left ); |
---|
| 126 | } else { |
---|
| 127 | $cursor.css({ 'float': 'right', display: 'inline-block'}); |
---|
| 128 | this.offset.x = Math.round( this.element.outerWidth() - $cursor.position().left ); |
---|
| 129 | } |
---|
| 130 | $cursor.remove(); |
---|
| 131 | |
---|
| 132 | // add masonry class first time around |
---|
| 133 | var instance = this; |
---|
| 134 | setTimeout( function() { |
---|
| 135 | instance.element.addClass('masonry'); |
---|
| 136 | }, 0 ); |
---|
| 137 | |
---|
| 138 | // bind resize method |
---|
| 139 | if ( this.options.isResizable ) { |
---|
| 140 | $(window).bind( 'smartresize.masonry', function() { |
---|
| 141 | instance.resize(); |
---|
| 142 | }); |
---|
| 143 | } |
---|
| 144 | |
---|
| 145 | }, |
---|
| 146 | |
---|
| 147 | // _init fires when your instance is first created |
---|
| 148 | // (from the constructor above), and when you |
---|
| 149 | // attempt to initialize the widget again (by the bridge) |
---|
| 150 | // after it has already been initialized. |
---|
| 151 | _init : function( callback ) { |
---|
| 152 | |
---|
| 153 | this.reLayout( callback ); |
---|
| 154 | |
---|
| 155 | }, |
---|
| 156 | |
---|
| 157 | option: function( key, value ){ |
---|
| 158 | |
---|
| 159 | // get/change options AFTER initialization: |
---|
| 160 | // you don't have to support all these cases, |
---|
| 161 | // but here's how: |
---|
| 162 | |
---|
| 163 | // signature: $('#foo').bar({ cool:false }); |
---|
| 164 | if ( $.isPlainObject( key ) ){ |
---|
| 165 | this.options = $.extend(true, this.options, key); |
---|
| 166 | |
---|
| 167 | // signature: $('#foo').option('cool'); - getter |
---|
| 168 | } else if ( key && typeof value === "undefined" ){ |
---|
| 169 | return this.options[ key ]; |
---|
| 170 | |
---|
| 171 | // signature: $('#foo').bar('option', 'baz', false); |
---|
| 172 | } else { |
---|
| 173 | this.options[ key ] = value; |
---|
| 174 | } |
---|
| 175 | |
---|
| 176 | return this; // make sure to return the instance! |
---|
| 177 | }, |
---|
| 178 | |
---|
| 179 | // ====================== General Layout ====================== |
---|
| 180 | |
---|
| 181 | // used on collection of atoms (should be filtered, and sorted before ) |
---|
| 182 | // accepts atoms-to-be-laid-out to start with |
---|
| 183 | layout : function( $bricks, callback ) { |
---|
| 184 | |
---|
| 185 | // layout logic |
---|
| 186 | var $brick, colSpan, groupCount, groupY, groupColY, j; |
---|
| 187 | |
---|
| 188 | for (var i=0, len = $bricks.length; i < len; i++) { |
---|
| 189 | $brick = $( $bricks[i] ); |
---|
| 190 | //how many columns does this brick span |
---|
| 191 | colSpan = Math.ceil( $brick.outerWidth(true) / this.columnWidth ); |
---|
| 192 | colSpan = Math.min( colSpan, this.cols ); |
---|
| 193 | |
---|
| 194 | if ( colSpan === 1 ) { |
---|
| 195 | // if brick spans only one column, just like singleMode |
---|
| 196 | this._placeBrick( $brick, this.cols, this.colYs ); |
---|
| 197 | } else { |
---|
| 198 | // brick spans more than one column |
---|
| 199 | // how many different places could this brick fit horizontally |
---|
| 200 | groupCount = this.cols + 1 - colSpan; |
---|
| 201 | groupY = []; |
---|
| 202 | |
---|
| 203 | // for each group potential horizontal position |
---|
| 204 | for ( j=0; j < groupCount; j++ ) { |
---|
| 205 | // make an array of colY values for that one group |
---|
| 206 | groupColY = this.colYs.slice( j, j+colSpan ); |
---|
| 207 | // and get the max value of the array |
---|
| 208 | groupY[j] = Math.max.apply( Math, groupColY ); |
---|
| 209 | } |
---|
| 210 | |
---|
| 211 | this._placeBrick( $brick, groupCount, groupY ); |
---|
| 212 | } |
---|
| 213 | } |
---|
| 214 | |
---|
| 215 | // set the size of the container |
---|
| 216 | var containerSize = {}; |
---|
| 217 | containerSize.height = Math.max.apply( Math, this.colYs ) - this.offset.y; |
---|
| 218 | if ( this.options.isFitWidth ) { |
---|
| 219 | containerSize.width = this.cols * this.columnWidth - this.options.gutterWidth; |
---|
| 220 | } |
---|
| 221 | this.styleQueue.push({ $el: this.element, style: containerSize }); |
---|
| 222 | |
---|
| 223 | // are we animating the layout arrangement? |
---|
| 224 | // use plugin-ish syntax for css or animate |
---|
| 225 | var styleFn = !this.isLaidOut ? 'css' : ( |
---|
| 226 | this.options.isAnimated ? 'animate' : 'css' |
---|
| 227 | ), |
---|
| 228 | animOpts = this.options.animationOptions; |
---|
| 229 | |
---|
| 230 | // process styleQueue |
---|
| 231 | var obj; |
---|
| 232 | for (i=0, len = this.styleQueue.length; i < len; i++) { |
---|
| 233 | obj = this.styleQueue[i]; |
---|
| 234 | obj.$el[ styleFn ]( obj.style, animOpts ); |
---|
| 235 | } |
---|
| 236 | |
---|
| 237 | // clear out queue for next time |
---|
| 238 | this.styleQueue = []; |
---|
| 239 | |
---|
| 240 | // provide $elems as context for the callback |
---|
| 241 | if ( callback ) { |
---|
| 242 | callback.call( $bricks ); |
---|
| 243 | } |
---|
| 244 | |
---|
| 245 | this.isLaidOut = true; |
---|
| 246 | |
---|
| 247 | return this; |
---|
| 248 | }, |
---|
| 249 | |
---|
| 250 | // calculates number of columns |
---|
| 251 | // i.e. this.columnWidth = 200 |
---|
| 252 | _getColumns : function() { |
---|
| 253 | var container = this.options.isFitWidth ? this.element.parent() : this.element, |
---|
| 254 | containerWidth = container.width(); |
---|
| 255 | |
---|
| 256 | this.columnWidth = this.options.columnWidth || |
---|
| 257 | // or use the size of the first item |
---|
| 258 | this.$bricks.outerWidth(true) || |
---|
| 259 | // if there's no items, use size of container |
---|
| 260 | containerWidth; |
---|
| 261 | |
---|
| 262 | this.columnWidth += this.options.gutterWidth; |
---|
| 263 | |
---|
| 264 | this.cols = Math.floor( ( containerWidth + this.options.gutterWidth ) / this.columnWidth ); |
---|
| 265 | this.cols = Math.max( this.cols, 1 ); |
---|
| 266 | |
---|
| 267 | return this; |
---|
| 268 | |
---|
| 269 | }, |
---|
| 270 | |
---|
| 271 | _placeBrick : function( $brick, setCount, setY ) { |
---|
| 272 | // get the minimum Y value from the columns |
---|
| 273 | var minimumY = Math.min.apply( Math, setY ), |
---|
| 274 | setHeight = minimumY + $brick.outerHeight(true), |
---|
| 275 | i = setY.length, |
---|
| 276 | shortCol = i, |
---|
| 277 | setSpan = this.cols + 1 - i, |
---|
| 278 | position = {}; |
---|
| 279 | // Which column has the minY value, closest to the left |
---|
| 280 | while (i--) { |
---|
| 281 | if ( setY[i] === minimumY ) { |
---|
| 282 | shortCol = i; |
---|
| 283 | } |
---|
| 284 | } |
---|
| 285 | |
---|
| 286 | // position the brick |
---|
| 287 | position.top = minimumY; |
---|
| 288 | // position.left or position.right |
---|
| 289 | position[ this.horizontalDirection ] = this.columnWidth * shortCol + this.offset.x; |
---|
| 290 | this.styleQueue.push({ $el: $brick, style: position }); |
---|
| 291 | |
---|
| 292 | // apply setHeight to necessary columns |
---|
| 293 | for ( i=0; i < setSpan; i++ ) { |
---|
| 294 | this.colYs[ shortCol + i ] = setHeight; |
---|
| 295 | } |
---|
| 296 | |
---|
| 297 | }, |
---|
| 298 | |
---|
| 299 | |
---|
| 300 | resize : function() { |
---|
| 301 | var prevColCount = this.cols; |
---|
| 302 | // get updated colCount |
---|
| 303 | this._getColumns('masonry'); |
---|
| 304 | if ( this.cols !== prevColCount ) { |
---|
| 305 | // if column count has changed, do a new column cound |
---|
| 306 | this._reloadLayout(); |
---|
| 307 | } |
---|
| 308 | }, |
---|
| 309 | |
---|
| 310 | |
---|
| 311 | reLayout : function( callback ) { |
---|
| 312 | this._getColumns('masonry'); |
---|
| 313 | this._reloadLayout( callback ); |
---|
| 314 | }, |
---|
| 315 | |
---|
| 316 | _reloadLayout : function( callback ) { |
---|
| 317 | // reset columns |
---|
| 318 | var i = this.cols; |
---|
| 319 | this.colYs = []; |
---|
| 320 | while (i--) { |
---|
| 321 | this.colYs.push( this.offset.y ); |
---|
| 322 | } |
---|
| 323 | // apply layout logic to all bricks |
---|
| 324 | this.layout( this.$bricks, callback ); |
---|
| 325 | }, |
---|
| 326 | |
---|
| 327 | // ====================== Convenience methods ====================== |
---|
| 328 | |
---|
| 329 | // goes through all children again and gets bricks in proper order |
---|
| 330 | reloadItems : function() { |
---|
| 331 | this.$bricks = this._getBricks( this.element.children() ); |
---|
| 332 | }, |
---|
| 333 | |
---|
| 334 | |
---|
| 335 | reload : function( callback ) { |
---|
| 336 | this.reloadItems(); |
---|
| 337 | this.reLayout( callback ); |
---|
| 338 | }, |
---|
| 339 | |
---|
| 340 | |
---|
| 341 | // convienence method for working with Infinite Scroll |
---|
| 342 | appended : function( $content, callback ) { |
---|
| 343 | var $newBricks = this._getBricks( $content ); |
---|
| 344 | // add new bricks to brick pool |
---|
| 345 | this.$bricks = this.$bricks.add( $newBricks ); |
---|
| 346 | this.layout( $newBricks, callback ); |
---|
| 347 | }, |
---|
| 348 | |
---|
| 349 | // removes elements from Masonry widget |
---|
| 350 | remove : function( $content ) { |
---|
| 351 | this.$bricks = this.$bricks.not( $content ); |
---|
| 352 | $content.remove(); |
---|
| 353 | }, |
---|
| 354 | |
---|
| 355 | // destroys widget, returns elements and container back (close) to original style |
---|
| 356 | destroy : function() { |
---|
| 357 | |
---|
| 358 | this.$bricks |
---|
| 359 | .removeClass('masonry-brick') |
---|
| 360 | .each(function(){ |
---|
| 361 | this.style.position = null; |
---|
| 362 | this.style.top = null; |
---|
| 363 | this.style.left = null; |
---|
| 364 | }); |
---|
| 365 | |
---|
| 366 | // re-apply saved container styles |
---|
| 367 | var elemStyle = this.element[0].style; |
---|
| 368 | for ( var i=0, len = masonryContainerStyles.length; i < len; i++ ) { |
---|
| 369 | var prop = masonryContainerStyles[i]; |
---|
| 370 | elemStyle[ prop ] = this.originalStyle[ prop ]; |
---|
| 371 | } |
---|
| 372 | |
---|
| 373 | this.element |
---|
| 374 | .unbind('.masonry') |
---|
| 375 | .removeClass('masonry') |
---|
| 376 | .removeData('masonry'); |
---|
| 377 | |
---|
| 378 | $(window).unbind('.masonry'); |
---|
| 379 | |
---|
| 380 | } |
---|
| 381 | |
---|
| 382 | }; |
---|
| 383 | |
---|
| 384 | |
---|
| 385 | // ======================= imagesLoaded Plugin =============================== |
---|
| 386 | // A fork of http://gist.github.com/268257 by Paul Irish |
---|
| 387 | |
---|
| 388 | // mit license. paul irish. 2010. |
---|
| 389 | // webkit fix from Oren Solomianik. thx! |
---|
| 390 | |
---|
| 391 | $.fn.imagesLoaded = function(callback){ |
---|
| 392 | var elems = this.find('img'), |
---|
| 393 | len = elems.length, |
---|
| 394 | blank = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==", |
---|
| 395 | _this = this; |
---|
| 396 | |
---|
| 397 | // if no images, trigger immediately |
---|
| 398 | if ( !len ) { |
---|
| 399 | callback.call( this ); |
---|
| 400 | return this; |
---|
| 401 | } |
---|
| 402 | |
---|
| 403 | elems.bind('load', function() { |
---|
| 404 | if ( --len <= 0 && this.src !== blank ) { |
---|
| 405 | callback.call( _this ); |
---|
| 406 | } |
---|
| 407 | }).each(function(){ |
---|
| 408 | // cached images don't fire load sometimes, so we reset src. |
---|
| 409 | if (this.complete || this.complete === undefined){ |
---|
| 410 | var src = this.src; |
---|
| 411 | // webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f |
---|
| 412 | // data uri bypasses webkit log warning (thx doug jones) |
---|
| 413 | this.src = blank; |
---|
| 414 | this.src = src; |
---|
| 415 | } |
---|
| 416 | }); |
---|
| 417 | |
---|
| 418 | return this; |
---|
| 419 | }; |
---|
| 420 | |
---|
| 421 | |
---|
| 422 | // ======================= Plugin bridge =============================== |
---|
| 423 | // leverages data method to either create or return $.Mason constructor |
---|
| 424 | // A bit from jQuery UI |
---|
| 425 | // https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js |
---|
| 426 | // A bit from jcarousel |
---|
| 427 | // https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarousel.js |
---|
| 428 | |
---|
| 429 | $.fn.masonry = function( options ) { |
---|
| 430 | if ( typeof options === 'string' ) { |
---|
| 431 | // call method |
---|
| 432 | var args = Array.prototype.slice.call( arguments, 1 ); |
---|
| 433 | |
---|
| 434 | return this.each(function(){ |
---|
| 435 | var instance = $.data( this, 'masonry' ); |
---|
| 436 | if ( !instance ) { |
---|
| 437 | return $.error( "cannot call methods on masonry prior to initialization; " + |
---|
| 438 | "attempted to call method '" + options + "'" ); |
---|
| 439 | } |
---|
| 440 | if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) { |
---|
| 441 | return $.error( "no such method '" + options + "' for masonry instance" ); |
---|
| 442 | } |
---|
| 443 | // apply method |
---|
| 444 | instance[ options ].apply( instance, args ); |
---|
| 445 | }); |
---|
| 446 | } else { |
---|
| 447 | return this.each(function() { |
---|
| 448 | var instance = $.data( this, 'masonry' ); |
---|
| 449 | if ( instance ) { |
---|
| 450 | // apply options & init |
---|
| 451 | instance.option( options || {} )._init(); |
---|
| 452 | } else { |
---|
| 453 | // initialize new instance |
---|
| 454 | $.data( this, 'masonry', new $.Mason( options, this ) ); |
---|
| 455 | } |
---|
| 456 | }); |
---|
| 457 | } |
---|
| 458 | }; |
---|
| 459 | |
---|
| 460 | })( window, jQuery ); |
---|