1/*! 2 * Colcade v0.2.0 3 * Lightweight masonry layout 4 * by David DeSandro 5 * MIT license 6 */ 7 8/*jshint browser: true, undef: true, unused: true */ 9 10( function( window, factory ) { 11 // universal module definition 12 /*jshint strict: false */ 13 /*global define: false, module: false */ 14 if ( typeof define == 'function' && define.amd ) { 15 // AMD 16 define( factory ); 17 } else if ( typeof module == 'object' && module.exports ) { 18 // CommonJS 19 module.exports = factory(); 20 } else { 21 // browser global 22 window.Colcade = factory(); 23 } 24 25}( window, function factory() { 26 27// -------------------------- Colcade -------------------------- // 28 29function Colcade( element, options ) { 30 element = getQueryElement( element ); 31 32 // do not initialize twice on same element 33 if ( element && element.colcadeGUID ) { 34 var instance = instances[ element.colcadeGUID ]; 35 instance.option( options ); 36 return instance; 37 } 38 39 this.element = element; 40 // options 41 this.options = {}; 42 this.option( options ); 43 // kick things off 44 this.create(); 45} 46 47var proto = Colcade.prototype; 48 49proto.option = function( options ) { 50 this.options = extend( this.options, options ); 51}; 52 53// globally unique identifiers 54var GUID = 0; 55// internal store of all Colcade intances 56var instances = {}; 57 58proto.create = function() { 59 this.errorCheck(); 60 // add guid for Colcade.data 61 var guid = this.guid = ++GUID; 62 this.element.colcadeGUID = guid; 63 instances[ guid ] = this; // associate via id 64 // update initial properties & layout 65 this.reload(); 66 // events 67 this._windowResizeHandler = this.onWindowResize.bind( this ); 68 this._loadHandler = this.onLoad.bind( this ); 69 window.addEventListener( 'resize', this._windowResizeHandler ); 70 this.element.addEventListener( 'load', this._loadHandler, true ); 71}; 72 73proto.errorCheck = function() { 74 var errors = []; 75 if ( !this.element ) { 76 errors.push( 'Bad element: ' + this.element ); 77 } 78 if ( !this.options.columns ) { 79 errors.push( 'columns option required: ' + this.options.columns ); 80 } 81 if ( !this.options.items ) { 82 errors.push( 'items option required: ' + this.options.items ); 83 } 84 85 if ( errors.length ) { 86 throw new Error( '[Colcade error] ' + errors.join('. ') ); 87 } 88}; 89 90// update properties and do layout 91proto.reload = function() { 92 this.updateColumns(); 93 this.updateItems(); 94 this.layout(); 95}; 96 97proto.updateColumns = function() { 98 this.columns = querySelect( this.options.columns, this.element ); 99}; 100 101proto.updateItems = function() { 102 this.items = querySelect( this.options.items, this.element ); 103}; 104 105proto.getActiveColumns = function() { 106 return this.columns.filter( function( column ) { 107 var style = getComputedStyle( column ); 108 return style.display != 'none'; 109 }); 110}; 111 112// ----- layout ----- // 113 114// public, updates activeColumns 115proto.layout = function() { 116 this.activeColumns = this.getActiveColumns(); 117 this._layout(); 118}; 119 120// private, does not update activeColumns 121proto._layout = function() { 122 // reset column heights 123 this.columnHeights = this.activeColumns.map( function() { 124 return 0; 125 }); 126 // layout all items 127 this.layoutItems( this.items ); 128}; 129 130proto.layoutItems = function( items ) { 131 items.forEach( this.layoutItem, this ); 132}; 133 134proto.layoutItem = function( item ) { 135 // layout item by appending to column 136 var minHeight = Math.min.apply( Math, this.columnHeights ); 137 var index = this.columnHeights.indexOf( minHeight ); 138 this.activeColumns[ index ].appendChild( item ); 139 // at least 1px, if item hasn't loaded 140 // Not exactly accurate, but it's cool 141 this.columnHeights[ index ] += item.offsetHeight || 1; 142}; 143 144// ----- adding items ----- // 145 146proto.append = function( elems ) { 147 var items = this.getQueryItems( elems ); 148 // add items to collection 149 this.items = this.items.concat( items ); 150 // lay them out 151 this.layoutItems( items ); 152}; 153 154proto.prepend = function( elems ) { 155 var items = this.getQueryItems( elems ); 156 // add items to collection 157 this.items = items.concat( this.items ); 158 // lay out everything 159 this._layout(); 160}; 161 162proto.getQueryItems = function( elems ) { 163 elems = makeArray( elems ); 164 var fragment = document.createDocumentFragment();
165 elems.forEach( function( elem ) { 166 fragment.appendChild( elem ); 167 }); 168 return querySelect( this.options.items, fragment ); 169}; 170 171// ----- measure column height ----- // 172 173proto.measureColumnHeight = function( elem ) { 174 var boundingRect = this.element.getBoundingClientRect(); 175 this.activeColumns.forEach( function( column, i ) { 176 // if elem, measure only that column 177 // if no elem, measure all columns 178 if ( !elem || column.contains( elem ) ) { 179 var lastChildRect = column.lastElementChild.getBoundingClientRect(); 180 // not an exact calculation as it includes top border, and excludes item bottom margin 181 this.columnHeights[ i ] = lastChildRect.bottom - boundingRect.top; 182 } 183 }, this ); 184}; 185 186// ----- events ----- // 187 188proto.onWindowResize = function() { 189 clearTimeout( this.resizeTimeout ); 190 this.resizeTimeout = setTimeout( function() { 191 this.onDebouncedResize(); 192 }.bind( this ), 100 ); 193}; 194 195proto.onDebouncedResize = function() { 196 var activeColumns = this.getActiveColumns(); 197 // check if columns changed 198 var isSameLength = activeColumns.length == this.activeColumns.length; 199 var isSameColumns = true; 200 this.activeColumns.forEach( function( column, i ) { 201 isSameColumns = isSameColumns && column == activeColumns[i]; 202 }); 203 if ( isSameLength && isSameColumns ) { 204 return; 205 } 206 // activeColumns changed 207 this.activeColumns = activeColumns; 208 this._layout(); 209}; 210 211proto.onLoad = function( event ) { 212 this.measureColumnHeight( event.target ); 213}; 214 215// ----- destroy ----- // 216 217proto.destroy = function() { 218 // move items back to container 219 this.items.forEach( function( item ) { 220 this.element.appendChild( item ); 221 }, this ); 222 // remove events 223 window.removeEventListener( 'resize', this._windowResizeHandler ); 224 this.element.removeEventListener( 'load', this._loadHandler, true ); 225 // remove data 226 delete this.element.colcadeGUID; 227 delete instances[ this.guid ]; 228}; 229 230// -------------------------- HTML init -------------------------- // 231 232docReady( function() { 233 var dataElems = querySelect('[data-colcade]'); 234 dataElems.forEach( htmlInit ); 235}); 236 237function htmlInit( elem ) { 238 // convert attribute "foo: bar, qux: baz" into object 239 var attr = elem.getAttribute('data-colcade'); 240 var attrParts = attr.split(','); 241 var options = {}; 242 attrParts.forEach( function( part ) { 243 var pair = part.split(':'); 244 var key = pair[0].trim(); 245 var value = pair[1].trim(); 246 options[ key ] = value; 247 }); 248 249 new Colcade( elem, options ); 250} 251 252Colcade.data = function( elem ) { 253 elem = getQueryElement( elem ); 254 var id = elem && elem.colcadeGUID; 255 return id && instances[ id ]; 256}; 257 258// -------------------------- jQuery -------------------------- // 259 260Colcade.makeJQueryPlugin = function( $ ) { 261 $ = $ || window.jQuery; 262 if ( !$ ) { 263 return; 264 } 265 266 $.fn.colcade = function( arg0 /*, arg1 */) { 267 // method call $().colcade( 'method', { options } ) 268 if ( typeof arg0 == 'string' ) { 269 // shift arguments by 1 270 var args = Array.prototype.slice.call( arguments, 1 ); 271 return methodCall( this, arg0, args ); 272 } 273 // just $().colcade({ options }) 274 plainCall( this, arg0 ); 275 return this; 276 }; 277 278 function methodCall( $elems, methodName, args ) { 279 var returnValue; 280 $elems.each( function( i, elem ) { 281 // get instance 282 var colcade = $.data( elem, 'colcade' ); 283 if ( !colcade ) { 284 return; 285 } 286 // apply method, get return value 287 var value = colcade[ methodName ].apply( colcade, args ); 288 // set return value if value is returned, use only first value 289 returnValue = returnValue === undefined ? value : returnValue; 290 }); 291 return returnValue !== undefined ? returnValue : $elems; 292 } 293 294 function plainCall( $elems, options ) { 295 $elems.each( function( i, elem ) { 296 var colcade = $.data( elem, 'colcade' ); 297 if ( colcade ) { 298 // set options & init 299 colcade.option( options ); 300 colcade.layout(); 301 } else { 302 // initialize new instance 303 colcade = new Colcade( elem, options ); 304 $.data( elem, 'colcade', colcade ); 305 } 306 }); 307 } 308}; 309 310// try making plugin 311Colcade.makeJQueryPlugin(); 312 313// -------------------------- utils -------------------------- // 314 315function extend( a, b ) { 316 for ( var prop in b ) { 317 a[ prop ] = b[ prop ]; 318 } 319 return a; 320} 321 322// turn element or nodeList into an array 323function makeArray( obj ) { 324 var ary = []; 325 if ( Array.isArray( obj ) ) { 326 // use object if already an array 327 ary = obj; 328 } else if ( obj && typeof obj.length == 'number' ) { 329 // convert nodeList to array 330 for ( var i=0; i < obj.length; i++ ) { 331 ary.push( obj[i] ); 332 } 333 } else { 334 // array of single index 335 ary.push( obj ); 336 } 337 return ary; 338} 339 340// get array of elements 341function querySelect( selector, elem ) { 342 elem = elem || document; 343 var elems = elem.querySelectorAll( selector ); 344 return makeArray( elems ); 345} 346 347function getQueryElement( elem ) { 348 if ( typeof elem == 'string' ) { 349 elem = document.querySelector( elem ); 350 } 351 return elem; 352} 353 354function docReady( onReady ) { 355 if ( document.readyState == 'complete' ) { 356 onReady(); 357 return; 358 } 359 document.addEventListener( 'DOMContentLoaded', onReady ); 360} 361 362// -------------------------- end -------------------------- // 363 364return Colcade; 365 366}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.