/** * filterizr is a jquery plugin that sorts, shuffles and applies stunning filters over * responsive galleries using css3 transitions and custom css effects. * * @author yiotis kaltsikis * @see {@link http://yiotis.net/filterizr} * @version 1.2.5 * @license mit license */ (function(global, $) { 'use strict'; //make sure jquery exists if (!$) throw new error('filterizr requires jquery to work.'); /** * modified version of jake gordon's bin packing algorithm used for filterizr's 'packed' layout * @see {@link https://github.com/jakesgordon/bin-packing} */ var packer = function(w) { this.init(w); }; packer.prototype = { init: function(w) { this.root = { x: 0, y: 0, w: w }; }, fit: function(blocks) { var n, node, block, len = blocks.length; var h = len > 0 ? blocks[0].h : 0; this.root.h = h; for (n = 0; n < len ; n++) { block = blocks[n]; if ((node = this.findnode(this.root, block.w, block.h))) block.fit = this.splitnode(node, block.w, block.h); else block.fit = this.growdown(block.w, block.h); } }, findnode: function(root, w, h) { if (root.used) return this.findnode(root.right, w, h) || this.findnode(root.down, w, h); else if ((w <= root.w) && (h <= root.h)) return root; else return null; }, splitnode: function(node, w, h) { node.used = true; node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h }; node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h }; return node; }, growdown: function(w, h) { var node; this.root = { used: true, x: 0, y: 0, w: this.root.w, h: this.root.h + h, down: { x: 0, y: this.root.h, w: this.root.w, h: h }, right: this.root }; if ((node = this.findnode(this.root, w, h))) return this.splitnode(node, w, h); else return null; } }; /** * only filterizr method extracted on jquery.fn. * instantiates a new filterizr or calls any of the public filterizr methods. * @return {jquery} this - to facilitate jquery method chaining. */ $.fn.filterizr = function() { var self = this, args = arguments; //create the filterizr obj as an internal private property on the current object //to serve as a private namespace if (!self._fltr) { self._fltr = $.fn.filterizr.prototype.init(self, (typeof args[0] === 'object' ? args[0] : undefined)); } //call all public filterizr methods through the private filterizr object if (typeof args[0] === 'string') { if (args[0].lastindexof('_') > -1) throw new error('filterizr error: you cannot call private methods'); if (typeof self._fltr[args[0]] === 'function') { self._fltr[args[0]](args[1], args[2]); } else throw new error('filterizr error: there is no such function'); } return self; }; /** * filterizr prototype */ $.fn.filterizr.prototype = { /** * filterizr constructor. * @param {object} [container] - your container. * @param {object} [options] - user options to override defaults. * @constructor */ init: function(container, options) { var self = $(container).extend($.fn.filterizr.prototype); //default options self.options = { animationduration: 0.5, callbacks: { onfilteringstart: function() { }, onfilteringend: function() { }, onshufflingstart: function() { }, onshufflingend: function() { }, onsortingstart: function() { }, onsortingend: function() { } }, delay: 0, delaymode: 'progressive', easing: 'ease-out', filter: 'all', filteroutcss: { 'opacity': 0, 'transform': 'scale(0.5)' }, filterincss: { 'opacity': 1, 'transform': 'scale(1)' }, layout: 'samesize', setupcontrols: true }; //no arguments constructor if (arguments.length === 0) { options = self.options; } //one argument constructor (only options) if (arguments.length === 1 && typeof arguments[0] === 'object') options = arguments[0]; //if options argument provided, override defaults if (options) { self.setoptions(options); } //private properties self.css({ //cache reference to container as jquery obj and init its css 'padding' : 0, 'position': 'relative' }); self._lastcategory = 0; //highest value in data-category of .filtr-items self._isanimating = false; self._isshuffling = false; self._issorting = false; //.filtr-item collections self._mainarray = self._getfiltritems(); self._subarrays = self._makesubarrays(); self._activearray = self._getcollectionbyfilter(self.options.filter); //used for multiple category filtering self._toggledcategories = { }; //used for search feature self._typedtext = $('input[data-search]').val() || ''; //generate unique id for resize events self._uid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); return v.tostring(16); }); //set up filterizr events self._setupevents(); //set up standard filterizr controls (for multiple filterizrs in your scene, set to false) if (self.options.setupcontrols) self._setupcontrols(); //start filterizr! self.filter(self.options.filter); return self; }, /*********************************** * public methods ***********************************/ /** * filters the items * @param {number} targetfilter - the applied filter towards which items transition */ filter: function(targetfilter) { var self = this, target = self._getcollectionbyfilter(targetfilter); self.options.filter = targetfilter; self.trigger('filteringstart'); //filter items self._handlefiltering(target); //apply search filter on top if activated if (self._issearchactivated()) self.search(self._typedtext); }, /** * toggles filters on/off and renders the new collection * @param {number} toggledfilter - the filter to toggle */ togglefilter: function(toggledfilter) { var self = this, target = [], i = 0; self.trigger('filteringstart'); //toggle the toggledfilter in the active categories //if undefined (in case of window resize) ignore if (toggledfilter) { if (!self._toggledcategories[toggledfilter]) self._toggledcategories[toggledfilter] = true; else delete self._toggledcategories[toggledfilter]; } //if a filter is toggled on then display only items belonging to that category if (self._multifiltermodeon()) { target = self._makemultifilterarray(); //filter items self._handlefiltering(target); //apply search filter on top if activated if (self._issearchactivated()) self.search(self._typedtext); } //if all filters toggled off then display unfiltered gallery else { //filter items self.filter('all'); //apply search filter on top if activated if (self._issearchactivated()) self.search(self._typedtext); } }, /** * searches the contents of .filtr-item elements, filters them and renders the results * @param {string} text to search in contents of .filtr-item elements */ search: function(text) { var self = this, //get active category array = self._multifiltermodeon() ? self._makemultifilterarray() : self._getcollectionbyfilter(self.options.filter), target = [], i = 0; if (self._issearchactivated()) { for (i = 0; i < array.length; i++) { //check if the text typed in the textbox is contained in the .filtr-item element //and add it to the target array var containstext = array[i].text().tolowercase().indexof(text.tolowercase()) > -1; if (containstext) { target.push(array[i]); } } } //show the results if (target.length > 0) { self._handlefiltering(target); } //if there are no results else { //and search is activated, show blank if (self._issearchactivated()) { for (i = 0; i < self._activearray.length; i++) { self._activearray[i]._filterout(); } } //if search is not activated display gallery with last applied filter else { self._handlefiltering(array); } } }, /** * shuffles the active collection and rearranges it on screen */ shuffle: function() { var self = this; //shufflingstart callback self._isanimating = true; self._isshuffling = true; self.trigger('shufflingstart'); self._mainarray = self._fisheryatesshuffle(self._mainarray); self._subarrays = self._makesubarrays(); var target = self._multifiltermodeon() ? self._makemultifilterarray() : self._getcollectionbyfilter(self.options.filter); if (self._issearchactivated()) self.search(self._typedtext); else self._placeitems(target); }, /** * sorts the active collection and rearranges it on screen. * @param {string} [attr] - attr based on which to sort (default: 'domindex' / possible: 'domindex', 'sortdata', 'w', 'h'). * @param {string} [sortorder] - asc/desc (default: 'asc'). */ sort: function(attr, sortorder) { var self = this; //set defaults attr = attr || 'domindex'; sortorder = sortorder || 'asc'; //sortingstart callback self._isanimating = true; self._issorting = true; self.trigger('sortingstart'); //register sort attr on all elements if it is a user-defined data-attribute var isuserattr = attr !== 'domindex' && attr !== 'sortdata' && attr !== 'w' && attr!== 'h'; if (isuserattr) { for (var i = 0; i < self._mainarray.length; i++) { self._mainarray[i][attr] = self._mainarray[i].data(attr); } } //sort items self._mainarray.sort(self._comparator(attr, sortorder)); self._subarrays = self._makesubarrays(); //place sorted collection to new positions var target = self._multifiltermodeon() ? self._makemultifilterarray() : self._getcollectionbyfilter(self.options.filter); if (self._issearchactivated()) self.search(self._typedtext); else self._placeitems(target); }, /** * overrides the default options with the user-provided ones. * @param {object} options - the user-provided options to override defaults. */ setoptions: function(options) { var self = this, i = 0; //override options for (var prop in options) { self.options[prop] = options[prop]; } //if the user tries to override animationduration, easing, delay or delaymode if (self._mainarray && (options.animationduration || options.delay || options.easing || options.delaymode)) { for (i = 0; i < self._mainarray.length; i++) { self._mainarray[i].css('transition', 'all ' + self.options.animationduration + 's ' + self.options.easing + ' ' + self._mainarray[i]._calcdelay() + 'ms'); } } //if the user tries to override a callback, make sure undefined callbacks are set to empty functions if (options.callbacks) { if (!options.callbacks.onfilteringstart) self.options.callbacks.onfilteringstart = function() { }; if (!options.callbacks.onfilteringend) self.options.callbacks.onfilteringend = function() { }; if (!options.callbacks.onshufflingstart) self.options.callbacks.onshufflingstart = function() { }; if (!options.callbacks.onshufflingend) self.options.callbacks.onshufflingend = function() { }; if (!options.callbacks.onsortingstart) self.options.callbacks.onsortingstart = function() { }; if (!options.callbacks.onsortingend) self.options.callbacks.onsortingend = function() { }; } //if the user has not defined a transform property in their css, add it //while overriding, including translates for movement if (!self.options.filterincss.transform) self.options.filterincss.transform = 'translate3d(0,0,0)'; if (!self.options.filteroutcss.transform) self.options.filteroutcss.transform = 'translate3d(0,0,0)'; }, /*********************************** * private & helper methods ***********************************/ /** * finds all .filtr-item elements in the .filtr-container and sets them up before returning them in an array. * @return {object[]} all .filtr-item elements contained in filterizr's container. * @private */ _getfiltritems: function() { var self = this, filtritems = $(self.find('.filtr-item')), itemsarray = []; $.each(filtritems, function(i, e) { //set item up as filtr object & push to array var item = $(e).extend(filtritemproto)._init(i, self); itemsarray.push(item); }); return itemsarray; }, /** * divide .filtr-item elements into sub-arrays based on data-category attribute. * @return {object[object[self._lastcategory]]} array of arrays including items grouped by data-category. * @private */ _makesubarrays: function() { var self = this, subarrays = []; for (var i = 0; i < self._lastcategory; i++) subarrays.push([]); //populate the sub-arrays for (i = 0; i < self._mainarray.length; i++) { //multiple categories scenario if (typeof self._mainarray[i]._category === 'object') { var length = self._mainarray[i]._category.length; for (var x = 0; x < length; x++) { subarrays[self._mainarray[i]._category[x] - 1].push(self._mainarray[i]); } } //single category else subarrays[self._mainarray[i]._category - 1].push(self._mainarray[i]); } return subarrays; }, /** * make a .filtr-item array based on the activated filters * @return {object[]} array consisting of the .filtr-item elements belonging to active filters * @private */ _makemultifilterarray: function() { var self = this, target = [], addedmap = {}; for (var i = 0; i < self._mainarray.length; i++) { //if the item belongs to multiple categories var item = self._mainarray[i], belongstocategory = false, isunique = item.domindex in addedmap === false; //check if item belongs to categories whose filters are toggled on if (array.isarray(item._category)) { for (var x = 0; x < item._category.length; x++) { if (item._category[x] in self._toggledcategories) { belongstocategory = true; break; } } } else { if (item._category in self._toggledcategories) belongstocategory = true; } //if the item is not already visible and belongs to a category //of the toggled on filters push it to target collection if (isunique && belongstocategory) { addedmap[item.domindex] = true; target.push(item); } } return target; }, /** * detect and set up preset controls. * @private */ _setupcontrols: function() { var self = this; //filter controls $('*[data-filter]').click(function() { var targetfilter = $(this).data('filter'); //exit case if (self.options.filter === targetfilter) return; self.filter(targetfilter); }); //multiple filter controls $('*[data-multifilter]').click(function() { var targetfilter = $(this).data('multifilter'); if (targetfilter === 'all') { self._toggledcategories = { }; self.filter('all'); } else { self.togglefilter(targetfilter); } }); //shuffle control $('*[data-shuffle]').click(function() { self.shuffle(); }); //sort controls $('*[data-sortasc]').click(function() { var sortattr = $('*[data-sortorder]').val(); self.sort(sortattr, 'asc'); }); $('*[data-sortdesc]').click(function() { var sortattr = $('*[data-sortorder]').val(); self.sort(sortattr, 'desc'); }); //search control $('input[data-search]').keyup(function() { self._typedtext = $(this).val(); self._delayevent(function() { self.search(self._typedtext); }, 250, self._uid); }); }, /** * set up window and filterizr events. * @private */ _setupevents: function() { var self = this; //window resize event $(global).resize(function() { self._delayevent(function() { self.trigger('resizefiltrcontainer'); }, 250, self._uid); }); //filterizr events self //container resize event .on('resizefiltrcontainer', function() { if (self._multifiltermodeon()) self.togglefilter(); else self.filter(self.options.filter); }) //onfilteringstart event .on('filteringstart', function() { self.options.callbacks.onfilteringstart(); }) //onfilteringend event .on('filteringend', function() { self.options.callbacks.onfilteringend(); }) //onshufflingstart event .on('shufflingstart', function() { self._isshuffling = true; self.options.callbacks.onshufflingstart(); }) //onfilteringend event .on('shufflingend', function() { self.options.callbacks.onshufflingend(); self._isshuffling = false; }) //onsortingstart event .on('sortingstart', function() { self._issorting = true; self.options.callbacks.onsortingstart(); }) //onsortingend event .on('sortingend', function() { self.options.callbacks.onsortingend(); self._issorting = false; }); }, /** * calculates the final positions of items being filtered in, updates the height of .filtr-container. * @return {object[]} array of future item positions. * @private */ _calcitempositions: function() { var self = this, array = self._activearray, //container data containerheight = 0, cols = math.round(self.width() / self.find('.filtr-item').outerwidth()), rows = 0, //item data itemwidth = array[0].outerwidth(), itemheight = 0, //position calculation vars left = 0, top = 0, //loop vars i = 0, x = 0, //array of positions to return posarray = []; //layout for items of varying sizes if (self.options.layout === 'packed') { //cache current item width/height $.each(self._activearray, function(i, e) { e._updatedimensions(); }); //instantiate new packer, set up grid var packer = new packer(self.outerwidth()); packer.fit(self._activearray); for (i = 0; i < array.length; i++) { posarray.push({ left: array[i].fit.x, top: array[i].fit.y }); } containerheight = packer.root.h; } //horizontal layout if (self.options.layout === 'horizontal') { rows = 1; for (i = 1; i <= array.length; i++) { itemwidth = array[i - 1].outerwidth(); itemheight = array[i - 1].outerheight(); posarray.push({ left: left, top: top }); left += itemwidth; if (containerheight < itemheight) containerheight = itemheight; } } //vertical layout else if (self.options.layout === 'vertical') { for (i = 1; i <= array.length; i++) { itemheight = array[i - 1].outerheight(); posarray.push({ left: left, top: top }); top += itemheight; } containerheight = top; } //layout of items for same height and varying width else if (self.options.layout === 'sameheight') { rows = 1; var rowwidth = self.outerwidth(); for (i = 1; i <= array.length; i++) { itemwidth = array[i - 1].width(); var itemouterwidth = array[i - 1].outerwidth(), nextitemwidth = 0; if (array[i]) nextitemwidth = array[i].width(); posarray.push({ left: left, top: top }); x = left + itemwidth + nextitemwidth; if (x > rowwidth) { x = 0; left = 0; top += array[0].outerheight(); rows++; } else left += itemouterwidth; } containerheight = rows * array[0].outerheight(); } //layout for items of same width and varying height else if (self.options.layout === 'samewidth') { //get positions for (i = 1; i <= array.length; i++) { posarray.push({ left: left, top: top }); if (i % cols === 0) rows++; left += itemwidth; top = 0; if (rows > 0) { x = rows; while (x > 0) { top += array[i - (cols * x)].outerheight(); x--; } } if (i % cols === 0) left = 0; } //calculate containerheight for (i = 0; i < cols; i++) { var columnheight = 0, index = i; while(array[index]) { columnheight += array[index].outerheight(); index += cols; } if (columnheight > containerheight) { containerheight = columnheight; columnheight = 0; } else columnheight = 0; } } //layout for items of exactly same size else if (self.options.layout === 'samesize') { for (i = 1; i <= array.length; i++) { //push first point at (left: 0, top: 0) posarray.push({ left: left, top: top }); //set left and top properties for next point before next iteration left += itemwidth; //on row change calc new top and reset left if (i % cols === 0) { top += array[0].outerheight(); left = 0; } } rows = math.ceil(array.length / cols); containerheight = rows * array[0].outerheight(); } //update the height of .filtr-container based on new positions self.css('height', containerheight); return posarray; }, /** * handles filtering in/out and reposition items when transition between categories * @param {object[]} the target array towards which to filter * @private */ _handlefiltering: function(target) { var self = this, tofilterout = self._getarrayofuniqueitems(self._activearray, target); //minimize all .filtr-item elements that are not the same between categories for (var i = 0; i < tofilterout.length; i++) { tofilterout[i]._filterout(); } self._activearray = target; //reposition same items and filter in new self._placeitems(target); }, /** * determines if the user is using data-multifilter controls or simple data-filter controls * @return {boolean} indicating whether multiple filter mode is on * @private */ _multifiltermodeon: function() { var self = this; return object.keys(self._toggledcategories).length > 0; }, /** * determines if the user has something typed in the search box * @return {boolean} indicating whether the user has searched * @private */ _issearchactivated: function() { var self = this; return self._typedtext.length > 0; }, /** * places .filtr-item elements on the grid positions * @param {object[]} arr - an array consisting of .filtr-item elements * @private */ _placeitems: function(arr) { var self = this; //tag gallery state as animating self._isanimating = true; //recalculate positions and filter in items self._itempositions = self._calcitempositions(); for (var i = 0; i < arr.length; i++) { arr[i]._filterin(self._itempositions[i]); } }, /** * returns item collection based on a certain filter * @param {string|number} filter of category to return * @return {object[]} one of the item collections based on filter * @private */ _getcollectionbyfilter: function(filter) { var self = this; return filter === 'all' ? self._mainarray : self._subarrays[filter - 1]; }, /** * used to make deep copies of the predefined filters * in the options for the filterin/out methods of items. * @see _filterin and _filterout methods in filtritemproto. * @param {object} obj - is the source object to make a deep copy from. * @return {object} deep copy of the obj param. * @private */ _makedeepcopy: function(obj) { var r = {}; for (var p in obj) r[p] = obj[p]; return r; }, /** * comparator factory used to produce camparers for sorting. * @see filterizr.prototype.sort. * @param {string} prop - property based on which to sort ('domindex', 'sortdata', 'w', 'h') * @param {string} sortorder - 'asc'/'desc' * @return {function} comparer which takes arguments * @private */ _comparator: function(prop, sortorder) { return function(a, b) { if (sortorder === 'asc') { if (a[prop] < b[prop]) return -1; else if (a[prop] > b[prop]) return 1; else return 0; } else if (sortorder === 'desc') { if (b[prop] < a[prop]) return -1; else if (b[prop] > a[prop]) return 1; else return 0; } }; }, /** * modified version of jeffery to's array intersection method * @see {@link http://www.falsepositives.com/index.php/2009/12/01/javascript-function-to-get-the-intersect-of-2-arrays/} * @return {object[]} a disjoint array containing the elements of the first array not found in the second * @private */ _getarrayofuniqueitems: function(arr1, arr2) { var r = [], o = {}, l = arr2.length, i, v; for (i = 0; i < l; i++) { o[arr2[i].domindex] = true; } l = arr1.length; for (i = 0; i < l; i++) { v = arr1[i]; if (!(v.domindex in o)) { r.push(v); } } return r; }, /** * brahn's take on cms's solution to calling the window.resize event at set * intervals in multiple places in the code using a java-like uuid with a regexp * @see {@link http://stackoverflow.com/questions/2854407/javascript-jquery-window-resize-how-to-fire-after-the-resize-is-completed} * @return {function} which calls the callback or just clears the timer * @private */ _delayevent: (function () { var self = this, timers = {}; return function (callback, ms, uniqueid) { if (uniqueid === null) { throw error("uniqueid needed"); } if (timers[uniqueid]) { cleartimeout (timers[uniqueid]); } timers[uniqueid] = settimeout(callback, ms); }; })(), /** * fisher-yates array shuffling algorithm implemented for javascript. * @return {object[]} shuffled array. * @private */ _fisheryatesshuffle: function shuffle(array) { var m = array.length, t, i; // while there remain elements to shuffle鈥? while (m) { // pick a remaining element鈥? i = math.floor(math.random() * m--); // and swap it with the current element. t = array[m]; array[m] = array[i]; array[i] = t; } return array; } }; /** * filtritem prototype */ var filtritemproto = { /** * transforms a jquery item with .filtr-item class into a filtritem. * @param {number} index - initial item order based on position in dom. * @param {filterizr} parent - reference to filterizr container containing this filtr item. * @return {jquery} this - to facilitate method chaining. * @constructor */ _init: function(index, parent) { var self = this, delay = 0; //private item properties self._parent = parent; //ref to parent filterizr object self._category = self._getcategory(); //data-category values self._lastpos = {}; //used for animations //public properties - used for sorting self.domindex = index; self.sortdata = self.data('sort'); //item dimensions used for bin packing algorithm (packed layout) and sorting. self.w = 0; self.h = 0; //self states self._isfilteredout = true; self._filteringout = false; self._filteringin = false; //determine delay & set initial item styles self.css(parent.options.filteroutcss) .css({ '-webkit-backface-visibility': 'hidden', 'perspective': '1000px', '-webkit-perspective': '1000px', '-webkit-transform-style': 'preserve-3d', 'position': 'absolute', 'transition': 'all ' + parent.options.animationduration + 's ' + parent.options.easing + ' ' + self._calcdelay() + 'ms' }); //events self.on("transitionend webkittransitionend otransitionend mstransitionend", function(){ self._ontransitionend(); }); return self; }, /** * updates the dimensions (width/height) of the item. * @private */ _updatedimensions: function() { var self = this; self.w = self.outerwidth(); self.h = self.outerheight(); }, /** * calculates and returns the value of delay to apply to transition-delay in ms, depending on delaymode * @return {number} value to apply to transition-delay in ms. * @private */ _calcdelay: function() { var self = this, r = 0; if (self._parent.options.delaymode === 'progressive') r = self._parent.options.delay * self.domindex; else if (self.domindex % 2 === 0) r = self._parent.options.delay; return r; }, /** * determines which categories this items belongs to and updates the _lastcategory prop of filterizr. * @throws {invalidargumentexception} data-category of .filtr-item elements must be integer or string of integers delimited by ', ' * @return {object[]|number} the categories this item belongs to. * @private */ _getcategory: function() { var self = this, ret = self.data('category'); //if more than one category provided if (typeof ret === 'string') { ret = ret.split(', '); for (var i = 0; i < ret.length; i++) { //error checking: make sure data-category has an integer as its value if (isnan(parseint(ret[i]))) { throw new error('filterizr: the value of data-category must be a number, starting from value 1 and increasing.'); } if (parseint(ret[i]) > self._parent._lastcategory) { self._parent._lastcategory = parseint(ret[i]); } } } else { if (ret > self._parent._lastcategory) self._parent._lastcategory = ret; } return ret; }, /** * handles the transitionend event. * @private */ _ontransitionend: function() { var self = this; //finished filtering out if (self._filteringout) { $(self).addclass('filteredout'); self._isfilteredout = true; self._filteringout = false; } //finished filtering in else if (self._filteringin) { self._isfilteredout = false; self._filteringin = false; } //if animating trigger filteringend event once on parent if (self._parent._isanimating) { if (self._parent._isshuffling) self._parent.trigger('shufflingend'); else if (self._parent._issorting) self._parent.trigger('sortingend'); else self._parent.trigger('filteringend'); self._parent._isanimating = false; } }, /** * filters out the item. * @private */ _filterout: function() { var self = this, filteroutcss = self._parent._makedeepcopy(self._parent.options.filteroutcss); //auto add translate to transform over user-defined filterout styles filteroutcss.transform += ' translate3d(' + self._lastpos.left + 'px,' + self._lastpos.top + 'px, 0)'; //play animation self.css(filteroutcss); //make unclickable self.css('pointer-events', 'none'); //tag as filteringout for transitionend event self._filteringout = true; }, /** * filters in the item. * @param {object} targetpos - is the position to move to with transform-translate * @private */ _filterin: function(targetpos) { var self = this, filterincss = self._parent._makedeepcopy(self._parent.options.filterincss); //remove the filteredout class $(self).removeclass('filteredout'); //tag as filtering in for transitionend event self._filteringin = true; self._lastpos = targetpos; //make clickable self.css('pointer-events', 'auto'); //auto add translate to transform over user-defined filterin styles filterincss.transform += ' translate3d(' + targetpos.left + 'px,' + targetpos.top + 'px, 0)'; //play animation self.css(filterincss); } }; })(this, jquery); (function($) { "use strict"; /** * jquery.imageloader * (c) 2012, takashi mizohata * http://beatak.github.com/jquery-imageloader/ * mit license */ var default_options = { selector: '', dataattr: 'src', background: false, each: null, eacherror: null, callback: null, timeout: 5000 }; var init = function (_i, self, opts) { var q = queue.getinstance(); var $this = $(self); var defaults = $.extend({}, default_options, opts || {}); var ns = '_' + ('' + (new date()).valueof()).slice(-7); var $elms; var len = 0; if (defaults.selector === '' && $this.data(defaults.dataattr) ) { $elms = $this; len = 1; } else { $elms = $this.find([defaults.selector, '[data-', defaults.dataattr, ']'].join('')); len = $elms.length; } $this.data( ns, { each: defaults.each, eacherror: defaults.eacherror, callback: defaults.callback, isloading: true, loadedimagecounter: 0, length: len } ); if (len === 0) { finishimageload(self, ns); } else { $elms.each( function (i, elm) { q.add(buildimageloadfunc(elm, self, ns, defaults.background, defaults.dataattr, defaults.timeout)); } ); // console.log(['we are gonna load ', len, ' image(s) on ', ns].join('')); $this.on('loadimage.' + ns, onloadimage); q.run(); } return self; }; // =================================================================== var onloadimage = function (ev, elm, img, iserror) { // console.log('onloadimage: ', ev.namespace); var parent = ev.currenttarget; var defaults = $(parent).data(ev.namespace); if (!defaults.isloading) { // console.log('onloadimage: is not loading but still called?'); return; } if (iserror) { if (typeof defaults.eacherror === 'function') { defaults.eacherror(elm); } else { if (elm.parentnode !== null) { elm.parentnode.removechild(elm); } } } else if (typeof defaults.each === 'function') { defaults.each(elm, img); } ++defaults.loadedimagecounter; if (defaults.loadedimagecounter >= defaults.length) { finishimageload(parent, ev.namespace); } }; var finishimageload = function (parent, ns) { // console.log('finishimageload: ', ns); var $parent = $(parent); var data = $parent.data(); var callback = data[ns].callback; $parent.off('loadimage.' + ns, onloadimage); delete data[ns]; if (typeof callback === 'function') { settimeout( function () { callback(parent); }, $.imageloader.queueinterval * 2 ); } }; var buildimageloadfunc = function (elm, parent, namespace, isbg, attr, milsec_timeout) { var $elm = $(elm); var src = $elm.data(attr); var hasfinished = false; var onfinishloagimage = function (ev, img) { // delete attribute $elm.removeattr( ['data-', attr].join('') ); $(parent).triggerhandler('loadimage.' + namespace, [elm, img, (ev && ev.type === 'error')]); }; return function () { var timer_handler; var $img = $(''); // this statement is kinda silly, but ie needs this separated. $img .bind( 'error', function (ev) { hasfinished = true; cleartimeout(timer_handler); $(this).unbind('error').unbind('load'); onfinishloagimage(ev); } ) .bind( 'load', function(ev) { hasfinished = true; cleartimeout(timer_handler); $(this).unbind('error').unbind('load'); if (isbg) { $elm.css('background-image', ['url(', src, ')'].join('')); } else { $elm.attr('src', src); } onfinishloagimage(ev, $img[0]); } ) .attr('src', src); timer_handler = settimeout( function () { if (hasfinished === false) { // console.log('timeout'); $img.trigger('error'); } }, milsec_timeout ); }; }; // =================================================================== var _queue_instance_; var queue = { getinstance: function () { if (_queue_instance_ instanceof queueimpl === false) { _queue_instance_ = new queueimpl(); } return _queue_instance_; } }; var queueimpl = function () { this.index = 0; this.queue = []; this.isrunning = false; }; queueimpl.prototype.add = function (func) { if (typeof func !== 'function') { throw new error('you can only pass function.'); } this.queue.push(func); }; queueimpl.prototype.run = function (firenow) { var run = $.proxy(this.run, this); firenow = firenow || false; if (this.isrunning && !firenow) { return; } this.isrunning = true; this.queue[this.index++](); if (this.index < this.queue.length) { settimeout( function () { run(true); }, $.imageloader.queueinterval ); } else { this.isrunning = false; } }; // =================================================================== $.imageloader = { queueinterval: 17 }; $.fn.imageloader = function (opts) { return this.each( function (i, elm) { init(i, elm, opts); } ); }; })(jquery);