1// initialize and store reference in Cargo.Plugins 2var Cargo = Cargo || {}; 3 4Cargo.Plugins = Cargo.Plugins || {}; 5 6// use a closure so we have private and public methods. 7Cargo.Plugins.columnizer = (function($) { 8 var self = { 9 10 vars: { 11 targets: {}, 12 ticking: false, 13 init: true, 14 targetIndex: 0 15 }, 16 17 options: { 18 'updateEvents': ['resize'], 19 fluid: true, 20 attributeConfig: { 21 '[data-columnize]' : { 22 role: 'initializer', 23 type: 'string' 24 } 25 } 26 }, 27 28 init: function(){ 29 30 // reqest ticks to make sure the update function only fires on animationFrames 31 $.each(self.options.updateEvents, function(key, event){ 32 bindEvent(window, event, function(){ 33 self.requestTick(); 34 }); 35 }); 36 37 // stupid IE8 fallback 38 function bindEvent(el, eventName, eventHandler) { 39 if (el.addEventListener){ 40 el.addEventListener(eventName, eventHandler, false); 41 } else if (el.attachEvent){ 42 el.attachEvent('on'+eventName, eventHandler); 43 } 44 } 45 46 // update when (possibly) new content has been added 47 $(document).bind("paginationComplete", function(e) { 48 self.updateTargets(); 49 }); 50 51 if(Cargo.hasOwnProperty('Event')){ 52 53 Cargo.Event.on("inspector_preview", function() { 54 self.update(null, true); 55 }); 56 57 Cargo.Event.on("inspector_open", function() { 58 self.addPaddingHelperListeners(); 59 self.vars.inspector = true; 60 }); 61 62 // Feed designs intervention 63 64 var ignoreCollectionResets = false; 65 66 if(Cargo.View.Feed !== undefined) { 67 68 ignoreCollectionResets = true; 69 70 Cargo.Event.on("first_project_collection_reset", function(){ 71 ignoreCollectionResets = false; 72 }); 73 } 74 75 Cargo.Event.on("project_collection_reset", function() { 76 77 if(ignoreCollectionResets) { 78 return; 79 } 80 81 if(Cargo.View.Feed !== undefined) { 82 // Projects have been deleted and re-rendered. Clear our cache. 83 for(var key in self.vars.targets){ 84 85 if( self.vars.targets[key].container[0] === Cargo.View.Feed.el ){ 86 87 self.vars.targets[key].container.find('[data-columnized]').removeAttr('data-columnized'); 88 self.vars.targets[key].children = []; 89 self.vars.targets[key].shouldUpdate = true; 90 91 } 92 93 } 94 } 95 96 setTimeout(function(){ 97 self.updateTargets(); 98 }); 99 100 }); 101 102 Cargo.Event.on("project_load_complete", function(pid) { 103 self.updateTargets(); 104 }); 105 } 106 107 // initial check for targets. 108 self.updateTargets(); 109 self.vars.init = false; 110 }, 111 112 addPaddingHelperListeners: function(){ 113 var children, evtManager = CargoInspector.eventManager; 114
115 for(key in self.vars.targets){ 116 children = self.vars.targets[key].children; 117 118 for(var i = 0; i < children.length; i++){ 119 if(children[i].hasPaddingHelper && children[i].hasPaddingHelperListener !== true){ 120 121 (function(child){ 122 123 evtManager.subscribe('style_changed', function(source){ 124 125 child.paddingChild.style.removeProperty('padding'); 126 127 var styles = document.defaultView ? document.defaultView.getComputedStyle(child.paddingChild, null) : child.paddingChild.currentStyle; 128 129 child.paddingHelper.style.paddingLeft = styles.paddingLeft; 130 child.paddingHelper.style.paddingRight = styles.paddingRight; 131 132 child.paddingChild.style.paddingLeft = '0px'; 133 child.paddingChild.style.paddingRight = '0px'; 134 135 }); 136 137 child.hasPaddingHelperListener = true; 138 139 })(children[i]); 140 } 141 } 142 } 143 }, 144 145 // we use animation frames. This requests an animationFrame and updates the plugin when the 146 // animation frame has arrived. Only does this it once so we don't fire animationFrames 147 // when the page is static 148 requestTick: function(){ 149 if(!self.vars.ticking) { 150 requestAnimationFrame(self.update); 151 self.vars.ticking = true; 152 } 153 }, 154 155 // check how many columns we can fit in the available space 156 updateAvailableColumnCounts: function(){ 157 $.each(self.vars.targets, function(key, target){ 158 var containerWidth = target.container.width(); 159 var availableColumns = Math.floor(containerWidth / target.width); 160 161 if(target.fluid){ 162 availableColumns += 1; 163 } 164 165 // don't make more columns than there are items to fill 'em with. 166 if(availableColumns > target.visibleElementCount && target.visibleElementCount !== 0){ 167 availableColumns = target.visibleElementCount; 168 } else if(availableColumns < 1){ 169 availableColumns = 1; 170 } 171 172 target.availableColumns = availableColumns; 173 174 if(target.availableColumns != target.columnCount){ 175 target.shouldUpdate = true; 176 } 177 }); 178 }, 179 180 randomize: function(targetSelector){ 181 for(var key in self.vars.targets){ 182 var target = self.vars.targets[key]; 183 if(targetSelector !== undefined){ 184 if(!target.container.is(targetSelector)){ 185 continue; 186 } 187 } 188 shuffle(target.children); 189 }; 190 191 self.update(null, true); 192 193 function shuffle(array) { 194 var currentIndex = array.length, temporaryValue, randomIndex; 195 196 // While there remain elements to shuffle... 197 while (0 !== currentIndex) { 198 199 // Pick a remaining element... 200 randomIndex = Math.floor(Math.random() * currentIndex); 201 currentIndex -= 1; 202 203 // And swap it with the current element. 204 temporaryValue = array[currentIndex]; 205 array[currentIndex] = array[randomIndex]; 206 array[randomIndex] = temporaryValue; 207 } 208 209 return array; 210 } 211 }, 212 213 // updates all targets found in memory 214 // this thing should be all about speed so (almost) no jquery. 215 update: function(callback, force){ 216 // check if there's a change in the amount of columns 217 self.updateAvailableColumnCounts(); 218 219 for(var key in self.vars.targets){ 220 var target = self.vars.targets[key]; 221 222 if(target.children.length === 0) { 223 continue; 224 } 225 226 if(target.shouldUpdate || force){ 227 target.container.find('.column').detach(); 228 229 // store everything in a fragement so nothing in the DOM is changed 230 // and no reflows/paints are triggered. 231 var frag = document.createDocumentFragment(); 232 target.columnArr = []; 233 234 // Make the amount of columns 235 for (i = target.availableColumns; i > 0; i--) { 236 var col = document.createElement('div'); 237 238 col.id = 'column_' + i; 239 col.className = 'column'; 240 col.style.maxWidth = target.width + 'px'; 241 col.style.display = 'inline-block'; 242 col.style.verticalAlign = 'top'; 243 244 // fluid columns need a percentage to scale width the browser width 245 if(target.fluid){ 246 col.style.width = (100.0 / target.availableColumns) + '%'; 247 } else { 248 col.style.width = target.width + 'px'; 249 } 250 251 target.columnArr[i-1] = { 252 col: col, 253 height: 0 254 }; 255 } 256 257 target.columnCount = target.columnArr.length; 258 target.shouldUpdate = false;
259 260 // offloaded this to a function so it can be called from updateTargets too. 261 self.addElementsToColumn(target, target.children); 262 263 // append the columns to the documentFragment 264 for(var i = 0; i < target.columnArr.length; i++){ 265 frag.appendChild(target.columnArr[i].col); 266 } 267 268 // All done. Append the complete documentFragment to the DOM in one time. 269 target.container[0].appendChild(frag); 270 271 if(Cargo.Plugins.hasOwnProperty('elementResizer')){ 272 Cargo.Plugins.elementResizer.update(); 273 } 274 275 } 276 }; 277 278 self.vars.ticking = false; 279 280 if(typeof callback == "function"){ 281 callback(); 282 } 283 284 if(Cargo.hasOwnProperty('Event')){ 285 Cargo.Event.trigger('columnizer_update_complete'); 286 } 287 }, 288 289 addElementsToColumn: function(target, children){ 290 // add elements to columns. 291 if(target.availableColumns > 0){ 292 for(var b = 0; b < children.length; b++){ 293 // the column we're going to append the child to 294 var this_column; 295 296 // This makes adding padding easy for end-users. They just have to 297 // add padding to the children 298 if(!children[b].hasPaddingHelper){ 299 $(children[b].element).find('img').each(function(){ 300 this.style.maxWidth = this.getAttribute('width') + 'px'; 301 }); 302 303 var padding_helper = document.createElement("div"); 304 padding_helper.className = "padding_helper"; 305 306 // switch the padding from child to helper 307 padding_helper.style.paddingLeft = target.padding_left + 'px'; 308 padding_helper.style.paddingRight = target.padding_right + 'px'; 309 children[b].element.style.paddingLeft = '0px'; 310 children[b].element.style.paddingRight = '0px'; 311 312 // wrap element in helper 313 padding_helper.appendChild(children[b].element); 314 315 // update the cached element 316 children[b].paddingChild = children[b].element; 317 children[b].element = padding_helper; 318 children[b].paddingHelper = padding_helper; 319 children[b].hasPaddingHelper = true; 320 } 321 322 if(target.equalize){ 323 // find the shortest column to append the child to 324 this_column = self.getShortestColumn(target); 325 326 // don't add any height if we have an invisible element 327 if(children[b].visible){ 328 target.columnArr[this_column].height += (isNaN(children[b].lastUpdatedHeight) ? 1 : children[b].lastUpdatedHeight); 329 } 330 } else { 331 // just go from left to right 332 this_column = b % target.columnArr.length; 333 } 334 335 if(this_column >= 0){ 336 target.columnArr[this_column].col.appendChild(children[b].element); 337 } 338 } 339 } 340 }, 341 342 // Helper to figure out in what column we want to drop our next element 343 getShortestColumn: function(target) { 344 345 var lowest = 9e9, 346 lowestIndex = -1, 347 colHeight; 348 349 for(i = target.columnArr.length - 1; i > -1; i--){ 350 colHeight = target.columnArr[i].height; 351 if(colHeight <= lowest){ 352 lowestIndex = i;
353 lowest = colHeight; 354 } 355 } 356 357 return lowestIndex; 358 359 }, 360 361 imagesLoaded: function(target, callback){ 362 363 var incompleteImages = []; 364 365 if(target.nodeName == "IMG"){ 366 367 if(target.complete === false){ 368 incompleteImages.push(target); 369 } 370 371 } else { 372 373 $('img', target).each(function(){ 374 375 /* Todo: Get rid of this */ 376 if(this.getAttribute('src') === "/_gfx/thumb_custom.gif"){ 377 this.style.maxHeight = this.getAttribute('height') + 'px'; 378 } 379 380 if(this.complete === false){ 381 incompleteImages.push(this); 382 } 383 384 }); 385 386 } 387 388 if(incompleteImages.length === 0){ 389 390 // all images are done, callback. 391 typeof callback === "function" ? callback(0) : ''; 392 393 } else { 394 395 // not all images are done. Install timers to listen for when they receive dimensions. 396 var i, completeImageCount = 0; 397 398 for(i = 0; i < incompleteImages.length; i++){ 399 400 (function(img){ 401 402 var ival; 403 404 ival = setInterval(function(){ 405 406 if(img.naturalHeight !== 0 || img.complete){ 407 408 clearInterval(ival); 409 410 if(++completeImageCount === incompleteImages.length){ 411 typeof callback === "function" ? callback(incompleteImages.length) : ''; 412 } 413 414 } 415 416 }, 30); 417 418 })(incompleteImages[i]); 419 420 } 421 422 } 423 424 }, 425 426 updateTargets: function(){ 427 428 var targets = $('[data-columnize]'); 429 430 if(Cargo.hasOwnProperty('Event')){ 431 Cargo.Event.trigger('columnizer_update_targets'); 432 }; 433 434 targets.each(function(key, target){ 435 436 var $target = $(target), 437 selector = target.getAttribute('data-columnize'), 438 equalize = (target.getAttribute('data-columnize-equalize') == "true" || target.getAttribute('data-columnize-mason') == "true" ? true : false), 439 children = $target.find(selector).not('.padding_helper'); 440 441 // get options stored in the target data attributes 442 var targetID = target.getAttribute('data-columnize-id'), 443 first_element = children.first(), 444 padding_left = parseInt(first_element.css('padding-left')), 445 padding_right = parseInt(first_element.css('padding-right')), 446 childrenArr = [], 447 targetObj; 448 449 // make sure we get valid numbers 450 padding_left = (isNaN(padding_left) ? 0 : padding_left); 451 padding_right = (isNaN(padding_right) ? 0 : padding_right); 452 453 var horizontal_padding = padding_right + padding_left; 454 455 children.each(function(){ 456 457 // only process if the element is new 458 if(this.getAttribute('data-columnized') != "1"){ 459 // only process visible items 460 if($(this).is(':visible')){ 461 462 var height = this.offsetHeight; 463 464 if(height < 1){ 465 height = 1; 466 } 467 468 // add processed child to the array 469 childrenArr.push({ 470 element: this, 471 lastUpdatedHeight: height, 472 hasPaddingHelper: false 473 }); 474 475 this.setAttribute('data-columnized', '1'); 476 } 477 } 478 479 }); 480 481 if (typeof targetID == 'undefined' || targetID == false || targetID == null || $target.find('.column').length == 0) { 482 483 // hide while we're working 484 target.style.visibility = 'hidden'; 485 486 // this element hasn't been seen before so we're gonna create a new object for it. 487 // also generating a new ID for later retrieval. 488 targetID = ++self.vars.targetIndex; 489 490 // attach the ID to the element 491 $target.attr('data-columnize-id', targetID); 492 493 // create new object for this target 494 targetObj = { 495 container: $target, 496 selector: selector, 497 children: childrenArr, 498 columnCount: 0, 499 availableColumns: 0, 500 shouldUpdate: false, 501 padding_left: padding_left, 502 padding_right: padding_right, 503 fluid: (target.getAttribute('data-columnize-fluid') == "true" ? true : false), 504 width: (target.getAttribute('data-columnize-width') == "*" || isNaN(parseInt(target.getAttribute('data-columnize-width'))) || parseInt(target.getAttribute('data-columnize-width')) <= 0 ? 275 : parseInt(target.getAttribute('data-columnize-width')) + horizontal_padding),
505 equalize: equalize 506 }; 507 508 // add it to the plugin's target list 509 self.vars.targets[targetID] = targetObj; 510 511 } else { 512 513 // element has already been processed before. Retrieve it by it's ID. 514 targetObj = self.vars.targets[targetID]; 515 516 // update width & fluid 517 targetObj.fluid = (target.getAttribute('data-columnize-fluid') == "true" ? true : false); 518 targetObj.width = (target.getAttribute('data-columnize-width') == "*" || isNaN(parseInt(target.getAttribute('data-columnize-width'))) || parseInt(target.getAttribute('data-columnize-width')) <= 0 ? 275 : parseInt(target.getAttribute('data-columnize-width')) + (targetObj.padding_left + targetObj.padding_right)); 519 520 // concatenate the new children if any. 521 if(children.length > 0){ 522 targetObj.children = targetObj.children.concat(childrenArr); 523 } 524 525 // add the new children to the DOM. 526 self.addElementsToColumn(targetObj, childrenArr); 527 528 } 529 530 // check for hidden elements 531 var visibleElementCount = 0, 532 visible; 533 534 // TODO: this is expensive. Perhaps make an option to disable this if not needed 535 for(var i = 0; i < targetObj.children.length; i++){ 536 537 if(!targetObj.children[i].hasPaddingHelper){ 538 visible = $(targetObj.children[i].element).is(':visible'); 539 } else { 540 // don't use padding_helper as the user shouldn't deal with that element 541 visible = $(targetObj.children[i].element).find(selector).is(':visible'); 542 } 543 544 targetObj.children[i].visible = visible; 545 546 if(visible){ 547 visibleElementCount++; 548 } 549 550 } 551 552 // we need to store how many elements are visible to calculate the required amount of columns. 553 targetObj.visibleElementCount = visibleElementCount; 554 555 // check if we need to update. 556 self.update(function(){ 557 // all done. Show the container. 558 target.style.visibility = 'visible'; 559 560 if(Cargo.Plugins.hasOwnProperty('elementResizer')){ 561 Cargo.Plugins.elementResizer.update(); 562 } 563 564 var totalLoaded = 0, 565 height; 566 567 for(var i = 0; i < childrenArr.length; i++){ 568 (function(childObj){ 569 570 if(childObj.imagesLoadedStarted !== true){ 571 572 self.imagesLoaded(childObj.element, function(){ 573 574 height = childObj.element.clientHeight; 575 576 if(height < 1){ 577 height = 1; 578 } 579 580 childObj.lastUpdatedHeight = height; 581 582 if(++totalLoaded === childrenArr.length){ 583 self.update(undefined, true); 584 } else { 585 throttledUpdate(); 586 } 587 588 }); 589 590 childObj.imagesLoadedStarted = true; 591 592 } 593 594 })(childrenArr[i]); 595 }; 596 597 }, true); 598 599 600 }); 601 602 if(self.vars.inspector === true){ 603 self.addPaddingHelperListeners(); 604 } 605 606 } 607 }; 608 609 var throttledUpdate = throttle(function (event) { 610 self.update(undefined, true); 611 }, 100); 612 613 function throttle(fn, threshhold, scope) { 614 threshhold || (threshhold = 250); 615 var last, 616 deferTimer; 617 return function () { 618 var context = scope || this; 619 620 var now = +new Date, 621 args = arguments; 622 if (last && now < last + threshhold) { 623 // hold on to it 624 clearTimeout(deferTimer); 625 deferTimer = setTimeout(function () { 626 last = now; 627 fn.apply(context, args); 628 }, threshhold); 629 } else { 630 last = now; 631 fn.apply(context, args); 632 } 633 }; 634 } 635 636 $(function(){ 637 self.init(); 638 }); 639 640 // return some methods that should be public 641 return { 642 updateTargets: self.updateTargets, 643 update: self.update, 644 randomize: self.randomize, 645 getVars: function(){ 646 return self.vars; 647 }, 648 inspectorAPI: function(){ 649 if(self.options.hasOwnProperty('attributeConfig')){ 650 return self.options.attributeConfig; 651 } else { 652 // let the inspector know there's nothing to see here. 653 return false; 654 } 655 } 656 }; 657})(jQuery);
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.