PageSourceSearch

https://www.leice-lidar.com/jquery/jquery.filterizr.js

js leice-lidar.com collected 2026-10-07 15:32:51 UTC 38,016 bytes, 969 lines download raw bytes

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.1
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.selector, (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 {string} [selector] - the selector of your container.
105        * @param {Object} [options] - user options to override defaults.
106        * @constructor
107        */
108        init: function(selector, options) {
109            var self = $(selector).extend($.fn.filterizr.prototype);
110            //Default options
111            self.options = {
112                animationDuration: 0.5,
113                callbacks: {
114                    onFilteringStart: function() { },
115                    onFilteringEnd  : function() { }
116                },
117                delay: 0,
118                delayMode: 'progressive',
119                easing: 'ease-out',
120                filter: 'all',
121                filterOutCss: {
122                    'opacity': 0,
123                    'transform': 'scale(0.5)'
124                },
125                filterInCss: {
126                    'opacity': 1,
127                    'transform': 'scale(1)'
128                },
129                layout: 'sameSize',
130                selector: (typeof selector === 'string') ? selector : '.filtr-container',
131                setupControls: true
132            };
133            //No arguments constructor
134            if (arguments.length === 0) {
135                selector = self.options.selector;
136                options  = self.options;
137            }
138            //One argument constructor (only options)
139            if (arguments.length === 1 && typeof arguments[0] === 'object') options = arguments[0];
140            //If options argument provided, override defaults
141            if (options) {
142                self.setOptions(options);
143            }
144            //Private properties
145            self.css({ //Cache reference to container as jQuery obj and init its CSS
146                'padding' : 0,
147                'position': 'relative'
148            });
149            self._lastCategory = 0; //Highest value in data-category of .filtr-items
150            self._isAnimating  = false;
151            //.filtr-item collections
152            self._mainArray   = self._getFiltrItems();
153            self._subArrays   = self._makeSubarrays();
154            self._activeArray = self._getCollectionByFilter(self.options.filter);
155            //Used for multiple category filtering
156            self._toggledCategories = { };
157            //Used for search feature
158            self._typedText = $('input[data-search]').val() || '';
159            //Generate unique ID for resize events
160            self._uID = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
161                var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
162                return v.toString(16);
163            });
164            //Set up Filterizr events
165            self._setupEvents();
166            //Set up standard Filterizr controls (for multiple Filterizrs in your scene, set to false)
167            if (self.options.setupControls) self._setupControls();
168            //Start Filterizr!
169            self.filter(self.options.filter);
170            return self;
171        },
172
173        /***********************************
174        * Public methods
175        ***********************************/
176        /**
177        * Filters the items
178        * @param {number} targetFilter - the applied filter towards which items transition
179        */
180        filter: function(targetFilter) {
181            var self   = this,
182                target = self._getCollectionByFilter(targetFilter);
183
184            self.options.filter = targetFilter;
185            self.trigger('filteringStart');
186            //Filter items
187            self._handleFiltering(target);
188            //Apply search filter on top if activated
189            if (self._isSearchActivated()) self.search(self._typedText);
190        },
191
192        /**
193        * Toggles filters on/off and renders the new collection
194        * @param {number} toggledFilter - the filter to toggle
195        */
196        toggleFilter: function(toggledFilter) {
197            var self   = this,
198                target = [], i = 0;
199
200            self.trigger('filteringStart');
201            //Toggle the toggledFilter in the active categories
202            if (!self._toggledCategories[toggledFilter])
203                self._toggledCategories[toggledFilter] = true;
204            else
205                delete self._toggledCategories[toggledFilter];
206
207            //If a filter is toggled on then display only items belonging to that category
208            if (self._multifilterModeOn()) {
209                target = self._makeMultifilterArray();
210                //Filter items
211                self._handleFiltering(target);
212                //Apply search filter on top if activated
213                if (self._isSearchActivated()) self.search(self._typedText);
214            }
215            //If all filters toggled off then display unfiltered gallery
216            else {
217                //Filter items
218                self.filter('all');
219                //Apply search filter on top if activated
220                if (self._isSearchActivated()) self.search(self._typedText);
221            }
222        },
223
224        /**
225        * Searches the contents of .filtr-item elements, filters them and renders the results
226        * @param {string} text to search in contents of .filtr-item elements
227        */
228        search: function(text) {
229            var self   = this,
230                //get active category
231                array  = self._multifilterModeOn() ?
232                            self._makeMultifilterArray() :
233                            self._getCollectionByFilter(self.options.filter),
234                target = [], i = 0;
235
236            if (self._isSearchActivated()) {
237                for (i = 0; i < array.length; i++) {
238                    //Check if the text typed in the textbox is contained in the .filtr-item element
239                    //and add it to the target array
240                    var containsText = array[i].text().toLowerCase().indexOf(text.toLowerCase()) > -1;
241                    if (containsText) {
242                        target.push(array[i]);
243                    }
244                }
245            }
246            //Show the results
247            if (target.length > 0) {
248                self._handleFiltering(target);
249            }
250            //If there are no results
251            else {
252                //and search is activated, show blank
253                if (self._isSearchActivated()) {
254                    for (i = 0; i < self._activeArray.length; i++) {
255                        self._activeArray[i]._filterOut();
256                    }
257                }
258                //if search is not activated display gallery with last applied filter
259                else {
260                    self._handleFiltering(array);
261                }
262            }
263        },
264
265        /**
266        * Shuffles the active collection and rearranges it on screen
267        */
268        shuffle: function() {
269            var self = this;
270
271            self._mainArray = self._fisherYatesShuffle(self._mainArray);
272            self._subArrays = self._makeSubarrays();
273
274            var target = self._multifilterModeOn() ?
275                            self._makeMultifilterArray() :
276                            self._getCollectionByFilter(self.options.filter);
277
278            if (self._isSearchActivated())
279                self.search(self._typedText);
280            else
281                self._placeItems(target);
282        },
283
284        /**
285        * Sorts the active collection and rearranges it on screen.
286        * @param {string} [attr] - attr based on which to sort (default: 'domIndex' / possible: 'domIndex', 'sortData', 'w', 'h').
287        * @param {string} [sortOrder] - asc/desc (default: 'asc').
288        */
289        sort: function(attr, sortOrder) {
290            var self  = this;
291            //Set defaults
292            attr 	  = attr      || 'domIndex';
293            sortOrder = sortOrder || 'asc';
294
295            //Register sort attr on all elements if it is a user-defined data-attribute
296            var isUserAttr = attr !== 'domIndex' && attr !== 'sortData' && attr !== 'w' && attr!== 'h';
297            if (isUserAttr) {
298                for (var i = 0; i < self._mainArray.length; i++) {
299                    self._mainArray[i][attr] = self._mainArray[i].data(attr);
300                }
301            }
302            //Sort items
303            self._mainArray.sort(self._comparator(attr, sortOrder));
304            self._subArrays = self._makeSubarrays();
305            //Place sorted collection to new positions
306            var target = self._multifilterModeOn() ?
307                            self._makeMultifilterArray() :
308                            self._getCollectionByFilter(self.options.filter);
309
310            if (self._isSearchActivated())
311                self.search(self._typedText);
312            else
313                self._placeItems(target);
314        },
315
316        /**
317        * Overrides the default options with the user-provided ones.
318        * @param {object} options - the user-provided options to override defaults.
319        */
320        setOptions: function(options) {
321            var self = this, i = 0;
322            //Override options
323            for (var prop in options) {
324                self.options[prop] = options[prop];
325            }
326            //If the user tries to override animationDuration, easing, delay or delayMode
327            if (self._mainArray && (options.animationDuration || options.delay || options.easing || options.delayMode)) {
328                for (i = 0; i < self._mainArray.length; i++) {
329                    self._mainArray[i].css('transition', 'all ' + self.options.animationDuration + 's ' +  self.options.easing + ' ' + self._mainArray[i]._calcDelay() + 'ms');
330                }
331            }
332            //If the user has not defined a transform property in their CSS, add it
333            //while overriding, including translates for movement
334            if (!self.options.filterInCss.transform)  self.options.filterInCss.transform  = 'translate3d(0,0,0)';
335            if (!self.options.filterOutCss.transform) self.options.filterOutCss.transform = 'translate3d(0,0,0)';
336        },
337
338        /***********************************
339        * Private & helper methods
340        ***********************************/
341        /**
342        * Finds all .filtr-item elements in the .filtr-container and sets them up before returning them in an array.
343        * @return {Object[]}  all .filtr-item elements contained in Filterizr's container.
344        * @private
345        */
346        _getFiltrItems: function() {
347            var self       = this,
348            filtrItems = $(self.find('.filtr-item')),
349            itemsArray = [];
350
351            $.each(filtrItems, function(i, e) {
352                //Set item up as Filtr object & push to array
353                var item = $(e).extend(FiltrItemProto)._init(i, self);
354                itemsArray.push(item);
355            });
356            return itemsArray;
357        },
358
359        /**
360        * Divide .filtr-item elements into sub-arrays based on data-category attribute.
361        * @return {Object[Object[self._lastCategory]]} Array of arrays including items grouped by data-category.
362        * @private
363        */
364        _makeSubarrays: function() {
365            var self = this,
366            subArrays = [];
367
368            for (var i = 0; i < self._lastCategory; i++) subArrays.push([]);
369
370            //Populate the sub-arrays
371            for (i = 0; i < self._mainArray.length; i++) {
372                //Multiple categories scenario
373                if (typeof self._mainArray[i]._category === 'object') {
374                    for (var p in self._mainArray[i]._category)
375                        subArrays[self._mainArray[i]._category[p] - 1].push(self._mainArray[i]);
376                }
377                //Single category
378                else subArrays[self._mainArray[i]._category - 1].push(self._mainArray[i]);
379            }
380            return subArrays;
381        },
382
383        /**
384        * Make a .filtr-item array based on the activated filters
385        * @return {Object[]} array consisting of the .filtr-item elements belonging to active filters
386        * @private
387        */
388        _makeMultifilterArray: function() {
389            var self   = this,
390                target = [], addedMap = {};
391
392            for (var i = 0; i < self._mainArray.length; i++) {
393                //If the item belongs to multiple categories
394                var item = self._mainArray[i],
395                    belongsToCategory = false,
396                    isUnique = item.domIndex in addedMap === false;
397                //Check if item belongs to categories whose filters are toggled on
398                if (Array.isArray(item._category)) {
399                    for (var x = 0; x < item._category.length; x++) {
400                        if (item._category[x] in self._toggledCategories) {
401                            belongsToCategory = true;
402                            break;
403                        }
404                    }
405                }
406                else {
407                    if (item._category in self._toggledCategories) belongsToCategory = true;
408                }
409                //If the item is not already visible and belongs to a category
410                //of the toggled on filters push it to target collection
411                if (isUnique && belongsToCategory) {
412                    addedMap[item.domIndex] = true;
413                    target.push(item);
414                }
415            }
416            return target;
417        },
418
419        /**
420        * Detect and set up preset controls.
421        * @private
422        */
423        _setupControls: function() {
424            var self = this;
425            //Filter controls
426            $('*[data-filter]').click(function() {
427                var targetFilter = $(this).data('filter');
428                //Exit case
429                if (self.options.filter === targetFilter) return;
430                self.filter(targetFilter);
431            });
432            //Multiple filter controls
433            $('*[data-multifilter]').click(function() {
434                var targetFilter = $(this).data('multifilter');
435                if (targetFilter === 'all') {
436                    self._toggledCategories = { };
437                    self.filter('all');
438                }
439                else {
440                    self.toggleFilter(targetFilter);
441                }
442            });
443            //Shuffle control
444            $('*[data-shuffle]').click(function() {
445                self.shuffle();
446            });
447            //Sort controls
448            $('*[data-sortAsc]').click(function() {
449                var sortAttr = $('*[data-sortOrder]').val();
450                self.sort(sortAttr, 'asc');
451            });
452            $('*[data-sortDesc]').click(function() {
453                var sortAttr = $('*[data-sortOrder]').val();
454                self.sort(sortAttr, 'desc');
455            });
456            //Search control
457            $('input[data-search]').keyup(function() {
458                self._typedText = $(this).val();
459                self._delayEvent(function() {
460                    self.search(self._typedText);
461                }, 250, self._uID);
462            });
463        },
464
465        /**
466        * Set up window and Filterizr events.
467        * @private
468        */
469        _setupEvents: function() {
470            var self = this;
471            //Window resize event
472            $(global).resize(function() {
473                self._delayEvent(function() {
474                    self.trigger('resizeFiltrContainer');
475                }, 250, self._uID);
476            });
477            //Filterizr events
478            self
479            //Container resize event
480            .on('resizeFiltrContainer', function() {
481                if (self._multifilterModeOn())
482                    self.toggleFilter();
483                else
484                    self.filter(self.options.filter);
485            })
486            //onFilteringStart event
487            .on('filteringStart', function() {
488                self.options.callbacks.onFilteringStart();
489            })
490            //onFilteringEnd event
491            .on('filteringEnd', function() {
492                self.options.callbacks.onFilteringEnd();
493            });
494        },
495
496        /**
497        * Calculates the final positions of items being filtered in, updates the height of .filtr-container.
498        * @return {Object[]} array of future item positions.
499        * @private
500        */
501        _calcItemPositions: function() {
502            var self  = this,
503            array = self._activeArray,
504            //Container data
505            containerHeight = 0,
506            cols = Math.round(self.width() / self.find('.filtr-item').outerWidth()),
507            rows = 0,
508            //Item data
509            itemWidth  = array[0].outerWidth(),
510            itemHeight = 0,
511            //Position calculation vars
512            left = 0, top = 0,
513            //Loop vars
514            i = 0, x = 0,
515            //Array of positions to return
516            posArray = [];
517
518            //Layout for items of varying sizes
519            if (self.options.layout === 'packed') {
520                //Cache current item width/height
521                $.each(self._activeArray, function(i, e) {
522                    e._updateDimensions();
523                });
524                //Instantiate new Packer, set up grid
525                var packer = new Packer(self.outerWidth());
526                packer.fit(self._activeArray);
527                for (i = 0; i < array.length; i++) {
528                    posArray.push({
529                        left: array[i].fit.x,
530                        top: array[i].fit.y
531                    });
532                }
533                containerHeight = packer.root.h;
534            }
535            //Horizontal layout
536            if (self.options.layout === 'horizontal') {
537                rows = 1;
538                for (i = 1; i <= array.length; i++) {
539                    itemWidth = array[i - 1].outerWidth();
540                    itemHeight = array[i - 1].outerHeight();
541                    posArray.push({
542                        left: left,
543                        top: top
544                    });
545                    left += itemWidth;
546                    if (containerHeight < itemHeight) containerHeight = itemHeight;
547                }
548            }
549            //Vertical layout
550            else if (self.options.layout === 'vertical') {
551                for (i = 1; i <= array.length; i++) {
552                    itemHeight = array[i - 1].outerHeight();
553                    posArray.push({
554                        left: left,
555                        top: top
556                    });
557                    top += itemHeight;
558                }
559                containerHeight = top;
560            }
561            //Layout of items for same height and varying width
562            else if (self.options.layout === 'sameHeight') {
563                rows = 1;
564                var rowWidth = self.outerWidth();
565                for (i = 1; i <= array.length; i++) {
566                    itemWidth = array[i - 1].width();
567                    var itemOuterWidth = array[i - 1].outerWidth(),
568                    nextItemWidth = 0;
569                    if (array[i]) nextItemWidth = array[i].width();
570                    posArray.push({
571                        left: left,
572                        top: top
573                    });
574                    x = left + itemWidth + nextItemWidth;
575                    if (x > rowWidth) {
576                        x 	 = 0;
577                        left = 0;
578                        top  += array[0].outerHeight();
579                        rows++;
580                    }
581                    else left += itemOuterWidth;
582                }
583                containerHeight = rows * array[0].outerHeight();
584            }
585            //Layout for items of same width and varying height
586            else if (self.options.layout === 'sameWidth') {
587                //Get positions
588                for (i = 1; i <= array.length; i++) {
589                    posArray.push({
590                        left: left,
591                        top: top
592                    });
593                    if (i % cols === 0) rows++;
594                    left += itemWidth;
595                    top = 0;
596                    if (rows > 0) {
597                        x = rows;
598                        while (x > 0) {
599                            top += array[i - (cols * x)].outerHeight();
600                            x--;
601                        }
602                    }
603                    if (i % cols === 0) left = 0;
604                }
605                //Calculate containerHeight
606                for (i = 0; i < cols; i++) {
607                    var columnHeight = 0, index = i;
608                    while(array[index]) {
609                        columnHeight += array[index].outerHeight();
610                        index += cols;
611                    }
612                    if (columnHeight > containerHeight) {
613                        containerHeight = columnHeight;
614                        columnHeight = 0;
615                    }
616                    else
617                    columnHeight = 0;
618                }
619            }
620            //Layout for items of exactly same size
621            else if (self.options.layout === 'sameSize') {
622                for (i = 1; i <= array.length; i++) {
623                    //Push first point at (left: 0, top: 0)
624                    posArray.push({
625                        left: left,
626                        top: top
627                    });
628                    //Set left and top properties for next point before next iteration
629                    left += itemWidth;
630                    //On row change calc new top and reset left
631                    if (i % cols === 0) {
632                        top += array[0].outerHeight();
633                        left = 0;
634                    }
635                }
636                rows = Math.ceil(array.length / cols);
637                containerHeight = rows * array[0].outerHeight();
638            }
639            //Update the height of .filtr-container based on new positions
640            self.css('height', containerHeight);
641            return posArray;
642        },
643
644        /**
645        * Handles filtering in/out and reposition items when transition between categories
646        * @param {Object[]} the target array towards which to filter
647        * @private
648        */
649        _handleFiltering: function(target) {
650            var self = this,
651                toFilterOut = self._getArrayOfUniqueItems(self._activeArray, target);
652            //Minimize all .filtr-item elements that are not the same between categories
653            for (var i = 0; i < toFilterOut.length; i++) {
654                toFilterOut[i]._filterOut();
655            }
656            self._activeArray = target;
657            //Reposition same items and filter in new
658            self._placeItems(target);
659        },
660
661        /**
662        * Determines if the user is using data-multifilter controls or simple data-filter controls
663        * @return {boolean} indicating whether multiple filter mode is on
664        * @private
665        */
666        _multifilterModeOn: function() {
667            var self = this;
668            return Object.keys(self._toggledCategories).length > 0;
669        },
670
671        /**
672        * Determines if the user has something typed in the search box
673        * @return {boolean} indicating whether the user has searched
674        * @private
675        */
676        _isSearchActivated: function() {
677            var self = this;
678            return self._typedText.length > 0;
679        },
680
681        /**
682        * Places .filtr-item elements on the grid positions
683        * @param {Object[]} arr - an array consisting of .filtr-item elements
684        * @private
685        */
686        _placeItems: function(arr) {
687            var self = this;
688            //Tag gallery state as animating
689            self._isAnimating = true;
690            //Recalculate positions and filter in items
691            self._itemPositions = self._calcItemPositions();
692            for (var i = 0; i < arr.length; i++) {
693                arr[i]._filterIn(self._itemPositions[i]);
694            }
695        },
696
697        /**
698        * Returns item collection based on a certain filter
699        * @param {string|number} filter of category to return
700        * @return {Object[]} one of the item collections based on filter
701        * @private
702        */
703        _getCollectionByFilter: function(filter) {
704            var self = this;
705            return filter === 'all' ? self._mainArray : self._subArrays[filter - 1];
706        },
707
708        /**
709        * Used to make deep copies of the predefined filters
710        * in the options for the filterIn/Out methods of items.
711        * @see _filterIn and _filterOut methods in FiltrItemProto.
712        * @param {Object} obj - is the source object to make a deep copy from.
713        * @return {Object} Deep copy of the obj param.
714        * @private
715        */
716        _makeDeepCopy: function(obj) {
717            var r = {};
718            for (var p in obj) r[p] = obj[p];
719            return r;
720        },
721
722        /**
723        * Comparator factory used to produce camparers for sorting.
724        * @see Filterizr.prototype.sort.
725        * @param {string} prop - property based on which to sort ('domIndex', 'sortData', 'w', 'h')
726        * @param {string} sortOrder - 'asc'/'desc'
727        * @return {function} comparer which takes arguments
728        * @private
729        */
730        _comparator: function(prop, sortOrder) {
731            return function(a, b) {
732                if (sortOrder === 'asc') {
733                    if (a[prop] < b[prop])
734                    return -1;
735                    else if (a[prop] > b[prop])
736                    return 1;
737                    else
738                    return 0;
739                }
740                else if (sortOrder === 'desc') {
741                    if (b[prop] < a[prop])
742                    return -1;
743                    else if (b[prop] > a[prop])
744                    return 1;
745                    else
746                    return 0;
747                }
748            };
749        },
750
751        /**
752        * Modified version of Jeffery To's array intersection method
753        * @see {@link http://www.falsepositives.com/index.php/2009/12/01/javascript-function-to-get-the-intersect-of-2-arrays/}
754        * @return {Object[]} a disjoint array containing the elements of the first array not found in the second
755        * @private
756        */
757        _getArrayOfUniqueItems: function(arr1, arr2) {
758            var r = [], o = {}, l = arr2.length, i, v;
759            for (i = 0; i < l; i++) {
760                o[arr2[i].domIndex] = true;
761            }
762            l = arr1.length;
763            for (i = 0; i < l; i++) {
764                v = arr1[i];
765                if (!(v.domIndex in o)) {
766                    r.push(v);
767                }
768            }
769            return r;
770        },
771
772        /**
773        * Brahn's take on CMS's solution to calling the window.resize event at set
774        * intervals in multiple places in the code using a Java-like UUID with a regexp
775        * @see {@link http://stackoverflow.com/questions/2854407/javascript-jquery-window-resize-how-to-fire-after-the-resize-is-completed}
776        * @return {function} which calls the callback or just clears the timer
777        * @private
778        */
779        _delayEvent: (function () {
780            var self = this, timers = {};
781            return function (callback, ms, uniqueId) {
782                if (uniqueId === null) {
783                    throw Error("UniqueID needed");
784                }
785                if (timers[uniqueId]) {
786                    clearTimeout (timers[uniqueId]);
787                }
788                timers[uniqueId] = setTimeout(callback, ms);
789            };
790        })(),
791
792        /**
793        * Fisher-Yates array shuffling algorithm implemented for JavaScript.
794        * @return {Object[]} shuffled array.
795        * @private
796        */
797        _fisherYatesShuffle: function shuffle(array) {
798            var m = array.length, t, i;
799            // While there remain elements to shuffle…
800            while (m) {
801                // Pick a remaining element…
802                i = Math.floor(Math.random() * m--);
803                // And swap it with the current element.
804                t = array[m];
805                array[m] = array[i];
806                array[i] = t;
807            }
808            return array;
809        }
810    };
811
812    /**
813    * FiltrItem Prototype
814    */
815    var FiltrItemProto = {
816
817        /**
818        * Transforms a jQuery item with .filtr-item class into a FiltrItem.
819        * @param {number} index - initial item order based on position in DOM.
820        * @param {Filterizr} parent - reference to Filterizr container containing this Filtr Item.
821        * @return {jQuery} this - to facilitate method chaining.
822        * @constructor
823        */
824        _init: function(index, parent) {
825            var self = this, delay = 0;
826            //Private item properties
827            self._parent   = parent;			  //Ref to parent Filterizr object
828            self._category = self._getCategory(); //data-category values
829            self._lastPos  = {};				  //Used for animations
830            //Public properties - used for sorting
831            self.domIndex = index;
832            self.sortData = self.data('sort');
833            //Item Dimensions used for Bin Packing algorithm (packed layout) and sorting.
834            self.w = 0;
835            self.h = 0;
836            //self states
837            self._isFilteredOut = true;
838            self._filteringOut  = false;
839            self._filteringIn   = false;
840            //Determine delay & set initial item styles
841            self.css(parent.options.filterOutCss)
842            .css({
843                '-webkit-backface-visibility': 'hidden',
844                'perspective': '1000px',
845                '-webkit-perspective': '1000px',
846                '-webkit-transform-style': 'preserve-3d',
847                'position': 'absolute',
848                'transition': 'all ' + parent.options.animationDuration + 's ' + parent.options.easing + ' ' + self._calcDelay() + 'ms'
849            });
850            //Events
851            self.on("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(){
852                self._onTransitionEnd();
853            });
854            return self;
855        },
856
857        /**
858        * Updates the dimensions (width/height) of the item.
859        * @private
860        */
861        _updateDimensions: function() {
862            var self = this;
863            self.w = self.outerWidth();
864            self.h = self.outerHeight();
865        },
866
867        /**
868        * Calculates and returns the value of delay to apply to transition-delay in ms, depending on delayMode
869        * @return {number} value to apply to transition-delay in ms.
870        * @private
871        */
872        _calcDelay: function() {
873            var self = this, r = 0;
874            if (self._parent.options.delayMode === 'progressive')
875            r = self._parent.options.delay * self.domIndex;
876            else
877            if (self.domIndex % 2 === 0) r = self._parent.options.delay;
878            return r;
879        },
880
881        /**
882        * Determines which categories this items belongs to and updates the _lastCategory prop of Filterizr.
883        * @throws {InvalidArgumentException} data-category of .filtr-item elements must be integer or string of integers delimited by ', '
884        * @return {Object[]|number} the categories this item belongs to.
885        * @private
886        */
887        _getCategory: function() {
888            var self = this,
889            ret  = self.data('category');
890            //If more than one category provided
891            if (typeof ret === 'string') {
892                ret = ret.split(', ');
893                for (var n in ret) {
894                    //Error checking: make sure data-category has an integer as its value
895                    if (isNaN(parseInt(ret[n]))) {
896                        throw new Error('Filterizr: the value of data-category must be a number, starting from value 1 and increasing.');
897                    }
898                    if (ret[n] > self._parent._lastCategory) {
899                        self._parent._lastCategory = ret[n];
900                    }
901                }
902            }
903            else {
904                if (ret > self._parent._lastCategory)
905                    self._parent._lastCategory = ret;
906            }
907            return ret;
908        },
909
910        /**
911        * Handles the transitionEnd event.
912        * @private
913        */
914        _onTransitionEnd: function() {
915            var self = this;
916            //finished filtering out
917            if (self._filteringOut) {
918                $(self).addClass('filteredOut');
919                self._isFilteredOut = true;
920                self._filteringOut  = false;
921            }
922            //finished filtering in
923            else if (self._filteringIn) {
924                self._isFilteredOut = false;
925                self._filteringIn 	= false;
926            }
927            //if animating trigger filteringEnd event once on parent
928            if (self._parent._isAnimating) {
929                self._parent.trigger('filteringEnd');
930                self._parent._isAnimating = false;
931            }
932        },
933
934        /**
935        * Filters out the item.
936        * @private
937        */
938        _filterOut: function() {
939            var self         = this,
940                filterOutCss = self._parent._makeDeepCopy(self._parent.options.filterOutCss);
941            //Auto add translate to transform over user-defined filterOut styles
942            filterOutCss.transform += ' translate3d(' + self._lastPos.left + 'px,' + self._lastPos.top + 'px, 0)';
943            //Play animation
944            self.css(filterOutCss);
945            //Tag as filteringOut for transitionend event
946            self._filteringOut = true;
947        },
948
949        /**
950        * Filters in the item.
951        * @param {Object} targetPos - is the position to move to with transform-translate
952        * @private
953        */
954        _filterIn: function(targetPos) {
955            var self  	    = this,
956                filterInCss = self._parent._makeDeepCopy(self._parent.options.filterInCss);
957            //Remove the filteredOut class
958            $(self).removeClass('filteredOut');
959            //Tag as filtering in for transitionend event
960            self._filteringIn = true;
961            self._lastPos 	  = targetPos;
962            //Auto add translate to transform over user-defined filterIn styles
963            filterInCss.transform += ' translate3d(' + targetPos.left + 'px,' + targetPos.top + 'px, 0)';
964            //Play animation
965            self.css(filterInCss);
966        }
967    };
968
969})(this, 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.