1/** 2 * Mosaic Flow 3 * 4 * Pinterest like responsive image grid that doesnt suck 5 * 6 * @requires jQuery 7 * @author Artem Sapegin 8 * @copyright 2012 Artem Sapegin, http://sapegin.me 9 * @license MIT 10 */ 11/* taken at commit https://github.com/sapegin/jquery.mosaicflow/commit/20cea9ae73bab6bfeda54434564aaf0f86bed9ac 12 * which includes fn.load() 13/* 14/*jshint browser:true, jquery:true, white:false, smarttabs:true */ 15/*global jQuery:false, define:false*/ 16(function(factory) { // Try to register as an anonymous AMD module 17 if (typeof define === 'function' && define.amd) { 18 define(['jquery'], factory); 19 } 20 else { 21 factory(jQuery); 22 } 23}(function($) { 24 'use strict'; 25 var cnt = 0; 26 27 $.fn.mosaicflow = function(options) { 28 var args = Array.prototype.slice.call(arguments, 0); 29 30 return this.each(function() { 31 var elm = $(this); 32 var data = elm.data('mosaicflow'); 33 34 if (!data) { 35 options = $.extend({}, $.fn.mosaicflow.defaults, options, dataToOptions(elm)); 36 data = new Mosaicflow(elm, options); 37 elm.data('mosaicflow', data); 38 } 39 else if (typeof options === 'string') { 40 data[options](args[1]); 41 } 42 }); 43 }; 44 45 $.fn.mosaicflow.defaults = { 46 itemSelector: '> *', 47 columnClass: 'mosaicflow__column', 48 minItemWidth: 240, 49 minColumns: 2, 50 itemHeightCalculation: 'auto', 51 threshold: 40,
52 fallbackHTML: false 53 }; 54 55 function Mosaicflow(container, options) { 56 this.container = container; 57 this.options = options; 58 59 this.container.trigger('mosaicflow-start'); 60 this.init(); 61 this.container.trigger('mosaicflow-ready'); 62 } 63 64 Mosaicflow.prototype = { 65 init: function() { 66 this.__uid = cnt++; 67 this.__uidItemCounter = 0; 68 this.items = this.container.find(this.options.itemSelector); 69 this.columns = $([]); 70 this.columnsHeights = []; 71 this.itemsHeights = {}; 72 this.tempContainer = $('<' + ( this.options.fallbackHTML ? 'div' : 'li' ) + '>', { 73 css: { 'visibility': 'hidden', 'width': '100%' }, 74 role: this.options.fallbackHTML ? 'listitem' : null, 75 }); 76 this.workOnTemp = false; 77 this.autoCalculation = this.options.itemHeightCalculation === 'auto'; 78 79 this.container.append(this.tempContainer); 80 81 var that = this; 82 this.items.each(function() { 83 var elm = $(this); 84 var id = elm.attr('id'); 85 if (!id) { 86 // Generate an unique id 87 id = that.generateUniqueId(); 88 elm.attr('id', id); 89 } 90 }); 91 92 this.container.css('visibility', 'hidden'); 93 if (this.autoCalculation) { 94 $(window).on('load', $.proxy(this.refill, this)); 95 } 96 else { 97 this.refill(); 98 } 99 $(window).resize($.proxy(this.refill, this)); 100 }, 101 102 refill: function() { 103 this.container.trigger('mosaicflow-fill'); 104 this.numberOfColumns = Math.floor(this.container.width() / this.options.minItemWidth); 105 // Always keep min columns number 106 if (this.numberOfColumns < this.options.minColumns) 107 this.numberOfColumns = this.options.minColumns; 108 109 var needToRefill = this.ensureColumns(); 110 if (needToRefill) { 111 this.fillColumns(); 112 113 // Remove excess columns, only if there are visible columns remaining 114 if (this.columns.filter(':visible').length > 0) { 115 this.columns.filter(':hidden').remove(); 116 } 117 } 118 this.container.css('visibility', 'visible'); 119 this.container.trigger('mosaicflow-filled'); 120 }, 121 122 ensureColumns: function() { 123 var createdCnt = this.columns.filter(':visible').length; 124 var calculatedCnt = this.numberOfColumns; 125 126 this.workingContainer = createdCnt === 0 ? this.tempContainer : this.container; 127 128 if (calculatedCnt > createdCnt) { 129 var neededCnt = calculatedCnt - createdCnt; 130 for (var columnIdx = 0; columnIdx < neededCnt; columnIdx++) { 131 var column = $('<' + ( this.options.fallbackHTML ? 'div' : 'li' ) + '>', { 132 class: this.options.columnClass, 133 role: this.options.fallbackHTML ? 'listitem' : null 134 }); 135 136 this.workingContainer.append(column); 137 } 138 } 139 else if (calculatedCnt < createdCnt) { 140 var lastColumn = createdCnt; 141 while (calculatedCnt <= lastColumn) { 142 // We can't remove columns here becase it will remove items to. So we hide it and will remove later. 143 this.columns.eq(lastColumn).hide(); 144 lastColumn--; 145 } 146 147 var diff = createdCnt - calculatedCnt; 148 this.columnsHeights.splice(this.columnsHeights.length - diff, diff); 149 } 150 151 if (calculatedCnt !== createdCnt) { 152 this.columns = this.workingContainer.find('.' + this.options.columnClass); 153 this.columns.css('width', (100 / calculatedCnt) + '%'); 154 return true; 155 } 156 157 return false; 158 }, 159 160 fillColumns: function() { 161 var columnsCnt = this.numberOfColumns; 162 var itemsCnt = this.items.length; 163 164 for (var columnIdx = 0; columnIdx < columnsCnt; columnIdx++) { 165 var column = this.columns.eq(columnIdx); 166 this.columnsHeights[columnIdx] = 0; 167 for (var itemIdx = columnIdx; itemIdx < itemsCnt; itemIdx += columnsCnt) { 168 var item = this.items.eq(itemIdx); 169 var height = 0; 170 column.append(item); 171 172 if (this.autoCalculation) { 173 // Check height after being placed in its column 174 height = item.outerHeight(); 175 } 176 else { 177 // Read img height attribute 178 height = parseInt(item.find('img').attr('height'), 10); 179 } 180 181 this.itemsHeights[item.attr('id')] = height; 182 this.columnsHeights[columnIdx] += height; 183 } 184 } 185 186 this.levelBottomEdge(this.itemsHeights, this.columnsHeights); 187 188 if (this.workingContainer === this.tempContainer) { 189 this.container.append(this.tempContainer.children()); 190 } 191 this.container.trigger('mosaicflow-layout'); 192 }, 193 194 levelBottomEdge: function(itemsHeights, columnsHeights) { 195 while (true) { 196 var lowestColumn = $.inArray(Math.min.apply(null, columnsHeights), columnsHeights); 197 var highestColumn = $.inArray(Math.max.apply(null, columnsHeights), columnsHeights); 198 if (lowestColumn === highestColumn) return; 199
200 var lastInHighestColumn = this.columns.eq(highestColumn).children().last(); 201 var lastInHighestColumnHeight = itemsHeights[lastInHighestColumn.attr('id')]; 202 var lowestHeight = columnsHeights[lowestColumn]; 203 var highestHeight = columnsHeights[highestColumn]; 204 var newLowestHeight = lowestHeight + lastInHighestColumnHeight; 205 206 if (newLowestHeight >= highestHeight) return; 207 208 if (highestHeight - newLowestHeight < this.options.threshold) return; 209 210 this.columns.eq(lowestColumn).append(lastInHighestColumn); 211 columnsHeights[highestColumn] -= lastInHighestColumnHeight; 212 columnsHeights[lowestColumn] += lastInHighestColumnHeight; 213 } 214 }, 215 216 add: function(elm) { 217 this.container.trigger('mosaicflow-item-add', [elm]); 218 var lowestColumn = $.inArray(Math.min.apply(null, this.columnsHeights), this.columnsHeights); 219 var height = 0; 220 221 if (this.autoCalculation) { 222 223 // Get height of elm 224 elm.css({ 225 position: 'static', 226 visibility: 'hidden', 227 display: 'block' 228 }).appendTo(this.columns.eq(lowestColumn)); 229 230 height = elm.outerHeight(); 231 232 var inlineImages = elm.find('img'); 233 if (inlineImages.length !== 0) { 234 inlineImages.each(function() { 235 var image = $(this); 236 var imageSizes = getImageSizes(image); 237 var actualHeight = (image.width() * imageSizes.height) / imageSizes.width; 238 239 height += actualHeight; 240 }); 241 } 242 243 elm.detach().css({ 244 position: 'static', 245 visibility: 'visible' 246 }); 247 } 248 else { 249 height = parseInt(elm.find('img').attr('height'), 10); 250 } 251 252 if (!elm.attr('id')) { 253 // Generate a unique id 254 elm.attr('id', this.generateUniqueId()); 255 } 256 257 // Update item collection. 258 // Item needs to be placed at the end of this.items to keep order of elements 259 var itemsArr = this.items.toArray(); 260 itemsArr.push(elm); 261 this.items = $(itemsArr); 262 263 this.itemsHeights[elm.attr('id')] = height; 264 this.columnsHeights[lowestColumn] += height; 265 this.columns.eq(lowestColumn).append(elm); 266 267 this.levelBottomEdge(this.itemsHeights, this.columnsHeights); 268 this.container.trigger('mosaicflow-layout'); 269 this.container.trigger('mosaicflow-item-added', [elm]); 270 }, 271 272 remove: function(elm) { 273 this.container.trigger('mosaicflow-item-remove', [elm]); 274 var column = elm.parents('.' + this.options.columnClass); 275 276 // Update column height 277 this.columnsHeights[column.index() - 1] -= this.itemsHeights[elm.attr('id')]; 278 279 elm.detach(); 280 281 // Update item collection 282 this.items = this.items.not(elm); 283 this.levelBottomEdge(this.itemsHeights, this.columnsHeights); 284 this.container.trigger('mosaicflow-layout'); 285 this.container.trigger('mosaicflow-item-removed', [elm]); 286 }, 287 288 empty: function() { 289 var columnsCnt = this.numberOfColumns; 290 291 this.items = $([]); 292 this.itemsHeights = {}; 293 294 for (var columnIdx = 0; columnIdx < columnsCnt; columnIdx++) { 295 var column = this.columns.eq(columnIdx); 296 this.columnsHeights[columnIdx] = 0; 297 column.empty(); 298 } 299 this.container.trigger('mosaicflow-layout'); 300 }, 301 302 recomputeHeights: function() { 303 function computeHeight(idx, item) { 304 item = $(item); 305 var height = 0; 306 if (that.autoCalculation) { 307 // Check height after being placed in its column 308 height = item.outerHeight(); 309 } 310 else { 311 // Read img height attribute 312 height = parseInt(item.find('img').attr('height'), 10); 313 } 314 315 that.itemsHeights[item.attr('id')] = height; 316 that.columnsHeights[columnIdx] += height; 317 } 318 319 var that = this; 320 var columnsCnt = this.numberOfColumns; 321 322 for (var columnIdx = 0; columnIdx < columnsCnt; columnIdx++) { 323 var column = this.columns.eq(columnIdx); 324 325 this.columnsHeights[columnIdx] = 0; 326 column.children().each(computeHeight); 327 } 328 }, 329 330 generateUniqueId: function() { 331 // Increment the counter 332 this.__uidItemCounter++; 333 334 // Return an unique ID 335 return 'mosaic-' + this.__uid + '-itemid-' + this.__uidItemCounter; 336 } 337 }; 338 339 // Camelize data-attributes 340 function dataToOptions(elem) { 341 function upper(m, l) { 342 return l.toUpper(); 343 } 344 var options = {}; 345 var data = elem.data(); 346 for (var key in data) { 347 options[key.replace(/-(\w)/g, upper)] = data[key]; 348 } 349 return options; 350 } 351 352 function getImageSizes(image) { 353 var sizes = {}; 354 355 sizes.height = parseInt(image.attr('height'), 10); 356 sizes.width = parseInt(image.attr('width'), 10); 357 358 if (sizes.height === 0 || sizes.width === 0) { 359 var utilImage = new Image(); 360 utilImage.src = image.attr('src'); 361 362 sizes.width = utilImage.width; 363 sizes.height = utilImage.height; 364 } 365 366 return sizes; 367 } 368 369 // Auto init 370 $(function() { $('.mosaicflow').mosaicflow(); }); 371 372}));
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.