/* Modernizr 2.8.3 (Custom Build) | MIT & BSD * Build: http://modernizr.com/download/#-geolocation-touch-cssclasses-teststyles-hasevent-prefixes */ ; window.Modernizr = (function( window, document, undefined ) { var version = '2.8.3', Modernizr = {}, enableClasses = true, docElement = document.documentElement, mod = 'modernizr', modElem = document.createElement(mod), mStyle = modElem.style, inputElem , toString = {}.toString, prefixes = ' -webkit- -moz- -o- -ms- '.split(' '), tests = {}, inputs = {}, attrs = {}, classes = [], slice = classes.slice, featureName, injectElementWithStyles = function( rule, callback, nodes, testnames ) { var style, ret, node, docOverflow, div = document.createElement('div'), body = document.body, fakeBody = body || document.createElement('body'); if ( parseInt(nodes, 10) ) { while ( nodes-- ) { node = document.createElement('div'); node.id = testnames ? testnames[nodes] : mod + (nodes + 1); div.appendChild(node); } } style = ['­',''].join(''); div.id = mod; (body ? div : fakeBody).innerHTML += style; fakeBody.appendChild(div); if ( !body ) { fakeBody.style.background = ''; fakeBody.style.overflow = 'hidden'; docOverflow = docElement.style.overflow; docElement.style.overflow = 'hidden'; docElement.appendChild(fakeBody); } ret = callback(div, rule); if ( !body ) { fakeBody.parentNode.removeChild(fakeBody); docElement.style.overflow = docOverflow; } else { div.parentNode.removeChild(div); } return !!ret; }, isEventSupported = (function() { var TAGNAMES = { 'select': 'input', 'change': 'input', 'submit': 'form', 'reset': 'form', 'error': 'img', 'load': 'img', 'abort': 'img' }; function isEventSupported( eventName, element ) { element = element || document.createElement(TAGNAMES[eventName] || 'div'); eventName = 'on' + eventName; var isSupported = eventName in element; if ( !isSupported ) { if ( !element.setAttribute ) { element = document.createElement('div'); } if ( element.setAttribute && element.removeAttribute ) { element.setAttribute(eventName, ''); isSupported = is(element[eventName], 'function'); if ( !is(element[eventName], 'undefined') ) { element[eventName] = undefined; } element.removeAttribute(eventName); } } element = null; return isSupported; } return isEventSupported; })(), _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) { hasOwnProp = function (object, property) { return _hasOwnProperty.call(object, property); }; } else { hasOwnProp = function (object, property) { return ((property in object) && is(object.constructor.prototype[property], 'undefined')); }; } if (!Function.prototype.bind) { Function.prototype.bind = function bind(that) { var target = this; if (typeof target != "function") { throw new TypeError(); } var args = slice.call(arguments, 1), bound = function () { if (this instanceof bound) { var F = function(){}; F.prototype = target.prototype; var self = new F(); var result = target.apply( self, args.concat(slice.call(arguments)) ); if (Object(result) === result) { return result; } return self; } else { return target.apply( that, args.concat(slice.call(arguments)) ); } }; return bound; }; } function setCss( str ) { mStyle.cssText = str; } function setCssAll( str1, str2 ) { return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); } function is( obj, type ) { return typeof obj === type; } function contains( str, substr ) { return !!~('' + str).indexOf(substr); } function testDOMProps( props, obj, elem ) { for ( var i in props ) { var item = obj[props[i]]; if ( item !== undefined) { if (elem === false) return props[i]; if (is(item, 'function')){ return item.bind(elem || obj); } return item; } } return false; } tests['touch'] = function() { var bool; if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { bool = true; } else { injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) { bool = node.offsetTop === 9; }); } return bool; }; tests['geolocation'] = function() { return 'geolocation' in navigator; }; for ( var feature in tests ) { if ( hasOwnProp(tests, feature) ) { featureName = feature.toLowerCase(); Modernizr[featureName] = tests[feature](); classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); } } Modernizr.addTest = function ( feature, test ) { if ( typeof feature == 'object' ) { for ( var key in feature ) { if ( hasOwnProp( feature, key ) ) { Modernizr.addTest( key, feature[ key ] ); } } } else { feature = feature.toLowerCase(); if ( Modernizr[feature] !== undefined ) { return Modernizr; } test = typeof test == 'function' ? test() : test; if (typeof enableClasses !== "undefined" && enableClasses) { docElement.className += ' ' + (test ? '' : 'no-') + feature; } Modernizr[feature] = test; } return Modernizr; }; setCss(''); modElem = inputElem = null; Modernizr._version = version; Modernizr._prefixes = prefixes; Modernizr.hasEvent = isEventSupported; Modernizr.testStyles = injectElementWithStyles; docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') + (enableClasses ? ' js ' + classes.join(' ') : ''); return Modernizr; })(this, this.document); ; /** * Utilities; useful scripts * * @author Tijs Verkoyen * @author Thomas Deceuninck */ var utils = { debug: false }; /** * Functions related to arrays * * @author Tijs Verkoyen */ utils.array = { /** * Is the given value present in the array * * @return bool */ inArray: function(needle, array) { // loop values for(var i in array) { if(array[i] == needle) return true; } // fallback return false; } }; /** * Function related to cookies * * @author Tijs Verkoyen */ utils.cookies = { /** * Are cookies enabled? * * @return bool */ isEnabled: function() { // try to grab the property var cookiesEnabled = !!(navigator.cookieEnabled); // unknown property? if(typeof navigator.cookieEnabled == 'undefined' && !cookiesEnabled) { // try to set a cookie document.cookie = 'testcookie'; cookiesEnabled = ($.inArray('testcookie', document.cookie) != -1); } // return return cookiesEnabled; }, /** * Read a cookie * * @return mixed */ readCookie: function(name) { // get cookies var cookies = document.cookie.split(';'); name = name + '='; for(var i = 0; i < cookies.length; i++) { var cookie = cookies[i]; while(cookie.charAt(0) === ' ') cookie = cookie.substring(1, cookie.length); if(cookie.indexOf(name) === 0) return cookie.substring(name.length, cookie.length); } // fallback return null; }, setCookie: function(name, value, days) { if(typeof days == 'undefined') days = 7; var expireDate = new Date(); expireDate.setDate(expireDate.getDate() + days); document.cookie = name + '=' + escape(value) + ';expires=' + expireDate.toUTCString() + ';path=/'; } }; /** * Functions related to forms * * @author Tijs Verkoyen */ utils.form = { /** * Is a checkbox checked? * * @return bool * @param object element */ isChecked: function(element) { return ($('input[name="' + element.attr('name') + '"]:checked').length >= 1); }, /** * Is the value inside the element a valid email address * * @return bool * @param object element */ isEmail: function(element) { var regexp = /^[a-z0-9!#\$%&'*+-\/=?^_`{|}\.~]+@([a-z0-9]+([\-]+[a-z0-9]+)*\.)+[a-z]{2,7}$/i; return regexp.test(element.val()); }, /** * Is the element filled * * @return bool * @param object element */ isFilled: function(element) { return (utils.string.trim(element.val()) !== ''); }, /** * Is the value inside the element a valid number * * @return bool * @param object element */ isNumber: function(element) { return (!isNaN(element.val()) && element.val() !== ''); }, /** * Is the value inside the element a valid URL * * @return bool * @param object element */ isURL: function(element) { var regexp = /^((http|ftp|https):\/{2})?(([0-9a-zA-Z_-]+\.)+[0-9a-zA-Z]+)((:[0-9]+)?)((\/([~0-9a-zA-Z\#%@\.\/_-]+)?(\?[0-9a-zA-Z%@\/&=_-]+)?)?)$/i; return regexp.test(element.val()); } }; /** * Functions related to strings * * @author Tijs Verkoyen * @author Dieter Vanden Eynde * @author Matthias Mullie */ utils.string = { // data member div: false, /** * Fix a HTML5-chunk, so IE can render it * * @return string * @param string html */ html5: function(html) { var html5 = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '); // create div if needed if(utils.string.div === false) { utils.string.div = document.createElement('div'); utils.string.div.innerHTML = ''; if(utils.string.div.childNodes.length !== 1) { var fragment = document.createDocumentFragment(); var i = html5.length; while(i--) fragment.createElement(html5[i]); fragment.appendChild(utils.string.div); } } html = html.replace(/^\s\s*/, '').replace(/\s\s*$/, '') .replace(/)<[^<]*)*<\/script>/gi, ''); // fix for when in a table var inTable = html.match(/^<(tbody|tr|td|th|col|colgroup|thead|tfoot)[\s\/>]/i); if(inTable) utils.string.div.innerHTML = '' + html + '
'; else utils.string.div.innerHTML = html; var scope; if(inTable) scope = utils.string.div.getElementsByTagName(inTable[1])[0].parentNode; else scope = utils.string.div; var returnedFragment = document.createDocumentFragment(); var i = scope.childNodes.length; while(i--) returnedFragment.appendChild(scope.firstChild); return returnedFragment; }, /** * Encode the string as HTML * * @return string * @param string value */ htmlEncode: function(value) { return $('
').text(value).html(); }, /** * Decode the string as HTML * * @return string * @param string value */ htmlDecode: function(value) { return $('
').html(value).text(); }, /** * Replace all occurences of one string into a string * * @return string * @param string value * @param string needle * @param string replacement */ replaceAll: function(value, needle, replacement) { if(typeof value === 'undefined') return ''; return value.replace(new RegExp(needle, 'g'), replacement); }, /** * Sprintf replaces all arguments that occur in the string (%1$s, %2$s, ...) * * @return string * @param string value * @params string arguments */ sprintf: function(value) { if(arguments.length < 2) return value; else { // replace $ symbol first, because our RegExp won't except this symbol value = value.replace(/\$s/g, 'Ss'); // find all variables and replace them for(var i = 1; i < arguments.length; i++) { value = utils.string.replaceAll(value, '%' + i + 'Ss', arguments[i]); } } return value; }, /** * Strip HTML tags * * @return string */ stripTags: function(value) { return value.replace(/<[^>]*>/ig, ''); }, /** * Strip whitespace from the beginning and end of a string * * @return string * @param string value * @param string[optional] charList */ trim: function(value, charList) { if(typeof value === 'undefined') return ''; if(typeof charList === 'undefined') charList = ' '; var pattern = new RegExp('^[' + charList + ']*|[' + charList + ']*$', 'g'); return value.replace(pattern, ''); }, /** * PHP-like urlencode * * @see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Functions/encodeURIComponent#Description * @return string * @param string value */ urlEncode: function(value) { return encodeURIComponent(value).replace(/\%20/g, '+').replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A').replace(/\~/g, '%7E'); }, /** * PHP-like urlencode * * @see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Functions/encodeURIComponent#Description * @return string * @param string value */ urlDecode: function(value) { return decodeURIComponent(value.replace(/\+/g, '%20').replace(/\%21/g, '!').replace(/\%27/g, "'").replace(/\%28/g, '(').replace(/\%29/g, ')').replace(/\%2A/g, '*').replace(/\%7E/g, '~')); }, /** * Urlise a string (cfr. SpoonFilter::urlise) * * @return string * @param string value */ urlise: function(value) { // reserved characters (RFC 3986) reservedCharacters = new Array( '/', '?', ':', '@', '#', '[', ']', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' ); // remove reserved characters for(var i in reservedCharacters) value = value.replace(reservedCharacters[i], ' '); // replace double quote, since this one might cause problems in html (e.g. ) value = utils.string.replaceAll(value, '"', ' '); // replace spaces by dashes value = utils.string.replaceAll(value, ' ', '-'); // only urlencode if not yet urlencoded if(utils.string.urlDecode(value) == value) { // to lowercase value = value.toLowerCase(); // urlencode value = utils.string.urlEncode(value); } // convert "--" to "-" value = value.replace(/-+/, '-'); // trim - signs return utils.string.trim(value, '-'); }, /** * Adds a capital letter to a string * * @return string * @param string $value */ ucfirst: function(value) { return value.charAt(0).toUpperCase() + value.slice(1); }, /** * Convert a HTML string to a XHTML string. * * @return string * @param string value */ xhtml: function(value) { // break tags should end with a slash value = value.replace(/
/g,'
'); value = value.replace(/
$/g,''); value = value.replace(/^
/g,''); // image tags should end with a slash value = value.replace(/(]+[^\/])>/gi,'$1 />'); // input tags should end with a slash value = value.replace(/(]+[^\/])>/gi,'$1 />'); // big no-no to value = value.replace(/]*>(.*?)<\/b[^>]*>/g,'$1'); value = value.replace(/]*>(.*?)<\/i[^>]*>/g,'$1'); value = value.replace(/]*>(.*?)<\/u[^>]*>/g,'$1'); // XHTML return value; } }; /** * Functions related to the current url * * @author Dieter Vanden Eynde * @author Tijs Verkoyen */ utils.url = { extractParamFromUri: function (uri, paramName) { if(!uri) return; uri = uri.split('#')[0]; var parts = uri.split('?'); if (parts.length == 1) return; var query = decodeURI(parts[1]); paramName += '='; var params = query.split('&'); for(var i=0, param; param = params[i]; ++i) { if(param.indexOf(paramName) === 0) return unescape(param.split('=')[1]); } }, /** * Get a GET parameter * * @return string * @param string name */ getGetValue: function(name) { // init return value var getValue = ''; // get GET chunks from url var hashes = window.location.search.slice(window.location.search.indexOf('?') + 1).split('&'); // find requested parameter $.each(hashes, function(index, value) { // split name/value up var chunks = value.split('='); // found the requested parameter if(chunks[0] == name) { // set for return getValue = chunks[1]; // break loop return false; } }); // cough up value return getValue; } }; /** * Frontend related objects * * @author Tijs Verkoyen * @author Thomas Deceuninck */ var jsFrontend = { debug: false, current: {}, // init, something like a constructor init: function() { jsFrontend.current.language = jsFrontend.data.get('FRONTEND_LANGUAGE'); // init stuff jsFrontend.initAjax(); jsFrontend.cookieBar.init(); // init controls jsFrontend.controls.init(); // init form jsFrontend.forms.init(); // init gravatar jsFrontend.gravatar.init(); // init search jsFrontend.search.init(); // init statistics jsFrontend.statistics.init(); // init twitter jsFrontend.twitter.init(); }, // init initAjax: function() { // set defaults for AJAX $.ajaxSetup( { url: '/frontend/ajax', cache: false, type: 'POST', dataType: 'json', timeout: 10000, data: { fork: { module: null, action: null, language: jsFrontend.current.language } } }); } }; /** * Controls related javascript * * @author Tijs Verkoyen */ jsFrontend.controls = { // init, something like a constructor init: function() { jsFrontend.controls.bindTargetBlank(); }, // bind target blank bindTargetBlank: function() { $('a.targetBlank').attr('target', '_blank'); } }; /** * Handles the cookieBar */ jsFrontend.cookieBar = { init: function() { // if there is no cookiebar we shouldn't do anything if($('#cookieBar').length === 0) return; $cookieBar = $('#cookieBar'); // @remark: as you can see we use PHP-serialized values so we can use them in PHP too. // hide the cookieBar if needed if(utils.cookies.readCookie('cookie_bar_hide') == 'b%3A1%3B') { $cookieBar.hide(); } $cookieBar.on('click', '#cookieBarAgree', function(e) { utils.cookies.setCookie('cookie_bar_agree', 'b:1;'); utils.cookies.setCookie('cookie_bar_hide', 'b:1;'); $cookieBar.hide(); }); $cookieBar.on('click', '#cookieBarDisagree', function(e) { utils.cookies.setCookie('cookie_bar_agree', 'b:0;'); utils.cookies.setCookie('cookie_bar_hide', 'b:1;'); $cookieBar.hide(); }); } }; /** * Data related methods * * @author Tijs Verkoyen */ jsFrontend.data = { initialized: false, data: {}, init: function() { // check if var is available if(typeof jsData == 'undefined') throw 'jsData is not available'; // populate jsFrontend.data.data = jsData; jsFrontend.data.initialized = true; }, exists: function(key) { return (typeof eval('jsFrontend.data.data.' + key) != 'undefined'); }, get: function(key) { // init if needed if(!jsFrontend.data.initialized) jsFrontend.data.init(); // return return eval('jsFrontend.data.data.' + key); } }; /** * Facebook related * * @author Tijs Verkoyen */ jsFrontend.facebook = { // will be called after Facebook is initialized afterInit: function() { // is GA available? if(typeof _gaq == 'object') { // subscribe and track like FB.Event.subscribe('edge.create', function(targetUrl) { _gaq.push(['_trackSocial', 'facebook', 'like', targetUrl]); }); // subscribe and track unlike FB.Event.subscribe('edge.remove', function(targetUrl) { _gaq.push(['_trackSocial', 'facebook', 'unlike', targetUrl]); }); // subscribe and track message FB.Event.subscribe('message.send', function(targetUrl) { _gaq.push(['_trackSocial', 'facebook', 'send', targetUrl]); }); } else if(typeof ga == 'object') { // subscribe and track like FB.Event.subscribe('edge.create', function(targetUrl) { ga('send', 'social', 'facebook', 'like', targetUrl); }); // subscribe and track unlike FB.Event.subscribe('edge.remove', function(targetUrl) { ga('send', 'social', 'facebook', 'unlike', targetUrl); }); // subscribe and track message FB.Event.subscribe('message.send', function(targetUrl) { ga('send', 'social', 'facebook', 'send', targetUrl); }); } } }; /** * Form related javascript * * @author Tijs Verkoyen */ jsFrontend.forms = { // init, something like a constructor init: function() { jsFrontend.forms.placeholders(); jsFrontend.forms.datefields(); jsFrontend.forms.filled(); }, // once text has been filled add another class to it (so it's possible to style it differently) filled: function() { $(document).on('blur', 'form input, form textarea, form select', function() { if($(this).val() === '') $(this).removeClass('filled'); else $(this).addClass('filled'); }); }, // initialize the date fields datefields: function() { // jQuery datapicker fallback for browsers that don't support the HTML5 date type var $inputDateType = $('input.inputDatefield'); if ($inputDateType.length) { // the browser does not support the HTML5 data type if ('date' !== $inputDateType.get(0).type) { $inputDateType.addClass('inputDatefieldNormal'); } } var $inputDatefields = $('.inputDatefieldNormal, .inputDatefieldFrom, .inputDatefieldTill, .inputDatefieldRange'); var $inputDatefieldNormal = $('.inputDatefieldNormal'); var $inputDatefieldFrom = $('.inputDatefieldFrom'); var $inputDatefieldTill = $('.inputDatefieldTill'); var $inputDatefieldRange = $('.inputDatefieldRange'); if($inputDatefields.length > 0) { var dayNames = [jsFrontend.locale.loc('DayLongSun'), jsFrontend.locale.loc('DayLongMon'), jsFrontend.locale.loc('DayLongTue'), jsFrontend.locale.loc('DayLongWed'), jsFrontend.locale.loc('DayLongThu'), jsFrontend.locale.loc('DayLongFri'), jsFrontend.locale.loc('DayLongSat')]; var dayNamesMin = [jsFrontend.locale.loc('DayShortSun'), jsFrontend.locale.loc('DayShortMon'), jsFrontend.locale.loc('DayShortTue'), jsFrontend.locale.loc('DayShortWed'), jsFrontend.locale.loc('DayShortThu'), jsFrontend.locale.loc('DayShortFri'), jsFrontend.locale.loc('DayShortSat')]; var dayNamesShort = [jsFrontend.locale.loc('DayShortSun'), jsFrontend.locale.loc('DayShortMon'), jsFrontend.locale.loc('DayShortTue'), jsFrontend.locale.loc('DayShortWed'), jsFrontend.locale.loc('DayShortThu'), jsFrontend.locale.loc('DayShortFri'), jsFrontend.locale.loc('DayShortSat')]; var monthNames = [jsFrontend.locale.loc('MonthLong1'), jsFrontend.locale.loc('MonthLong2'), jsFrontend.locale.loc('MonthLong3'), jsFrontend.locale.loc('MonthLong4'), jsFrontend.locale.loc('MonthLong5'), jsFrontend.locale.loc('MonthLong6'), jsFrontend.locale.loc('MonthLong7'), jsFrontend.locale.loc('MonthLong8'), jsFrontend.locale.loc('MonthLong9'), jsFrontend.locale.loc('MonthLong10'), jsFrontend.locale.loc('MonthLong11'), jsFrontend.locale.loc('MonthLong12')]; var monthNamesShort = [jsFrontend.locale.loc('MonthShort1'), jsFrontend.locale.loc('MonthShort2'), jsFrontend.locale.loc('MonthShort3'), jsFrontend.locale.loc('MonthShort4'), jsFrontend.locale.loc('MonthShort5'), jsFrontend.locale.loc('MonthShort6'), jsFrontend.locale.loc('MonthShort7'), jsFrontend.locale.loc('MonthShort8'), jsFrontend.locale.loc('MonthShort9'), jsFrontend.locale.loc('MonthShort10'), jsFrontend.locale.loc('MonthShort11'), jsFrontend.locale.loc('MonthShort12')]; $inputDatefieldNormal.each(function() { // Create a hidden clone (before datepicker init!), which will contain the actual value var clone = $(this).clone(); clone.insertAfter(this); clone.hide(); // Rename the original field, used to contain the display value $(this).attr('id', $(this).attr('id') + '-display'); $(this).attr('name', $(this).attr('name') + '-display'); }); $inputDatefields.datepicker({ dayNames: dayNames, dayNamesMin: dayNamesMin, dayNamesShort: dayNamesShort, hideIfNoPrevNext: true, monthNames: monthNames, monthNamesShort: monthNamesShort, nextText: jsFrontend.locale.lbl('Next'), prevText: jsFrontend.locale.lbl('Previous'), showAnim: 'slideDown' }); // the default, nothing special $inputDatefieldNormal.each(function() { // get data var data = $(this).data(); var phpDate = new Date($(this).val()); // Get date from php in YYYY-MM-DD format var value = $.datepicker.formatDate(data.mask, phpDate); // Convert the value to the data-mask to display it // Create the datepicker with the desired display format and alt field $(this).datepicker('option', { dateFormat: data.mask, firstDay: data.firstday, altField: "#" + $(this).attr('id').replace('-display', ''), altFormat: "yy-mm-dd" }).datepicker('setDate', value); }); // date fields that have a certain start date $inputDatefieldFrom.each(function() { // get data var data = $(this).data(); var value = $(this).val(); // set options $(this).datepicker('option', { dateFormat: data.mask, firstDay: data.firstday, minDate: new Date(parseInt(data.startdate.split('-')[0], 10), parseInt(data.startdate.split('-')[1], 10) - 1, parseInt(data.startdate.split('-')[2], 10)) }).datepicker('setDate', value); }); // date fields that have a certain enddate $inputDatefieldTill.each(function() { // get data var data = $(this).data(); var value = $(this).val(); // set options $(this).datepicker('option', { dateFormat: data.mask, firstDay: data.firstday, maxDate: new Date(parseInt(data.enddate.split('-')[0], 10), parseInt(data.enddate.split('-')[1], 10) -1, parseInt(data.enddate.split('-')[2], 10)) }).datepicker('setDate', value); }); // date fields that have a certain range $inputDatefieldRange.each(function() { // get data var data = $(this).data(); var value = $(this).val(); // set options $(this).datepicker('option', { dateFormat: data.mask, firstDay: data.firstday, minDate: new Date(parseInt(data.startdate.split('-')[0], 10), parseInt(data.startdate.split('-')[1], 10) - 1, parseInt(data.startdate.split('-')[2], 10), 0, 0, 0, 0), maxDate: new Date(parseInt(data.enddate.split('-')[0], 10), parseInt(data.enddate.split('-')[1], 10) - 1, parseInt(data.enddate.split('-')[2], 10), 23, 59, 59) }).datepicker('setDate', value); }); } }, // placeholder fallback for browsers that don't support placeholder placeholders: function() { // detect if placeholder-attribute is supported jQuery.support.placeholder = ('placeholder' in document.createElement('input')); if(!jQuery.support.placeholder) { // bind focus $('input[placeholder], textarea[placeholder]').on('focus', function() { // grab element var input = $(this); // only do something when the current value and the placeholder are the same if(input.val() == input.attr('placeholder')) { // clear input.val(''); // remove class input.removeClass('placeholder'); } }); $('input[placeholder], textarea[placeholder]').on('blur', function() { // grab element var input = $(this); // only do something when the input is empty or the value is the same as the placeholder if(input.val() === '' || input.val() === input.attr('placeholder')) { // set placeholder input.val(input.attr('placeholder')); // add class input.addClass('placeholder'); } }); // call blur to initialize $('input[placeholder], textarea[placeholder]').blur(); // hijack the form so placeholders aren't submitted as values $('input[placeholder], textarea[placeholder]').parents('form').submit(function() { // find elements with placeholders $(this).find('input[placeholder]').each(function() { // grab element var input = $(this); // if the value and the placeholder are the same reset the value if(input.val() == input.attr('placeholder')) input.val(''); }); }); } } }; /** * Gravatar related javascript * * @author Tijs Verkoyen */ jsFrontend.gravatar = { // init, something like a constructor init: function() { $('.replaceWithGravatar').each(function() { var element = $(this); var gravatarId = element.data('gravatarId'); var size = element.attr('height'); // valid gravatar id if(gravatarId !== '') { // build url var url = 'http://www.gravatar.com/avatar/' + gravatarId + '?r=g&d=404'; // add size if set before if(size !== '') url += '&s=' + size; // create new image var gravatar = new Image(); gravatar.src = url; // reset src gravatar.onload = function() { element.attr('src', url).addClass('gravatarLoaded'); }; } }); } }; /** * Locale * * @author Tijs Verkoyen */ jsFrontend.locale = { initialized: false, data: {}, // init, something like a constructor init: function() { $.ajax({ url: '/src/Frontend/Cache/Locale/' + jsFrontend.current.language + '.json', type: 'GET', dataType: 'json', async: false, success: function(data) { jsFrontend.locale.data = data; jsFrontend.locale.initialized = true; }, error: function(jqXHR, textStatus, errorThrown) { throw 'Regenerate your locale-files.'; } }); }, // get an item from the locale get: function(type, key) { // initialize if needed if(!jsFrontend.locale.initialized) jsFrontend.locale.init(); // validate if(typeof jsFrontend.locale.data[type][key] == 'undefined') return '{$' + type + key + '}'; return jsFrontend.locale.data[type][key]; }, // get an action act: function(key) { return jsFrontend.locale.get('act', key); }, // get an error err: function(key) { return jsFrontend.locale.get('err', key); }, // get a label lbl: function(key) { return jsFrontend.locale.get('lbl', key); }, // get localization loc: function(key) { return jsFrontend.locale.get('loc', key); }, // get a message msg: function(key) { return jsFrontend.locale.get('msg', key); } }; /** * Search controls * * @author Matthias Mullie */ jsFrontend.search = { // init, something like a constructor init: function() { // auto suggest (search widget) if($('input.autoSuggest').length > 0) jsFrontend.search.autosuggest(55); // autocomplete (search results page: autocomplete based on known search terms) if($('input.autoComplete').length > 0) jsFrontend.search.autocomplete(); // live suggest (search results page: live feed of matches) if($('input.liveSuggest').length > 0 && $('#searchContainer').length > 0) jsFrontend.search.livesuggest(); }, // autocomplete (search results page: autocomplete based on known search terms) autocomplete: function() { // grab element var $input = $('input.autoComplete'); // autocomplete (based on saved search terms) on results page $input.autocomplete( { minLength: 1, source: function(request, response) { // ajax call! $.ajax( { data: { fork: { module: 'Search', action: 'Autocomplete' }, term: request.term }, success: function(data, textStatus) { // init var var realData = []; // alert the user if(data.code != 200 && jsFrontend.debug) { alert(data.message); } if(data.code == 200) { for(var i in data.data) realData.push({ label: data.data[i].term, value: data.data[i].term, url: data.data[i].url }); } // set response response(realData); } }); }, select: function(e, ui) { window.location.href = ui.item.url; } }) // when we have been typing in the search textfield and we blur out of it, we're ready to save it .on('blur', function() { if($(this).val() !== '') { // ajax call! $.ajax( { data: { fork: { module: 'Search', action: 'Save' }, term: $(this).val() } }); } }); }, // auto suggest (search widget) autosuggest: function(length) { // set default values if(typeof length == 'undefined') length = 100; // grab element var $input = $('input.autoSuggest'); // search widget suggestions $input.autocomplete( { minLength: 1, source: function(request, response) { // ajax call! $.ajax( { data: { fork: { module: 'Search', action: 'Autosuggest' }, term: request.term, length: length }, success: function(data, textStatus) { // init var var realData = []; // alert the user if(data.code != 200 && jsFrontend.debug) { alert(data.message); } if(data.code == 200) { for(var i in data.data) realData.push({ label: data.data[i].title, value: data.data[i].title, url: data.data[i].full_url, desc: data.data[i].text }); } // set response response(realData); } }); }, select: function(e, ui) { window.location.href = ui.item.url; }, open: function(event, ui){ var autocomplete = $(".ui-autocomplete"); var position = autocomplete.offset().left - autocomplete.width() + $input.outerWidth(); autocomplete.css("left", position); } }) // when we have been typing in the search textfield and we blur out of it, we're ready to save it .on('blur', function() { if($(this).val() !== '') { // ajax call! $.ajax( { data: { fork: { module: 'Search', action: 'Save' }, term: $(this).val() } }); } }) // and also: alter the autocomplete style: add description! .data('ui-autocomplete')._renderItem = function(ul, item) { return $('
  • ') .data('item.autocomplete', item) .append('
    ' + item.label + '
    ' + item.desc + '
    ' ) .appendTo(ul); }; }, // livesuggest (search results page: live feed of matches) livesuggest: function() { // check if calls for live suggest are allowed var allowCall = true; // grab element var $input = $('input.liveSuggest'); // change in input = do the dance: live search results completion $input.on('keyup', function() { var $searchContainer = $('#searchContainer'); // make sure we're allowed to do the call (= previous call is no longer processing) if(allowCall) { // temporarily allow no more calls allowCall = false; // fade out $searchContainer.fadeTo(0, 0.5); // ajax call! $.ajax( { data: { fork: { module: 'Search', action: 'Livesuggest' }, term: $(this).val() }, success: function(data, textStatus) { // allow for new calls allowCall = true; // alert the user if(data.code != 200 && jsFrontend.debug) { alert(data.message); } if(data.code == 200) { // replace search results $searchContainer.html(utils.string.html5(data.data)); // fade in $searchContainer.fadeTo(0, 1); } }, error: function() { // allow for new calls allowCall = true; // replace search results $searchContainer.html(''); // fade in $searchContainer.fadeTo(0, 1); } }); } }); } }; /** * Google analytics related javascript * * @author Tijs Verkoyen */ jsFrontend.statistics = { // init, something like a constructor init: function() { jsFrontend.statistics.trackOutboundLinks(); }, // track all outbound links trackOutboundLinks: function() { // check if Google Analytics is available if(typeof _gaq === 'object' || typeof ga === 'object') { // create a new selector $.expr[':'].external = function(obj) { return (typeof obj.href != 'undefined') && !obj.href.match(/^mailto:/) && (obj.hostname != location.hostname); }; // bind on all links that don't have the class noTracking $(document).on('click', 'a:external:not(.noTracking)', function(e) { // only simulate direct links var hasTarget = (typeof $(this).attr('target') != 'undefined'); if(!hasTarget) e.preventDefault(); var link = $(this).attr('href'); // outbound link by default var type = 'Outbound Links'; var pageView = '/Outbound Links/' + link; // set mailto if(link.match(/^mailto:/)) { type = 'Mailto'; pageView = '/Mailto/' + link.substring(7); } // set anchor if(link.match(/^#/)) { type = 'Anchors'; pageView = '/Anchor/' + link.substring(1); } // track in Google Analytics if(typeof _gaq === 'object') { _gaq.push(['_trackEvent', type, pageView]); } else { ga('send', 'event', type, pageView); } // set time out if(!hasTarget) setTimeout(function() { document.location.href = link; }, 100); }); } } }; /** * Twitter related stuff * * @author Tijs Verkoyen */ jsFrontend.twitter = { init: function() { // if GA is integrated and a tweet button is used if(typeof twttr === 'object' && (typeof _gaq === 'object' || typeof ga === 'object')) { // bind event, so we can track the tweets twttr.events.on('tweet', function(e) { // valid event? if(e) { // init var var targetUrl = null; // get url if(e.target && e.target.nodeName == 'IFRAME') targetUrl = utils.url.extractParamFromUri(e.target.src, 'url'); // push to GA if(typeof _gaq === 'object') { _gaq.push(['_trackSocial', 'twitter', 'tweet', targetUrl]); } else { ga('send', 'social', 'twitter', 'tweet', targetUrl); } } }); } } }; $(jsFrontend.init); /* * debouncedresize: special jQuery event that happens once after a window resize * * latest version and complete README available on Github: * https://github.com/louisremi/jquery-smartresize * * Copyright 2012 @louis_remi * Licensed under the MIT license. * * This saved you an hour of work? * Send me music http://www.amazon.co.uk/wishlist/HNTU0468LQON */ (function($) { var $event = $.event, $special, resizeTimeout; $special = $event.special.debouncedresize = { setup: function() { $( this ).on( "resize", $special.handler ); }, teardown: function() { $( this ).off( "resize", $special.handler ); }, handler: function( event, execAsap ) { // Save the context var context = this, args = arguments, dispatch = function() { // set correct event type event.type = "debouncedresize"; $event.dispatch.apply( context, args ); }; if ( resizeTimeout ) { clearTimeout( resizeTimeout ); } execAsap ? dispatch() : resizeTimeout = setTimeout( dispatch, $special.threshold ); }, threshold: 150 }; })(jQuery); /* equalHeights */ (function($) { $.fn.equalHeights = function() { // Variables var currentTallest = 0, currentRowStart = 0, rowDivs = new Array(), $el, topPosition = 0; // Store $(this) var $this = $(this); $this.each(function() { $el = $(this); $($el).height('auto'); topPostion = $el.position().top; if (currentRowStart != topPostion) { for (currentDiv = 0 ; currentDiv < rowDivs.length ; currentDiv++) { rowDivs[currentDiv].outerHeight(currentTallest); } rowDivs.length = 0; // empty the array currentRowStart = topPostion; currentTallest = $el.outerHeight(); rowDivs.push($el); } else { rowDivs.push($el); currentTallest = (currentTallest < $el.outerHeight()) ? ($el.outerHeight()) : (currentTallest); } for (currentDiv = 0 ; currentDiv < rowDivs.length ; currentDiv++) { rowDivs[currentDiv].outerHeight(currentTallest); } }); }; })(jQuery); /*! * fancyBox - jQuery Plugin * version: 2.1.5 (Fri, 14 Jun 2013) * requires jQuery v1.6 or later * * Examples at http://fancyapps.com/fancybox/ * License: www.fancyapps.com/fancybox/#license * * Copyright 2012 Janis Skarnelis - janis@fancyapps.com * */ ;(function (window, document, $, undefined) { "use strict"; var H = $("html"), W = $(window), D = $(document), F = $.fancybox = function () { F.open.apply( this, arguments ); }, IE = navigator.userAgent.match(/msie/i), didUpdate = null, isTouch = document.createTouch !== undefined, isQuery = function(obj) { return obj && obj.hasOwnProperty && obj instanceof $; }, isString = function(str) { return str && $.type(str) === "string"; }, isPercentage = function(str) { return isString(str) && str.indexOf('%') > 0; }, isScrollable = function(el) { return (el && !(el.style.overflow && el.style.overflow === 'hidden') && ((el.clientWidth && el.scrollWidth > el.clientWidth) || (el.clientHeight && el.scrollHeight > el.clientHeight))); }, getScalar = function(orig, dim) { var value = parseInt(orig, 10) || 0; if (dim && isPercentage(orig)) { value = F.getViewport()[ dim ] / 100 * value; } return Math.ceil(value); }, getValue = function(value, dim) { return getScalar(value, dim) + 'px'; }; $.extend(F, { // The current version of fancyBox version: '2.1.5', defaults: { padding : 15, margin : 20, width : 800, height : 600, minWidth : 100, minHeight : 100, maxWidth : 9999, maxHeight : 9999, pixelRatio: 1, // Set to 2 for retina display support autoSize : true, autoHeight : false, autoWidth : false, autoResize : true, autoCenter : !isTouch, fitToView : true, aspectRatio : false, topRatio : 0.5, leftRatio : 0.5, scrolling : 'auto', // 'auto', 'yes' or 'no' wrapCSS : '', arrows : true, closeBtn : true, closeClick : false, nextClick : false, mouseWheel : true, autoPlay : false, playSpeed : 3000, preload : 3, modal : false, loop : true, ajax : { dataType : 'html', headers : { 'X-fancyBox': true } }, iframe : { scrolling : 'auto', preload : true }, swf : { wmode: 'transparent', allowfullscreen : 'true', allowscriptaccess : 'always' }, keys : { next : { 13 : 'left', // enter 34 : 'up', // page down 39 : 'left', // right arrow 40 : 'up' // down arrow }, prev : { 8 : 'right', // backspace 33 : 'down', // page up 37 : 'right', // left arrow 38 : 'down' // up arrow }, close : [27], // escape key play : [32], // space - start/stop slideshow toggle : [70] // letter "f" - toggle fullscreen }, direction : { next : 'left', prev : 'right' }, scrollOutside : true, // Override some properties index : 0, type : null, href : null, content : null, title : null, // HTML templates tpl: { wrap : '
    ', image : '', iframe : '', error : '

    The requested content cannot be loaded.
    Please try again later.

    ', closeBtn : '', next : '', prev : '', loading : '
    ' }, // Properties for each animation type // Opening fancyBox openEffect : 'fade', // 'elastic', 'fade' or 'none' openSpeed : 250, openEasing : 'swing', openOpacity : true, openMethod : 'zoomIn', // Closing fancyBox closeEffect : 'fade', // 'elastic', 'fade' or 'none' closeSpeed : 250, closeEasing : 'swing', closeOpacity : true, closeMethod : 'zoomOut', // Changing next gallery item nextEffect : 'elastic', // 'elastic', 'fade' or 'none' nextSpeed : 250, nextEasing : 'swing', nextMethod : 'changeIn', // Changing previous gallery item prevEffect : 'elastic', // 'elastic', 'fade' or 'none' prevSpeed : 250, prevEasing : 'swing', prevMethod : 'changeOut', // Enable default helpers helpers : { overlay : true, title : true }, // Callbacks onCancel : $.noop, // If canceling beforeLoad : $.noop, // Before loading afterLoad : $.noop, // After loading beforeShow : $.noop, // Before changing in current item afterShow : $.noop, // After opening beforeChange : $.noop, // Before changing gallery item beforeClose : $.noop, // Before closing afterClose : $.noop // After closing }, //Current state group : {}, // Selected group opts : {}, // Group options previous : null, // Previous element coming : null, // Element being loaded current : null, // Currently loaded element isActive : false, // Is activated isOpen : false, // Is currently open isOpened : false, // Have been fully opened at least once wrap : null, skin : null, outer : null, inner : null, player : { timer : null, isActive : false }, // Loaders ajaxLoad : null, imgPreload : null, // Some collections transitions : {}, helpers : {}, /* * Static methods */ open: function (group, opts) { if (!group) { return; } if (!$.isPlainObject(opts)) { opts = {}; } // Close if already active if (false === F.close(true)) { return; } // Normalize group if (!$.isArray(group)) { group = isQuery(group) ? $(group).get() : [group]; } // Recheck if the type of each element is `object` and set content type (image, ajax, etc) $.each(group, function(i, element) { var obj = {}, href, title, content, type, rez, hrefParts, selector; if ($.type(element) === "object") { // Check if is DOM element if (element.nodeType) { element = $(element); } if (isQuery(element)) { obj = { href : element.data('fancybox-href') || element.attr('href'), title : $('
    ').text( element.data('fancybox-title') || element.attr('title') || '' ).html(), isDom : true, element : element }; if ($.metadata) { $.extend(true, obj, element.metadata()); } } else { obj = element; } } href = opts.href || obj.href || (isString(element) ? element : null); title = opts.title !== undefined ? opts.title : obj.title || ''; content = opts.content || obj.content; type = content ? 'html' : (opts.type || obj.type); if (!type && obj.isDom) { type = element.data('fancybox-type'); if (!type) { rez = element.prop('class').match(/fancybox\.(\w+)/); type = rez ? rez[1] : null; } } if (isString(href)) { // Try to guess the content type if (!type) { if (F.isImage(href)) { type = 'image'; } else if (F.isSWF(href)) { type = 'swf'; } else if (href.charAt(0) === '#') { type = 'inline'; } else if (isString(element)) { type = 'html'; content = element; } } // Split url into two pieces with source url and content selector, e.g, // "/mypage.html #my_id" will load "/mypage.html" and display element having id "my_id" if (type === 'ajax') { hrefParts = href.split(/\s+/, 2); href = hrefParts.shift(); selector = hrefParts.shift(); } } if (!content) { if (type === 'inline') { if (href) { content = $( isString(href) ? href.replace(/.*(?=#[^\s]+$)/, '') : href ); //strip for ie7 } else if (obj.isDom) { content = element; } } else if (type === 'html') { content = href; } else if (!type && !href && obj.isDom) { type = 'inline'; content = element; } } $.extend(obj, { href : href, type : type, content : content, title : title, selector : selector }); group[ i ] = obj; }); // Extend the defaults F.opts = $.extend(true, {}, F.defaults, opts); // All options are merged recursive except keys if (opts.keys !== undefined) { F.opts.keys = opts.keys ? $.extend({}, F.defaults.keys, opts.keys) : false; } F.group = group; return F._start(F.opts.index); }, // Cancel image loading or abort ajax request cancel: function () { var coming = F.coming; if (coming && false === F.trigger('onCancel')) { return; } F.hideLoading(); if (!coming) { return; } if (F.ajaxLoad) { F.ajaxLoad.abort(); } F.ajaxLoad = null; if (F.imgPreload) { F.imgPreload.onload = F.imgPreload.onerror = null; } if (coming.wrap) { coming.wrap.stop(true, true).trigger('onReset').remove(); } F.coming = null; // If the first item has been canceled, then clear everything if (!F.current) { F._afterZoomOut( coming ); } }, // Start closing animation if is open; remove immediately if opening/closing close: function (event) { F.cancel(); if (false === F.trigger('beforeClose')) { return; } F.unbindEvents(); if (!F.isActive) { return; } if (!F.isOpen || event === true) { $('.fancybox-wrap').stop(true).trigger('onReset').remove(); F._afterZoomOut(); } else { F.isOpen = F.isOpened = false; F.isClosing = true; $('.fancybox-item, .fancybox-nav').remove(); F.wrap.stop(true, true).removeClass('fancybox-opened'); F.transitions[ F.current.closeMethod ](); } }, // Manage slideshow: // $.fancybox.play(); - toggle slideshow // $.fancybox.play( true ); - start // $.fancybox.play( false ); - stop play: function ( action ) { var clear = function () { clearTimeout(F.player.timer); }, set = function () { clear(); if (F.current && F.player.isActive) { F.player.timer = setTimeout(F.next, F.current.playSpeed); } }, stop = function () { clear(); D.unbind('.player'); F.player.isActive = false; F.trigger('onPlayEnd'); }, start = function () { if (F.current && (F.current.loop || F.current.index < F.group.length - 1)) { F.player.isActive = true; D.bind({ 'onCancel.player beforeClose.player' : stop, 'onUpdate.player' : set, 'beforeLoad.player' : clear }); set(); F.trigger('onPlayStart'); } }; if (action === true || (!F.player.isActive && action !== false)) { start(); } else { stop(); } }, // Navigate to next gallery item next: function ( direction ) { var current = F.current; if (current) { if (!isString(direction)) { direction = current.direction.next; } F.jumpto(current.index + 1, direction, 'next'); } }, // Navigate to previous gallery item prev: function ( direction ) { var current = F.current; if (current) { if (!isString(direction)) { direction = current.direction.prev; } F.jumpto(current.index - 1, direction, 'prev'); } }, // Navigate to gallery item by index jumpto: function ( index, direction, router ) { var current = F.current; if (!current) { return; } index = getScalar(index); F.direction = direction || current.direction[ (index >= current.index ? 'next' : 'prev') ]; F.router = router || 'jumpto'; if (current.loop) { if (index < 0) { index = current.group.length + (index % current.group.length); } index = index % current.group.length; } if (current.group[ index ] !== undefined) { F.cancel(); F._start(index); } }, // Center inside viewport and toggle position type to fixed or absolute if needed reposition: function (e, onlyAbsolute) { var current = F.current, wrap = current ? current.wrap : null, pos; if (wrap) { pos = F._getPosition(onlyAbsolute); if (e && e.type === 'scroll') { delete pos.position; wrap.stop(true, true).animate(pos, 200); } else { wrap.css(pos); current.pos = $.extend({}, current.dim, pos); } } }, update: function (e) { var type = (e && e.originalEvent && e.originalEvent.type), anyway = !type || type === 'orientationchange'; if (anyway) { clearTimeout(didUpdate); didUpdate = null; } if (!F.isOpen || didUpdate) { return; } didUpdate = setTimeout(function() { var current = F.current; if (!current || F.isClosing) { return; } F.wrap.removeClass('fancybox-tmp'); if (anyway || type === 'load' || (type === 'resize' && current.autoResize)) { F._setDimension(); } if (!(type === 'scroll' && current.canShrink)) { F.reposition(e); } F.trigger('onUpdate'); didUpdate = null; }, (anyway && !isTouch ? 0 : 300)); }, // Shrink content to fit inside viewport or restore if resized toggle: function ( action ) { if (F.isOpen) { F.current.fitToView = $.type(action) === "boolean" ? action : !F.current.fitToView; // Help browser to restore document dimensions if (isTouch) { F.wrap.removeAttr('style').addClass('fancybox-tmp'); F.trigger('onUpdate'); } F.update(); } }, hideLoading: function () { D.unbind('.loading'); $('#fancybox-loading').remove(); }, showLoading: function () { var el, viewport; F.hideLoading(); el = $(F.opts.tpl.loading).click(F.cancel).appendTo('body'); // If user will press the escape-button, the request will be canceled D.bind('keydown.loading', function(e) { if ((e.which || e.keyCode) === 27) { e.preventDefault(); F.cancel(); } }); if (!F.defaults.fixed) { viewport = F.getViewport(); el.css({ position : 'absolute', top : (viewport.h * 0.5) + viewport.y, left : (viewport.w * 0.5) + viewport.x }); } F.trigger('onLoading'); }, getViewport: function () { var locked = (F.current && F.current.locked) || false, rez = { x: W.scrollLeft(), y: W.scrollTop() }; if (locked && locked.length) { rez.w = locked[0].clientWidth; rez.h = locked[0].clientHeight; } else { // See http://bugs.jquery.com/ticket/6724 rez.w = isTouch && window.innerWidth ? window.innerWidth : W.width(); rez.h = isTouch && window.innerHeight ? window.innerHeight : W.height(); } return rez; }, // Unbind the keyboard / clicking actions unbindEvents: function () { if (F.wrap && isQuery(F.wrap)) { F.wrap.unbind('.fb'); } D.unbind('.fb'); W.unbind('.fb'); }, bindEvents: function () { var current = F.current, keys; if (!current) { return; } // Changing document height on iOS devices triggers a 'resize' event, // that can change document height... repeating infinitely W.bind('orientationchange.fb' + (isTouch ? '' : ' resize.fb') + (current.autoCenter && !current.locked ? ' scroll.fb' : ''), F.update); keys = current.keys; if (keys) { D.bind('keydown.fb', function (e) { var code = e.which || e.keyCode, target = e.target || e.srcElement; // Skip esc key if loading, because showLoading will cancel preloading if (code === 27 && F.coming) { return false; } // Ignore key combinations and key events within form elements if (!e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey && !(target && (target.type || $(target).is('[contenteditable]')))) { $.each(keys, function(i, val) { if (current.group.length > 1 && val[ code ] !== undefined) { F[ i ]( val[ code ] ); e.preventDefault(); return false; } if ($.inArray(code, val) > -1) { F[ i ] (); e.preventDefault(); return false; } }); } }); } if ($.fn.mousewheel && current.mouseWheel) { F.wrap.bind('mousewheel.fb', function (e, delta, deltaX, deltaY) { var target = e.target || null, parent = $(target), canScroll = false; while (parent.length) { if (canScroll || parent.is('.fancybox-skin') || parent.is('.fancybox-wrap')) { break; } canScroll = isScrollable( parent[0] ); parent = $(parent).parent(); } if (delta !== 0 && !canScroll) { if (F.group.length > 1 && !current.canShrink) { if (deltaY > 0 || deltaX > 0) { F.prev( deltaY > 0 ? 'down' : 'left' ); } else if (deltaY < 0 || deltaX < 0) { F.next( deltaY < 0 ? 'up' : 'right' ); } e.preventDefault(); } } }); } }, trigger: function (event, o) { var ret, obj = o || F.coming || F.current; if (obj) { if ($.isFunction( obj[event] )) { ret = obj[event].apply(obj, Array.prototype.slice.call(arguments, 1)); } if (ret === false) { return false; } if (obj.helpers) { $.each(obj.helpers, function (helper, opts) { if (opts && F.helpers[helper] && $.isFunction(F.helpers[helper][event])) { F.helpers[helper][event]($.extend(true, {}, F.helpers[helper].defaults, opts), obj); } }); } } D.trigger(event); }, isImage: function (str) { return isString(str) && str.match(/(^data:image\/.*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg)((\?|#).*)?$)/i); }, isSWF: function (str) { return isString(str) && str.match(/\.(swf)((\?|#).*)?$/i); }, _start: function (index) { var coming = {}, obj, href, type, margin, padding; index = getScalar( index ); obj = F.group[ index ] || null; if (!obj) { return false; } coming = $.extend(true, {}, F.opts, obj); // Convert margin and padding properties to array - top, right, bottom, left margin = coming.margin; padding = coming.padding; if ($.type(margin) === 'number') { coming.margin = [margin, margin, margin, margin]; } if ($.type(padding) === 'number') { coming.padding = [padding, padding, padding, padding]; } // 'modal' propery is just a shortcut if (coming.modal) { $.extend(true, coming, { closeBtn : false, closeClick : false, nextClick : false, arrows : false, mouseWheel : false, keys : null, helpers: { overlay : { closeClick : false } } }); } // 'autoSize' property is a shortcut, too if (coming.autoSize) { coming.autoWidth = coming.autoHeight = true; } if (coming.width === 'auto') { coming.autoWidth = true; } if (coming.height === 'auto') { coming.autoHeight = true; } /* * Add reference to the group, so it`s possible to access from callbacks, example: * afterLoad : function() { * this.title = 'Image ' + (this.index + 1) + ' of ' + this.group.length + (this.title ? ' - ' + this.title : ''); * } */ coming.group = F.group; coming.index = index; // Give a chance for callback or helpers to update coming item (type, title, etc) F.coming = coming; if (false === F.trigger('beforeLoad')) { F.coming = null; return; } type = coming.type; href = coming.href; if (!type) { F.coming = null; //If we can not determine content type then drop silently or display next/prev item if looping through gallery if (F.current && F.router && F.router !== 'jumpto') { F.current.index = index; return F[ F.router ]( F.direction ); } return false; } F.isActive = true; if (type === 'image' || type === 'swf') { coming.autoHeight = coming.autoWidth = false; coming.scrolling = 'visible'; } if (type === 'image') { coming.aspectRatio = true; } if (type === 'iframe' && isTouch) { coming.scrolling = 'scroll'; } // Build the neccessary markup coming.wrap = $(coming.tpl.wrap).addClass('fancybox-' + (isTouch ? 'mobile' : 'desktop') + ' fancybox-type-' + type + ' fancybox-tmp ' + coming.wrapCSS).appendTo( coming.parent || 'body' ); $.extend(coming, { skin : $('.fancybox-skin', coming.wrap), outer : $('.fancybox-outer', coming.wrap), inner : $('.fancybox-inner', coming.wrap) }); $.each(["Top", "Right", "Bottom", "Left"], function(i, v) { coming.skin.css('padding' + v, getValue(coming.padding[ i ])); }); F.trigger('onReady'); // Check before try to load; 'inline' and 'html' types need content, others - href if (type === 'inline' || type === 'html') { if (!coming.content || !coming.content.length) { return F._error( 'content' ); } } else if (!href) { return F._error( 'href' ); } if (type === 'image') { F._loadImage(); } else if (type === 'ajax') { F._loadAjax(); } else if (type === 'iframe') { F._loadIframe(); } else { F._afterLoad(); } }, _error: function ( type ) { $.extend(F.coming, { type : 'html', autoWidth : true, autoHeight : true, minWidth : 0, minHeight : 0, scrolling : 'no', hasError : type, content : F.coming.tpl.error }); F._afterLoad(); }, _loadImage: function () { // Reset preload image so it is later possible to check "complete" property var img = F.imgPreload = new Image(); img.onload = function () { this.onload = this.onerror = null; F.coming.width = this.width / F.opts.pixelRatio; F.coming.height = this.height / F.opts.pixelRatio; F._afterLoad(); }; img.onerror = function () { this.onload = this.onerror = null; F._error( 'image' ); }; img.src = F.coming.href; if (img.complete !== true) { F.showLoading(); } }, _loadAjax: function () { var coming = F.coming; F.showLoading(); F.ajaxLoad = $.ajax($.extend({}, coming.ajax, { url: coming.href, error: function (jqXHR, textStatus) { if (F.coming && textStatus !== 'abort') { F._error( 'ajax', jqXHR ); } else { F.hideLoading(); } }, success: function (data, textStatus) { if (textStatus === 'success') { coming.content = data; F._afterLoad(); } } })); }, _loadIframe: function() { var coming = F.coming, iframe = $(coming.tpl.iframe.replace(/\{rnd\}/g, new Date().getTime())) .attr('scrolling', isTouch ? 'auto' : coming.iframe.scrolling) .attr('src', coming.href); // This helps IE $(coming.wrap).bind('onReset', function () { try { $(this).find('iframe').hide().attr('src', '//about:blank').end().empty(); } catch (e) {} }); if (coming.iframe.preload) { F.showLoading(); iframe.one('load', function() { $(this).data('ready', 1); // iOS will lose scrolling if we resize if (!isTouch) { $(this).bind('load.fb', F.update); } // Without this trick: // - iframe won't scroll on iOS devices // - IE7 sometimes displays empty iframe $(this).parents('.fancybox-wrap').width('100%').removeClass('fancybox-tmp').show(); F._afterLoad(); }); } coming.content = iframe.appendTo( coming.inner ); if (!coming.iframe.preload) { F._afterLoad(); } }, _preloadImages: function() { var group = F.group, current = F.current, len = group.length, cnt = current.preload ? Math.min(current.preload, len - 1) : 0, item, i; for (i = 1; i <= cnt; i += 1) { item = group[ (current.index + i ) % len ]; if (item.type === 'image' && item.href) { new Image().src = item.href; } } }, _afterLoad: function () { var coming = F.coming, previous = F.current, placeholder = 'fancybox-placeholder', current, content, type, scrolling, href, embed; F.hideLoading(); if (!coming || F.isActive === false) { return; } if (false === F.trigger('afterLoad', coming, previous)) { coming.wrap.stop(true).trigger('onReset').remove(); F.coming = null; return; } if (previous) { F.trigger('beforeChange', previous); previous.wrap.stop(true).removeClass('fancybox-opened') .find('.fancybox-item, .fancybox-nav') .remove(); } F.unbindEvents(); current = coming; content = coming.content; type = coming.type; scrolling = coming.scrolling; $.extend(F, { wrap : current.wrap, skin : current.skin, outer : current.outer, inner : current.inner, current : current, previous : previous }); href = current.href; switch (type) { case 'inline': case 'ajax': case 'html': if (current.selector) { content = $('
    ').html(content).find(current.selector); } else if (isQuery(content)) { if (!content.data(placeholder)) { content.data(placeholder, $('
    ').insertAfter( content ).hide() ); } content = content.show().detach(); current.wrap.bind('onReset', function () { if ($(this).find(content).length) { content.hide().replaceAll( content.data(placeholder) ).data(placeholder, false); } }); } break; case 'image': content = current.tpl.image.replace(/\{href\}/g, href); break; case 'swf': content = ''; embed = ''; $.each(current.swf, function(name, val) { content += ''; embed += ' ' + name + '="' + val + '"'; }); content += ''; break; } if (!(isQuery(content) && content.parent().is(current.inner))) { current.inner.append( content ); } // Give a chance for helpers or callbacks to update elements F.trigger('beforeShow'); // Set scrolling before calculating dimensions current.inner.css('overflow', scrolling === 'yes' ? 'scroll' : (scrolling === 'no' ? 'hidden' : scrolling)); // Set initial dimensions and start position F._setDimension(); F.reposition(); F.isOpen = false; F.coming = null; F.bindEvents(); if (!F.isOpened) { $('.fancybox-wrap').not( current.wrap ).stop(true).trigger('onReset').remove(); } else if (previous.prevMethod) { F.transitions[ previous.prevMethod ](); } F.transitions[ F.isOpened ? current.nextMethod : current.openMethod ](); F._preloadImages(); }, _setDimension: function () { var viewport = F.getViewport(), steps = 0, canShrink = false, canExpand = false, wrap = F.wrap, skin = F.skin, inner = F.inner, current = F.current, width = current.width, height = current.height, minWidth = current.minWidth, minHeight = current.minHeight, maxWidth = current.maxWidth, maxHeight = current.maxHeight, scrolling = current.scrolling, scrollOut = current.scrollOutside ? current.scrollbarWidth : 0, margin = current.margin, wMargin = getScalar(margin[1] + margin[3]), hMargin = getScalar(margin[0] + margin[2]), wPadding, hPadding, wSpace, hSpace, origWidth, origHeight, origMaxWidth, origMaxHeight, ratio, width_, height_, maxWidth_, maxHeight_, iframe, body; // Reset dimensions so we could re-check actual size wrap.add(skin).add(inner).width('auto').height('auto').removeClass('fancybox-tmp'); wPadding = getScalar(skin.outerWidth(true) - skin.width()); hPadding = getScalar(skin.outerHeight(true) - skin.height()); // Any space between content and viewport (margin, padding, border, title) wSpace = wMargin + wPadding; hSpace = hMargin + hPadding; origWidth = isPercentage(width) ? (viewport.w - wSpace) * getScalar(width) / 100 : width; origHeight = isPercentage(height) ? (viewport.h - hSpace) * getScalar(height) / 100 : height; if (current.type === 'iframe') { iframe = current.content; if (current.autoHeight && iframe.data('ready') === 1) { try { if (iframe[0].contentWindow.document.location) { inner.width( origWidth ).height(9999); body = iframe.contents().find('body'); if (scrollOut) { body.css('overflow-x', 'hidden'); } origHeight = body.outerHeight(true); } } catch (e) {} } } else if (current.autoWidth || current.autoHeight) { inner.addClass( 'fancybox-tmp' ); // Set width or height in case we need to calculate only one dimension if (!current.autoWidth) { inner.width( origWidth ); } if (!current.autoHeight) { inner.height( origHeight ); } if (current.autoWidth) { origWidth = inner.width(); } if (current.autoHeight) { origHeight = inner.height(); } inner.removeClass( 'fancybox-tmp' ); } width = getScalar( origWidth ); height = getScalar( origHeight ); ratio = origWidth / origHeight; // Calculations for the content minWidth = getScalar(isPercentage(minWidth) ? getScalar(minWidth, 'w') - wSpace : minWidth); maxWidth = getScalar(isPercentage(maxWidth) ? getScalar(maxWidth, 'w') - wSpace : maxWidth); minHeight = getScalar(isPercentage(minHeight) ? getScalar(minHeight, 'h') - hSpace : minHeight); maxHeight = getScalar(isPercentage(maxHeight) ? getScalar(maxHeight, 'h') - hSpace : maxHeight); // These will be used to determine if wrap can fit in the viewport origMaxWidth = maxWidth; origMaxHeight = maxHeight; if (current.fitToView) { maxWidth = Math.min(viewport.w - wSpace, maxWidth); maxHeight = Math.min(viewport.h - hSpace, maxHeight); } maxWidth_ = viewport.w - wMargin; maxHeight_ = viewport.h - hMargin; if (current.aspectRatio) { if (width > maxWidth) { width = maxWidth; height = getScalar(width / ratio); } if (height > maxHeight) { height = maxHeight; width = getScalar(height * ratio); } if (width < minWidth) { width = minWidth; height = getScalar(width / ratio); } if (height < minHeight) { height = minHeight; width = getScalar(height * ratio); } } else { width = Math.max(minWidth, Math.min(width, maxWidth)); if (current.autoHeight && current.type !== 'iframe') { inner.width( width ); height = inner.height(); } height = Math.max(minHeight, Math.min(height, maxHeight)); } // Try to fit inside viewport (including the title) if (current.fitToView) { inner.width( width ).height( height ); wrap.width( width + wPadding ); // Real wrap dimensions width_ = wrap.width(); height_ = wrap.height(); if (current.aspectRatio) { while ((width_ > maxWidth_ || height_ > maxHeight_) && width > minWidth && height > minHeight) { if (steps++ > 19) { break; } height = Math.max(minHeight, Math.min(maxHeight, height - 10)); width = getScalar(height * ratio); if (width < minWidth) { width = minWidth; height = getScalar(width / ratio); } if (width > maxWidth) { width = maxWidth; height = getScalar(width / ratio); } inner.width( width ).height( height ); wrap.width( width + wPadding ); width_ = wrap.width(); height_ = wrap.height(); } } else { width = Math.max(minWidth, Math.min(width, width - (width_ - maxWidth_))); height = Math.max(minHeight, Math.min(height, height - (height_ - maxHeight_))); } } if (scrollOut && scrolling === 'auto' && height < origHeight && (width + wPadding + scrollOut) < maxWidth_) { width += scrollOut; } inner.width( width ).height( height ); wrap.width( width + wPadding ); width_ = wrap.width(); height_ = wrap.height(); canShrink = (width_ > maxWidth_ || height_ > maxHeight_) && width > minWidth && height > minHeight; canExpand = current.aspectRatio ? (width < origMaxWidth && height < origMaxHeight && width < origWidth && height < origHeight) : ((width < origMaxWidth || height < origMaxHeight) && (width < origWidth || height < origHeight)); $.extend(current, { dim : { width : getValue( width_ ), height : getValue( height_ ) }, origWidth : origWidth, origHeight : origHeight, canShrink : canShrink, canExpand : canExpand, wPadding : wPadding, hPadding : hPadding, wrapSpace : height_ - skin.outerHeight(true), skinSpace : skin.height() - height }); if (!iframe && current.autoHeight && height > minHeight && height < maxHeight && !canExpand) { inner.height('auto'); } }, _getPosition: function (onlyAbsolute) { var current = F.current, viewport = F.getViewport(), margin = current.margin, width = F.wrap.width() + margin[1] + margin[3], height = F.wrap.height() + margin[0] + margin[2], rez = { position: 'absolute', top : margin[0], left : margin[3] }; if (current.autoCenter && current.fixed && !onlyAbsolute && height <= viewport.h && width <= viewport.w) { rez.position = 'fixed'; } else if (!current.locked) { rez.top += viewport.y; rez.left += viewport.x; } rez.top = getValue(Math.max(rez.top, rez.top + ((viewport.h - height) * current.topRatio))); rez.left = getValue(Math.max(rez.left, rez.left + ((viewport.w - width) * current.leftRatio))); return rez; }, _afterZoomIn: function () { var current = F.current; if (!current) { return; } F.isOpen = F.isOpened = true; F.wrap.css('overflow', 'visible').addClass('fancybox-opened').hide().show(0); F.update(); // Assign a click event if ( current.closeClick || (current.nextClick && F.group.length > 1) ) { F.inner.css('cursor', 'pointer').bind('click.fb', function(e) { if (!$(e.target).is('a') && !$(e.target).parent().is('a')) { e.preventDefault(); F[ current.closeClick ? 'close' : 'next' ](); } }); } // Create a close button if (current.closeBtn) { $(current.tpl.closeBtn).appendTo(F.skin).bind('click.fb', function(e) { e.preventDefault(); F.close(); }); } // Create navigation arrows if (current.arrows && F.group.length > 1) { if (current.loop || current.index > 0) { $(current.tpl.prev).appendTo(F.outer).bind('click.fb', F.prev); } if (current.loop || current.index < F.group.length - 1) { $(current.tpl.next).appendTo(F.outer).bind('click.fb', F.next); } } F.trigger('afterShow'); // Stop the slideshow if this is the last item if (!current.loop && current.index === current.group.length - 1) { F.play( false ); } else if (F.opts.autoPlay && !F.player.isActive) { F.opts.autoPlay = false; F.play(true); } }, _afterZoomOut: function ( obj ) { obj = obj || F.current; $('.fancybox-wrap').trigger('onReset').remove(); $.extend(F, { group : {}, opts : {}, router : false, current : null, isActive : false, isOpened : false, isOpen : false, isClosing : false, wrap : null, skin : null, outer : null, inner : null }); F.trigger('afterClose', obj); } }); /* * Default transitions */ F.transitions = { getOrigPosition: function () { var current = F.current, element = current.element, orig = current.orig, pos = {}, width = 50, height = 50, hPadding = current.hPadding, wPadding = current.wPadding, viewport = F.getViewport(); if (!orig && current.isDom && element.is(':visible')) { orig = element.find('img:first'); if (!orig.length) { orig = element; } } if (isQuery(orig)) { pos = orig.offset(); if (orig.is('img')) { width = orig.outerWidth(); height = orig.outerHeight(); } } else { pos.top = viewport.y + (viewport.h - height) * current.topRatio; pos.left = viewport.x + (viewport.w - width) * current.leftRatio; } if (F.wrap.css('position') === 'fixed' || current.locked) { pos.top -= viewport.y; pos.left -= viewport.x; } pos = { top : getValue(pos.top - hPadding * current.topRatio), left : getValue(pos.left - wPadding * current.leftRatio), width : getValue(width + wPadding), height : getValue(height + hPadding) }; return pos; }, step: function (now, fx) { var ratio, padding, value, prop = fx.prop, current = F.current, wrapSpace = current.wrapSpace, skinSpace = current.skinSpace; if (prop === 'width' || prop === 'height') { ratio = fx.end === fx.start ? 1 : (now - fx.start) / (fx.end - fx.start); if (F.isClosing) { ratio = 1 - ratio; } padding = prop === 'width' ? current.wPadding : current.hPadding; value = now - padding; F.skin[ prop ]( getScalar( prop === 'width' ? value : value - (wrapSpace * ratio) ) ); F.inner[ prop ]( getScalar( prop === 'width' ? value : value - (wrapSpace * ratio) - (skinSpace * ratio) ) ); } }, zoomIn: function () { var current = F.current, startPos = current.pos, effect = current.openEffect, elastic = effect === 'elastic', endPos = $.extend({opacity : 1}, startPos); // Remove "position" property that breaks older IE delete endPos.position; if (elastic) { startPos = this.getOrigPosition(); if (current.openOpacity) { startPos.opacity = 0.1; } } else if (effect === 'fade') { startPos.opacity = 0.1; } F.wrap.css(startPos).animate(endPos, { duration : effect === 'none' ? 0 : current.openSpeed, easing : current.openEasing, step : elastic ? this.step : null, complete : F._afterZoomIn }); }, zoomOut: function () { var current = F.current, effect = current.closeEffect, elastic = effect === 'elastic', endPos = {opacity : 0.1}; if (elastic) { endPos = this.getOrigPosition(); if (current.closeOpacity) { endPos.opacity = 0.1; } } F.wrap.animate(endPos, { duration : effect === 'none' ? 0 : current.closeSpeed, easing : current.closeEasing, step : elastic ? this.step : null, complete : F._afterZoomOut }); }, changeIn: function () { var current = F.current, effect = current.nextEffect, startPos = current.pos, endPos = { opacity : 1 }, direction = F.direction, distance = 200, field; startPos.opacity = 0.1; if (effect === 'elastic') { field = direction === 'down' || direction === 'up' ? 'top' : 'left'; if (direction === 'down' || direction === 'right') { startPos[ field ] = getValue(getScalar(startPos[ field ]) - distance); endPos[ field ] = '+=' + distance + 'px'; } else { startPos[ field ] = getValue(getScalar(startPos[ field ]) + distance); endPos[ field ] = '-=' + distance + 'px'; } } // Workaround for http://bugs.jquery.com/ticket/12273 if (effect === 'none') { F._afterZoomIn(); } else { F.wrap.css(startPos).animate(endPos, { duration : current.nextSpeed, easing : current.nextEasing, complete : F._afterZoomIn }); } }, changeOut: function () { var previous = F.previous, effect = previous.prevEffect, endPos = { opacity : 0.1 }, direction = F.direction, distance = 200; if (effect === 'elastic') { endPos[ direction === 'down' || direction === 'up' ? 'top' : 'left' ] = ( direction === 'up' || direction === 'left' ? '-' : '+' ) + '=' + distance + 'px'; } previous.wrap.animate(endPos, { duration : effect === 'none' ? 0 : previous.prevSpeed, easing : previous.prevEasing, complete : function () { $(this).trigger('onReset').remove(); } }); } }; /* * Overlay helper */ F.helpers.overlay = { defaults : { closeClick : true, // if true, fancyBox will be closed when user clicks on the overlay speedOut : 200, // duration of fadeOut animation showEarly : true, // indicates if should be opened immediately or wait until the content is ready css : {}, // custom CSS properties locked : !isTouch, // if true, the content will be locked into overlay fixed : true // if false, the overlay CSS position property will not be set to "fixed" }, overlay : null, // current handle fixed : false, // indicates if the overlay has position "fixed" el : $('html'), // element that contains "the lock" // Public methods create : function(opts) { var parent; opts = $.extend({}, this.defaults, opts); if (this.overlay) { this.close(); } parent = F.coming ? F.coming.parent : opts.parent; this.overlay = $('
    ').appendTo( parent && parent.length ? parent : 'body' ); this.fixed = false; if (opts.fixed && F.defaults.fixed) { this.overlay.addClass('fancybox-overlay-fixed'); this.fixed = true; } }, open : function(opts) { var that = this; opts = $.extend({}, this.defaults, opts); if (this.overlay) { this.overlay.unbind('.overlay').width('auto').height('auto'); } else { this.create(opts); } if (!this.fixed) { W.bind('resize.overlay', $.proxy( this.update, this) ); this.update(); } if (opts.closeClick) { this.overlay.bind('click.overlay', function(e) { if ($(e.target).hasClass('fancybox-overlay')) { if (F.isActive) { F.close(); } else { that.close(); } return false; } }); } this.overlay.css( opts.css ).show(); }, close : function() { W.unbind('resize.overlay'); if (this.el.hasClass('fancybox-lock')) { $('.fancybox-margin').removeClass('fancybox-margin'); this.el.removeClass('fancybox-lock'); W.scrollTop( this.scrollV ).scrollLeft( this.scrollH ); } $('.fancybox-overlay').remove().hide(); $.extend(this, { overlay : null, fixed : false }); }, // Private, callbacks update : function () { var width = '100%', offsetWidth; // Reset width/height so it will not mess this.overlay.width(width).height('100%'); // jQuery does not return reliable result for IE if (IE) { offsetWidth = Math.max(document.documentElement.offsetWidth, document.body.offsetWidth); if (D.width() > offsetWidth) { width = D.width(); } } else if (D.width() > W.width()) { width = D.width(); } this.overlay.width(width).height(D.height()); }, // This is where we can manipulate DOM, because later it would cause iframes to reload onReady : function (opts, obj) { var overlay = this.overlay; $('.fancybox-overlay').stop(true, true); if (!overlay) { this.create(opts); } if (opts.locked && this.fixed && obj.fixed) { obj.locked = this.overlay.append( obj.wrap ); obj.fixed = false; } if (opts.showEarly === true) { this.beforeShow.apply(this, arguments); } }, beforeShow : function(opts, obj) { if (obj.locked && !this.el.hasClass('fancybox-lock')) { if (this.fixPosition !== false) { $('*').filter(function(){ return ($(this).css('position') === 'fixed' && !$(this).hasClass("fancybox-overlay") && !$(this).hasClass("fancybox-wrap") ); }).addClass('fancybox-margin'); } this.el.addClass('fancybox-margin'); this.scrollV = W.scrollTop(); this.scrollH = W.scrollLeft(); this.el.addClass('fancybox-lock'); W.scrollTop( this.scrollV ).scrollLeft( this.scrollH ); } this.open(opts); }, onUpdate : function() { if (!this.fixed) { this.update(); } }, afterClose: function (opts) { // Remove overlay if exists and fancyBox is not opening // (e.g., it is not being open using afterClose callback) if (this.overlay && !F.coming) { this.overlay.fadeOut(opts.speedOut, $.proxy( this.close, this )); } } }; /* * Title helper */ F.helpers.title = { defaults : { type : 'float', // 'float', 'inside', 'outside' or 'over', position : 'bottom' // 'top' or 'bottom' }, beforeShow: function (opts) { var current = F.current, text = current.title, type = opts.type, title, target; if ($.isFunction(text)) { text = text.call(current.element, current); } if (!isString(text) || $.trim(text) === '') { return; } title = $('
    ' + text + '
    '); switch (type) { case 'inside': target = F.skin; break; case 'outside': target = F.wrap; break; case 'over': target = F.inner; break; default: // 'float' target = F.skin; title.appendTo('body'); if (IE) { title.width( title.width() ); } title.wrapInner(''); //Increase bottom margin so this title will also fit into viewport F.current.margin[2] += Math.abs( getScalar(title.css('margin-bottom')) ); break; } title[ (opts.position === 'top' ? 'prependTo' : 'appendTo') ](target); } }; // jQuery plugin initialization $.fn.fancybox = function (options) { var index, that = $(this), selector = this.selector || '', run = function(e) { var what = $(this).blur(), idx = index, relType, relVal; if (!(e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) && !what.is('.fancybox-wrap')) { relType = options.groupAttr || 'data-fancybox-group'; relVal = what.attr(relType); if (!relVal) { relType = 'rel'; relVal = what.get(0)[ relType ]; } if (relVal && relVal !== '' && relVal !== 'nofollow') { what = selector.length ? $(selector) : that; what = what.filter('[' + relType + '="' + relVal + '"]'); idx = what.index(this); } options.index = idx; // Stop an event from bubbling if everything is fine if (F.open(what, options) !== false) { e.preventDefault(); } } }; options = options || {}; index = options.index || 0; if (!selector || options.live === false) { that.unbind('click.fb-start').bind('click.fb-start', run); } else { D.undelegate(selector, 'click.fb-start').delegate(selector + ":not('.fancybox-item, .fancybox-nav')", 'click.fb-start', run); } this.filter('[data-fancybox-start=1]').trigger('click'); return this; }; // Tests that need a body at doc ready D.ready(function() { var w1, w2; if ( $.scrollbarWidth === undefined ) { // http://benalman.com/projects/jquery-misc-plugins/#scrollbarwidth $.scrollbarWidth = function() { var parent = $('
    ').appendTo('body'), child = parent.children(), width = child.innerWidth() - child.height( 99 ).innerWidth(); parent.remove(); return width; }; } if ( $.support.fixedPosition === undefined ) { $.support.fixedPosition = (function() { var elem = $('
    ').appendTo('body'), fixed = ( elem[0].offsetTop === 20 || elem[0].offsetTop === 15 ); elem.remove(); return fixed; }()); } $.extend(F.defaults, { scrollbarWidth : $.scrollbarWidth(), fixed : $.support.fixedPosition, parent : $('body') }); //Get real width of page scroll-bar w1 = $(window).width(); H.addClass('fancybox-lock-test'); w2 = $(window).width(); H.removeClass('fancybox-lock-test'); $("").appendTo("head"); }); }(window, document, jQuery)); /* * jQuery FlexSlider v2.5.0 * Copyright 2012 WooThemes * Contributing Author: Tyler Smith */ ; (function ($) { //FlexSlider: Object Instance $.flexslider = function(el, options) { var slider = $(el); // making variables public slider.vars = $.extend({}, $.flexslider.defaults, options); var namespace = slider.vars.namespace, msGesture = window.navigator && window.navigator.msPointerEnabled && window.MSGesture, touch = (( "ontouchstart" in window ) || msGesture || window.DocumentTouch && document instanceof DocumentTouch) && slider.vars.touch, // depricating this idea, as devices are being released with both of these events //eventType = (touch) ? "touchend" : "click", eventType = "click touchend MSPointerUp keyup", watchedEvent = "", watchedEventClearTimer, vertical = slider.vars.direction === "vertical", reverse = slider.vars.reverse, carousel = (slider.vars.itemWidth > 0), fade = slider.vars.animation === "fade", asNav = slider.vars.asNavFor !== "", methods = {}, focused = true; // Store a reference to the slider object $.data(el, "flexslider", slider); // Private slider methods methods = { init: function() { slider.animating = false; // Get current slide and make sure it is a number slider.currentSlide = parseInt( ( slider.vars.startAt ? slider.vars.startAt : 0), 10 ); if ( isNaN( slider.currentSlide ) ) { slider.currentSlide = 0; } slider.animatingTo = slider.currentSlide; slider.atEnd = (slider.currentSlide === 0 || slider.currentSlide === slider.last); slider.containerSelector = slider.vars.selector.substr(0,slider.vars.selector.search(' ')); slider.slides = $(slider.vars.selector, slider); slider.container = $(slider.containerSelector, slider); slider.count = slider.slides.length; // SYNC: slider.syncExists = $(slider.vars.sync).length > 0; // SLIDE: if (slider.vars.animation === "slide") { slider.vars.animation = "swing"; } slider.prop = (vertical) ? "top" : "marginLeft"; slider.args = {}; // SLIDESHOW: slider.manualPause = false; slider.stopped = false; //PAUSE WHEN INVISIBLE slider.started = false; slider.startTimeout = null; // TOUCH/USECSS: slider.transitions = !slider.vars.video && !fade && slider.vars.useCSS && (function() { var obj = document.createElement('div'), props = ['perspectiveProperty', 'WebkitPerspective', 'MozPerspective', 'OPerspective', 'msPerspective']; for (var i in props) { if ( obj.style[ props[i] ] !== undefined ) { slider.pfx = props[i].replace('Perspective','').toLowerCase(); slider.prop = "-" + slider.pfx + "-transform"; return true; } } return false; }()); slider.ensureAnimationEnd = ''; // CONTROLSCONTAINER: if (slider.vars.controlsContainer !== "") slider.controlsContainer = $(slider.vars.controlsContainer).length > 0 && $(slider.vars.controlsContainer); // MANUAL: if (slider.vars.manualControls !== "") slider.manualControls = $(slider.vars.manualControls).length > 0 && $(slider.vars.manualControls); // CUSTOM DIRECTION NAV: if (slider.vars.customDirectionNav !== "") slider.customDirectionNav = $(slider.vars.customDirectionNav).length === 2 && $(slider.vars.customDirectionNav); // RANDOMIZE: if (slider.vars.randomize) { slider.slides.sort(function() { return (Math.round(Math.random())-0.5); }); slider.container.empty().append(slider.slides); } slider.doMath(); // INIT slider.setup("init"); // CONTROLNAV: if (slider.vars.controlNav) { methods.controlNav.setup(); } // DIRECTIONNAV: if (slider.vars.directionNav) { methods.directionNav.setup(); } // KEYBOARD: if (slider.vars.keyboard && ($(slider.containerSelector).length === 1 || slider.vars.multipleKeyboard)) { $(document).bind('keyup', function(event) { var keycode = event.keyCode; if (!slider.animating && (keycode === 39 || keycode === 37)) { var target = (keycode === 39) ? slider.getTarget('next') : (keycode === 37) ? slider.getTarget('prev') : false; slider.flexAnimate(target, slider.vars.pauseOnAction); } }); } // MOUSEWHEEL: if (slider.vars.mousewheel) { slider.bind('mousewheel', function(event, delta, deltaX, deltaY) { event.preventDefault(); var target = (delta < 0) ? slider.getTarget('next') : slider.getTarget('prev'); slider.flexAnimate(target, slider.vars.pauseOnAction); }); } // PAUSEPLAY if (slider.vars.pausePlay) { methods.pausePlay.setup(); } //PAUSE WHEN INVISIBLE if (slider.vars.slideshow && slider.vars.pauseInvisible) { methods.pauseInvisible.init(); } // SLIDSESHOW if (slider.vars.slideshow) { if (slider.vars.pauseOnHover) { slider.hover(function() { if (!slider.manualPlay && !slider.manualPause) { slider.pause(); } }, function() { if (!slider.manualPause && !slider.manualPlay && !slider.stopped) { slider.play(); } }); } // initialize animation //If we're visible, or we don't use PageVisibility API if(!slider.vars.pauseInvisible || !methods.pauseInvisible.isHidden()) { (slider.vars.initDelay > 0) ? slider.startTimeout = setTimeout(slider.play, slider.vars.initDelay) : slider.play(); } } // ASNAV: if (asNav) { methods.asNav.setup(); } // TOUCH if (touch && slider.vars.touch) { methods.touch(); } // FADE&&SMOOTHHEIGHT || SLIDE: if (!fade || (fade && slider.vars.smoothHeight)) { $(window).bind("resize orientationchange focus", methods.resize); } slider.find("img").attr("draggable", "false"); // API: start() Callback setTimeout(function(){ slider.vars.start(slider); }, 200); }, asNav: { setup: function() { slider.asNav = true; slider.animatingTo = Math.floor(slider.currentSlide/slider.move); slider.currentItem = slider.currentSlide; slider.slides.removeClass(namespace + "active-slide").eq(slider.currentItem).addClass(namespace + "active-slide"); if(!msGesture){ slider.slides.on(eventType, function(e){ e.preventDefault(); var $slide = $(this), target = $slide.index(); var posFromLeft = $slide.offset().left - $(slider).scrollLeft(); // Find position of slide relative to left of slider container if( posFromLeft <= 0 && $slide.hasClass( namespace + 'active-slide' ) ) { slider.flexAnimate(slider.getTarget("prev"), true); } else if (!$(slider.vars.asNavFor).data('flexslider').animating && !$slide.hasClass(namespace + "active-slide")) { slider.direction = (slider.currentItem < target) ? "next" : "prev"; slider.flexAnimate(target, slider.vars.pauseOnAction, false, true, true); } }); }else{ el._slider = slider; slider.slides.each(function (){ var that = this; that._gesture = new MSGesture(); that._gesture.target = that; that.addEventListener("MSPointerDown", function (e){ e.preventDefault(); if(e.currentTarget._gesture) { e.currentTarget._gesture.addPointer(e.pointerId); } }, false); that.addEventListener("MSGestureTap", function (e){ e.preventDefault(); var $slide = $(this), target = $slide.index(); if (!$(slider.vars.asNavFor).data('flexslider').animating && !$slide.hasClass('active')) { slider.direction = (slider.currentItem < target) ? "next" : "prev"; slider.flexAnimate(target, slider.vars.pauseOnAction, false, true, true); } }); }); } } }, controlNav: { setup: function() { if (!slider.manualControls) { methods.controlNav.setupPaging(); } else { // MANUALCONTROLS: methods.controlNav.setupManual(); } }, setupPaging: function() { var type = (slider.vars.controlNav === "thumbnails") ? 'control-thumbs' : 'control-paging', j = 1, item, slide; slider.controlNavScaffold = $('
      '); if (slider.pagingCount > 1) { for (var i = 0; i < slider.pagingCount; i++) { slide = slider.slides.eq(i); item = (slider.vars.controlNav === "thumbnails") ? '' : '' + j + ''; if ( 'thumbnails' === slider.vars.controlNav && true === slider.vars.thumbCaptions ) { var captn = slide.attr( 'data-thumbcaption' ); if ( '' !== captn && undefined !== captn ) { item += '' + captn + ''; } } slider.controlNavScaffold.append('
    1. ' + item + '
    2. '); j++; } } // CONTROLSCONTAINER: (slider.controlsContainer) ? $(slider.controlsContainer).append(slider.controlNavScaffold) : slider.append(slider.controlNavScaffold); methods.controlNav.set(); methods.controlNav.active(); slider.controlNavScaffold.delegate('a, img', eventType, function(event) { event.preventDefault(); if (watchedEvent === "" || watchedEvent === event.type) { var $this = $(this), target = slider.controlNav.index($this); if (!$this.hasClass(namespace + 'active')) { slider.direction = (target > slider.currentSlide) ? "next" : "prev"; slider.flexAnimate(target, slider.vars.pauseOnAction); } } // setup flags to prevent event duplication if (watchedEvent === "") { watchedEvent = event.type; } methods.setToClearWatchedEvent(); }); }, setupManual: function() { slider.controlNav = slider.manualControls; methods.controlNav.active(); slider.controlNav.bind(eventType, function(event) { event.preventDefault(); if (watchedEvent === "" || watchedEvent === event.type) { var $this = $(this), target = slider.controlNav.index($this); if (!$this.hasClass(namespace + 'active')) { (target > slider.currentSlide) ? slider.direction = "next" : slider.direction = "prev"; slider.flexAnimate(target, slider.vars.pauseOnAction); } } // setup flags to prevent event duplication if (watchedEvent === "") { watchedEvent = event.type; } methods.setToClearWatchedEvent(); }); }, set: function() { var selector = (slider.vars.controlNav === "thumbnails") ? 'img' : 'a'; slider.controlNav = $('.' + namespace + 'control-nav li ' + selector, (slider.controlsContainer) ? slider.controlsContainer : slider); }, active: function() { slider.controlNav.removeClass(namespace + "active").eq(slider.animatingTo).addClass(namespace + "active"); }, update: function(action, pos) { if (slider.pagingCount > 1 && action === "add") { slider.controlNavScaffold.append($('
    3. ' + slider.count + '
    4. ')); } else if (slider.pagingCount === 1) { slider.controlNavScaffold.find('li').remove(); } else { slider.controlNav.eq(pos).closest('li').remove(); } methods.controlNav.set(); (slider.pagingCount > 1 && slider.pagingCount !== slider.controlNav.length) ? slider.update(pos, action) : methods.controlNav.active(); } }, directionNav: { setup: function() { var directionNavScaffold = $(''); // CUSTOM DIRECTION NAV: if (slider.customDirectionNav) { slider.directionNav = slider.customDirectionNav; // CONTROLSCONTAINER: } else if (slider.controlsContainer) { $(slider.controlsContainer).append(directionNavScaffold); slider.directionNav = $('.' + namespace + 'direction-nav li a', slider.controlsContainer); } else { slider.append(directionNavScaffold); slider.directionNav = $('.' + namespace + 'direction-nav li a', slider); } methods.directionNav.update(); slider.directionNav.bind(eventType, function(event) { event.preventDefault(); var target; if (watchedEvent === "" || watchedEvent === event.type) { target = ($(this).hasClass(namespace + 'next')) ? slider.getTarget('next') : slider.getTarget('prev'); slider.flexAnimate(target, slider.vars.pauseOnAction); } // setup flags to prevent event duplication if (watchedEvent === "") { watchedEvent = event.type; } methods.setToClearWatchedEvent(); }); }, update: function() { var disabledClass = namespace + 'disabled'; if (slider.pagingCount === 1) { slider.directionNav.addClass(disabledClass).attr('tabindex', '-1'); } else if (!slider.vars.animationLoop) { if (slider.animatingTo === 0) { slider.directionNav.removeClass(disabledClass).filter('.' + namespace + "prev").addClass(disabledClass).attr('tabindex', '-1'); } else if (slider.animatingTo === slider.last) { slider.directionNav.removeClass(disabledClass).filter('.' + namespace + "next").addClass(disabledClass).attr('tabindex', '-1'); } else { slider.directionNav.removeClass(disabledClass).removeAttr('tabindex'); } } else { slider.directionNav.removeClass(disabledClass).removeAttr('tabindex'); } } }, pausePlay: { setup: function() { var pausePlayScaffold = $('
      '); // CONTROLSCONTAINER: if (slider.controlsContainer) { slider.controlsContainer.append(pausePlayScaffold); slider.pausePlay = $('.' + namespace + 'pauseplay a', slider.controlsContainer); } else { slider.append(pausePlayScaffold); slider.pausePlay = $('.' + namespace + 'pauseplay a', slider); } methods.pausePlay.update((slider.vars.slideshow) ? namespace + 'pause' : namespace + 'play'); slider.pausePlay.bind(eventType, function(event) { event.preventDefault(); if (watchedEvent === "" || watchedEvent === event.type) { if ($(this).hasClass(namespace + 'pause')) { slider.manualPause = true; slider.manualPlay = false; slider.pause(); } else { slider.manualPause = false; slider.manualPlay = true; slider.play(); } } // setup flags to prevent event duplication if (watchedEvent === "") { watchedEvent = event.type; } methods.setToClearWatchedEvent(); }); }, update: function(state) { (state === "play") ? slider.pausePlay.removeClass(namespace + 'pause').addClass(namespace + 'play').html(slider.vars.playText) : slider.pausePlay.removeClass(namespace + 'play').addClass(namespace + 'pause').html(slider.vars.pauseText); } }, touch: function() { var startX, startY, offset, cwidth, dx, startT, onTouchStart, onTouchMove, onTouchEnd, scrolling = false, localX = 0, localY = 0, accDx = 0; if(!msGesture){ onTouchStart = function(e) { if (slider.animating) { e.preventDefault(); } else if ( ( window.navigator.msPointerEnabled ) || e.touches.length === 1 ) { slider.pause(); // CAROUSEL: cwidth = (vertical) ? slider.h : slider. w; startT = Number(new Date()); // CAROUSEL: // Local vars for X and Y points. localX = e.touches[0].pageX; localY = e.touches[0].pageY; offset = (carousel && reverse && slider.animatingTo === slider.last) ? 0 : (carousel && reverse) ? slider.limit - (((slider.itemW + slider.vars.itemMargin) * slider.move) * slider.animatingTo) : (carousel && slider.currentSlide === slider.last) ? slider.limit : (carousel) ? ((slider.itemW + slider.vars.itemMargin) * slider.move) * slider.currentSlide : (reverse) ? (slider.last - slider.currentSlide + slider.cloneOffset) * cwidth : (slider.currentSlide + slider.cloneOffset) * cwidth; startX = (vertical) ? localY : localX; startY = (vertical) ? localX : localY; el.addEventListener('touchmove', onTouchMove, false); el.addEventListener('touchend', onTouchEnd, false); } }; onTouchMove = function(e) { // Local vars for X and Y points. localX = e.touches[0].pageX; localY = e.touches[0].pageY; dx = (vertical) ? startX - localY : startX - localX; scrolling = (vertical) ? (Math.abs(dx) < Math.abs(localX - startY)) : (Math.abs(dx) < Math.abs(localY - startY)); var fxms = 500; if ( ! scrolling || Number( new Date() ) - startT > fxms ) { e.preventDefault(); if (!fade && slider.transitions) { if (!slider.vars.animationLoop) { dx = dx/((slider.currentSlide === 0 && dx < 0 || slider.currentSlide === slider.last && dx > 0) ? (Math.abs(dx)/cwidth+2) : 1); } slider.setProps(offset + dx, "setTouch"); } } }; onTouchEnd = function(e) { // finish the touch by undoing the touch session el.removeEventListener('touchmove', onTouchMove, false); if (slider.animatingTo === slider.currentSlide && !scrolling && !(dx === null)) { var updateDx = (reverse) ? -dx : dx, target = (updateDx > 0) ? slider.getTarget('next') : slider.getTarget('prev'); if (slider.canAdvance(target) && (Number(new Date()) - startT < 550 && Math.abs(updateDx) > 50 || Math.abs(updateDx) > cwidth/2)) { slider.flexAnimate(target, slider.vars.pauseOnAction); } else { if (!fade) { slider.flexAnimate(slider.currentSlide, slider.vars.pauseOnAction, true); } } } el.removeEventListener('touchend', onTouchEnd, false); startX = null; startY = null; dx = null; offset = null; }; el.addEventListener('touchstart', onTouchStart, false); }else{ el.style.msTouchAction = "none"; el._gesture = new MSGesture(); el._gesture.target = el; el.addEventListener("MSPointerDown", onMSPointerDown, false); el._slider = slider; el.addEventListener("MSGestureChange", onMSGestureChange, false); el.addEventListener("MSGestureEnd", onMSGestureEnd, false); function onMSPointerDown(e){ e.stopPropagation(); if (slider.animating) { e.preventDefault(); }else{ slider.pause(); el._gesture.addPointer(e.pointerId); accDx = 0; cwidth = (vertical) ? slider.h : slider. w; startT = Number(new Date()); // CAROUSEL: offset = (carousel && reverse && slider.animatingTo === slider.last) ? 0 : (carousel && reverse) ? slider.limit - (((slider.itemW + slider.vars.itemMargin) * slider.move) * slider.animatingTo) : (carousel && slider.currentSlide === slider.last) ? slider.limit : (carousel) ? ((slider.itemW + slider.vars.itemMargin) * slider.move) * slider.currentSlide : (reverse) ? (slider.last - slider.currentSlide + slider.cloneOffset) * cwidth : (slider.currentSlide + slider.cloneOffset) * cwidth; } } function onMSGestureChange(e) { e.stopPropagation(); var slider = e.target._slider; if(!slider){ return; } var transX = -e.translationX, transY = -e.translationY; //Accumulate translations. accDx = accDx + ((vertical) ? transY : transX); dx = accDx; scrolling = (vertical) ? (Math.abs(accDx) < Math.abs(-transX)) : (Math.abs(accDx) < Math.abs(-transY)); if(e.detail === e.MSGESTURE_FLAG_INERTIA){ setImmediate(function (){ el._gesture.stop(); }); return; } if (!scrolling || Number(new Date()) - startT > 500) { e.preventDefault(); if (!fade && slider.transitions) { if (!slider.vars.animationLoop) { dx = accDx / ((slider.currentSlide === 0 && accDx < 0 || slider.currentSlide === slider.last && accDx > 0) ? (Math.abs(accDx) / cwidth + 2) : 1); } slider.setProps(offset + dx, "setTouch"); } } } function onMSGestureEnd(e) { e.stopPropagation(); var slider = e.target._slider; if(!slider){ return; } if (slider.animatingTo === slider.currentSlide && !scrolling && !(dx === null)) { var updateDx = (reverse) ? -dx : dx, target = (updateDx > 0) ? slider.getTarget('next') : slider.getTarget('prev'); if (slider.canAdvance(target) && (Number(new Date()) - startT < 550 && Math.abs(updateDx) > 50 || Math.abs(updateDx) > cwidth/2)) { slider.flexAnimate(target, slider.vars.pauseOnAction); } else { if (!fade) { slider.flexAnimate(slider.currentSlide, slider.vars.pauseOnAction, true); } } } startX = null; startY = null; dx = null; offset = null; accDx = 0; } } }, resize: function() { if (!slider.animating && slider.is(':visible')) { if (!carousel) { slider.doMath(); } if (fade) { // SMOOTH HEIGHT: methods.smoothHeight(); } else if (carousel) { //CAROUSEL: slider.slides.width(slider.computedW); slider.update(slider.pagingCount); slider.setProps(); } else if (vertical) { //VERTICAL: slider.viewport.height(slider.h); slider.setProps(slider.h, "setTotal"); } else { // SMOOTH HEIGHT: if (slider.vars.smoothHeight) { methods.smoothHeight(); } slider.newSlides.width(slider.computedW); slider.setProps(slider.computedW, "setTotal"); } } }, smoothHeight: function(dur) { if (!vertical || fade) { var $obj = (fade) ? slider : slider.viewport; (dur) ? $obj.animate({"height": slider.slides.eq(slider.animatingTo).height()}, dur) : $obj.height(slider.slides.eq(slider.animatingTo).height()); } }, sync: function(action) { var $obj = $(slider.vars.sync).data("flexslider"), target = slider.animatingTo; switch (action) { case "animate": $obj.flexAnimate(target, slider.vars.pauseOnAction, false, true); break; case "play": if (!$obj.playing && !$obj.asNav) { $obj.play(); } break; case "pause": $obj.pause(); break; } }, uniqueID: function($clone) { // Append _clone to current level and children elements with id attributes $clone.filter( '[id]' ).add($clone.find( '[id]' )).each(function() { var $this = $(this); $this.attr( 'id', $this.attr( 'id' ) + '_clone' ); }); return $clone; }, pauseInvisible: { visProp: null, init: function() { var visProp = methods.pauseInvisible.getHiddenProp(); if (visProp) { var evtname = visProp.replace(/[H|h]idden/,'') + 'visibilitychange'; document.addEventListener(evtname, function() { if (methods.pauseInvisible.isHidden()) { if(slider.startTimeout) { clearTimeout(slider.startTimeout); //If clock is ticking, stop timer and prevent from starting while invisible } else { slider.pause(); //Or just pause } } else { if(slider.started) { slider.play(); //Initiated before, just play } else { if (slider.vars.initDelay > 0) { setTimeout(slider.play, slider.vars.initDelay); } else { slider.play(); //Didn't init before: simply init or wait for it } } } }); } }, isHidden: function() { var prop = methods.pauseInvisible.getHiddenProp(); if (!prop) { return false; } return document[prop]; }, getHiddenProp: function() { var prefixes = ['webkit','moz','ms','o']; // if 'hidden' is natively supported just return it if ('hidden' in document) { return 'hidden'; } // otherwise loop over all the known prefixes until we find one for ( var i = 0; i < prefixes.length; i++ ) { if ((prefixes[i] + 'Hidden') in document) { return prefixes[i] + 'Hidden'; } } // otherwise it's not supported return null; } }, setToClearWatchedEvent: function() { clearTimeout(watchedEventClearTimer); watchedEventClearTimer = setTimeout(function() { watchedEvent = ""; }, 3000); } }; // public methods slider.flexAnimate = function(target, pause, override, withSync, fromNav) { if (!slider.vars.animationLoop && target !== slider.currentSlide) { slider.direction = (target > slider.currentSlide) ? "next" : "prev"; } if (asNav && slider.pagingCount === 1) slider.direction = (slider.currentItem < target) ? "next" : "prev"; if (!slider.animating && (slider.canAdvance(target, fromNav) || override) && slider.is(":visible")) { if (asNav && withSync) { var master = $(slider.vars.asNavFor).data('flexslider'); slider.atEnd = target === 0 || target === slider.count - 1; master.flexAnimate(target, true, false, true, fromNav); slider.direction = (slider.currentItem < target) ? "next" : "prev"; master.direction = slider.direction; if (Math.ceil((target + 1)/slider.visible) - 1 !== slider.currentSlide && target !== 0) { slider.currentItem = target; slider.slides.removeClass(namespace + "active-slide").eq(target).addClass(namespace + "active-slide"); target = Math.floor(target/slider.visible); } else { slider.currentItem = target; slider.slides.removeClass(namespace + "active-slide").eq(target).addClass(namespace + "active-slide"); return false; } } slider.animating = true; slider.animatingTo = target; // SLIDESHOW: if (pause) { slider.pause(); } // API: before() animation Callback slider.vars.before(slider); // SYNC: if (slider.syncExists && !fromNav) { methods.sync("animate"); } // CONTROLNAV if (slider.vars.controlNav) { methods.controlNav.active(); } // !CAROUSEL: // CANDIDATE: slide active class (for add/remove slide) if (!carousel) { slider.slides.removeClass(namespace + 'active-slide').eq(target).addClass(namespace + 'active-slide'); } // INFINITE LOOP: // CANDIDATE: atEnd slider.atEnd = target === 0 || target === slider.last; // DIRECTIONNAV: if (slider.vars.directionNav) { methods.directionNav.update(); } if (target === slider.last) { // API: end() of cycle Callback slider.vars.end(slider); // SLIDESHOW && !INFINITE LOOP: if (!slider.vars.animationLoop) { slider.pause(); } } // SLIDE: if (!fade) { var dimension = (vertical) ? slider.slides.filter(':first').height() : slider.computedW, margin, slideString, calcNext; // INFINITE LOOP / REVERSE: if (carousel) { //margin = (slider.vars.itemWidth > slider.w) ? slider.vars.itemMargin * 2 : slider.vars.itemMargin; margin = slider.vars.itemMargin; calcNext = ((slider.itemW + margin) * slider.move) * slider.animatingTo; slideString = (calcNext > slider.limit && slider.visible !== 1) ? slider.limit : calcNext; } else if (slider.currentSlide === 0 && target === slider.count - 1 && slider.vars.animationLoop && slider.direction !== "next") { slideString = (reverse) ? (slider.count + slider.cloneOffset) * dimension : 0; } else if (slider.currentSlide === slider.last && target === 0 && slider.vars.animationLoop && slider.direction !== "prev") { slideString = (reverse) ? 0 : (slider.count + 1) * dimension; } else { slideString = (reverse) ? ((slider.count - 1) - target + slider.cloneOffset) * dimension : (target + slider.cloneOffset) * dimension; } slider.setProps(slideString, "", slider.vars.animationSpeed); if (slider.transitions) { if (!slider.vars.animationLoop || !slider.atEnd) { slider.animating = false; slider.currentSlide = slider.animatingTo; } // Unbind previous transitionEnd events and re-bind new transitionEnd event slider.container.unbind("webkitTransitionEnd transitionend"); slider.container.bind("webkitTransitionEnd transitionend", function() { clearTimeout(slider.ensureAnimationEnd); slider.wrapup(dimension); }); // Insurance for the ever-so-fickle transitionEnd event clearTimeout(slider.ensureAnimationEnd); slider.ensureAnimationEnd = setTimeout(function() { slider.wrapup(dimension); }, slider.vars.animationSpeed + 100); } else { slider.container.animate(slider.args, slider.vars.animationSpeed, slider.vars.easing, function(){ slider.wrapup(dimension); }); } } else { // FADE: if (!touch) { //slider.slides.eq(slider.currentSlide).fadeOut(slider.vars.animationSpeed, slider.vars.easing); //slider.slides.eq(target).fadeIn(slider.vars.animationSpeed, slider.vars.easing, slider.wrapup); slider.slides.eq(slider.currentSlide).css({"zIndex": 1}).animate({"opacity": 0}, slider.vars.animationSpeed, slider.vars.easing); slider.slides.eq(target).css({"zIndex": 2}).animate({"opacity": 1}, slider.vars.animationSpeed, slider.vars.easing, slider.wrapup); } else { slider.slides.eq(slider.currentSlide).css({ "opacity": 0, "zIndex": 1 }); slider.slides.eq(target).css({ "opacity": 1, "zIndex": 2 }); slider.wrapup(dimension); } } // SMOOTH HEIGHT: if (slider.vars.smoothHeight) { methods.smoothHeight(slider.vars.animationSpeed); } } }; slider.wrapup = function(dimension) { // SLIDE: if (!fade && !carousel) { if (slider.currentSlide === 0 && slider.animatingTo === slider.last && slider.vars.animationLoop) { slider.setProps(dimension, "jumpEnd"); } else if (slider.currentSlide === slider.last && slider.animatingTo === 0 && slider.vars.animationLoop) { slider.setProps(dimension, "jumpStart"); } } slider.animating = false; slider.currentSlide = slider.animatingTo; // API: after() animation Callback slider.vars.after(slider); }; // SLIDESHOW: slider.animateSlides = function() { if (!slider.animating && focused ) { slider.flexAnimate(slider.getTarget("next")); } }; // SLIDESHOW: slider.pause = function() { clearInterval(slider.animatedSlides); slider.animatedSlides = null; slider.playing = false; // PAUSEPLAY: if (slider.vars.pausePlay) { methods.pausePlay.update("play"); } // SYNC: if (slider.syncExists) { methods.sync("pause"); } }; // SLIDESHOW: slider.play = function() { if (slider.playing) { clearInterval(slider.animatedSlides); } slider.animatedSlides = slider.animatedSlides || setInterval(slider.animateSlides, slider.vars.slideshowSpeed); slider.started = slider.playing = true; // PAUSEPLAY: if (slider.vars.pausePlay) { methods.pausePlay.update("pause"); } // SYNC: if (slider.syncExists) { methods.sync("play"); } }; // STOP: slider.stop = function () { slider.pause(); slider.stopped = true; }; slider.canAdvance = function(target, fromNav) { // ASNAV: var last = (asNav) ? slider.pagingCount - 1 : slider.last; return (fromNav) ? true : (asNav && slider.currentItem === slider.count - 1 && target === 0 && slider.direction === "prev") ? true : (asNav && slider.currentItem === 0 && target === slider.pagingCount - 1 && slider.direction !== "next") ? false : (target === slider.currentSlide && !asNav) ? false : (slider.vars.animationLoop) ? true : (slider.atEnd && slider.currentSlide === 0 && target === last && slider.direction !== "next") ? false : (slider.atEnd && slider.currentSlide === last && target === 0 && slider.direction === "next") ? false : true; }; slider.getTarget = function(dir) { slider.direction = dir; if (dir === "next") { return (slider.currentSlide === slider.last) ? 0 : slider.currentSlide + 1; } else { return (slider.currentSlide === 0) ? slider.last : slider.currentSlide - 1; } }; // SLIDE: slider.setProps = function(pos, special, dur) { var target = (function() { var posCheck = (pos) ? pos : ((slider.itemW + slider.vars.itemMargin) * slider.move) * slider.animatingTo, posCalc = (function() { if (carousel) { return (special === "setTouch") ? pos : (reverse && slider.animatingTo === slider.last) ? 0 : (reverse) ? slider.limit - (((slider.itemW + slider.vars.itemMargin) * slider.move) * slider.animatingTo) : (slider.animatingTo === slider.last) ? slider.limit : posCheck; } else { switch (special) { case "setTotal": return (reverse) ? ((slider.count - 1) - slider.currentSlide + slider.cloneOffset) * pos : (slider.currentSlide + slider.cloneOffset) * pos; case "setTouch": return (reverse) ? pos : pos; case "jumpEnd": return (reverse) ? pos : slider.count * pos; case "jumpStart": return (reverse) ? slider.count * pos : pos; default: return pos; } } }()); return (posCalc * -1) + "px"; }()); if (slider.transitions) { target = (vertical) ? "translate3d(0," + target + ",0)" : "translate3d(" + target + ",0,0)"; dur = (dur !== undefined) ? (dur/1000) + "s" : "0s"; slider.container.css("-" + slider.pfx + "-transition-duration", dur); slider.container.css("transition-duration", dur); } slider.args[slider.prop] = target; if (slider.transitions || dur === undefined) { slider.container.css(slider.args); } slider.container.css('transform',target); }; slider.setup = function(type) { // SLIDE: if (!fade) { var sliderOffset, arr; if (type === "init") { slider.viewport = $('
      ').css({"overflow": "hidden", "position": "relative"}).appendTo(slider).append(slider.container); // INFINITE LOOP: slider.cloneCount = 0; slider.cloneOffset = 0; // REVERSE: if (reverse) { arr = $.makeArray(slider.slides).reverse(); slider.slides = $(arr); slider.container.empty().append(slider.slides); } } // INFINITE LOOP && !CAROUSEL: if (slider.vars.animationLoop && !carousel) { slider.cloneCount = 2; slider.cloneOffset = 1; // clear out old clones if (type !== "init") { slider.container.find('.clone').remove(); } slider.container.append(methods.uniqueID(slider.slides.first().clone().addClass('clone')).attr('aria-hidden', 'true')) .prepend(methods.uniqueID(slider.slides.last().clone().addClass('clone')).attr('aria-hidden', 'true')); } slider.newSlides = $(slider.vars.selector, slider); sliderOffset = (reverse) ? slider.count - 1 - slider.currentSlide + slider.cloneOffset : slider.currentSlide + slider.cloneOffset; // VERTICAL: if (vertical && !carousel) { slider.container.height((slider.count + slider.cloneCount) * 200 + "%").css("position", "absolute").width("100%"); setTimeout(function(){ slider.newSlides.css({"display": "block"}); slider.doMath(); slider.viewport.height(slider.h); slider.setProps(sliderOffset * slider.h, "init"); }, (type === "init") ? 100 : 0); } else { slider.container.width((slider.count + slider.cloneCount) * 200 + "%"); slider.setProps(sliderOffset * slider.computedW, "init"); setTimeout(function(){ slider.doMath(); slider.newSlides.css({"width": slider.computedW, "float": "left", "display": "block"}); // SMOOTH HEIGHT: if (slider.vars.smoothHeight) { methods.smoothHeight(); } }, (type === "init") ? 100 : 0); } } else { // FADE: slider.slides.css({"width": "100%", "float": "left", "marginRight": "-100%", "position": "relative"}); if (type === "init") { if (!touch) { //slider.slides.eq(slider.currentSlide).fadeIn(slider.vars.animationSpeed, slider.vars.easing); if (slider.vars.fadeFirstSlide == false) { slider.slides.css({ "opacity": 0, "display": "block", "zIndex": 1 }).eq(slider.currentSlide).css({"zIndex": 2}).css({"opacity": 1}); } else { slider.slides.css({ "opacity": 0, "display": "block", "zIndex": 1 }).eq(slider.currentSlide).css({"zIndex": 2}).animate({"opacity": 1},slider.vars.animationSpeed,slider.vars.easing); } } else { slider.slides.css({ "opacity": 0, "display": "block", "webkitTransition": "opacity " + slider.vars.animationSpeed / 1000 + "s ease", "zIndex": 1 }).eq(slider.currentSlide).css({ "opacity": 1, "zIndex": 2}); } } // SMOOTH HEIGHT: if (slider.vars.smoothHeight) { methods.smoothHeight(); } } // !CAROUSEL: // CANDIDATE: active slide if (!carousel) { slider.slides.removeClass(namespace + "active-slide").eq(slider.currentSlide).addClass(namespace + "active-slide"); } //FlexSlider: init() Callback slider.vars.init(slider); }; slider.doMath = function() { var slide = slider.slides.first(), slideMargin = slider.vars.itemMargin, minItems = slider.vars.minItems, maxItems = slider.vars.maxItems; slider.w = (slider.viewport===undefined) ? slider.width() : slider.viewport.width(); slider.h = slide.height(); slider.boxPadding = slide.outerWidth() - slide.width(); // CAROUSEL: if (carousel) { slider.itemT = slider.vars.itemWidth + slideMargin; slider.minW = (minItems) ? minItems * slider.itemT : slider.w; slider.maxW = (maxItems) ? (maxItems * slider.itemT) - slideMargin : slider.w; slider.itemW = (slider.minW > slider.w) ? (slider.w - (slideMargin * (minItems - 1)))/minItems : (slider.maxW < slider.w) ? (slider.w - (slideMargin * (maxItems - 1)))/maxItems : (slider.vars.itemWidth > slider.w) ? slider.w : slider.vars.itemWidth; slider.visible = Math.floor(slider.w/(slider.itemW)); slider.move = (slider.vars.move > 0 && slider.vars.move < slider.visible ) ? slider.vars.move : slider.visible; slider.pagingCount = Math.ceil(((slider.count - slider.visible)/slider.move) + 1); slider.last = slider.pagingCount - 1; slider.limit = (slider.pagingCount === 1) ? 0 : (slider.vars.itemWidth > slider.w) ? (slider.itemW * (slider.count - 1)) + (slideMargin * (slider.count - 1)) : ((slider.itemW + slideMargin) * slider.count) - slider.w - slideMargin; } else { slider.itemW = slider.w; slider.pagingCount = slider.count; slider.last = slider.count - 1; } slider.computedW = slider.itemW - slider.boxPadding; }; slider.update = function(pos, action) { slider.doMath(); // update currentSlide and slider.animatingTo if necessary if (!carousel) { if (pos < slider.currentSlide) { slider.currentSlide += 1; } else if (pos <= slider.currentSlide && pos !== 0) { slider.currentSlide -= 1; } slider.animatingTo = slider.currentSlide; } // update controlNav if (slider.vars.controlNav && !slider.manualControls) { if ((action === "add" && !carousel) || slider.pagingCount > slider.controlNav.length) { methods.controlNav.update("add"); } else if ((action === "remove" && !carousel) || slider.pagingCount < slider.controlNav.length) { if (carousel && slider.currentSlide > slider.last) { slider.currentSlide -= 1; slider.animatingTo -= 1; } methods.controlNav.update("remove", slider.last); } } // update directionNav if (slider.vars.directionNav) { methods.directionNav.update(); } }; slider.addSlide = function(obj, pos) { var $obj = $(obj); slider.count += 1; slider.last = slider.count - 1; // append new slide if (vertical && reverse) { (pos !== undefined) ? slider.slides.eq(slider.count - pos).after($obj) : slider.container.prepend($obj); } else { (pos !== undefined) ? slider.slides.eq(pos).before($obj) : slider.container.append($obj); } // update currentSlide, animatingTo, controlNav, and directionNav slider.update(pos, "add"); // update slider.slides slider.slides = $(slider.vars.selector + ':not(.clone)', slider); // re-setup the slider to accomdate new slide slider.setup(); //FlexSlider: added() Callback slider.vars.added(slider); }; slider.removeSlide = function(obj) { var pos = (isNaN(obj)) ? slider.slides.index($(obj)) : obj; // update count slider.count -= 1; slider.last = slider.count - 1; // remove slide if (isNaN(obj)) { $(obj, slider.slides).remove(); } else { (vertical && reverse) ? slider.slides.eq(slider.last).remove() : slider.slides.eq(obj).remove(); } // update currentSlide, animatingTo, controlNav, and directionNav slider.doMath(); slider.update(pos, "remove"); // update slider.slides slider.slides = $(slider.vars.selector + ':not(.clone)', slider); // re-setup the slider to accomdate new slide slider.setup(); // FlexSlider: removed() Callback slider.vars.removed(slider); }; //FlexSlider: Initialize methods.init(); }; // Ensure the slider isn't focussed if the window loses focus. $( window ).blur( function ( e ) { focused = false; }).focus( function ( e ) { focused = true; }); //FlexSlider: Default Settings $.flexslider.defaults = { namespace: "flex-", //{NEW} String: Prefix string attached to the class of every element generated by the plugin selector: ".slides > li", //{NEW} Selector: Must match a simple pattern. '{container} > {slide}' -- Ignore pattern at your own peril animation: "fade", //String: Select your animation type, "fade" or "slide" easing: "swing", //{NEW} String: Determines the easing method used in jQuery transitions. jQuery easing plugin is supported! direction: "horizontal", //String: Select the sliding direction, "horizontal" or "vertical" reverse: false, //{NEW} Boolean: Reverse the animation direction animationLoop: true, //Boolean: Should the animation loop? If false, directionNav will received "disable" classes at either end smoothHeight: false, //{NEW} Boolean: Allow height of the slider to animate smoothly in horizontal mode startAt: 0, //Integer: The slide that the slider should start on. Array notation (0 = first slide) slideshow: true, //Boolean: Animate slider automatically slideshowSpeed: 7000, //Integer: Set the speed of the slideshow cycling, in milliseconds animationSpeed: 600, //Integer: Set the speed of animations, in milliseconds initDelay: 0, //{NEW} Integer: Set an initialization delay, in milliseconds randomize: false, //Boolean: Randomize slide order fadeFirstSlide: true, //Boolean: Fade in the first slide when animation type is "fade" thumbCaptions: false, //Boolean: Whether or not to put captions on thumbnails when using the "thumbnails" controlNav. // Usability features pauseOnAction: true, //Boolean: Pause the slideshow when interacting with control elements, highly recommended. pauseOnHover: false, //Boolean: Pause the slideshow when hovering over slider, then resume when no longer hovering pauseInvisible: true, //{NEW} Boolean: Pause the slideshow when tab is invisible, resume when visible. Provides better UX, lower CPU usage. useCSS: true, //{NEW} Boolean: Slider will use CSS3 transitions if available touch: true, //{NEW} Boolean: Allow touch swipe navigation of the slider on touch-enabled devices video: false, //{NEW} Boolean: If using video in the slider, will prevent CSS3 3D Transforms to avoid graphical glitches // Primary Controls controlNav: true, //Boolean: Create navigation for paging control of each slide? Note: Leave true for manualControls usage directionNav: true, //Boolean: Create navigation for previous/next navigation? (true/false) prevText: "", //String: Set the text for the "previous" directionNav item nextText: "", //String: Set the text for the "next" directionNav item // Secondary Navigation keyboard: true, //Boolean: Allow slider navigating via keyboard left/right keys multipleKeyboard: false, //{NEW} Boolean: Allow keyboard navigation to affect multiple sliders. Default behavior cuts out keyboard navigation with more than one slider present. mousewheel: false, //{UPDATED} Boolean: Requires jquery.mousewheel.js (https://github.com/brandonaaron/jquery-mousewheel) - Allows slider navigating via mousewheel pausePlay: false, //Boolean: Create pause/play dynamic element pauseText: "Pause", //String: Set the text for the "pause" pausePlay item playText: "Play", //String: Set the text for the "play" pausePlay item // Special properties controlsContainer: "", //{UPDATED} jQuery Object/Selector: Declare which container the navigation elements should be appended too. Default container is the FlexSlider element. Example use would be $(".flexslider-container"). Property is ignored if given element is not found. manualControls: "", //{UPDATED} jQuery Object/Selector: Declare custom control navigation. Examples would be $(".flex-control-nav li") or "#tabs-nav li img", etc. The number of elements in your controlNav should match the number of slides/tabs. customDirectionNav: "", //{NEW} jQuery Object/Selector: Custom prev / next button. Must be two jQuery elements. In order to make the events work they have to have the classes "prev" and "next" (plus namespace) sync: "", //{NEW} Selector: Mirror the actions performed on this slider with another slider. Use with care. asNavFor: "", //{NEW} Selector: Internal property exposed for turning the slider into a thumbnail navigation for another slider // Carousel Options itemWidth: 0, //{NEW} Integer: Box-model width of individual carousel items, including horizontal borders and padding. itemMargin: 0, //{NEW} Integer: Margin between carousel items. minItems: 1, //{NEW} Integer: Minimum number of carousel items that should be visible. Items will resize fluidly when below this. maxItems: 0, //{NEW} Integer: Maxmimum number of carousel items that should be visible. Items will resize fluidly when above this limit. move: 0, //{NEW} Integer: Number of carousel items that should move on animation. If 0, slider will move all visible items. allowOneSlide: true, //{NEW} Boolean: Whether or not to allow a slider comprised of a single slide // Callback API start: function(){}, //Callback: function(slider) - Fires when the slider loads the first slide before: function(){}, //Callback: function(slider) - Fires asynchronously with each slider animation after: function(){}, //Callback: function(slider) - Fires after each slider animation completes end: function(){}, //Callback: function(slider) - Fires when the slider reaches the last slide (asynchronous) added: function(){}, //{NEW} Callback: function(slider) - Fires after a slide is added removed: function(){}, //{NEW} Callback: function(slider) - Fires after a slide is removed init: function() {} //{NEW} Callback: function(slider) - Fires after the slider is initially setup }; //FlexSlider: Plugin Function $.fn.flexslider = function(options) { if (options === undefined) { options = {}; } if (typeof options === "object") { return this.each(function() { var $this = $(this), selector = (options.selector) ? options.selector : ".slides > li", $slides = $this.find(selector); if ( ( $slides.length === 1 && options.allowOneSlide === true ) || $slides.length === 0 ) { $slides.fadeIn(400); if (options.start) { options.start($this); } } else if ($this.data('flexslider') === undefined) { new $.flexslider(this, options); } }); } else { // Helper strings to quickly perform functions on the slider var $slider = $(this).data('flexslider'); switch (options) { case "play": $slider.play(); break; case "pause": $slider.pause(); break; case "stop": $slider.stop(); break; case "next": $slider.flexAnimate($slider.getTarget("next"), true); break; case "prev": case "previous": $slider.flexAnimate($slider.getTarget("prev"), true); break; default: if (typeof options === "number") { $slider.flexAnimate(options, true); } } } }; })(jQuery); /*! * imagesLoaded PACKAGED v3.1.8 * JavaScript is all like "You images are done yet or what?" * MIT License */ /*! * EventEmitter v4.2.6 - git.io/ee * Oliver Caldwell * MIT license * @preserve */ (function () { /** * Class for managing events. * Can be extended to provide event functionality in other classes. * * @class EventEmitter Manages event registering and emitting. */ function EventEmitter() {} // Shortcuts to improve speed and size var proto = EventEmitter.prototype; var exports = this; var originalGlobalValue = exports.EventEmitter; /** * Finds the index of the listener for the event in it's storage array. * * @param {Function[]} listeners Array of listeners to search through. * @param {Function} listener Method to look for. * @return {Number} Index of the specified listener, -1 if not found * @api private */ function indexOfListener(listeners, listener) { var i = listeners.length; while (i--) { if (listeners[i].listener === listener) { return i; } } return -1; } /** * Alias a method while keeping the context correct, to allow for overwriting of target method. * * @param {String} name The name of the target method. * @return {Function} The aliased method * @api private */ function alias(name) { return function aliasClosure() { return this[name].apply(this, arguments); }; } /** * Returns the listener array for the specified event. * Will initialise the event object and listener arrays if required. * Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them. * Each property in the object response is an array of listener functions. * * @param {String|RegExp} evt Name of the event to return the listeners from. * @return {Function[]|Object} All listener functions for the event. */ proto.getListeners = function getListeners(evt) { var events = this._getEvents(); var response; var key; // Return a concatenated array of all matching events if // the selector is a regular expression. if (typeof evt === 'object') { response = {}; for (key in events) { if (events.hasOwnProperty(key) && evt.test(key)) { response[key] = events[key]; } } } else { response = events[evt] || (events[evt] = []); } return response; }; /** * Takes a list of listener objects and flattens it into a list of listener functions. * * @param {Object[]} listeners Raw listener objects. * @return {Function[]} Just the listener functions. */ proto.flattenListeners = function flattenListeners(listeners) { var flatListeners = []; var i; for (i = 0; i < listeners.length; i += 1) { flatListeners.push(listeners[i].listener); } return flatListeners; }; /** * Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful. * * @param {String|RegExp} evt Name of the event to return the listeners from. * @return {Object} All listener functions for an event in an object. */ proto.getListenersAsObject = function getListenersAsObject(evt) { var listeners = this.getListeners(evt); var response; if (listeners instanceof Array) { response = {}; response[evt] = listeners; } return response || listeners; }; /** * Adds a listener function to the specified event. * The listener will not be added if it is a duplicate. * If the listener returns true then it will be removed after it is called. * If you pass a regular expression as the event name then the listener will be added to all events that match it. * * @param {String|RegExp} evt Name of the event to attach the listener to. * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling. * @return {Object} Current instance of EventEmitter for chaining. */ proto.addListener = function addListener(evt, listener) { var listeners = this.getListenersAsObject(evt); var listenerIsWrapped = typeof listener === 'object'; var key; for (key in listeners) { if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) { listeners[key].push(listenerIsWrapped ? listener : { listener: listener, once: false }); } } return this; }; /** * Alias of addListener */ proto.on = alias('addListener'); /** * Semi-alias of addListener. It will add a listener that will be * automatically removed after it's first execution. * * @param {String|RegExp} evt Name of the event to attach the listener to. * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling. * @return {Object} Current instance of EventEmitter for chaining. */ proto.addOnceListener = function addOnceListener(evt, listener) { return this.addListener(evt, { listener: listener, once: true }); }; /** * Alias of addOnceListener. */ proto.once = alias('addOnceListener'); /** * Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad. * You need to tell it what event names should be matched by a regex. * * @param {String} evt Name of the event to create. * @return {Object} Current instance of EventEmitter for chaining. */ proto.defineEvent = function defineEvent(evt) { this.getListeners(evt); return this; }; /** * Uses defineEvent to define multiple events. * * @param {String[]} evts An array of event names to define. * @return {Object} Current instance of EventEmitter for chaining. */ proto.defineEvents = function defineEvents(evts) { for (var i = 0; i < evts.length; i += 1) { this.defineEvent(evts[i]); } return this; }; /** * Removes a listener function from the specified event. * When passed a regular expression as the event name, it will remove the listener from all events that match it. * * @param {String|RegExp} evt Name of the event to remove the listener from. * @param {Function} listener Method to remove from the event. * @return {Object} Current instance of EventEmitter for chaining. */ proto.removeListener = function removeListener(evt, listener) { var listeners = this.getListenersAsObject(evt); var index; var key; for (key in listeners) { if (listeners.hasOwnProperty(key)) { index = indexOfListener(listeners[key], listener); if (index !== -1) { listeners[key].splice(index, 1); } } } return this; }; /** * Alias of removeListener */ proto.off = alias('removeListener'); /** * Adds listeners in bulk using the manipulateListeners method. * If you pass an object as the second argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added. * You can also pass it a regular expression to add the array of listeners to all events that match it. * Yeah, this function does quite a bit. That's probably a bad thing. * * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once. * @param {Function[]} [listeners] An optional array of listener functions to add. * @return {Object} Current instance of EventEmitter for chaining. */ proto.addListeners = function addListeners(evt, listeners) { // Pass through to manipulateListeners return this.manipulateListeners(false, evt, listeners); }; /** * Removes listeners in bulk using the manipulateListeners method. * If you pass an object as the second argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. * You can also pass it an event name and an array of listeners to be removed. * You can also pass it a regular expression to remove the listeners from all events that match it. * * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once. * @param {Function[]} [listeners] An optional array of listener functions to remove. * @return {Object} Current instance of EventEmitter for chaining. */ proto.removeListeners = function removeListeners(evt, listeners) { // Pass through to manipulateListeners return this.manipulateListeners(true, evt, listeners); }; /** * Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level. * The first argument will determine if the listeners are removed (true) or added (false). * If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. * You can also pass it an event name and an array of listeners to be added/removed. * You can also pass it a regular expression to manipulate the listeners of all events that match it. * * @param {Boolean} remove True if you want to remove listeners, false if you want to add. * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once. * @param {Function[]} [listeners] An optional array of listener functions to add/remove. * @return {Object} Current instance of EventEmitter for chaining. */ proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) { var i; var value; var single = remove ? this.removeListener : this.addListener; var multiple = remove ? this.removeListeners : this.addListeners; // If evt is an object then pass each of it's properties to this method if (typeof evt === 'object' && !(evt instanceof RegExp)) { for (i in evt) { if (evt.hasOwnProperty(i) && (value = evt[i])) { // Pass the single listener straight through to the singular method if (typeof value === 'function') { single.call(this, i, value); } else { // Otherwise pass back to the multiple function multiple.call(this, i, value); } } } } else { // So evt must be a string // And listeners must be an array of listeners // Loop over it and pass each one to the multiple method i = listeners.length; while (i--) { single.call(this, evt, listeners[i]); } } return this; }; /** * Removes all listeners from a specified event. * If you do not specify an event then all listeners will be removed. * That means every event will be emptied. * You can also pass a regex to remove all events that match it. * * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed. * @return {Object} Current instance of EventEmitter for chaining. */ proto.removeEvent = function removeEvent(evt) { var type = typeof evt; var events = this._getEvents(); var key; // Remove different things depending on the state of evt if (type === 'string') { // Remove all listeners for the specified event delete events[evt]; } else if (type === 'object') { // Remove all events matching the regex. for (key in events) { if (events.hasOwnProperty(key) && evt.test(key)) { delete events[key]; } } } else { // Remove all listeners in all events delete this._events; } return this; }; /** * Alias of removeEvent. * * Added to mirror the node API. */ proto.removeAllListeners = alias('removeEvent'); /** * Emits an event of your choice. * When emitted, every listener attached to that event will be executed. * If you pass the optional argument array then those arguments will be passed to every listener upon execution. * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately. * So they will not arrive within the array on the other side, they will be separate. * You can also pass a regular expression to emit to all events that match it. * * @param {String|RegExp} evt Name of the event to emit and execute listeners for. * @param {Array} [args] Optional array of arguments to be passed to each listener. * @return {Object} Current instance of EventEmitter for chaining. */ proto.emitEvent = function emitEvent(evt, args) { var listeners = this.getListenersAsObject(evt); var listener; var i; var key; var response; for (key in listeners) { if (listeners.hasOwnProperty(key)) { i = listeners[key].length; while (i--) { // If the listener returns true then it shall be removed from the event // The function is executed either with a basic call or an apply if there is an args array listener = listeners[key][i]; if (listener.once === true) { this.removeListener(evt, listener.listener); } response = listener.listener.apply(this, args || []); if (response === this._getOnceReturnValue()) { this.removeListener(evt, listener.listener); } } } } return this; }; /** * Alias of emitEvent */ proto.trigger = alias('emitEvent'); /** * Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on. * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it. * * @param {String|RegExp} evt Name of the event to emit and execute listeners for. * @param {...*} Optional additional arguments to be passed to each listener. * @return {Object} Current instance of EventEmitter for chaining. */ proto.emit = function emit(evt) { var args = Array.prototype.slice.call(arguments, 1); return this.emitEvent(evt, args); }; /** * Sets the current value to check against when executing listeners. If a * listeners return value matches the one set here then it will be removed * after execution. This value defaults to true. * * @param {*} value The new value to check for when executing listeners. * @return {Object} Current instance of EventEmitter for chaining. */ proto.setOnceReturnValue = function setOnceReturnValue(value) { this._onceReturnValue = value; return this; }; /** * Fetches the current value to check against when executing listeners. If * the listeners return value matches this one then it should be removed * automatically. It will return true by default. * * @return {*|Boolean} The current value to check for or the default, true. * @api private */ proto._getOnceReturnValue = function _getOnceReturnValue() { if (this.hasOwnProperty('_onceReturnValue')) { return this._onceReturnValue; } else { return true; } }; /** * Fetches the events object and creates one if required. * * @return {Object} The events storage object. * @api private */ proto._getEvents = function _getEvents() { return this._events || (this._events = {}); }; /** * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version. * * @return {Function} Non conflicting EventEmitter class. */ EventEmitter.noConflict = function noConflict() { exports.EventEmitter = originalGlobalValue; return EventEmitter; }; // Expose the class either via AMD, CommonJS or the global object if (typeof define === 'function' && define.amd) { define('eventEmitter/EventEmitter',[],function () { return EventEmitter; }); } else if (typeof module === 'object' && module.exports){ module.exports = EventEmitter; } else { this.EventEmitter = EventEmitter; } }.call(this)); /*! * eventie v1.0.4 * event binding helper * eventie.bind( elem, 'click', myFn ) * eventie.unbind( elem, 'click', myFn ) */ /*jshint browser: true, undef: true, unused: true */ /*global define: false */ ( function( window ) { var docElem = document.documentElement; var bind = function() {}; function getIEEvent( obj ) { var event = window.event; // add event.target event.target = event.target || event.srcElement || obj; return event; } if ( docElem.addEventListener ) { bind = function( obj, type, fn ) { obj.addEventListener( type, fn, false ); }; } else if ( docElem.attachEvent ) { bind = function( obj, type, fn ) { obj[ type + fn ] = fn.handleEvent ? function() { var event = getIEEvent( obj ); fn.handleEvent.call( fn, event ); } : function() { var event = getIEEvent( obj ); fn.call( obj, event ); }; obj.attachEvent( "on" + type, obj[ type + fn ] ); }; } var unbind = function() {}; if ( docElem.removeEventListener ) { unbind = function( obj, type, fn ) { obj.removeEventListener( type, fn, false ); }; } else if ( docElem.detachEvent ) { unbind = function( obj, type, fn ) { obj.detachEvent( "on" + type, obj[ type + fn ] ); try { delete obj[ type + fn ]; } catch ( err ) { // can't delete window object properties obj[ type + fn ] = undefined; } }; } var eventie = { bind: bind, unbind: unbind }; // transport if ( typeof define === 'function' && define.amd ) { // AMD define( 'eventie/eventie',eventie ); } else { // browser global window.eventie = eventie; } })( this ); /*! * imagesLoaded v3.1.8 * JavaScript is all like "You images are done yet or what?" * MIT License */ ( function( window, factory ) { // universal module definition /*global define: false, module: false, require: false */ if ( typeof define === 'function' && define.amd ) { // AMD define( [ 'eventEmitter/EventEmitter', 'eventie/eventie' ], function( EventEmitter, eventie ) { return factory( window, EventEmitter, eventie ); }); } else if ( typeof exports === 'object' ) { // CommonJS module.exports = factory( window, require('wolfy87-eventemitter'), require('eventie') ); } else { // browser global window.imagesLoaded = factory( window, window.EventEmitter, window.eventie ); } })( window, // -------------------------- factory -------------------------- // function factory( window, EventEmitter, eventie ) { var $ = window.jQuery; var console = window.console; var hasConsole = typeof console !== 'undefined'; // -------------------------- helpers -------------------------- // // extend objects function extend( a, b ) { for ( var prop in b ) { a[ prop ] = b[ prop ]; } return a; } var objToString = Object.prototype.toString; function isArray( obj ) { return objToString.call( obj ) === '[object Array]'; } // turn element or nodeList into an array function makeArray( obj ) { var ary = []; if ( isArray( obj ) ) { // use object if already an array ary = obj; } else if ( typeof obj.length === 'number' ) { // convert nodeList to array for ( var i=0, len = obj.length; i < len; i++ ) { ary.push( obj[i] ); } } else { // array of single index ary.push( obj ); } return ary; } // -------------------------- imagesLoaded -------------------------- // /** * @param {Array, Element, NodeList, String} elem * @param {Object or Function} options - if function, use as callback * @param {Function} onAlways - callback function */ function ImagesLoaded( elem, options, onAlways ) { // coerce ImagesLoaded() without new, to be new ImagesLoaded() if ( !( this instanceof ImagesLoaded ) ) { return new ImagesLoaded( elem, options ); } // use elem as selector string if ( typeof elem === 'string' ) { elem = document.querySelectorAll( elem ); } this.elements = makeArray( elem ); this.options = extend( {}, this.options ); if ( typeof options === 'function' ) { onAlways = options; } else { extend( this.options, options ); } if ( onAlways ) { this.on( 'always', onAlways ); } this.getImages(); if ( $ ) { // add jQuery Deferred object this.jqDeferred = new $.Deferred(); } // HACK check async to allow time to bind listeners var _this = this; setTimeout( function() { _this.check(); }); } ImagesLoaded.prototype = new EventEmitter(); ImagesLoaded.prototype.options = {}; ImagesLoaded.prototype.getImages = function() { this.images = []; // filter & find items if we have an item selector for ( var i=0, len = this.elements.length; i < len; i++ ) { var elem = this.elements[i]; // filter siblings if ( elem.nodeName === 'IMG' ) { this.addImage( elem ); } // find children // no non-element nodes, #143 var nodeType = elem.nodeType; if ( !nodeType || !( nodeType === 1 || nodeType === 9 || nodeType === 11 ) ) { continue; } var childElems = elem.querySelectorAll('img'); // concat childElems to filterFound array for ( var j=0, jLen = childElems.length; j < jLen; j++ ) { var img = childElems[j]; this.addImage( img ); } } }; /** * @param {Image} img */ ImagesLoaded.prototype.addImage = function( img ) { var loadingImage = new LoadingImage( img ); this.images.push( loadingImage ); }; ImagesLoaded.prototype.check = function() { var _this = this; var checkedCount = 0; var length = this.images.length; this.hasAnyBroken = false; // complete if no images if ( !length ) { this.complete(); return; } function onConfirm( image, message ) { if ( _this.options.debug && hasConsole ) { console.log( 'confirm', image, message ); } _this.progress( image ); checkedCount++; if ( checkedCount === length ) { _this.complete(); } return true; // bind once } for ( var i=0; i < length; i++ ) { var loadingImage = this.images[i]; loadingImage.on( 'confirm', onConfirm ); loadingImage.check(); } }; ImagesLoaded.prototype.progress = function( image ) { this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded; // HACK - Chrome triggers event before object properties have changed. #83 var _this = this; setTimeout( function() { _this.emit( 'progress', _this, image ); if ( _this.jqDeferred && _this.jqDeferred.notify ) { _this.jqDeferred.notify( _this, image ); } }); }; ImagesLoaded.prototype.complete = function() { var eventName = this.hasAnyBroken ? 'fail' : 'done'; this.isComplete = true; var _this = this; // HACK - another setTimeout so that confirm happens after progress setTimeout( function() { _this.emit( eventName, _this ); _this.emit( 'always', _this ); if ( _this.jqDeferred ) { var jqMethod = _this.hasAnyBroken ? 'reject' : 'resolve'; _this.jqDeferred[ jqMethod ]( _this ); } }); }; // -------------------------- jquery -------------------------- // if ( $ ) { $.fn.imagesLoaded = function( options, callback ) { var instance = new ImagesLoaded( this, options, callback ); return instance.jqDeferred.promise( $(this) ); }; } // -------------------------- -------------------------- // function LoadingImage( img ) { this.img = img; } LoadingImage.prototype = new EventEmitter(); LoadingImage.prototype.check = function() { // first check cached any previous images that have same src var resource = cache[ this.img.src ] || new Resource( this.img.src ); if ( resource.isConfirmed ) { this.confirm( resource.isLoaded, 'cached was confirmed' ); return; } // If complete is true and browser supports natural sizes, // try to check for image status manually. if ( this.img.complete && this.img.naturalWidth !== undefined ) { // report based on naturalWidth this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' ); return; } // If none of the checks above matched, simulate loading on detached element. var _this = this; resource.on( 'confirm', function( resrc, message ) { _this.confirm( resrc.isLoaded, message ); return true; }); resource.check(); }; LoadingImage.prototype.confirm = function( isLoaded, message ) { this.isLoaded = isLoaded; this.emit( 'confirm', this, message ); }; // -------------------------- Resource -------------------------- // // Resource checks each src, only once // separate class from LoadingImage to prevent memory leaks. See #115 var cache = {}; function Resource( src ) { this.src = src; // add to cache cache[ src ] = this; } Resource.prototype = new EventEmitter(); Resource.prototype.check = function() { // only trigger checking once if ( this.isChecked ) { return; } // simulate loading on detached element var proxyImage = new Image(); eventie.bind( proxyImage, 'load', this ); eventie.bind( proxyImage, 'error', this ); proxyImage.src = this.src; // set flag this.isChecked = true; }; // ----- events ----- // // trigger specified handler for event type Resource.prototype.handleEvent = function( event ) { var method = 'on' + event.type; if ( this[ method ] ) { this[ method ]( event ); } }; Resource.prototype.onload = function( event ) { this.confirm( true, 'onload' ); this.unbindProxyEvents( event ); }; Resource.prototype.onerror = function( event ) { this.confirm( false, 'onerror' ); this.unbindProxyEvents( event ); }; // ----- confirm ----- // Resource.prototype.confirm = function( isLoaded, message ) { this.isConfirmed = true; this.isLoaded = isLoaded; this.emit( 'confirm', this, message ); }; Resource.prototype.unbindProxyEvents = function( event ) { eventie.unbind( event.target, 'load', this ); eventie.unbind( event.target, 'error', this ); }; // ----- ----- // return ImagesLoaded; }); /**! * MixItUp v2.1.9 * * @copyright Copyright 2015 KunkaLabs Limited. * @author KunkaLabs Limited. * @link https://mixitup.kunkalabs.com * * @license Commercial use requires a commercial license. * https://mixitup.kunkalabs.com/licenses/ * * Non-commercial use permitted under terms of CC-BY-NC license. * http://creativecommons.org/licenses/by-nc/3.0/ */ (function($, undf){ /** * MixItUp Constructor Function * @constructor * @extends jQuery */ $.MixItUp = function(){ var self = this; self._execAction('_constructor', 0); $.extend(self, { /* Public Properties ---------------------------------------------------------------------- */ selectors: { target: '.mix', filter: '.filter', sort: '.sort' }, animation: { enable: true, effects: 'fade scale', duration: 600, easing: 'ease', perspectiveDistance: '3000', perspectiveOrigin: '50% 50%', queue: true, queueLimit: 1, animateChangeLayout: false, animateResizeContainer: true, animateResizeTargets: false, staggerSequence: false, reverseOut: false }, callbacks: { onMixLoad: false, onMixStart: false, onMixBusy: false, onMixEnd: false, onMixFail: false, _user: false }, controls: { enable: true, live: false, toggleFilterButtons: false, toggleLogic: 'or', activeClass: 'active' }, layout: { display: 'inline-block', containerClass: '', containerClassFail: 'fail' }, load: { filter: 'all', sort: false }, /* Private Properties ---------------------------------------------------------------------- */ _$body: null, _$container: null, _$targets: null, _$parent: null, _$sortButtons: null, _$filterButtons: null, _suckMode: false, _mixing: false, _sorting: false, _clicking: false, _loading: true, _changingLayout: false, _changingClass: false, _changingDisplay: false, _origOrder: [], _startOrder: [], _newOrder: [], _activeFilter: null, _toggleArray: [], _toggleString: '', _activeSort: 'default:asc', _newSort: null, _startHeight: null, _newHeight: null, _incPadding: true, _newDisplay: null, _newClass: null, _targetsBound: 0, _targetsDone: 0, _queue: [], _$show: $(), _$hide: $() }); self._execAction('_constructor', 1); }; /** * MixItUp Prototype * @override */ $.MixItUp.prototype = { constructor: $.MixItUp, /* Static Properties ---------------------------------------------------------------------- */ _instances: {}, _handled: { _filter: {}, _sort: {} }, _bound: { _filter: {}, _sort: {} }, _actions: {}, _filters: {}, /* Static Methods ---------------------------------------------------------------------- */ /** * Extend * @since 2.1.0 * @param {object} new properties/methods * @extends {object} prototype */ extend: function(extension){ for(var key in extension){ $.MixItUp.prototype[key] = extension[key]; } }, /** * Add Action * @since 2.1.0 * @param {string} hook name * @param {string} namespace * @param {function} function to execute * @param {number} priority * @extends {object} $.MixItUp.prototype._actions */ addAction: function(hook, name, func, priority){ $.MixItUp.prototype._addHook('_actions', hook, name, func, priority); }, /** * Add Filter * @since 2.1.0 * @param {string} hook name * @param {string} namespace * @param {function} function to execute * @param {number} priority * @extends {object} $.MixItUp.prototype._filters */ addFilter: function(hook, name, func, priority){ $.MixItUp.prototype._addHook('_filters', hook, name, func, priority); }, /** * Add Hook * @since 2.1.0 * @param {string} type of hook * @param {string} hook name * @param {function} function to execute * @param {number} priority * @extends {object} $.MixItUp.prototype._filters */ _addHook: function(type, hook, name, func, priority){ var collection = $.MixItUp.prototype[type], obj = {}; priority = (priority === 1 || priority === 'post') ? 'post' : 'pre'; obj[hook] = {}; obj[hook][priority] = {}; obj[hook][priority][name] = func; $.extend(true, collection, obj); }, /* Private Methods ---------------------------------------------------------------------- */ /** * Initialise * @since 2.0.0 * @param {object} domNode * @param {object} config */ _init: function(domNode, config){ var self = this; self._execAction('_init', 0, arguments); config && $.extend(true, self, config); self._$body = $('body'); self._domNode = domNode; self._$container = $(domNode); self._$container.addClass(self.layout.containerClass); self._id = domNode.id; self._platformDetect(); self._brake = self._getPrefixedCSS('transition', 'none'); self._refresh(true); self._$parent = self._$targets.parent().length ? self._$targets.parent() : self._$container; if(self.load.sort){ self._newSort = self._parseSort(self.load.sort); self._newSortString = self.load.sort; self._activeSort = self.load.sort; self._sort(); self._printSort(); } self._activeFilter = self.load.filter === 'all' ? self.selectors.target : self.load.filter === 'none' ? '' : self.load.filter; self.controls.enable && self._bindHandlers(); if(self.controls.toggleFilterButtons){ self._buildToggleArray(); for(var i = 0; i < self._toggleArray.length; i++){ self._updateControls({filter: self._toggleArray[i], sort: self._activeSort}, true); }; } else if(self.controls.enable){ self._updateControls({filter: self._activeFilter, sort: self._activeSort}); } self._filter(); self._init = true; self._$container.data('mixItUp',self); self._execAction('_init', 1, arguments); self._buildState(); self._$targets.css(self._brake); self._goMix(self.animation.enable); }, /** * Platform Detect * @since 2.0.0 */ _platformDetect: function(){ var self = this, vendorsTrans = ['Webkit', 'Moz', 'O', 'ms'], vendorsRAF = ['webkit', 'moz'], chrome = window.navigator.appVersion.match(/Chrome\/(\d+)\./) || false, ff = typeof InstallTrigger !== 'undefined', prefix = function(el){ for (var i = 0; i < vendorsTrans.length; i++){ if (vendorsTrans[i] + 'Transition' in el.style){ return { prefix: '-'+vendorsTrans[i].toLowerCase()+'-', vendor: vendorsTrans[i] }; }; }; return 'transition' in el.style ? '' : false; }, transPrefix = prefix(self._domNode); self._execAction('_platformDetect', 0); self._chrome = chrome ? parseInt(chrome[1], 10) : false; self._ff = ff ? parseInt(window.navigator.userAgent.match(/rv:([^)]+)\)/)[1]) : false; self._prefix = transPrefix.prefix; self._vendor = transPrefix.vendor; self._suckMode = window.atob && self._prefix ? false : true; self._suckMode && (self.animation.enable = false); (self._ff && self._ff <= 4) && (self.animation.enable = false); /* Polyfills ---------------------------------------------------------------------- */ /** * window.requestAnimationFrame */ for(var x = 0; x < vendorsRAF.length && !window.requestAnimationFrame; x++){ window.requestAnimationFrame = window[vendorsRAF[x]+'RequestAnimationFrame']; } /** * Object.getPrototypeOf */ if(typeof Object.getPrototypeOf !== 'function'){ if(typeof 'test'.__proto__ === 'object'){ Object.getPrototypeOf = function(object){ return object.__proto__; }; } else { Object.getPrototypeOf = function(object){ return object.constructor.prototype; }; } } /** * Element.nextElementSibling */ if(self._domNode.nextElementSibling === undf){ Object.defineProperty(Element.prototype, 'nextElementSibling',{ get: function(){ var el = this.nextSibling; while(el){ if(el.nodeType ===1){ return el; } el = el.nextSibling; } return null; } }); } self._execAction('_platformDetect', 1); }, /** * Refresh * @since 2.0.0 * @param {boolean} init * @param {boolean} force */ _refresh: function(init, force){ var self = this; self._execAction('_refresh', 0, arguments); self._$targets = self._$container.find(self.selectors.target); for(var i = 0; i < self._$targets.length; i++){ var target = self._$targets[i]; if(target.dataset === undf || force){ target.dataset = {}; for(var j = 0; j < target.attributes.length; j++){ var attr = target.attributes[j], name = attr.name, val = attr.value; if(name.indexOf('data-') > -1){ var dataName = self._helpers._camelCase(name.substring(5,name.length)); target.dataset[dataName] = val; } } } if(target.mixParent === undf){ target.mixParent = self._id; } } if( (self._$targets.length && init) || (!self._origOrder.length && self._$targets.length) ){ self._origOrder = []; for(var i = 0; i < self._$targets.length; i++){ var target = self._$targets[i]; self._origOrder.push(target); } } self._execAction('_refresh', 1, arguments); }, /** * Bind Handlers * @since 2.0.0 */ _bindHandlers: function(){ var self = this, filters = $.MixItUp.prototype._bound._filter, sorts = $.MixItUp.prototype._bound._sort; self._execAction('_bindHandlers', 0); if(self.controls.live){ self._$body .on('click.mixItUp.'+self._id, self.selectors.sort, function(){ self._processClick($(this), 'sort'); }) .on('click.mixItUp.'+self._id, self.selectors.filter, function(){ self._processClick($(this), 'filter'); }); } else { self._$sortButtons = $(self.selectors.sort); self._$filterButtons = $(self.selectors.filter); self._$sortButtons.on('click.mixItUp.'+self._id, function(){ self._processClick($(this), 'sort'); }); self._$filterButtons.on('click.mixItUp.'+self._id, function(){ self._processClick($(this), 'filter'); }); } filters[self.selectors.filter] = (filters[self.selectors.filter] === undf) ? 1 : filters[self.selectors.filter] + 1; sorts[self.selectors.sort] = (sorts[self.selectors.sort] === undf) ? 1 : sorts[self.selectors.sort] + 1; self._execAction('_bindHandlers', 1); }, /** * Process Click * @since 2.0.0 * @param {object} $button * @param {string} type */ _processClick: function($button, type){ var self = this, trackClick = function($button, type, off){ var proto = $.MixItUp.prototype; proto._handled['_'+type][self.selectors[type]] = (proto._handled['_'+type][self.selectors[type]] === undf) ? 1 : proto._handled['_'+type][self.selectors[type]] + 1; if(proto._handled['_'+type][self.selectors[type]] === proto._bound['_'+type][self.selectors[type]]){ $button[(off ? 'remove' : 'add')+'Class'](self.controls.activeClass); delete proto._handled['_'+type][self.selectors[type]]; } }; self._execAction('_processClick', 0, arguments); if(!self._mixing || (self.animation.queue && self._queue.length < self.animation.queueLimit)){ self._clicking = true; if(type === 'sort'){ var sort = $button.attr('data-sort'); if(!$button.hasClass(self.controls.activeClass) || sort.indexOf('random') > -1){ $(self.selectors.sort).removeClass(self.controls.activeClass); trackClick($button, type); self.sort(sort); } } if(type === 'filter') { var filter = $button.attr('data-filter'), ndx, seperator = self.controls.toggleLogic === 'or' ? ',' : ''; if(!self.controls.toggleFilterButtons){ if(!$button.hasClass(self.controls.activeClass)){ $(self.selectors.filter).removeClass(self.controls.activeClass); trackClick($button, type); self.filter(filter); } } else { self._buildToggleArray(); if(!$button.hasClass(self.controls.activeClass)){ trackClick($button, type); self._toggleArray.push(filter); } else { trackClick($button, type, true); ndx = self._toggleArray.indexOf(filter); self._toggleArray.splice(ndx, 1); } self._toggleArray = $.grep(self._toggleArray,function(n){return(n);}); self._toggleString = self._toggleArray.join(seperator); self.filter(self._toggleString); } } self._execAction('_processClick', 1, arguments); } else { if(typeof self.callbacks.onMixBusy === 'function'){ self.callbacks.onMixBusy.call(self._domNode, self._state, self); } self._execAction('_processClickBusy', 1, arguments); } }, /** * Build Toggle Array * @since 2.0.0 */ _buildToggleArray: function(){ var self = this, activeFilter = self._activeFilter.replace(/\s/g, ''); self._execAction('_buildToggleArray', 0, arguments); if(self.controls.toggleLogic === 'or'){ self._toggleArray = activeFilter.split(','); } else { self._toggleArray = activeFilter.split('.'); !self._toggleArray[0] && self._toggleArray.shift(); for(var i = 0, filter; filter = self._toggleArray[i]; i++){ self._toggleArray[i] = '.'+filter; } } self._execAction('_buildToggleArray', 1, arguments); }, /** * Update Controls * @since 2.0.0 * @param {object} command * @param {boolean} multi */ _updateControls: function(command, multi){ var self = this, output = { filter: command.filter, sort: command.sort }, update = function($el, filter){ try { (multi && type === 'filter' && !(output.filter === 'none' || output.filter === '')) ? $el.filter(filter).addClass(self.controls.activeClass) : $el.removeClass(self.controls.activeClass).filter(filter).addClass(self.controls.activeClass); } catch(e) {} }, type = 'filter', $el = null; self._execAction('_updateControls', 0, arguments); (command.filter === undf) && (output.filter = self._activeFilter); (command.sort === undf) && (output.sort = self._activeSort); (output.filter === self.selectors.target) && (output.filter = 'all'); for(var i = 0; i < 2; i++){ $el = self.controls.live ? $(self.selectors[type]) : self['_$'+type+'Buttons']; $el && update($el, '[data-'+type+'="'+output[type]+'"]'); type = 'sort'; } self._execAction('_updateControls', 1, arguments); }, /** * Filter (private) * @since 2.0.0 */ _filter: function(){ var self = this; self._execAction('_filter', 0); for(var i = 0; i < self._$targets.length; i++){ var $target = $(self._$targets[i]); if($target.is(self._activeFilter)){ self._$show = self._$show.add($target); } else { self._$hide = self._$hide.add($target); } } self._execAction('_filter', 1); }, /** * Sort (private) * @since 2.0.0 */ _sort: function(){ var self = this, arrayShuffle = function(oldArray){ var newArray = oldArray.slice(), len = newArray.length, i = len; while(i--){ var p = parseInt(Math.random()*len); var t = newArray[i]; newArray[i] = newArray[p]; newArray[p] = t; }; return newArray; }; self._execAction('_sort', 0); self._startOrder = []; for(var i = 0; i < self._$targets.length; i++){ var target = self._$targets[i]; self._startOrder.push(target); } switch(self._newSort[0].sortBy){ case 'default': self._newOrder = self._origOrder; break; case 'random': self._newOrder = arrayShuffle(self._startOrder); break; case 'custom': self._newOrder = self._newSort[0].order; break; default: self._newOrder = self._startOrder.concat().sort(function(a, b){ return self._compare(a, b); }); } self._execAction('_sort', 1); }, /** * Compare Algorithm * @since 2.0.0 * @param {string|number} a * @param {string|number} b * @param {number} depth (recursion) * @return {number} */ _compare: function(a, b, depth){ depth = depth ? depth : 0; var self = this, order = self._newSort[depth].order, getData = function(el){ return el.dataset[self._newSort[depth].sortBy] || 0; }, attrA = isNaN(getData(a) * 1) ? getData(a).toLowerCase() : getData(a) * 1, attrB = isNaN(getData(b) * 1) ? getData(b).toLowerCase() : getData(b) * 1; if(attrA < attrB) return order === 'asc' ? -1 : 1; if(attrA > attrB) return order === 'asc' ? 1 : -1; if(attrA === attrB && self._newSort.length > depth+1) return self._compare(a, b, depth+1); return 0; }, /** * Print Sort * @since 2.0.0 * @param {boolean} reset */ _printSort: function(reset){ var self = this, order = reset ? self._startOrder : self._newOrder, targets = self._$parent[0].querySelectorAll(self.selectors.target), nextSibling = targets.length ? targets[targets.length -1].nextElementSibling : null, frag = document.createDocumentFragment(); self._execAction('_printSort', 0, arguments); for(var i = 0; i < targets.length; i++){ var target = targets[i], whiteSpace = target.nextSibling; if(target.style.position === 'absolute') continue; if(whiteSpace && whiteSpace.nodeName === '#text'){ self._$parent[0].removeChild(whiteSpace); } self._$parent[0].removeChild(target); } for(var i = 0; i < order.length; i++){ var el = order[i]; if(self._newSort[0].sortBy === 'default' && self._newSort[0].order === 'desc' && !reset){ var firstChild = frag.firstChild; frag.insertBefore(el, firstChild); frag.insertBefore(document.createTextNode(' '), el); } else { frag.appendChild(el); frag.appendChild(document.createTextNode(' ')); } } nextSibling ? self._$parent[0].insertBefore(frag, nextSibling) : self._$parent[0].appendChild(frag); self._execAction('_printSort', 1, arguments); }, /** * Parse Sort * @since 2.0.0 * @param {string} sortString * @return {array} newSort */ _parseSort: function(sortString){ var self = this, rules = typeof sortString === 'string' ? sortString.split(' ') : [sortString], newSort = []; for(var i = 0; i < rules.length; i++){ var rule = typeof sortString === 'string' ? rules[i].split(':') : ['custom', rules[i]], ruleObj = { sortBy: self._helpers._camelCase(rule[0]), order: rule[1] || 'asc' }; newSort.push(ruleObj); if(ruleObj.sortBy === 'default' || ruleObj.sortBy === 'random') break; } return self._execFilter('_parseSort', newSort, arguments); }, /** * Parse Effects * @since 2.0.0 * @return {object} effects */ _parseEffects: function(){ var self = this, effects = { opacity: '', transformIn: '', transformOut: '', filter: '' }, parse = function(effect, extract, reverse){ if(self.animation.effects.indexOf(effect) > -1){ if(extract){ var propIndex = self.animation.effects.indexOf(effect+'('); if(propIndex > -1){ var str = self.animation.effects.substring(propIndex), match = /\(([^)]+)\)/.exec(str), val = match[1]; return {val: val}; } } return true; } else { return false; } }, negate = function(value, invert){ if(invert){ return value.charAt(0) === '-' ? value.substr(1, value.length) : '-'+value; } else { return value; } }, buildTransform = function(key, invert){ var transforms = [ ['scale', '.01'], ['translateX', '20px'], ['translateY', '20px'], ['translateZ', '20px'], ['rotateX', '90deg'], ['rotateY', '90deg'], ['rotateZ', '180deg'], ]; for(var i = 0; i < transforms.length; i++){ var prop = transforms[i][0], def = transforms[i][1], inverted = invert && prop !== 'scale'; effects[key] += parse(prop) ? prop+'('+negate(parse(prop, true).val || def, inverted)+') ' : ''; } }; effects.opacity = parse('fade') ? parse('fade',true).val || '0' : '1'; buildTransform('transformIn'); self.animation.reverseOut ? buildTransform('transformOut', true) : (effects.transformOut = effects.transformIn); effects.transition = {}; effects.transition = self._getPrefixedCSS('transition','all '+self.animation.duration+'ms '+self.animation.easing+', opacity '+self.animation.duration+'ms linear'); self.animation.stagger = parse('stagger') ? true : false; self.animation.staggerDuration = parseInt(parse('stagger') ? (parse('stagger',true).val ? parse('stagger',true).val : 100) : 100); return self._execFilter('_parseEffects', effects); }, /** * Build State * @since 2.0.0 * @param {boolean} future * @return {object} futureState */ _buildState: function(future){ var self = this, state = {}; self._execAction('_buildState', 0); state = { activeFilter: self._activeFilter === '' ? 'none' : self._activeFilter, activeSort: future && self._newSortString ? self._newSortString : self._activeSort, fail: !self._$show.length && self._activeFilter !== '', $targets: self._$targets, $show: self._$show, $hide: self._$hide, totalTargets: self._$targets.length, totalShow: self._$show.length, totalHide: self._$hide.length, display: future && self._newDisplay ? self._newDisplay : self.layout.display }; if(future){ return self._execFilter('_buildState', state); } else { self._state = state; self._execAction('_buildState', 1); } }, /** * Go Mix * @since 2.0.0 * @param {boolean} animate */ _goMix: function(animate){ var self = this, phase1 = function(){ if(self._chrome && (self._chrome === 31)){ chromeFix(self._$parent[0]); } self._setInter(); phase2(); }, phase2 = function(){ var scrollTop = window.pageYOffset, scrollLeft = window.pageXOffset, docHeight = document.documentElement.scrollHeight; self._getInterMixData(); self._setFinal(); self._getFinalMixData(); (window.pageYOffset !== scrollTop) && window.scrollTo(scrollLeft, scrollTop); self._prepTargets(); if(window.requestAnimationFrame){ requestAnimationFrame(phase3); } else { setTimeout(function(){ phase3(); },20); } }, phase3 = function(){ self._animateTargets(); if(self._targetsBound === 0){ self._cleanUp(); } }, chromeFix = function(grid){ var parent = grid.parentElement, placeholder = document.createElement('div'), frag = document.createDocumentFragment(); parent.insertBefore(placeholder, grid); frag.appendChild(grid); parent.replaceChild(grid, placeholder); }, futureState = self._buildState(true); self._execAction('_goMix', 0, arguments); !self.animation.duration && (animate = false); self._mixing = true; self._$container.removeClass(self.layout.containerClassFail); if(typeof self.callbacks.onMixStart === 'function'){ self.callbacks.onMixStart.call(self._domNode, self._state, futureState, self); } self._$container.trigger('mixStart', [self._state, futureState, self]); self._getOrigMixData(); if(animate && !self._suckMode){ window.requestAnimationFrame ? requestAnimationFrame(phase1) : phase1(); } else { self._cleanUp(); } self._execAction('_goMix', 1, arguments); }, /** * Get Target Data * @since 2.0.0 */ _getTargetData: function(el, stage){ var self = this, elStyle; el.dataset[stage+'PosX'] = el.offsetLeft; el.dataset[stage+'PosY'] = el.offsetTop; if(self.animation.animateResizeTargets){ elStyle = !self._suckMode ? window.getComputedStyle(el) : { marginBottom: '', marginRight: '' }; el.dataset[stage+'MarginBottom'] = parseInt(elStyle.marginBottom); el.dataset[stage+'MarginRight'] = parseInt(elStyle.marginRight); el.dataset[stage+'Width'] = el.offsetWidth; el.dataset[stage+'Height'] = el.offsetHeight; } }, /** * Get Original Mix Data * @since 2.0.0 */ _getOrigMixData: function(){ var self = this, parentStyle = !self._suckMode ? window.getComputedStyle(self._$parent[0]) : {boxSizing: ''}, parentBS = parentStyle.boxSizing || parentStyle[self._vendor+'BoxSizing']; self._incPadding = (parentBS === 'border-box'); self._execAction('_getOrigMixData', 0); !self._suckMode && (self.effects = self._parseEffects()); self._$toHide = self._$hide.filter(':visible'); self._$toShow = self._$show.filter(':hidden'); self._$pre = self._$targets.filter(':visible'); self._startHeight = self._incPadding ? self._$parent.outerHeight() : self._$parent.height(); for(var i = 0; i < self._$pre.length; i++){ var el = self._$pre[i]; self._getTargetData(el, 'orig'); } self._execAction('_getOrigMixData', 1); }, /** * Set Intermediate Positions * @since 2.0.0 */ _setInter: function(){ var self = this; self._execAction('_setInter', 0); if(self._changingLayout && self.animation.animateChangeLayout){ self._$toShow.css('display',self._newDisplay); if(self._changingClass){ self._$container .removeClass(self.layout.containerClass) .addClass(self._newClass); } } else { self._$toShow.css('display', self.layout.display); } self._execAction('_setInter', 1); }, /** * Get Intermediate Mix Data * @since 2.0.0 */ _getInterMixData: function(){ var self = this; self._execAction('_getInterMixData', 0); for(var i = 0; i < self._$toShow.length; i++){ var el = self._$toShow[i]; self._getTargetData(el, 'inter'); } for(var i = 0; i < self._$pre.length; i++){ var el = self._$pre[i]; self._getTargetData(el, 'inter'); } self._execAction('_getInterMixData', 1); }, /** * Set Final Positions * @since 2.0.0 */ _setFinal: function(){ var self = this; self._execAction('_setFinal', 0); self._sorting && self._printSort(); self._$toHide.removeStyle('display'); if(self._changingLayout && self.animation.animateChangeLayout){ self._$pre.css('display',self._newDisplay); } self._execAction('_setFinal', 1); }, /** * Get Final Mix Data * @since 2.0.0 */ _getFinalMixData: function(){ var self = this; self._execAction('_getFinalMixData', 0); for(var i = 0; i < self._$toShow.length; i++){ var el = self._$toShow[i]; self._getTargetData(el, 'final'); } for(var i = 0; i < self._$pre.length; i++){ var el = self._$pre[i]; self._getTargetData(el, 'final'); } self._newHeight = self._incPadding ? self._$parent.outerHeight() : self._$parent.height(); self._sorting && self._printSort(true); self._$toShow.removeStyle('display'); self._$pre.css('display',self.layout.display); if(self._changingClass && self.animation.animateChangeLayout){ self._$container .removeClass(self._newClass) .addClass(self.layout.containerClass); } self._execAction('_getFinalMixData', 1); }, /** * Prepare Targets * @since 2.0.0 */ _prepTargets: function(){ var self = this, transformCSS = { _in: self._getPrefixedCSS('transform', self.effects.transformIn), _out: self._getPrefixedCSS('transform', self.effects.transformOut) }; self._execAction('_prepTargets', 0); if(self.animation.animateResizeContainer){ self._$parent.css('height',self._startHeight+'px'); } for(var i = 0; i < self._$toShow.length; i++){ var el = self._$toShow[i], $el = $(el); el.style.opacity = self.effects.opacity; el.style.display = (self._changingLayout && self.animation.animateChangeLayout) ? self._newDisplay : self.layout.display; $el.css(transformCSS._in); if(self.animation.animateResizeTargets){ el.style.width = el.dataset.finalWidth+'px'; el.style.height = el.dataset.finalHeight+'px'; el.style.marginRight = -(el.dataset.finalWidth - el.dataset.interWidth) + (el.dataset.finalMarginRight * 1)+'px'; el.style.marginBottom = -(el.dataset.finalHeight - el.dataset.interHeight) + (el.dataset.finalMarginBottom * 1)+'px'; } } for(var i = 0; i < self._$pre.length; i++){ var el = self._$pre[i], $el = $(el), translate = { x: el.dataset.origPosX - el.dataset.interPosX, y: el.dataset.origPosY - el.dataset.interPosY }, transformCSS = self._getPrefixedCSS('transform','translate('+translate.x+'px,'+translate.y+'px)'); $el.css(transformCSS); if(self.animation.animateResizeTargets){ el.style.width = el.dataset.origWidth+'px'; el.style.height = el.dataset.origHeight+'px'; if(el.dataset.origWidth - el.dataset.finalWidth){ el.style.marginRight = -(el.dataset.origWidth - el.dataset.interWidth) + (el.dataset.origMarginRight * 1)+'px'; } if(el.dataset.origHeight - el.dataset.finalHeight){ el.style.marginBottom = -(el.dataset.origHeight - el.dataset.interHeight) + (el.dataset.origMarginBottom * 1) +'px'; } } } self._execAction('_prepTargets', 1); }, /** * Animate Targets * @since 2.0.0 */ _animateTargets: function(){ var self = this; self._execAction('_animateTargets', 0); self._targetsDone = 0; self._targetsBound = 0; self._$parent .css(self._getPrefixedCSS('perspective', self.animation.perspectiveDistance+'px')) .css(self._getPrefixedCSS('perspective-origin', self.animation.perspectiveOrigin)); if(self.animation.animateResizeContainer){ self._$parent .css(self._getPrefixedCSS('transition','height '+self.animation.duration+'ms ease')) .css('height',self._newHeight+'px'); } for(var i = 0; i < self._$toShow.length; i++){ var el = self._$toShow[i], $el = $(el), translate = { x: el.dataset.finalPosX - el.dataset.interPosX, y: el.dataset.finalPosY - el.dataset.interPosY }, delay = self._getDelay(i), toShowCSS = {}; el.style.opacity = ''; for(var j = 0; j < 2; j++){ var a = j === 0 ? a = self._prefix : ''; if(self._ff && self._ff <= 20){ toShowCSS[a+'transition-property'] = 'all'; toShowCSS[a+'transition-timing-function'] = self.animation.easing+'ms'; toShowCSS[a+'transition-duration'] = self.animation.duration+'ms'; } toShowCSS[a+'transition-delay'] = delay+'ms'; toShowCSS[a+'transform'] = 'translate('+translate.x+'px,'+translate.y+'px)'; } if(self.effects.transform || self.effects.opacity){ self._bindTargetDone($el); } (self._ff && self._ff <= 20) ? $el.css(toShowCSS) : $el.css(self.effects.transition).css(toShowCSS); } for(var i = 0; i < self._$pre.length; i++){ var el = self._$pre[i], $el = $(el), translate = { x: el.dataset.finalPosX - el.dataset.interPosX, y: el.dataset.finalPosY - el.dataset.interPosY }, delay = self._getDelay(i); if(!( el.dataset.finalPosX === el.dataset.origPosX && el.dataset.finalPosY === el.dataset.origPosY )){ self._bindTargetDone($el); } $el.css(self._getPrefixedCSS('transition', 'all '+self.animation.duration+'ms '+self.animation.easing+' '+delay+'ms')); $el.css(self._getPrefixedCSS('transform', 'translate('+translate.x+'px,'+translate.y+'px)')); if(self.animation.animateResizeTargets){ if(el.dataset.origWidth - el.dataset.finalWidth && el.dataset.finalWidth * 1){ el.style.width = el.dataset.finalWidth+'px'; el.style.marginRight = -(el.dataset.finalWidth - el.dataset.interWidth)+(el.dataset.finalMarginRight * 1)+'px'; } if(el.dataset.origHeight - el.dataset.finalHeight && el.dataset.finalHeight * 1){ el.style.height = el.dataset.finalHeight+'px'; el.style.marginBottom = -(el.dataset.finalHeight - el.dataset.interHeight)+(el.dataset.finalMarginBottom * 1) +'px'; } } } if(self._changingClass){ self._$container .removeClass(self.layout.containerClass) .addClass(self._newClass); } for(var i = 0; i < self._$toHide.length; i++){ var el = self._$toHide[i], $el = $(el), delay = self._getDelay(i), toHideCSS = {}; for(var j = 0; j<2; j++){ var a = j === 0 ? a = self._prefix : ''; toHideCSS[a+'transition-delay'] = delay+'ms'; toHideCSS[a+'transform'] = self.effects.transformOut; toHideCSS.opacity = self.effects.opacity; } $el.css(self.effects.transition).css(toHideCSS); if(self.effects.transform || self.effects.opacity){ self._bindTargetDone($el); }; } self._execAction('_animateTargets', 1); }, /** * Bind Targets TransitionEnd * @since 2.0.0 * @param {object} $el */ _bindTargetDone: function($el){ var self = this, el = $el[0]; self._execAction('_bindTargetDone', 0, arguments); if(!el.dataset.bound){ el.dataset.bound = true; self._targetsBound++; $el.on('webkitTransitionEnd.mixItUp transitionend.mixItUp',function(e){ if( (e.originalEvent.propertyName.indexOf('transform') > -1 || e.originalEvent.propertyName.indexOf('opacity') > -1) && $(e.originalEvent.target).is(self.selectors.target) ){ $el.off('.mixItUp'); delete el.dataset.bound; self._targetDone(); } }); } self._execAction('_bindTargetDone', 1, arguments); }, /** * Target Done * @since 2.0.0 */ _targetDone: function(){ var self = this; self._execAction('_targetDone', 0); self._targetsDone++; (self._targetsDone === self._targetsBound) && self._cleanUp(); self._execAction('_targetDone', 1); }, /** * Clean Up * @since 2.0.0 */ _cleanUp: function(){ var self = this, targetStyles = self.animation.animateResizeTargets ? 'transform opacity width height margin-bottom margin-right' : 'transform opacity'; unBrake = function(){ self._$targets.removeStyle('transition', self._prefix); }; self._execAction('_cleanUp', 0); !self._changingLayout ? self._$show.css('display',self.layout.display) : self._$show.css('display',self._newDisplay); self._$targets.css(self._brake); self._$targets .removeStyle(targetStyles, self._prefix) .removeAttr('data-inter-pos-x data-inter-pos-y data-final-pos-x data-final-pos-y data-orig-pos-x data-orig-pos-y data-orig-height data-orig-width data-final-height data-final-width data-inter-width data-inter-height data-orig-margin-right data-orig-margin-bottom data-inter-margin-right data-inter-margin-bottom data-final-margin-right data-final-margin-bottom'); self._$hide.removeStyle('display'); self._$parent.removeStyle('height transition perspective-distance perspective perspective-origin-x perspective-origin-y perspective-origin perspectiveOrigin', self._prefix); if(self._sorting){ self._printSort(); self._activeSort = self._newSortString; self._sorting = false; } if(self._changingLayout){ if(self._changingDisplay){ self.layout.display = self._newDisplay; self._changingDisplay = false; } if(self._changingClass){ self._$parent.removeClass(self.layout.containerClass).addClass(self._newClass); self.layout.containerClass = self._newClass; self._changingClass = false; } self._changingLayout = false; } self._refresh(); self._buildState(); if(self._state.fail){ self._$container.addClass(self.layout.containerClassFail); } self._$show = $(); self._$hide = $(); if(window.requestAnimationFrame){ requestAnimationFrame(unBrake); } self._mixing = false; if(typeof self.callbacks._user === 'function'){ self.callbacks._user.call(self._domNode, self._state, self); } if(typeof self.callbacks.onMixEnd === 'function'){ self.callbacks.onMixEnd.call(self._domNode, self._state, self); } self._$container.trigger('mixEnd', [self._state, self]); if(self._state.fail){ (typeof self.callbacks.onMixFail === 'function') && self.callbacks.onMixFail.call(self._domNode, self._state, self); self._$container.trigger('mixFail', [self._state, self]); } if(self._loading){ (typeof self.callbacks.onMixLoad === 'function') && self.callbacks.onMixLoad.call(self._domNode, self._state, self); self._$container.trigger('mixLoad', [self._state, self]); } if(self._queue.length){ self._execAction('_queue', 0); self.multiMix(self._queue[0][0],self._queue[0][1],self._queue[0][2]); self._queue.splice(0, 1); } self._execAction('_cleanUp', 1); self._loading = false; }, /** * Get Prefixed CSS * @since 2.0.0 * @param {string} property * @param {string} value * @param {boolean} prefixValue * @return {object} styles */ _getPrefixedCSS: function(property, value, prefixValue){ var self = this, styles = {}, prefix = '', i = -1; for(i = 0; i < 2; i++){ prefix = i === 0 ? self._prefix : ''; prefixValue ? styles[prefix+property] = prefix+value : styles[prefix+property] = value; } return self._execFilter('_getPrefixedCSS', styles, arguments); }, /** * Get Delay * @since 2.0.0 * @param {number} i * @return {number} delay */ _getDelay: function(i){ var self = this, n = typeof self.animation.staggerSequence === 'function' ? self.animation.staggerSequence.call(self._domNode, i, self._state) : i, delay = self.animation.stagger ? n * self.animation.staggerDuration : 0; return self._execFilter('_getDelay', delay, arguments); }, /** * Parse MultiMix Arguments * @since 2.0.0 * @param {array} args * @return {object} output */ _parseMultiMixArgs: function(args){ var self = this, output = { command: null, animate: self.animation.enable, callback: null }; for(var i = 0; i < args.length; i++){ var arg = args[i]; if(arg !== null){ if(typeof arg === 'object' || typeof arg === 'string'){ output.command = arg; } else if(typeof arg === 'boolean'){ output.animate = arg; } else if(typeof arg === 'function'){ output.callback = arg; } } } return self._execFilter('_parseMultiMixArgs', output, arguments); }, /** * Parse Insert Arguments * @since 2.0.0 * @param {array} args * @return {object} output */ _parseInsertArgs: function(args){ var self = this, output = { index: 0, $object: $(), multiMix: {filter: self._state.activeFilter}, callback: null }; for(var i = 0; i < args.length; i++){ var arg = args[i]; if(typeof arg === 'number'){ output.index = arg; } else if(typeof arg === 'object' && arg instanceof $){ output.$object = arg; } else if(typeof arg === 'object' && self._helpers._isElement(arg)){ output.$object = $(arg); } else if(typeof arg === 'object' && arg !== null){ output.multiMix = arg; } else if(typeof arg === 'boolean' && !arg){ output.multiMix = false; } else if(typeof arg === 'function'){ output.callback = arg; } } return self._execFilter('_parseInsertArgs', output, arguments); }, /** * Execute Action * @since 2.0.0 * @param {string} methodName * @param {boolean} isPost * @param {array} args */ _execAction: function(methodName, isPost, args){ var self = this, context = isPost ? 'post' : 'pre'; if(!self._actions.isEmptyObject && self._actions.hasOwnProperty(methodName)){ for(var key in self._actions[methodName][context]){ self._actions[methodName][context][key].call(self, args); } } }, /** * Execute Filter * @since 2.0.0 * @param {string} methodName * @param {mixed} value * @return {mixed} value */ _execFilter: function(methodName, value, args){ var self = this; if(!self._filters.isEmptyObject && self._filters.hasOwnProperty(methodName)){ for(var key in self._filters[methodName]){ return self._filters[methodName][key].call(self, args); } } else { return value; } }, /* Helpers ---------------------------------------------------------------------- */ _helpers: { /** * CamelCase * @since 2.0.0 * @param {string} * @return {string} */ _camelCase: function(string){ return string.replace(/-([a-z])/g, function(g){ return g[1].toUpperCase(); }); }, /** * Is Element * @since 2.1.3 * @param {object} element to test * @return {boolean} */ _isElement: function(el){ if(window.HTMLElement){ return el instanceof HTMLElement; } else { return ( el !== null && el.nodeType === 1 && el.nodeName === 'string' ); } } }, /* Public Methods ---------------------------------------------------------------------- */ /** * Is Mixing * @since 2.0.0 * @return {boolean} */ isMixing: function(){ var self = this; return self._execFilter('isMixing', self._mixing); }, /** * Filter (public) * @since 2.0.0 * @param {array} arguments */ filter: function(){ var self = this, args = self._parseMultiMixArgs(arguments); self._clicking && (self._toggleString = ''); self.multiMix({filter: args.command}, args.animate, args.callback); }, /** * Sort (public) * @since 2.0.0 * @param {array} arguments */ sort: function(){ var self = this, args = self._parseMultiMixArgs(arguments); self.multiMix({sort: args.command}, args.animate, args.callback); }, /** * Change Layout (public) * @since 2.0.0 * @param {array} arguments */ changeLayout: function(){ var self = this, args = self._parseMultiMixArgs(arguments); self.multiMix({changeLayout: args.command}, args.animate, args.callback); }, /** * MultiMix * @since 2.0.0 * @param {array} arguments */ multiMix: function(){ var self = this, args = self._parseMultiMixArgs(arguments); self._execAction('multiMix', 0, arguments); if(!self._mixing){ if(self.controls.enable && !self._clicking){ self.controls.toggleFilterButtons && self._buildToggleArray(); self._updateControls(args.command, self.controls.toggleFilterButtons); } (self._queue.length < 2) && (self._clicking = false); delete self.callbacks._user; if(args.callback) self.callbacks._user = args.callback; var sort = args.command.sort, filter = args.command.filter, changeLayout = args.command.changeLayout; self._refresh(); if(sort){ self._newSort = self._parseSort(sort); self._newSortString = sort; self._sorting = true; self._sort(); } if(filter !== undf){ filter = (filter === 'all') ? self.selectors.target : filter; self._activeFilter = filter; } self._filter(); if(changeLayout){ self._newDisplay = (typeof changeLayout === 'string') ? changeLayout : changeLayout.display || self.layout.display; self._newClass = changeLayout.containerClass || ''; if( self._newDisplay !== self.layout.display || self._newClass !== self.layout.containerClass ){ self._changingLayout = true; self._changingClass = (self._newClass !== self.layout.containerClass); self._changingDisplay = (self._newDisplay !== self.layout.display); } } self._$targets.css(self._brake); self._goMix(args.animate ^ self.animation.enable ? args.animate : self.animation.enable); self._execAction('multiMix', 1, arguments); } else { if(self.animation.queue && self._queue.length < self.animation.queueLimit){ self._queue.push(arguments); (self.controls.enable && !self._clicking) && self._updateControls(args.command); self._execAction('multiMixQueue', 1, arguments); } else { if(typeof self.callbacks.onMixBusy === 'function'){ self.callbacks.onMixBusy.call(self._domNode, self._state, self); } self._$container.trigger('mixBusy', [self._state, self]); self._execAction('multiMixBusy', 1, arguments); } } }, /** * Insert * @since 2.0.0 * @param {array} arguments */ insert: function(){ var self = this, args = self._parseInsertArgs(arguments), callback = (typeof args.callback === 'function') ? args.callback : null, frag = document.createDocumentFragment(), target = (function(){ self._refresh(); if(self._$targets.length){ return (args.index < self._$targets.length || !self._$targets.length) ? self._$targets[args.index] : self._$targets[self._$targets.length-1].nextElementSibling; } else { return self._$parent[0].children[0]; } })(); self._execAction('insert', 0, arguments); if(args.$object){ for(var i = 0; i < args.$object.length; i++){ var el = args.$object[i]; frag.appendChild(el); frag.appendChild(document.createTextNode(' ')); } self._$parent[0].insertBefore(frag, target); } self._execAction('insert', 1, arguments); if(typeof args.multiMix === 'object'){ self.multiMix(args.multiMix, callback); } }, /** * Prepend * @since 2.0.0 * @param {array} arguments */ prepend: function(){ var self = this, args = self._parseInsertArgs(arguments); self.insert(0, args.$object, args.multiMix, args.callback); }, /** * Append * @since 2.0.0 * @param {array} arguments */ append: function(){ var self = this, args = self._parseInsertArgs(arguments); self.insert(self._state.totalTargets, args.$object, args.multiMix, args.callback); }, /** * Get Option * @since 2.0.0 * @param {string} string * @return {mixed} value */ getOption: function(string){ var self = this, getProperty = function(obj, prop){ var parts = prop.split('.'), last = parts.pop(), l = parts.length, i = 1, current = parts[0] || prop; while((obj = obj[current]) && i < l){ current = parts[i]; i++; } if(obj !== undf){ return obj[last] !== undf ? obj[last] : obj; } }; return string ? self._execFilter('getOption', getProperty(self, string), arguments) : self; }, /** * Set Options * @since 2.0.0 * @param {object} config */ setOptions: function(config){ var self = this; self._execAction('setOptions', 0, arguments); typeof config === 'object' && $.extend(true, self, config); self._execAction('setOptions', 1, arguments); }, /** * Get State * @since 2.0.0 * @return {object} state */ getState: function(){ var self = this; return self._execFilter('getState', self._state, self); }, /** * Force Refresh * @since 2.1.2 */ forceRefresh: function(){ var self = this; self._refresh(false, true); }, /** * Destroy * @since 2.0.0 * @param {boolean} hideAll */ destroy: function(hideAll){ var self = this, filters = $.MixItUp.prototype._bound._filter, sorts = $.MixItUp.prototype._bound._sort; self._execAction('destroy', 0, arguments); self._$body .add($(self.selectors.sort)) .add($(self.selectors.filter)) .off('.mixItUp'); for(var i = 0; i < self._$targets.length; i++){ var target = self._$targets[i]; hideAll && (target.style.display = ''); delete target.mixParent; } self._execAction('destroy', 1, arguments); if(filters[self.selectors.filter] && filters[self.selectors.filter] > 1) { filters[self.selectors.filter]--; } else if(filters[self.selectors.filter] === 1) { delete filters[self.selectors.filter]; } if(sorts[self.selectors.sort] && sorts[self.selectors.sort] > 1) { sorts[self.selectors.sort]--; } else if(sorts[self.selectors.sort] === 1) { delete sorts[self.selectors.sort]; } delete $.MixItUp.prototype._instances[self._id]; } }; /* jQuery Methods ---------------------------------------------------------------------- */ /** * jQuery .mixItUp() method * @since 2.0.0 * @extends $.fn */ $.fn.mixItUp = function(){ var args = arguments, dataReturn = [], eachReturn, _instantiate = function(domNode, settings){ var instance = new $.MixItUp(), rand = function(){ return ('00000'+(Math.random()*16777216<<0).toString(16)).substr(-6).toUpperCase(); }; instance._execAction('_instantiate', 0, arguments); domNode.id = !domNode.id ? 'MixItUp'+rand() : domNode.id; if(!instance._instances[domNode.id]){ instance._instances[domNode.id] = instance; instance._init(domNode, settings); } instance._execAction('_instantiate', 1, arguments); }; eachReturn = this.each(function(){ if(args && typeof args[0] === 'string'){ var instance = $.MixItUp.prototype._instances[this.id]; if(args[0] === 'isLoaded'){ dataReturn.push(instance ? true : false); } else { var data = instance[args[0]](args[1], args[2], args[3]); if(data !== undf)dataReturn.push(data); } } else { _instantiate(this, args[0]); } }); if(dataReturn.length){ return dataReturn.length > 1 ? dataReturn : dataReturn[0]; } else { return eachReturn; } }; /** * jQuery .removeStyle() method * @since 2.0.0 * @extends $.fn */ $.fn.removeStyle = function(style, prefix){ prefix = prefix ? prefix : ''; return this.each(function(){ var el = this, styles = style.split(' '); for(var i = 0; i < styles.length; i++){ for(var j = 0; j < 4; j++){ switch (j) { case 0: var prop = styles[i]; break; case 1: var prop = $.MixItUp.prototype._helpers._camelCase(prop); break; case 2: var prop = prefix+styles[i]; break; case 3: var prop = $.MixItUp.prototype._helpers._camelCase(prefix+styles[i]); } if( el.style[prop] !== undf && typeof el.style[prop] !== 'unknown' && el.style[prop].length > 0 ){ el.style[prop] = ''; } if(!prefix && j === 1)break; } } if(el.attributes && el.attributes.style && el.attributes.style !== undf && el.attributes.style.value === ''){ el.attributes.removeNamedItem('style'); } }); }; })(jQuery); /**! * MixItUp Pagination v1.1.0 * A Premium Extension for MixItUp * * @copyright Copyright 2014 KunkaLabs Limited. * @author KunkaLabs Limited. * @link https://mixitup.kunkalabs.com * * @license To be used under the same terms as MixItUp core. * https://mixitup.kunkalabs.com/licenses/ */ (function($, undf){ /* Add Actions ---------------------------------------------------------------------- */ /** * Constructor * @extends _constructor */ $.MixItUp.prototype.addAction('_constructor', 'pagination', function(){ var self = this; self.pagination = { limit: 0, loop: false, generatePagers: true, maxPagers: 5, pagerClass: '', prevButtonHTML: 'previous', nextButtonHTML: 'next;' }; $.extend(self.selectors, { pagersWrapper: '.pager-list', pager: '.pager' }); $.extend(self.load, { page: 1 }); self._activePage = null; self._totalPages = null; self._$pagersWrapper = $(); }, 1); /** * Initialize * @extends $.MixItUp.prototype._init * @priority 1 */ $.MixItUp.prototype.addAction('_init', 'pagination', function(){ var self = this; self._activePage = self.load.page * 1; self.pagination.maxPagers = ( typeof self.pagination.maxPagers === 'number' && self.pagination.maxPagers < 5 ) ? 5 : self.pagination.maxPagers; }, 1); /** * Bind Handlers * @extends $.MixItUp.prototype._bindHandlers * @priority 1 */ $.MixItUp.prototype.addAction('_bindHandlers', 'pagination', function(){ var self = this; if(self.pagination.generatePagers){ self._$pagersWrapper = $(self.selectors.pagersWrapper); }; if(self.controls.live){ self._$body.on('click.mixItUp.'+self._id, self.selectors.pager, function(){ self._processClick($(this), 'page'); }); } else { self._$pagersWrapper.on('click.mixItUp.'+self._id, self.selectors.pager, function(){; self._processClick($(this), 'page'); }); } }, 1); /** * Process Click * @extends $.MixItUp.prototype._processClick * @priority 1 */ $.MixItUp.prototype.addAction('_processClick', 'pagination', function(args){ var self = this, pageNumber = null, $button = args[0], type = args[1]; if(type === 'page'){ pageNumber = $button.attr('data-page') || false; if(pageNumber === 'prev'){ pageNumber = self._getPrevPage(); } else if(pageNumber === 'next'){ pageNumber = self._getNextPage(); } else if(pageNumber){ pageNumber = pageNumber * 1; } else { return false; } if(!$button.hasClass(self.controls.activeClass)){ self.paginate(pageNumber); } } }, 1); /** * Build State * @extends $.MixItUp.prototype._buildState * @priority 1 */ $.MixItUp.prototype.addAction('_buildState', 'pagination', function(){ var self = this; $.extend(self._state, { limit: self.pagination.limit, activePage: self._activePage, totalPages: self._totalPages }); }, 1); /** * Sort * @extends $.MixItUp.prototype._sort * @priority 1 */ $.MixItUp.prototype.addAction('_sort', 'pagination', function(){ var self = this; if(self.pagination.limit > 0){ self._printSort(); } }, 1); /** * Filter * @extends $.MixItUp.prototype._filter * @priority 1 */ $.MixItUp.prototype.addAction('_filter', 'pagination', function(){ var self = this, startPageAt = self.pagination.limit * (self.load.page - 1), endPageAt = (self.pagination.limit * self.load.page) - 1, $inPage = null, $notInPage = null; self._activePage = self.load.page; self._totalPages = self.pagination.limit ? Math.ceil(self._$show.length / self.pagination.limit) : 1; if(self.pagination.limit > 0){ $inPage = self._$show.filter(function(index){ return index >= startPageAt && index <= endPageAt; }); $notInPage = self._$show.filter(function(index){ return index < startPageAt || index > endPageAt; }); self._$show = $inPage; self._$hide = self._$hide.add($notInPage); if(self._sorting){ self._printSort(true); } } if(self.pagination.generatePagers && self._$pagersWrapper.length){ self._generatePagers(); }; }, 1); /** * MultiMix * @extends $.MixItUp.prototype.multiMix * @priority 0 */ $.MixItUp.prototype.addAction('multiMix', 'pagination', function(args){ var self = this, args = self._parseMultiMixArgs(args); if(args.command.paginate !== undf){ typeof args.command.paginate === 'object' ? $.extend(self.pagination, args.command.paginate) : self.load.page = args.command.paginate; } else if(args.command.filter !== undf || args.command.sort !== undf){ self.load.page = 1; } }, 0); /** * Destory * @extends $.MixItUp.prototype.destroy * @priority 1 */ $.MixItUp.prototype.addAction('destroy', 'pagination', function(){ var self = this; self._$pagersWrapper.off('.mixItUp').html(''); }, 1); /* Add Private Methods ---------------------------------------------------------------------- */ $.MixItUp.prototype.extend({ /** * Get Next Page * @return {number} page */ _getNextPage: function(){ var self = this, page = self._activePage + 1, page = self._activePage < self._totalPages ? page : self.pagination.loop ? 1 : self._activePage; return self._execFilter('_getNextPage', page * 1); }, /** * Get Previous Page * @return {number} page */ _getPrevPage: function(){ var self = this, page = self._activePage - 1, page = self._activePage > 1 ? page : self.pagination.loop ? self._totalPages : self._activePage; return self._execFilter('_getPrevPage', page * 1); }, /** * Generate Pagination Controls */ _generatePagers: function(){ var self = this, pagerTag = self._$pagersWrapper[0].nodeName === 'UL' ? 'li' : 'span', pagerClass = self.pagination.pagerClass ? self.pagination.pagerClass+' ' : '', prevButtonHTML = '<'+pagerTag+' class="'+pagerClass+'pager page-prev" data-page="prev">'+self.pagination.prevButtonHTML+'', prevButtonHTML = (self._activePage > 1) ? prevButtonHTML : self.pagination.loop ? prevButtonHTML : '<'+pagerTag+' class="'+pagerClass+'pager page-prev disabled">'+self.pagination.prevButtonHTML+''; nextButtonHTML = '<'+pagerTag+' class="'+pagerClass+'pager page-next" data-page="next">'+self.pagination.nextButtonHTML+'', nextButtonHTML = (self._activePage < self._totalPages) ? nextButtonHTML : self.pagination.loop ? nextButtonHTML : '<'+pagerTag+' class="'+pagerClass+'pager page-next disabled">'+self.pagination.nextButtonHTML+''; totalButtons = ( self.pagination.maxPagers !== false && self._totalPages > self.pagination.maxPagers ) ? self.pagination.maxPagers : self._totalPages, pagerButtonsHTML = '', pagersHTML = '', wrapperClass = ''; self._execAction('_generatePagers', 0); for(var i = 0; i < totalButtons; i++){ var pagerNumber = null, classes = ''; if(i === 0){ pagerNumber = 1; if( self.pagination.maxPagers !== false && self._activePage > (self.pagination.maxPagers - 2) && self._totalPages > self.pagination.maxPagers ){ classes = ' page-first'; } } else { if( self.pagination.maxPagers === false || totalButtons < self.pagination.maxPagers ){ pagerNumber = i + 1; } else { if(i === self.pagination.maxPagers - 1){ pagerNumber = self._totalPages; if(self._activePage < self._totalPages - 2 && self._totalPages > self.pagination.maxPagers){ classes = ' page-last'; } } else{ if( self._activePage > self.pagination.maxPagers - 2 && self._activePage < self._totalPages - 2 ){ pagerNumber = self._activePage - (2 - i); } else if(self._activePage < self.pagination.maxPagers - 1){ pagerNumber = i + 1; } else if(self._activePage >= self._totalPages - 2){ pagerNumber = self._totalPages - (self.pagination.maxPagers - 1 - i); } } } } classes = (pagerNumber == self._activePage) ? classes+' '+self.controls.activeClass : classes; pagerButtonsHTML += '<'+pagerTag+' class="'+pagerClass+'pager page-number'+classes+'" data-page="'+pagerNumber+'">'+pagerNumber+' '; } pagersHTML = self._totalPages > 1 ? prevButtonHTML+' '+pagerButtonsHTML+' '+nextButtonHTML : ''; wrapperClass = self._totalPages > 1 ? '' : 'no-pagers'; self._$pagersWrapper.html(pagersHTML).removeClass('no-pagers').addClass(wrapperClass); self._execAction('_generatePagers', 1); }, /** * Parse Paginate Arguments * @param {array} args * @return {object} output */ _parsePaginateArgs: function(args){ var self = this, output = { command: null, animate: self.animation.enable, callback: null }; for(var i = 0; i < args.length; i++){ var arg = args[i]; if(arg !== null){ if(typeof arg === 'object' || typeof arg === 'number'){ output.command = arg; } else if(typeof arg === 'boolean'){ output.animate = arg; } else if(typeof arg === 'function'){ output.callback = arg; } } } return self._execFilter('_parsePaginateArgs', output, arguments); } }); /* Add Public Methods ---------------------------------------------------------------------- */ $.MixItUp.prototype.extend({ /** * Paginate * @param {array} arguments */ paginate: function(){ var self = this, args = self._parsePaginateArgs(arguments); self.multiMix({paginate: args.command}, args.animate, args.callback); }, /** * nextPage * @param {array} arguments */ nextPage: function(){ var self = this, args = self._parsePaginateArgs(arguments); self.multiMix({paginate: self._getNextPage()}, args.animate, args.callback); }, /** * prevPage * @param {array} arguments */ prevPage: function(){ var self = this, args = self._parsePaginateArgs(arguments); self.multiMix({paginate: self._getPrevPage()}, args.animate, args.callback); } }); })(jQuery); /*! * Packery PACKAGED v1.4.2 * bin-packing layout library * * Licensed GPLv3 for open source use * or Flickity Commercial License for commercial use * * http://packery.metafizzy.co * Copyright 2015 Metafizzy */ /** * Bridget makes jQuery widgets * v1.1.0 * MIT license */ ( function( window ) { // -------------------------- utils -------------------------- // var slice = Array.prototype.slice; function noop() {} // -------------------------- definition -------------------------- // function defineBridget( $ ) { // bail if no jQuery if ( !$ ) { return; } // -------------------------- addOptionMethod -------------------------- // /** * adds option method -> $().plugin('option', {...}) * @param {Function} PluginClass - constructor class */ function addOptionMethod( PluginClass ) { // don't overwrite original option method if ( PluginClass.prototype.option ) { return; } // option setter PluginClass.prototype.option = function( opts ) { // bail out if not an object if ( !$.isPlainObject( opts ) ){ return; } this.options = $.extend( true, this.options, opts ); }; } // -------------------------- plugin bridge -------------------------- // // helper function for logging errors // $.error breaks jQuery chaining var logError = typeof console === 'undefined' ? noop : function( message ) { console.error( message ); }; /** * jQuery plugin bridge, access methods like $elem.plugin('method') * @param {String} namespace - plugin name * @param {Function} PluginClass - constructor class */ function bridge( namespace, PluginClass ) { // add to jQuery fn namespace $.fn[ namespace ] = function( options ) { if ( typeof options === 'string' ) { // call plugin method when first argument is a string // get arguments for method var args = slice.call( arguments, 1 ); for ( var i=0, len = this.length; i < len; i++ ) { var elem = this[i]; var instance = $.data( elem, namespace ); if ( !instance ) { logError( "cannot call methods on " + namespace + " prior to initialization; " + "attempted to call '" + options + "'" ); continue; } if ( !$.isFunction( instance[options] ) || options.charAt(0) === '_' ) { logError( "no such method '" + options + "' for " + namespace + " instance" ); continue; } // trigger method with arguments var returnValue = instance[ options ].apply( instance, args ); // break look and return first value if provided if ( returnValue !== undefined ) { return returnValue; } } // return this if no return value return this; } else { return this.each( function() { var instance = $.data( this, namespace ); if ( instance ) { // apply options & init instance.option( options ); instance._init(); } else { // initialize new instance instance = new PluginClass( this, options ); $.data( this, namespace, instance ); } }); } }; } // -------------------------- bridget -------------------------- // /** * converts a Prototypical class into a proper jQuery plugin * the class must have a ._init method * @param {String} namespace - plugin name, used in $().pluginName * @param {Function} PluginClass - constructor class */ $.bridget = function( namespace, PluginClass ) { addOptionMethod( PluginClass ); bridge( namespace, PluginClass ); }; return $.bridget; } // transport if ( typeof define === 'function' && define.amd ) { // AMD define( 'jquery-bridget/jquery.bridget',[ 'jquery' ], defineBridget ); } else if ( typeof exports === 'object' ) { defineBridget( require('jquery') ); } else { // get jquery from browser global defineBridget( window.jQuery ); } })( window ); /*! * classie v1.0.1 * class helper functions * from bonzo https://github.com/ded/bonzo * MIT license * * classie.has( elem, 'my-class' ) -> true/false * classie.add( elem, 'my-new-class' ) * classie.remove( elem, 'my-unwanted-class' ) * classie.toggle( elem, 'my-class' ) */ /*jshint browser: true, strict: true, undef: true, unused: true */ /*global define: false, module: false */ ( function( window ) { // class helper functions from bonzo https://github.com/ded/bonzo function classReg( className ) { return new RegExp("(^|\\s+)" + className + "(\\s+|$)"); } // classList support for class management // altho to be fair, the api sucks because it won't accept multiple classes at once var hasClass, addClass, removeClass; if ( 'classList' in document.documentElement ) { hasClass = function( elem, c ) { return elem.classList.contains( c ); }; addClass = function( elem, c ) { elem.classList.add( c ); }; removeClass = function( elem, c ) { elem.classList.remove( c ); }; } else { hasClass = function( elem, c ) { return classReg( c ).test( elem.className ); }; addClass = function( elem, c ) { if ( !hasClass( elem, c ) ) { elem.className = elem.className + ' ' + c; } }; removeClass = function( elem, c ) { elem.className = elem.className.replace( classReg( c ), ' ' ); }; } function toggleClass( elem, c ) { var fn = hasClass( elem, c ) ? removeClass : addClass; fn( elem, c ); } var classie = { // full names hasClass: hasClass, addClass: addClass, removeClass: removeClass, toggleClass: toggleClass, // short names has: hasClass, add: addClass, remove: removeClass, toggle: toggleClass }; // transport if ( typeof define === 'function' && define.amd ) { // AMD define( 'classie/classie',classie ); } else if ( typeof exports === 'object' ) { // CommonJS module.exports = classie; } else { // browser global window.classie = classie; } })( window ); /*! * getStyleProperty v1.0.4 * original by kangax * http://perfectionkills.com/feature-testing-css-properties/ * MIT license */ /*jshint browser: true, strict: true, undef: true */ /*global define: false, exports: false, module: false */ ( function( window ) { var prefixes = 'Webkit Moz ms Ms O'.split(' '); var docElemStyle = document.documentElement.style; function getStyleProperty( propName ) { if ( !propName ) { return; } // test standard property first if ( typeof docElemStyle[ propName ] === 'string' ) { return propName; } // capitalize propName = propName.charAt(0).toUpperCase() + propName.slice(1); // test vendor specific properties var prefixed; for ( var i=0, len = prefixes.length; i < len; i++ ) { prefixed = prefixes[i] + propName; if ( typeof docElemStyle[ prefixed ] === 'string' ) { return prefixed; } } } // transport if ( typeof define === 'function' && define.amd ) { // AMD define( 'get-style-property/get-style-property',[],function() { return getStyleProperty; }); } else if ( typeof exports === 'object' ) { // CommonJS for Component module.exports = getStyleProperty; } else { // browser global window.getStyleProperty = getStyleProperty; } })( window ); /*! * getSize v1.2.2 * measure size of elements * MIT license */ /*jshint browser: true, strict: true, undef: true, unused: true */ /*global define: false, exports: false, require: false, module: false, console: false */ ( function( window, undefined ) { // -------------------------- helpers -------------------------- // // get a number from a string, not a percentage function getStyleSize( value ) { var num = parseFloat( value ); // not a percent like '100%', and a number var isValid = value.indexOf('%') === -1 && !isNaN( num ); return isValid && num; } function noop() {} var logError = typeof console === 'undefined' ? noop : function( message ) { console.error( message ); }; // -------------------------- measurements -------------------------- // var measurements = [ 'paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom', 'marginLeft', 'marginRight', 'marginTop', 'marginBottom', 'borderLeftWidth', 'borderRightWidth', 'borderTopWidth', 'borderBottomWidth' ]; function getZeroSize() { var size = { width: 0, height: 0, innerWidth: 0, innerHeight: 0, outerWidth: 0, outerHeight: 0 }; for ( var i=0, len = measurements.length; i < len; i++ ) { var measurement = measurements[i]; size[ measurement ] = 0; } return size; } function defineGetSize( getStyleProperty ) { // -------------------------- setup -------------------------- // var isSetup = false; var getStyle, boxSizingProp, isBoxSizeOuter; /** * setup vars and functions * do it on initial getSize(), rather than on script load * For Firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=548397 */ function setup() { // setup once if ( isSetup ) { return; } isSetup = true; var getComputedStyle = window.getComputedStyle; getStyle = ( function() { var getStyleFn = getComputedStyle ? function( elem ) { return getComputedStyle( elem, null ); } : function( elem ) { return elem.currentStyle; }; return function getStyle( elem ) { var style = getStyleFn( elem ); if ( !style ) { logError( 'Style returned ' + style + '. Are you running this code in a hidden iframe on Firefox? ' + 'See http://bit.ly/getsizebug1' ); } return style; }; })(); // -------------------------- box sizing -------------------------- // boxSizingProp = getStyleProperty('boxSizing'); /** * WebKit measures the outer-width on style.width on border-box elems * IE & Firefox measures the inner-width */ if ( boxSizingProp ) { var div = document.createElement('div'); div.style.width = '200px'; div.style.padding = '1px 2px 3px 4px'; div.style.borderStyle = 'solid'; div.style.borderWidth = '1px 2px 3px 4px'; div.style[ boxSizingProp ] = 'border-box'; var body = document.body || document.documentElement; body.appendChild( div ); var style = getStyle( div ); isBoxSizeOuter = getStyleSize( style.width ) === 200; body.removeChild( div ); } } // -------------------------- getSize -------------------------- // function getSize( elem ) { setup(); // use querySeletor if elem is string if ( typeof elem === 'string' ) { elem = document.querySelector( elem ); } // do not proceed on non-objects if ( !elem || typeof elem !== 'object' || !elem.nodeType ) { return; } var style = getStyle( elem ); // if hidden, everything is 0 if ( style.display === 'none' ) { return getZeroSize(); } var size = {}; size.width = elem.offsetWidth; size.height = elem.offsetHeight; var isBorderBox = size.isBorderBox = !!( boxSizingProp && style[ boxSizingProp ] && style[ boxSizingProp ] === 'border-box' ); // get all measurements for ( var i=0, len = measurements.length; i < len; i++ ) { var measurement = measurements[i]; var value = style[ measurement ]; value = mungeNonPixel( elem, value ); var num = parseFloat( value ); // any 'auto', 'medium' value will be 0 size[ measurement ] = !isNaN( num ) ? num : 0; } var paddingWidth = size.paddingLeft + size.paddingRight; var paddingHeight = size.paddingTop + size.paddingBottom; var marginWidth = size.marginLeft + size.marginRight; var marginHeight = size.marginTop + size.marginBottom; var borderWidth = size.borderLeftWidth + size.borderRightWidth; var borderHeight = size.borderTopWidth + size.borderBottomWidth; var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter; // overwrite width and height if we can get it from style var styleWidth = getStyleSize( style.width ); if ( styleWidth !== false ) { size.width = styleWidth + // add padding and border unless it's already including it ( isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth ); } var styleHeight = getStyleSize( style.height ); if ( styleHeight !== false ) { size.height = styleHeight + // add padding and border unless it's already including it ( isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight ); } size.innerWidth = size.width - ( paddingWidth + borderWidth ); size.innerHeight = size.height - ( paddingHeight + borderHeight ); size.outerWidth = size.width + marginWidth; size.outerHeight = size.height + marginHeight; return size; } // IE8 returns percent values, not pixels // taken from jQuery's curCSS function mungeNonPixel( elem, value ) { // IE8 and has percent value if ( window.getComputedStyle || value.indexOf('%') === -1 ) { return value; } var style = elem.style; // Remember the original values var left = style.left; var rs = elem.runtimeStyle; var rsLeft = rs && rs.left; // Put in the new values to get a computed value out if ( rsLeft ) { rs.left = elem.currentStyle.left; } style.left = value; value = style.pixelLeft; // Revert the changed values style.left = left; if ( rsLeft ) { rs.left = rsLeft; } return value; } return getSize; } // transport if ( typeof define === 'function' && define.amd ) { // AMD for RequireJS define( 'get-size/get-size',[ 'get-style-property/get-style-property' ], defineGetSize ); } else if ( typeof exports === 'object' ) { // CommonJS for Component module.exports = defineGetSize( require('desandro-get-style-property') ); } else { // browser global window.getSize = defineGetSize( window.getStyleProperty ); } })( window ); /*! * eventie v1.0.6 * event binding helper * eventie.bind( elem, 'click', myFn ) * eventie.unbind( elem, 'click', myFn ) * MIT license */ /*jshint browser: true, undef: true, unused: true */ /*global define: false, module: false */ ( function( window ) { var docElem = document.documentElement; var bind = function() {}; function getIEEvent( obj ) { var event = window.event; // add event.target event.target = event.target || event.srcElement || obj; return event; } if ( docElem.addEventListener ) { bind = function( obj, type, fn ) { obj.addEventListener( type, fn, false ); }; } else if ( docElem.attachEvent ) { bind = function( obj, type, fn ) { obj[ type + fn ] = fn.handleEvent ? function() { var event = getIEEvent( obj ); fn.handleEvent.call( fn, event ); } : function() { var event = getIEEvent( obj ); fn.call( obj, event ); }; obj.attachEvent( "on" + type, obj[ type + fn ] ); }; } var unbind = function() {}; if ( docElem.removeEventListener ) { unbind = function( obj, type, fn ) { obj.removeEventListener( type, fn, false ); }; } else if ( docElem.detachEvent ) { unbind = function( obj, type, fn ) { obj.detachEvent( "on" + type, obj[ type + fn ] ); try { delete obj[ type + fn ]; } catch ( err ) { // can't delete window object properties obj[ type + fn ] = undefined; } }; } var eventie = { bind: bind, unbind: unbind }; // ----- module definition ----- // if ( typeof define === 'function' && define.amd ) { // AMD define( 'eventie/eventie',eventie ); } else if ( typeof exports === 'object' ) { // CommonJS module.exports = eventie; } else { // browser global window.eventie = eventie; } })( window ); /*! * EventEmitter v4.2.11 - git.io/ee * Unlicense - http://unlicense.org/ * Oliver Caldwell - http://oli.me.uk/ * @preserve */ ;(function () { /** * Class for managing events. * Can be extended to provide event functionality in other classes. * * @class EventEmitter Manages event registering and emitting. */ function EventEmitter() {} // Shortcuts to improve speed and size var proto = EventEmitter.prototype; var exports = this; var originalGlobalValue = exports.EventEmitter; /** * Finds the index of the listener for the event in its storage array. * * @param {Function[]} listeners Array of listeners to search through. * @param {Function} listener Method to look for. * @return {Number} Index of the specified listener, -1 if not found * @api private */ function indexOfListener(listeners, listener) { var i = listeners.length; while (i--) { if (listeners[i].listener === listener) { return i; } } return -1; } /** * Alias a method while keeping the context correct, to allow for overwriting of target method. * * @param {String} name The name of the target method. * @return {Function} The aliased method * @api private */ function alias(name) { return function aliasClosure() { return this[name].apply(this, arguments); }; } /** * Returns the listener array for the specified event. * Will initialise the event object and listener arrays if required. * Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them. * Each property in the object response is an array of listener functions. * * @param {String|RegExp} evt Name of the event to return the listeners from. * @return {Function[]|Object} All listener functions for the event. */ proto.getListeners = function getListeners(evt) { var events = this._getEvents(); var response; var key; // Return a concatenated array of all matching events if // the selector is a regular expression. if (evt instanceof RegExp) { response = {}; for (key in events) { if (events.hasOwnProperty(key) && evt.test(key)) { response[key] = events[key]; } } } else { response = events[evt] || (events[evt] = []); } return response; }; /** * Takes a list of listener objects and flattens it into a list of listener functions. * * @param {Object[]} listeners Raw listener objects. * @return {Function[]} Just the listener functions. */ proto.flattenListeners = function flattenListeners(listeners) { var flatListeners = []; var i; for (i = 0; i < listeners.length; i += 1) { flatListeners.push(listeners[i].listener); } return flatListeners; }; /** * Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful. * * @param {String|RegExp} evt Name of the event to return the listeners from. * @return {Object} All listener functions for an event in an object. */ proto.getListenersAsObject = function getListenersAsObject(evt) { var listeners = this.getListeners(evt); var response; if (listeners instanceof Array) { response = {}; response[evt] = listeners; } return response || listeners; }; /** * Adds a listener function to the specified event. * The listener will not be added if it is a duplicate. * If the listener returns true then it will be removed after it is called. * If you pass a regular expression as the event name then the listener will be added to all events that match it. * * @param {String|RegExp} evt Name of the event to attach the listener to. * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling. * @return {Object} Current instance of EventEmitter for chaining. */ proto.addListener = function addListener(evt, listener) { var listeners = this.getListenersAsObject(evt); var listenerIsWrapped = typeof listener === 'object'; var key; for (key in listeners) { if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) { listeners[key].push(listenerIsWrapped ? listener : { listener: listener, once: false }); } } return this; }; /** * Alias of addListener */ proto.on = alias('addListener'); /** * Semi-alias of addListener. It will add a listener that will be * automatically removed after its first execution. * * @param {String|RegExp} evt Name of the event to attach the listener to. * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling. * @return {Object} Current instance of EventEmitter for chaining. */ proto.addOnceListener = function addOnceListener(evt, listener) { return this.addListener(evt, { listener: listener, once: true }); }; /** * Alias of addOnceListener. */ proto.once = alias('addOnceListener'); /** * Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad. * You need to tell it what event names should be matched by a regex. * * @param {String} evt Name of the event to create. * @return {Object} Current instance of EventEmitter for chaining. */ proto.defineEvent = function defineEvent(evt) { this.getListeners(evt); return this; }; /** * Uses defineEvent to define multiple events. * * @param {String[]} evts An array of event names to define. * @return {Object} Current instance of EventEmitter for chaining. */ proto.defineEvents = function defineEvents(evts) { for (var i = 0; i < evts.length; i += 1) { this.defineEvent(evts[i]); } return this; }; /** * Removes a listener function from the specified event. * When passed a regular expression as the event name, it will remove the listener from all events that match it. * * @param {String|RegExp} evt Name of the event to remove the listener from. * @param {Function} listener Method to remove from the event. * @return {Object} Current instance of EventEmitter for chaining. */ proto.removeListener = function removeListener(evt, listener) { var listeners = this.getListenersAsObject(evt); var index; var key; for (key in listeners) { if (listeners.hasOwnProperty(key)) { index = indexOfListener(listeners[key], listener); if (index !== -1) { listeners[key].splice(index, 1); } } } return this; }; /** * Alias of removeListener */ proto.off = alias('removeListener'); /** * Adds listeners in bulk using the manipulateListeners method. * If you pass an object as the second argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added. * You can also pass it a regular expression to add the array of listeners to all events that match it. * Yeah, this function does quite a bit. That's probably a bad thing. * * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once. * @param {Function[]} [listeners] An optional array of listener functions to add. * @return {Object} Current instance of EventEmitter for chaining. */ proto.addListeners = function addListeners(evt, listeners) { // Pass through to manipulateListeners return this.manipulateListeners(false, evt, listeners); }; /** * Removes listeners in bulk using the manipulateListeners method. * If you pass an object as the second argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. * You can also pass it an event name and an array of listeners to be removed. * You can also pass it a regular expression to remove the listeners from all events that match it. * * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once. * @param {Function[]} [listeners] An optional array of listener functions to remove. * @return {Object} Current instance of EventEmitter for chaining. */ proto.removeListeners = function removeListeners(evt, listeners) { // Pass through to manipulateListeners return this.manipulateListeners(true, evt, listeners); }; /** * Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level. * The first argument will determine if the listeners are removed (true) or added (false). * If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. * You can also pass it an event name and an array of listeners to be added/removed. * You can also pass it a regular expression to manipulate the listeners of all events that match it. * * @param {Boolean} remove True if you want to remove listeners, false if you want to add. * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once. * @param {Function[]} [listeners] An optional array of listener functions to add/remove. * @return {Object} Current instance of EventEmitter for chaining. */ proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) { var i; var value; var single = remove ? this.removeListener : this.addListener; var multiple = remove ? this.removeListeners : this.addListeners; // If evt is an object then pass each of its properties to this method if (typeof evt === 'object' && !(evt instanceof RegExp)) { for (i in evt) { if (evt.hasOwnProperty(i) && (value = evt[i])) { // Pass the single listener straight through to the singular method if (typeof value === 'function') { single.call(this, i, value); } else { // Otherwise pass back to the multiple function multiple.call(this, i, value); } } } } else { // So evt must be a string // And listeners must be an array of listeners // Loop over it and pass each one to the multiple method i = listeners.length; while (i--) { single.call(this, evt, listeners[i]); } } return this; }; /** * Removes all listeners from a specified event. * If you do not specify an event then all listeners will be removed. * That means every event will be emptied. * You can also pass a regex to remove all events that match it. * * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed. * @return {Object} Current instance of EventEmitter for chaining. */ proto.removeEvent = function removeEvent(evt) { var type = typeof evt; var events = this._getEvents(); var key; // Remove different things depending on the state of evt if (type === 'string') { // Remove all listeners for the specified event delete events[evt]; } else if (evt instanceof RegExp) { // Remove all events matching the regex. for (key in events) { if (events.hasOwnProperty(key) && evt.test(key)) { delete events[key]; } } } else { // Remove all listeners in all events delete this._events; } return this; }; /** * Alias of removeEvent. * * Added to mirror the node API. */ proto.removeAllListeners = alias('removeEvent'); /** * Emits an event of your choice. * When emitted, every listener attached to that event will be executed. * If you pass the optional argument array then those arguments will be passed to every listener upon execution. * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately. * So they will not arrive within the array on the other side, they will be separate. * You can also pass a regular expression to emit to all events that match it. * * @param {String|RegExp} evt Name of the event to emit and execute listeners for. * @param {Array} [args] Optional array of arguments to be passed to each listener. * @return {Object} Current instance of EventEmitter for chaining. */ proto.emitEvent = function emitEvent(evt, args) { var listeners = this.getListenersAsObject(evt); var listener; var i; var key; var response; for (key in listeners) { if (listeners.hasOwnProperty(key)) { i = listeners[key].length; while (i--) { // If the listener returns true then it shall be removed from the event // The function is executed either with a basic call or an apply if there is an args array listener = listeners[key][i]; if (listener.once === true) { this.removeListener(evt, listener.listener); } response = listener.listener.apply(this, args || []); if (response === this._getOnceReturnValue()) { this.removeListener(evt, listener.listener); } } } } return this; }; /** * Alias of emitEvent */ proto.trigger = alias('emitEvent'); /** * Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on. * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it. * * @param {String|RegExp} evt Name of the event to emit and execute listeners for. * @param {...*} Optional additional arguments to be passed to each listener. * @return {Object} Current instance of EventEmitter for chaining. */ proto.emit = function emit(evt) { var args = Array.prototype.slice.call(arguments, 1); return this.emitEvent(evt, args); }; /** * Sets the current value to check against when executing listeners. If a * listeners return value matches the one set here then it will be removed * after execution. This value defaults to true. * * @param {*} value The new value to check for when executing listeners. * @return {Object} Current instance of EventEmitter for chaining. */ proto.setOnceReturnValue = function setOnceReturnValue(value) { this._onceReturnValue = value; return this; }; /** * Fetches the current value to check against when executing listeners. If * the listeners return value matches this one then it should be removed * automatically. It will return true by default. * * @return {*|Boolean} The current value to check for or the default, true. * @api private */ proto._getOnceReturnValue = function _getOnceReturnValue() { if (this.hasOwnProperty('_onceReturnValue')) { return this._onceReturnValue; } else { return true; } }; /** * Fetches the events object and creates one if required. * * @return {Object} The events storage object. * @api private */ proto._getEvents = function _getEvents() { return this._events || (this._events = {}); }; /** * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version. * * @return {Function} Non conflicting EventEmitter class. */ EventEmitter.noConflict = function noConflict() { exports.EventEmitter = originalGlobalValue; return EventEmitter; }; // Expose the class either via AMD, CommonJS or the global object if (typeof define === 'function' && define.amd) { define('eventEmitter/EventEmitter',[],function () { return EventEmitter; }); } else if (typeof module === 'object' && module.exports){ module.exports = EventEmitter; } else { exports.EventEmitter = EventEmitter; } }.call(this)); /*! * docReady v1.0.4 * Cross browser DOMContentLoaded event emitter * MIT license */ /*jshint browser: true, strict: true, undef: true, unused: true*/ /*global define: false, require: false, module: false */ ( function( window ) { var document = window.document; // collection of functions to be triggered on ready var queue = []; function docReady( fn ) { // throw out non-functions if ( typeof fn !== 'function' ) { return; } if ( docReady.isReady ) { // ready now, hit it fn(); } else { // queue function when ready queue.push( fn ); } } docReady.isReady = false; // triggered on various doc ready events function onReady( event ) { // bail if already triggered or IE8 document is not ready just yet var isIE8NotReady = event.type === 'readystatechange' && document.readyState !== 'complete'; if ( docReady.isReady || isIE8NotReady ) { return; } trigger(); } function trigger() { docReady.isReady = true; // process queue for ( var i=0, len = queue.length; i < len; i++ ) { var fn = queue[i]; fn(); } } function defineDocReady( eventie ) { // trigger ready if page is ready if ( document.readyState === 'complete' ) { trigger(); } else { // listen for events eventie.bind( document, 'DOMContentLoaded', onReady ); eventie.bind( document, 'readystatechange', onReady ); eventie.bind( window, 'load', onReady ); } return docReady; } // transport if ( typeof define === 'function' && define.amd ) { // AMD define( 'doc-ready/doc-ready',[ 'eventie/eventie' ], defineDocReady ); } else if ( typeof exports === 'object' ) { module.exports = defineDocReady( require('eventie') ); } else { // browser global window.docReady = defineDocReady( window.eventie ); } })( window ); /** * matchesSelector v1.0.3 * matchesSelector( element, '.selector' ) * MIT license */ /*jshint browser: true, strict: true, undef: true, unused: true */ /*global define: false, module: false */ ( function( ElemProto ) { var matchesMethod = ( function() { // check for the standard method name first if ( ElemProto.matches ) { return 'matches'; } // check un-prefixed if ( ElemProto.matchesSelector ) { return 'matchesSelector'; } // check vendor prefixes var prefixes = [ 'webkit', 'moz', 'ms', 'o' ]; for ( var i=0, len = prefixes.length; i < len; i++ ) { var prefix = prefixes[i]; var method = prefix + 'MatchesSelector'; if ( ElemProto[ method ] ) { return method; } } })(); // ----- match ----- // function match( elem, selector ) { return elem[ matchesMethod ]( selector ); } // ----- appendToFragment ----- // function checkParent( elem ) { // not needed if already has parent if ( elem.parentNode ) { return; } var fragment = document.createDocumentFragment(); fragment.appendChild( elem ); } // ----- query ----- // // fall back to using QSA // thx @jonathantneal https://gist.github.com/3062955 function query( elem, selector ) { // append to fragment if no parent checkParent( elem ); // match elem with all selected elems of parent var elems = elem.parentNode.querySelectorAll( selector ); for ( var i=0, len = elems.length; i < len; i++ ) { // return true if match if ( elems[i] === elem ) { return true; } } // otherwise return false return false; } // ----- matchChild ----- // function matchChild( elem, selector ) { checkParent( elem ); return match( elem, selector ); } // ----- matchesSelector ----- // var matchesSelector; if ( matchesMethod ) { // IE9 supports matchesSelector, but doesn't work on orphaned elems // check for that var div = document.createElement('div'); var supportsOrphans = match( div, 'div' ); matchesSelector = supportsOrphans ? match : matchChild; } else { matchesSelector = query; } // transport if ( typeof define === 'function' && define.amd ) { // AMD define( 'matches-selector/matches-selector',[],function() { return matchesSelector; }); } else if ( typeof exports === 'object' ) { module.exports = matchesSelector; } else { // browser global window.matchesSelector = matchesSelector; } })( Element.prototype ); /** * Fizzy UI utils v1.0.1 * MIT license */ /*jshint browser: true, undef: true, unused: true, strict: true */ ( function( window, factory ) { /*global define: false, module: false, require: false */ // universal module definition if ( typeof define == 'function' && define.amd ) { // AMD define( 'fizzy-ui-utils/utils',[ 'doc-ready/doc-ready', 'matches-selector/matches-selector' ], function( docReady, matchesSelector ) { return factory( window, docReady, matchesSelector ); }); } else if ( typeof exports == 'object' ) { // CommonJS module.exports = factory( window, require('doc-ready'), require('desandro-matches-selector') ); } else { // browser global window.fizzyUIUtils = factory( window, window.docReady, window.matchesSelector ); } }( window, function factory( window, docReady, matchesSelector ) { var utils = {}; // ----- extend ----- // // extends objects utils.extend = function( a, b ) { for ( var prop in b ) { a[ prop ] = b[ prop ]; } return a; }; // ----- modulo ----- // utils.modulo = function( num, div ) { return ( ( num % div ) + div ) % div; }; // ----- isArray ----- // var objToString = Object.prototype.toString; utils.isArray = function( obj ) { return objToString.call( obj ) == '[object Array]'; }; // ----- makeArray ----- // // turn element or nodeList into an array utils.makeArray = function( obj ) { var ary = []; if ( utils.isArray( obj ) ) { // use object if already an array ary = obj; } else if ( obj && typeof obj.length == 'number' ) { // convert nodeList to array for ( var i=0, len = obj.length; i < len; i++ ) { ary.push( obj[i] ); } } else { // array of single index ary.push( obj ); } return ary; }; // ----- indexOf ----- // // index of helper cause IE8 utils.indexOf = Array.prototype.indexOf ? function( ary, obj ) { return ary.indexOf( obj ); } : function( ary, obj ) { for ( var i=0, len = ary.length; i < len; i++ ) { if ( ary[i] === obj ) { return i; } } return -1; }; // ----- removeFrom ----- // utils.removeFrom = function( ary, obj ) { var index = utils.indexOf( ary, obj ); if ( index != -1 ) { ary.splice( index, 1 ); } }; // ----- isElement ----- // // http://stackoverflow.com/a/384380/182183 utils.isElement = ( typeof HTMLElement == 'function' || typeof HTMLElement == 'object' ) ? function isElementDOM2( obj ) { return obj instanceof HTMLElement; } : function isElementQuirky( obj ) { return obj && typeof obj == 'object' && obj.nodeType == 1 && typeof obj.nodeName == 'string'; }; // ----- setText ----- // utils.setText = ( function() { var setTextProperty; function setText( elem, text ) { // only check setTextProperty once setTextProperty = setTextProperty || ( document.documentElement.textContent !== undefined ? 'textContent' : 'innerText' ); elem[ setTextProperty ] = text; } return setText; })(); // ----- getParent ----- // utils.getParent = function( elem, selector ) { while ( elem != document.body ) { elem = elem.parentNode; if ( matchesSelector( elem, selector ) ) { return elem; } } }; // ----- getQueryElement ----- // // use element as selector string utils.getQueryElement = function( elem ) { if ( typeof elem == 'string' ) { return document.querySelector( elem ); } return elem; }; // ----- handleEvent ----- // // enable .ontype to trigger from .addEventListener( elem, 'type' ) utils.handleEvent = function( event ) { var method = 'on' + event.type; if ( this[ method ] ) { this[ method ]( event ); } }; // ----- filterFindElements ----- // utils.filterFindElements = function( elems, selector ) { // make array of elems elems = utils.makeArray( elems ); var ffElems = []; for ( var i=0, len = elems.length; i < len; i++ ) { var elem = elems[i]; // check that elem is an actual element if ( !utils.isElement( elem ) ) { continue; } // filter & find items if we have a selector if ( selector ) { // filter siblings if ( matchesSelector( elem, selector ) ) { ffElems.push( elem ); } // find children var childElems = elem.querySelectorAll( selector ); // concat childElems to filterFound array for ( var j=0, jLen = childElems.length; j < jLen; j++ ) { ffElems.push( childElems[j] ); } } else { ffElems.push( elem ); } } return ffElems; }; // ----- debounceMethod ----- // utils.debounceMethod = function( _class, methodName, threshold ) { // original method var method = _class.prototype[ methodName ]; var timeoutName = methodName + 'Timeout'; _class.prototype[ methodName ] = function() { var timeout = this[ timeoutName ]; if ( timeout ) { clearTimeout( timeout ); } var args = arguments; var _this = this; this[ timeoutName ] = setTimeout( function() { method.apply( _this, args ); delete _this[ timeoutName ]; }, threshold || 100 ); }; }; // ----- htmlInit ----- // // http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/ utils.toDashed = function( str ) { return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) { return $1 + '-' + $2; }).toLowerCase(); }; var console = window.console; /** * allow user to initialize classes via .js-namespace class * htmlInit( Widget, 'widgetName' ) * options are parsed from data-namespace-option attribute */ utils.htmlInit = function( WidgetClass, namespace ) { docReady( function() { var dashedNamespace = utils.toDashed( namespace ); var elems = document.querySelectorAll( '.js-' + dashedNamespace ); var dataAttr = 'data-' + dashedNamespace + '-options'; for ( var i=0, len = elems.length; i < len; i++ ) { var elem = elems[i]; var attr = elem.getAttribute( dataAttr ); var options; try { options = attr && JSON.parse( attr ); } catch ( error ) { // log error, do not initialize if ( console ) { console.error( 'Error parsing ' + dataAttr + ' on ' + elem.nodeName.toLowerCase() + ( elem.id ? '#' + elem.id : '' ) + ': ' + error ); } continue; } // initialize var instance = new WidgetClass( elem, options ); // make available via $().data('layoutname') var jQuery = window.jQuery; if ( jQuery ) { jQuery.data( elem, namespace, instance ); } } }); }; // ----- ----- // return utils; })); /** * Outlayer Item */ ( function( window, factory ) { // universal module definition if ( typeof define === 'function' && define.amd ) { // AMD define( 'outlayer/item',[ 'eventEmitter/EventEmitter', 'get-size/get-size', 'get-style-property/get-style-property', 'fizzy-ui-utils/utils' ], function( EventEmitter, getSize, getStyleProperty, utils ) { return factory( window, EventEmitter, getSize, getStyleProperty, utils ); } ); } else if (typeof exports === 'object') { // CommonJS module.exports = factory( window, require('wolfy87-eventemitter'), require('get-size'), require('desandro-get-style-property'), require('fizzy-ui-utils') ); } else { // browser global window.Outlayer = {}; window.Outlayer.Item = factory( window, window.EventEmitter, window.getSize, window.getStyleProperty, window.fizzyUIUtils ); } }( window, function factory( window, EventEmitter, getSize, getStyleProperty, utils ) { // ----- helpers ----- // var getComputedStyle = window.getComputedStyle; var getStyle = getComputedStyle ? function( elem ) { return getComputedStyle( elem, null ); } : function( elem ) { return elem.currentStyle; }; function isEmptyObj( obj ) { for ( var prop in obj ) { return false; } prop = null; return true; } // -------------------------- CSS3 support -------------------------- // var transitionProperty = getStyleProperty('transition'); var transformProperty = getStyleProperty('transform'); var supportsCSS3 = transitionProperty && transformProperty; var is3d = !!getStyleProperty('perspective'); var transitionEndEvent = { WebkitTransition: 'webkitTransitionEnd', MozTransition: 'transitionend', OTransition: 'otransitionend', transition: 'transitionend' }[ transitionProperty ]; // properties that could have vendor prefix var prefixableProperties = [ 'transform', 'transition', 'transitionDuration', 'transitionProperty' ]; // cache all vendor properties var vendorProperties = ( function() { var cache = {}; for ( var i=0, len = prefixableProperties.length; i < len; i++ ) { var prop = prefixableProperties[i]; var supportedProp = getStyleProperty( prop ); if ( supportedProp && supportedProp !== prop ) { cache[ prop ] = supportedProp; } } return cache; })(); // -------------------------- Item -------------------------- // function Item( element, layout ) { if ( !element ) { return; } this.element = element; // parent layout class, i.e. Masonry, Isotope, or Packery this.layout = layout; this.position = { x: 0, y: 0 }; this._create(); } // inherit EventEmitter utils.extend( Item.prototype, EventEmitter.prototype ); Item.prototype._create = function() { // transition objects this._transn = { ingProperties: {}, clean: {}, onEnd: {} }; this.css({ position: 'absolute' }); }; // trigger specified handler for event type Item.prototype.handleEvent = function( event ) { var method = 'on' + event.type; if ( this[ method ] ) { this[ method ]( event ); } }; Item.prototype.getSize = function() { this.size = getSize( this.element ); }; /** * apply CSS styles to element * @param {Object} style */ Item.prototype.css = function( style ) { var elemStyle = this.element.style; for ( var prop in style ) { // use vendor property if available var supportedProp = vendorProperties[ prop ] || prop; elemStyle[ supportedProp ] = style[ prop ]; } }; // measure position, and sets it Item.prototype.getPosition = function() { var style = getStyle( this.element ); var layoutOptions = this.layout.options; var isOriginLeft = layoutOptions.isOriginLeft; var isOriginTop = layoutOptions.isOriginTop; var xValue = style[ isOriginLeft ? 'left' : 'right' ]; var yValue = style[ isOriginTop ? 'top' : 'bottom' ]; var x = parseInt( xValue, 10 ); var y = parseInt( yValue, 10 ); // convert percent to pixels var layoutSize = this.layout.size; x = xValue.indexOf('%') != -1 ? ( x / 100 ) * layoutSize.width : x; y = yValue.indexOf('%') != -1 ? ( y / 100 ) * layoutSize.height : y; // clean up 'auto' or other non-integer values x = isNaN( x ) ? 0 : x; y = isNaN( y ) ? 0 : y; // remove padding from measurement x -= isOriginLeft ? layoutSize.paddingLeft : layoutSize.paddingRight; y -= isOriginTop ? layoutSize.paddingTop : layoutSize.paddingBottom; this.position.x = x; this.position.y = y; }; // set settled position, apply padding Item.prototype.layoutPosition = function() { var layoutSize = this.layout.size; var layoutOptions = this.layout.options; var style = {}; // x var xPadding = layoutOptions.isOriginLeft ? 'paddingLeft' : 'paddingRight'; var xProperty = layoutOptions.isOriginLeft ? 'left' : 'right'; var xResetProperty = layoutOptions.isOriginLeft ? 'right' : 'left'; var x = this.position.x + layoutSize[ xPadding ]; // set in percentage or pixels style[ xProperty ] = this.getXValue( x ); // reset other property style[ xResetProperty ] = ''; // y var yPadding = layoutOptions.isOriginTop ? 'paddingTop' : 'paddingBottom'; var yProperty = layoutOptions.isOriginTop ? 'top' : 'bottom'; var yResetProperty = layoutOptions.isOriginTop ? 'bottom' : 'top'; var y = this.position.y + layoutSize[ yPadding ]; // set in percentage or pixels style[ yProperty ] = this.getYValue( y ); // reset other property style[ yResetProperty ] = ''; this.css( style ); this.emitEvent( 'layout', [ this ] ); }; Item.prototype.getXValue = function( x ) { var layoutOptions = this.layout.options; return layoutOptions.percentPosition && !layoutOptions.isHorizontal ? ( ( x / this.layout.size.width ) * 100 ) + '%' : x + 'px'; }; Item.prototype.getYValue = function( y ) { var layoutOptions = this.layout.options; return layoutOptions.percentPosition && layoutOptions.isHorizontal ? ( ( y / this.layout.size.height ) * 100 ) + '%' : y + 'px'; }; Item.prototype._transitionTo = function( x, y ) { this.getPosition(); // get current x & y from top/left var curX = this.position.x; var curY = this.position.y; var compareX = parseInt( x, 10 ); var compareY = parseInt( y, 10 ); var didNotMove = compareX === this.position.x && compareY === this.position.y; // save end position this.setPosition( x, y ); // if did not move and not transitioning, just go to layout if ( didNotMove && !this.isTransitioning ) { this.layoutPosition(); return; } var transX = x - curX; var transY = y - curY; var transitionStyle = {}; transitionStyle.transform = this.getTranslate( transX, transY ); this.transition({ to: transitionStyle, onTransitionEnd: { transform: this.layoutPosition }, isCleaning: true }); }; Item.prototype.getTranslate = function( x, y ) { // flip cooridinates if origin on right or bottom var layoutOptions = this.layout.options; x = layoutOptions.isOriginLeft ? x : -x; y = layoutOptions.isOriginTop ? y : -y; x = this.getXValue( x ); y = this.getYValue( y ); if ( is3d ) { return 'translate3d(' + x + ', ' + y + ', 0)'; } return 'translate(' + x + ', ' + y + ')'; }; // non transition + transform support Item.prototype.goTo = function( x, y ) { this.setPosition( x, y ); this.layoutPosition(); }; // use transition and transforms if supported Item.prototype.moveTo = supportsCSS3 ? Item.prototype._transitionTo : Item.prototype.goTo; Item.prototype.setPosition = function( x, y ) { this.position.x = parseInt( x, 10 ); this.position.y = parseInt( y, 10 ); }; // ----- transition ----- // /** * @param {Object} style - CSS * @param {Function} onTransitionEnd */ // non transition, just trigger callback Item.prototype._nonTransition = function( args ) { this.css( args.to ); if ( args.isCleaning ) { this._removeStyles( args.to ); } for ( var prop in args.onTransitionEnd ) { args.onTransitionEnd[ prop ].call( this ); } }; /** * proper transition * @param {Object} args - arguments * @param {Object} to - style to transition to * @param {Object} from - style to start transition from * @param {Boolean} isCleaning - removes transition styles after transition * @param {Function} onTransitionEnd - callback */ Item.prototype._transition = function( args ) { // redirect to nonTransition if no transition duration if ( !parseFloat( this.layout.options.transitionDuration ) ) { this._nonTransition( args ); return; } var _transition = this._transn; // keep track of onTransitionEnd callback by css property for ( var prop in args.onTransitionEnd ) { _transition.onEnd[ prop ] = args.onTransitionEnd[ prop ]; } // keep track of properties that are transitioning for ( prop in args.to ) { _transition.ingProperties[ prop ] = true; // keep track of properties to clean up when transition is done if ( args.isCleaning ) { _transition.clean[ prop ] = true; } } // set from styles if ( args.from ) { this.css( args.from ); // force redraw. http://blog.alexmaccaw.com/css-transitions var h = this.element.offsetHeight; // hack for JSHint to hush about unused var h = null; } // enable transition this.enableTransition( args.to ); // set styles that are transitioning this.css( args.to ); this.isTransitioning = true; }; // dash before all cap letters, including first for // WebkitTransform => -webkit-transform function toDashedAll( str ) { return str.replace( /([A-Z])/g, function( $1 ) { return '-' + $1.toLowerCase(); }); } var transitionProps = 'opacity,' + toDashedAll( vendorProperties.transform || 'transform' ); Item.prototype.enableTransition = function(/* style */) { // HACK changing transitionProperty during a transition // will cause transition to jump if ( this.isTransitioning ) { return; } // make `transition: foo, bar, baz` from style object // HACK un-comment this when enableTransition can work // while a transition is happening // var transitionValues = []; // for ( var prop in style ) { // // dash-ify camelCased properties like WebkitTransition // prop = vendorProperties[ prop ] || prop; // transitionValues.push( toDashedAll( prop ) ); // } // enable transition styles this.css({ transitionProperty: transitionProps, transitionDuration: this.layout.options.transitionDuration }); // listen for transition end event this.element.addEventListener( transitionEndEvent, this, false ); }; Item.prototype.transition = Item.prototype[ transitionProperty ? '_transition' : '_nonTransition' ]; // ----- events ----- // Item.prototype.onwebkitTransitionEnd = function( event ) { this.ontransitionend( event ); }; Item.prototype.onotransitionend = function( event ) { this.ontransitionend( event ); }; // properties that I munge to make my life easier var dashedVendorProperties = { '-webkit-transform': 'transform', '-moz-transform': 'transform', '-o-transform': 'transform' }; Item.prototype.ontransitionend = function( event ) { // disregard bubbled events from children if ( event.target !== this.element ) { return; } var _transition = this._transn; // get property name of transitioned property, convert to prefix-free var propertyName = dashedVendorProperties[ event.propertyName ] || event.propertyName; // remove property that has completed transitioning delete _transition.ingProperties[ propertyName ]; // check if any properties are still transitioning if ( isEmptyObj( _transition.ingProperties ) ) { // all properties have completed transitioning this.disableTransition(); } // clean style if ( propertyName in _transition.clean ) { // clean up style this.element.style[ event.propertyName ] = ''; delete _transition.clean[ propertyName ]; } // trigger onTransitionEnd callback if ( propertyName in _transition.onEnd ) { var onTransitionEnd = _transition.onEnd[ propertyName ]; onTransitionEnd.call( this ); delete _transition.onEnd[ propertyName ]; } this.emitEvent( 'transitionEnd', [ this ] ); }; Item.prototype.disableTransition = function() { this.removeTransitionStyles(); this.element.removeEventListener( transitionEndEvent, this, false ); this.isTransitioning = false; }; /** * removes style property from element * @param {Object} style **/ Item.prototype._removeStyles = function( style ) { // clean up transition styles var cleanStyle = {}; for ( var prop in style ) { cleanStyle[ prop ] = ''; } this.css( cleanStyle ); }; var cleanTransitionStyle = { transitionProperty: '', transitionDuration: '' }; Item.prototype.removeTransitionStyles = function() { // remove transition this.css( cleanTransitionStyle ); }; // ----- show/hide/remove ----- // // remove element from DOM Item.prototype.removeElem = function() { this.element.parentNode.removeChild( this.element ); // remove display: none this.css({ display: '' }); this.emitEvent( 'remove', [ this ] ); }; Item.prototype.remove = function() { // just remove element if no transition support or no transition if ( !transitionProperty || !parseFloat( this.layout.options.transitionDuration ) ) { this.removeElem(); return; } // start transition var _this = this; this.once( 'transitionEnd', function() { _this.removeElem(); }); this.hide(); }; Item.prototype.reveal = function() { delete this.isHidden; // remove display: none this.css({ display: '' }); var options = this.layout.options; var onTransitionEnd = {}; var transitionEndProperty = this.getHideRevealTransitionEndProperty('visibleStyle'); onTransitionEnd[ transitionEndProperty ] = this.onRevealTransitionEnd; this.transition({ from: options.hiddenStyle, to: options.visibleStyle, isCleaning: true, onTransitionEnd: onTransitionEnd }); }; Item.prototype.onRevealTransitionEnd = function() { // check if still visible // during transition, item may have been hidden if ( !this.isHidden ) { this.emitEvent('reveal'); } }; /** * get style property use for hide/reveal transition end * @param {String} styleProperty - hiddenStyle/visibleStyle * @returns {String} */ Item.prototype.getHideRevealTransitionEndProperty = function( styleProperty ) { var optionStyle = this.layout.options[ styleProperty ]; // use opacity if ( optionStyle.opacity ) { return 'opacity'; } // get first property for ( var prop in optionStyle ) { return prop; } }; Item.prototype.hide = function() { // set flag this.isHidden = true; // remove display: none this.css({ display: '' }); var options = this.layout.options; var onTransitionEnd = {}; var transitionEndProperty = this.getHideRevealTransitionEndProperty('hiddenStyle'); onTransitionEnd[ transitionEndProperty ] = this.onHideTransitionEnd; this.transition({ from: options.visibleStyle, to: options.hiddenStyle, // keep hidden stuff hidden isCleaning: true, onTransitionEnd: onTransitionEnd }); }; Item.prototype.onHideTransitionEnd = function() { // check if still hidden // during transition, item may have been un-hidden if ( this.isHidden ) { this.css({ display: 'none' }); this.emitEvent('hide'); } }; Item.prototype.destroy = function() { this.css({ position: '', left: '', right: '', top: '', bottom: '', transition: '', transform: '' }); }; return Item; })); /*! * Outlayer v1.4.1 * the brains and guts of a layout library * MIT license */ ( function( window, factory ) { // universal module definition if ( typeof define == 'function' && define.amd ) { // AMD define( 'outlayer/outlayer',[ 'eventie/eventie', 'eventEmitter/EventEmitter', 'get-size/get-size', 'fizzy-ui-utils/utils', './item' ], function( eventie, EventEmitter, getSize, utils, Item ) { return factory( window, eventie, EventEmitter, getSize, utils, Item); } ); } else if ( typeof exports == 'object' ) { // CommonJS module.exports = factory( window, require('eventie'), require('wolfy87-eventemitter'), require('get-size'), require('fizzy-ui-utils'), require('./item') ); } else { // browser global window.Outlayer = factory( window, window.eventie, window.EventEmitter, window.getSize, window.fizzyUIUtils, window.Outlayer.Item ); } }( window, function factory( window, eventie, EventEmitter, getSize, utils, Item ) { // ----- vars ----- // var console = window.console; var jQuery = window.jQuery; var noop = function() {}; // -------------------------- Outlayer -------------------------- // // globally unique identifiers var GUID = 0; // internal store of all Outlayer intances var instances = {}; /** * @param {Element, String} element * @param {Object} options * @constructor */ function Outlayer( element, options ) { var queryElement = utils.getQueryElement( element ); if ( !queryElement ) { if ( console ) { console.error( 'Bad element for ' + this.constructor.namespace + ': ' + ( queryElement || element ) ); } return; } this.element = queryElement; // add jQuery if ( jQuery ) { this.$element = jQuery( this.element ); } // options this.options = utils.extend( {}, this.constructor.defaults ); this.option( options ); // add id for Outlayer.getFromElement var id = ++GUID; this.element.outlayerGUID = id; // expando instances[ id ] = this; // associate via id // kick it off this._create(); if ( this.options.isInitLayout ) { this.layout(); } } // settings are for internal use only Outlayer.namespace = 'outlayer'; Outlayer.Item = Item; // default options Outlayer.defaults = { containerStyle: { position: 'relative' }, isInitLayout: true, isOriginLeft: true, isOriginTop: true, isResizeBound: true, isResizingContainer: true, // item options transitionDuration: '0.4s', hiddenStyle: { opacity: 0, transform: 'scale(0.001)' }, visibleStyle: { opacity: 1, transform: 'scale(1)' } }; // inherit EventEmitter utils.extend( Outlayer.prototype, EventEmitter.prototype ); /** * set options * @param {Object} opts */ Outlayer.prototype.option = function( opts ) { utils.extend( this.options, opts ); }; Outlayer.prototype._create = function() { // get items from children this.reloadItems(); // elements that affect layout, but are not laid out this.stamps = []; this.stamp( this.options.stamp ); // set container style utils.extend( this.element.style, this.options.containerStyle ); // bind resize method if ( this.options.isResizeBound ) { this.bindResize(); } }; // goes through all children again and gets bricks in proper order Outlayer.prototype.reloadItems = function() { // collection of item elements this.items = this._itemize( this.element.children ); }; /** * turn elements into Outlayer.Items to be used in layout * @param {Array or NodeList or HTMLElement} elems * @returns {Array} items - collection of new Outlayer Items */ Outlayer.prototype._itemize = function( elems ) { var itemElems = this._filterFindItemElements( elems ); var Item = this.constructor.Item; // create new Outlayer Items for collection var items = []; for ( var i=0, len = itemElems.length; i < len; i++ ) { var elem = itemElems[i]; var item = new Item( elem, this ); items.push( item ); } return items; }; /** * get item elements to be used in layout * @param {Array or NodeList or HTMLElement} elems * @returns {Array} items - item elements */ Outlayer.prototype._filterFindItemElements = function( elems ) { return utils.filterFindElements( elems, this.options.itemSelector ); }; /** * getter method for getting item elements * @returns {Array} elems - collection of item elements */ Outlayer.prototype.getItemElements = function() { var elems = []; for ( var i=0, len = this.items.length; i < len; i++ ) { elems.push( this.items[i].element ); } return elems; }; // ----- init & layout ----- // /** * lays out all items */ Outlayer.prototype.layout = function() { this._resetLayout(); this._manageStamps(); // don't animate first layout var isInstant = this.options.isLayoutInstant !== undefined ? this.options.isLayoutInstant : !this._isLayoutInited; this.layoutItems( this.items, isInstant ); // flag for initalized this._isLayoutInited = true; }; // _init is alias for layout Outlayer.prototype._init = Outlayer.prototype.layout; /** * logic before any new layout */ Outlayer.prototype._resetLayout = function() { this.getSize(); }; Outlayer.prototype.getSize = function() { this.size = getSize( this.element ); }; /** * get measurement from option, for columnWidth, rowHeight, gutter * if option is String -> get element from selector string, & get size of element * if option is Element -> get size of element * else use option as a number * * @param {String} measurement * @param {String} size - width or height * @private */ Outlayer.prototype._getMeasurement = function( measurement, size ) { var option = this.options[ measurement ]; var elem; if ( !option ) { // default to 0 this[ measurement ] = 0; } else { // use option as an element if ( typeof option === 'string' ) { elem = this.element.querySelector( option ); } else if ( utils.isElement( option ) ) { elem = option; } // use size of element, if element this[ measurement ] = elem ? getSize( elem )[ size ] : option; } }; /** * layout a collection of item elements * @api public */ Outlayer.prototype.layoutItems = function( items, isInstant ) { items = this._getItemsForLayout( items ); this._layoutItems( items, isInstant ); this._postLayout(); }; /** * get the items to be laid out * you may want to skip over some items * @param {Array} items * @returns {Array} items */ Outlayer.prototype._getItemsForLayout = function( items ) { var layoutItems = []; for ( var i=0, len = items.length; i < len; i++ ) { var item = items[i]; if ( !item.isIgnored ) { layoutItems.push( item ); } } return layoutItems; }; /** * layout items * @param {Array} items * @param {Boolean} isInstant */ Outlayer.prototype._layoutItems = function( items, isInstant ) { this._emitCompleteOnItems( 'layout', items ); if ( !items || !items.length ) { // no items, emit event with empty array return; } var queue = []; for ( var i=0, len = items.length; i < len; i++ ) { var item = items[i]; // get x/y object from method var position = this._getItemLayoutPosition( item ); // enqueue position.item = item; position.isInstant = isInstant || item.isLayoutInstant; queue.push( position ); } this._processLayoutQueue( queue ); }; /** * get item layout position * @param {Outlayer.Item} item * @returns {Object} x and y position */ Outlayer.prototype._getItemLayoutPosition = function( /* item */ ) { return { x: 0, y: 0 }; }; /** * iterate over array and position each item * Reason being - separating this logic prevents 'layout invalidation' * thx @paul_irish * @param {Array} queue */ Outlayer.prototype._processLayoutQueue = function( queue ) { for ( var i=0, len = queue.length; i < len; i++ ) { var obj = queue[i]; this._positionItem( obj.item, obj.x, obj.y, obj.isInstant ); } }; /** * Sets position of item in DOM * @param {Outlayer.Item} item * @param {Number} x - horizontal position * @param {Number} y - vertical position * @param {Boolean} isInstant - disables transitions */ Outlayer.prototype._positionItem = function( item, x, y, isInstant ) { if ( isInstant ) { // if not transition, just set CSS item.goTo( x, y ); } else { item.moveTo( x, y ); } }; /** * Any logic you want to do after each layout, * i.e. size the container */ Outlayer.prototype._postLayout = function() { this.resizeContainer(); }; Outlayer.prototype.resizeContainer = function() { if ( !this.options.isResizingContainer ) { return; } var size = this._getContainerSize(); if ( size ) { this._setContainerMeasure( size.width, true ); this._setContainerMeasure( size.height, false ); } }; /** * Sets width or height of container if returned * @returns {Object} size * @param {Number} width * @param {Number} height */ Outlayer.prototype._getContainerSize = noop; /** * @param {Number} measure - size of width or height * @param {Boolean} isWidth */ Outlayer.prototype._setContainerMeasure = function( measure, isWidth ) { if ( measure === undefined ) { return; } var elemSize = this.size; // add padding and border width if border box if ( elemSize.isBorderBox ) { measure += isWidth ? elemSize.paddingLeft + elemSize.paddingRight + elemSize.borderLeftWidth + elemSize.borderRightWidth : elemSize.paddingBottom + elemSize.paddingTop + elemSize.borderTopWidth + elemSize.borderBottomWidth; } measure = Math.max( measure, 0 ); this.element.style[ isWidth ? 'width' : 'height' ] = measure + 'px'; }; /** * emit eventComplete on a collection of items events * @param {String} eventName * @param {Array} items - Outlayer.Items */ Outlayer.prototype._emitCompleteOnItems = function( eventName, items ) { var _this = this; function onComplete() { _this.dispatchEvent( eventName + 'Complete', null, [ items ] ); } var count = items.length; if ( !items || !count ) { onComplete(); return; } var doneCount = 0; function tick() { doneCount++; if ( doneCount === count ) { onComplete(); } } // bind callback for ( var i=0, len = items.length; i < len; i++ ) { var item = items[i]; item.once( eventName, tick ); } }; /** * emits events via eventEmitter and jQuery events * @param {String} type - name of event * @param {Event} event - original event * @param {Array} args - extra arguments */ Outlayer.prototype.dispatchEvent = function( type, event, args ) { // add original event to arguments var emitArgs = event ? [ event ].concat( args ) : args; this.emitEvent( type, emitArgs ); if ( jQuery ) { // set this.$element this.$element = this.$element || jQuery( this.element ); if ( event ) { // create jQuery event var $event = jQuery.Event( event ); $event.type = type; this.$element.trigger( $event, args ); } else { // just trigger with type if no event available this.$element.trigger( type, args ); } } }; // -------------------------- ignore & stamps -------------------------- // /** * keep item in collection, but do not lay it out * ignored items do not get skipped in layout * @param {Element} elem */ Outlayer.prototype.ignore = function( elem ) { var item = this.getItem( elem ); if ( item ) { item.isIgnored = true; } }; /** * return item to layout collection * @param {Element} elem */ Outlayer.prototype.unignore = function( elem ) { var item = this.getItem( elem ); if ( item ) { delete item.isIgnored; } }; /** * adds elements to stamps * @param {NodeList, Array, Element, or String} elems */ Outlayer.prototype.stamp = function( elems ) { elems = this._find( elems ); if ( !elems ) { return; } this.stamps = this.stamps.concat( elems ); // ignore for ( var i=0, len = elems.length; i < len; i++ ) { var elem = elems[i]; this.ignore( elem ); } }; /** * removes elements to stamps * @param {NodeList, Array, or Element} elems */ Outlayer.prototype.unstamp = function( elems ) { elems = this._find( elems ); if ( !elems ){ return; } for ( var i=0, len = elems.length; i < len; i++ ) { var elem = elems[i]; // filter out removed stamp elements utils.removeFrom( this.stamps, elem ); this.unignore( elem ); } }; /** * finds child elements * @param {NodeList, Array, Element, or String} elems * @returns {Array} elems */ Outlayer.prototype._find = function( elems ) { if ( !elems ) { return; } // if string, use argument as selector string if ( typeof elems === 'string' ) { elems = this.element.querySelectorAll( elems ); } elems = utils.makeArray( elems ); return elems; }; Outlayer.prototype._manageStamps = function() { if ( !this.stamps || !this.stamps.length ) { return; } this._getBoundingRect(); for ( var i=0, len = this.stamps.length; i < len; i++ ) { var stamp = this.stamps[i]; this._manageStamp( stamp ); } }; // update boundingLeft / Top Outlayer.prototype._getBoundingRect = function() { // get bounding rect for container element var boundingRect = this.element.getBoundingClientRect(); var size = this.size; this._boundingRect = { left: boundingRect.left + size.paddingLeft + size.borderLeftWidth, top: boundingRect.top + size.paddingTop + size.borderTopWidth, right: boundingRect.right - ( size.paddingRight + size.borderRightWidth ), bottom: boundingRect.bottom - ( size.paddingBottom + size.borderBottomWidth ) }; }; /** * @param {Element} stamp **/ Outlayer.prototype._manageStamp = noop; /** * get x/y position of element relative to container element * @param {Element} elem * @returns {Object} offset - has left, top, right, bottom */ Outlayer.prototype._getElementOffset = function( elem ) { var boundingRect = elem.getBoundingClientRect(); var thisRect = this._boundingRect; var size = getSize( elem ); var offset = { left: boundingRect.left - thisRect.left - size.marginLeft, top: boundingRect.top - thisRect.top - size.marginTop, right: thisRect.right - boundingRect.right - size.marginRight, bottom: thisRect.bottom - boundingRect.bottom - size.marginBottom }; return offset; }; // -------------------------- resize -------------------------- // // enable event handlers for listeners // i.e. resize -> onresize Outlayer.prototype.handleEvent = function( event ) { var method = 'on' + event.type; if ( this[ method ] ) { this[ method ]( event ); } }; /** * Bind layout to window resizing */ Outlayer.prototype.bindResize = function() { // bind just one listener if ( this.isResizeBound ) { return; } eventie.bind( window, 'resize', this ); this.isResizeBound = true; }; /** * Unbind layout to window resizing */ Outlayer.prototype.unbindResize = function() { if ( this.isResizeBound ) { eventie.unbind( window, 'resize', this ); } this.isResizeBound = false; }; // original debounce by John Hann // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/ // this fires every resize Outlayer.prototype.onresize = function() { if ( this.resizeTimeout ) { clearTimeout( this.resizeTimeout ); } var _this = this; function delayed() { _this.resize(); delete _this.resizeTimeout; } this.resizeTimeout = setTimeout( delayed, 100 ); }; // debounced, layout on resize Outlayer.prototype.resize = function() { // don't trigger if size did not change // or if resize was unbound. See #9 if ( !this.isResizeBound || !this.needsResizeLayout() ) { return; } this.layout(); }; /** * check if layout is needed post layout * @returns Boolean */ Outlayer.prototype.needsResizeLayout = function() { var size = getSize( this.element ); // check that this.size and size are there // IE8 triggers resize on body size change, so they might not be var hasSizes = this.size && size; return hasSizes && size.innerWidth !== this.size.innerWidth; }; // -------------------------- methods -------------------------- // /** * add items to Outlayer instance * @param {Array or NodeList or Element} elems * @returns {Array} items - Outlayer.Items **/ Outlayer.prototype.addItems = function( elems ) { var items = this._itemize( elems ); // add items to collection if ( items.length ) { this.items = this.items.concat( items ); } return items; }; /** * Layout newly-appended item elements * @param {Array or NodeList or Element} elems */ Outlayer.prototype.appended = function( elems ) { var items = this.addItems( elems ); if ( !items.length ) { return; } // layout and reveal just the new items this.layoutItems( items, true ); this.reveal( items ); }; /** * Layout prepended elements * @param {Array or NodeList or Element} elems */ Outlayer.prototype.prepended = function( elems ) { var items = this._itemize( elems ); if ( !items.length ) { return; } // add items to beginning of collection var previousItems = this.items.slice(0); this.items = items.concat( previousItems ); // start new layout this._resetLayout(); this._manageStamps(); // layout new stuff without transition this.layoutItems( items, true ); this.reveal( items ); // layout previous items this.layoutItems( previousItems ); }; /** * reveal a collection of items * @param {Array of Outlayer.Items} items */ Outlayer.prototype.reveal = function( items ) { this._emitCompleteOnItems( 'reveal', items ); var len = items && items.length; for ( var i=0; len && i < len; i++ ) { var item = items[i]; item.reveal(); } }; /** * hide a collection of items * @param {Array of Outlayer.Items} items */ Outlayer.prototype.hide = function( items ) { this._emitCompleteOnItems( 'hide', items ); var len = items && items.length; for ( var i=0; len && i < len; i++ ) { var item = items[i]; item.hide(); } }; /** * reveal item elements * @param {Array}, {Element}, {NodeList} items */ Outlayer.prototype.revealItemElements = function( elems ) { var items = this.getItems( elems ); this.reveal( items ); }; /** * hide item elements * @param {Array}, {Element}, {NodeList} items */ Outlayer.prototype.hideItemElements = function( elems ) { var items = this.getItems( elems ); this.hide( items ); }; /** * get Outlayer.Item, given an Element * @param {Element} elem * @param {Function} callback * @returns {Outlayer.Item} item */ Outlayer.prototype.getItem = function( elem ) { // loop through items to get the one that matches for ( var i=0, len = this.items.length; i < len; i++ ) { var item = this.items[i]; if ( item.element === elem ) { // return item return item; } } }; /** * get collection of Outlayer.Items, given Elements * @param {Array} elems * @returns {Array} items - Outlayer.Items */ Outlayer.prototype.getItems = function( elems ) { elems = utils.makeArray( elems ); var items = []; for ( var i=0, len = elems.length; i < len; i++ ) { var elem = elems[i]; var item = this.getItem( elem ); if ( item ) { items.push( item ); } } return items; }; /** * remove element(s) from instance and DOM * @param {Array or NodeList or Element} elems */ Outlayer.prototype.remove = function( elems ) { var removeItems = this.getItems( elems ); this._emitCompleteOnItems( 'remove', removeItems ); // bail if no items to remove if ( !removeItems || !removeItems.length ) { return; } for ( var i=0, len = removeItems.length; i < len; i++ ) { var item = removeItems[i]; item.remove(); // remove item from collection utils.removeFrom( this.items, item ); } }; // ----- destroy ----- // // remove and disable Outlayer instance Outlayer.prototype.destroy = function() { // clean up dynamic styles var style = this.element.style; style.height = ''; style.position = ''; style.width = ''; // destroy items for ( var i=0, len = this.items.length; i < len; i++ ) { var item = this.items[i]; item.destroy(); } this.unbindResize(); var id = this.element.outlayerGUID; delete instances[ id ]; // remove reference to instance by id delete this.element.outlayerGUID; // remove data for jQuery if ( jQuery ) { jQuery.removeData( this.element, this.constructor.namespace ); } }; // -------------------------- data -------------------------- // /** * get Outlayer instance from element * @param {Element} elem * @returns {Outlayer} */ Outlayer.data = function( elem ) { elem = utils.getQueryElement( elem ); var id = elem && elem.outlayerGUID; return id && instances[ id ]; }; // -------------------------- create Outlayer class -------------------------- // /** * create a layout class * @param {String} namespace */ Outlayer.create = function( namespace, options ) { // sub-class Outlayer function Layout() { Outlayer.apply( this, arguments ); } // inherit Outlayer prototype, use Object.create if there if ( Object.create ) { Layout.prototype = Object.create( Outlayer.prototype ); } else { utils.extend( Layout.prototype, Outlayer.prototype ); } // set contructor, used for namespace and Item Layout.prototype.constructor = Layout; Layout.defaults = utils.extend( {}, Outlayer.defaults ); // apply new options utils.extend( Layout.defaults, options ); // keep prototype.settings for backwards compatibility (Packery v1.2.0) Layout.prototype.settings = {}; Layout.namespace = namespace; Layout.data = Outlayer.data; // sub-class Item Layout.Item = function LayoutItem() { Item.apply( this, arguments ); }; Layout.Item.prototype = new Item(); // -------------------------- declarative -------------------------- // utils.htmlInit( Layout, namespace ); // -------------------------- jQuery bridge -------------------------- // // make into jQuery plugin if ( jQuery && jQuery.bridget ) { jQuery.bridget( namespace, Layout ); } return Layout; }; // ----- fin ----- // // back in global Outlayer.Item = Item; return Outlayer; })); /** * Rect * low-level utility class for basic geometry */ ( function( window, factory ) { // universal module definition if ( typeof define == 'function' && define.amd ) { // AMD define( 'packery/js/rect',factory ); } else if ( typeof exports == 'object' ) { // CommonJS module.exports = factory(); } else { // browser global window.Packery = window.Packery || {}; window.Packery.Rect = factory(); } }( window, function factory() { // -------------------------- Packery -------------------------- // // global namespace var Packery = window.Packery = function() {}; // -------------------------- Rect -------------------------- // function Rect( props ) { // extend properties from defaults for ( var prop in Rect.defaults ) { this[ prop ] = Rect.defaults[ prop ]; } for ( prop in props ) { this[ prop ] = props[ prop ]; } } // make available Packery.Rect = Rect; Rect.defaults = { x: 0, y: 0, width: 0, height: 0 }; /** * Determines whether or not this rectangle wholly encloses another rectangle or point. * @param {Rect} rect * @returns {Boolean} **/ Rect.prototype.contains = function( rect ) { // points don't have width or height var otherWidth = rect.width || 0; var otherHeight = rect.height || 0; return this.x <= rect.x && this.y <= rect.y && this.x + this.width >= rect.x + otherWidth && this.y + this.height >= rect.y + otherHeight; }; /** * Determines whether or not the rectangle intersects with another. * @param {Rect} rect * @returns {Boolean} **/ Rect.prototype.overlaps = function( rect ) { var thisRight = this.x + this.width; var thisBottom = this.y + this.height; var rectRight = rect.x + rect.width; var rectBottom = rect.y + rect.height; // http://stackoverflow.com/a/306332 return this.x < rectRight && thisRight > rect.x && this.y < rectBottom && thisBottom > rect.y; }; /** * @param {Rect} rect - the overlapping rect * @returns {Array} freeRects - rects representing the area around the rect **/ Rect.prototype.getMaximalFreeRects = function( rect ) { // if no intersection, return false if ( !this.overlaps( rect ) ) { return false; } var freeRects = []; var freeRect; var thisRight = this.x + this.width; var thisBottom = this.y + this.height; var rectRight = rect.x + rect.width; var rectBottom = rect.y + rect.height; // top if ( this.y < rect.y ) { freeRect = new Rect({ x: this.x, y: this.y, width: this.width, height: rect.y - this.y }); freeRects.push( freeRect ); } // right if ( thisRight > rectRight ) { freeRect = new Rect({ x: rectRight, y: this.y, width: thisRight - rectRight, height: this.height }); freeRects.push( freeRect ); } // bottom if ( thisBottom > rectBottom ) { freeRect = new Rect({ x: this.x, y: rectBottom, width: this.width, height: thisBottom - rectBottom }); freeRects.push( freeRect ); } // left if ( this.x < rect.x ) { freeRect = new Rect({ x: this.x, y: this.y, width: rect.x - this.x, height: this.height }); freeRects.push( freeRect ); } return freeRects; }; Rect.prototype.canFit = function( rect ) { return this.width >= rect.width && this.height >= rect.height; }; return Rect; })); /** * Packer * bin-packing algorithm */ ( function( window, factory ) { // universal module definition if ( typeof define == 'function' && define.amd ) { // AMD define( 'packery/js/packer',[ './rect' ], factory ); } else if ( typeof exports == 'object' ) { // CommonJS module.exports = factory( require('./rect') ); } else { // browser global var Packery = window.Packery = window.Packery || {}; Packery.Packer = factory( Packery.Rect ); } }( window, function factory( Rect ) { // -------------------------- Packer -------------------------- // /** * @param {Number} width * @param {Number} height * @param {String} sortDirection * topLeft for vertical, leftTop for horizontal */ function Packer( width, height, sortDirection ) { this.width = width || 0; this.height = height || 0; this.sortDirection = sortDirection || 'downwardLeftToRight'; this.reset(); } Packer.prototype.reset = function() { this.spaces = []; this.newSpaces = []; var initialSpace = new Rect({ x: 0, y: 0, width: this.width, height: this.height }); this.spaces.push( initialSpace ); // set sorter this.sorter = sorters[ this.sortDirection ] || sorters.downwardLeftToRight; }; // change x and y of rect to fit with in Packer's available spaces Packer.prototype.pack = function( rect ) { for ( var i=0, len = this.spaces.length; i < len; i++ ) { var space = this.spaces[i]; if ( space.canFit( rect ) ) { this.placeInSpace( rect, space ); break; } } }; Packer.prototype.placeInSpace = function( rect, space ) { // place rect in space rect.x = space.x; rect.y = space.y; this.placed( rect ); }; // update spaces with placed rect Packer.prototype.placed = function( rect ) { // update spaces var revisedSpaces = []; for ( var i=0, len = this.spaces.length; i < len; i++ ) { var space = this.spaces[i]; var newSpaces = space.getMaximalFreeRects( rect ); // add either the original space or the new spaces to the revised spaces if ( newSpaces ) { revisedSpaces.push.apply( revisedSpaces, newSpaces ); } else { revisedSpaces.push( space ); } } this.spaces = revisedSpaces; this.mergeSortSpaces(); }; Packer.prototype.mergeSortSpaces = function() { // remove redundant spaces Packer.mergeRects( this.spaces ); this.spaces.sort( this.sorter ); }; // add a space back Packer.prototype.addSpace = function( rect ) { this.spaces.push( rect ); this.mergeSortSpaces(); }; // -------------------------- utility functions -------------------------- // /** * Remove redundant rectangle from array of rectangles * @param {Array} rects: an array of Rects * @returns {Array} rects: an array of Rects **/ Packer.mergeRects = function( rects ) { for ( var i=0, len = rects.length; i < len; i++ ) { var rect = rects[i]; // skip over this rect if it was already removed if ( !rect ) { continue; } // clone rects we're testing, remove this rect var compareRects = rects.slice(0); // do not compare with self compareRects.splice( i, 1 ); // compare this rect with others var removedCount = 0; for ( var j=0, jLen = compareRects.length; j < jLen; j++ ) { var compareRect = compareRects[j]; // if this rect contains another, // remove that rect from test collection var indexAdjust = i > j ? 0 : 1; if ( rect.contains( compareRect ) ) { // console.log( 'current test rects:' + testRects.length, testRects ); // console.log( i, j, indexAdjust, rect, compareRect ); rects.splice( j + indexAdjust - removedCount, 1 ); removedCount++; } } } return rects; }; // -------------------------- sorters -------------------------- // // functions for sorting rects in order var sorters = { // top down, then left to right downwardLeftToRight: function( a, b ) { return a.y - b.y || a.x - b.x; }, // left to right, then top down rightwardTopToBottom: function( a, b ) { return a.x - b.x || a.y - b.y; } }; // -------------------------- -------------------------- // return Packer; })); /** * Packery Item Element **/ ( function( window, factory ) { // universal module definition if ( typeof define == 'function' && define.amd ) { // AMD define( 'packery/js/item',[ 'get-style-property/get-style-property', 'outlayer/outlayer', './rect' ], factory ); } else if ( typeof exports == 'object' ) { // CommonJS module.exports = factory( require('desandro-get-style-property'), require('outlayer'), require('./rect') ); } else { // browser global window.Packery.Item = factory( window.getStyleProperty, window.Outlayer, window.Packery.Rect ); } }( window, function factory( getStyleProperty, Outlayer, Rect ) { // -------------------------- Item -------------------------- // var transformProperty = getStyleProperty('transform'); // sub-class Item var Item = function PackeryItem() { Outlayer.Item.apply( this, arguments ); }; Item.prototype = new Outlayer.Item(); var protoCreate = Item.prototype._create; Item.prototype._create = function() { // call default _create logic protoCreate.call( this ); this.rect = new Rect(); // rect used for placing, in drag or Packery.fit() this.placeRect = new Rect(); }; // -------------------------- drag -------------------------- // Item.prototype.dragStart = function() { this.getPosition(); this.removeTransitionStyles(); // remove transform property from transition if ( this.isTransitioning && transformProperty ) { this.element.style[ transformProperty ] = 'none'; } this.getSize(); // create place rect, used for position when dragged then dropped // or when positioning this.isPlacing = true; this.needsPositioning = false; this.positionPlaceRect( this.position.x, this.position.y ); this.isTransitioning = false; this.didDrag = false; }; /** * handle item when it is dragged * @param {Number} x - horizontal position of dragged item * @param {Number} y - vertical position of dragged item */ Item.prototype.dragMove = function( x, y ) { this.didDrag = true; var packerySize = this.layout.size; x -= packerySize.paddingLeft; y -= packerySize.paddingTop; this.positionPlaceRect( x, y ); }; Item.prototype.dragStop = function() { this.getPosition(); var isDiffX = this.position.x != this.placeRect.x; var isDiffY = this.position.y != this.placeRect.y; // set post-drag positioning flag this.needsPositioning = isDiffX || isDiffY; // reset flag this.didDrag = false; }; // -------------------------- placing -------------------------- // /** * position a rect that will occupy space in the packer * @param {Number} x * @param {Number} y * @param {Boolean} isMaxYContained */ Item.prototype.positionPlaceRect = function( x, y, isMaxYOpen ) { this.placeRect.x = this.getPlaceRectCoord( x, true ); this.placeRect.y = this.getPlaceRectCoord( y, false, isMaxYOpen ); }; /** * get x/y coordinate for place rect * @param {Number} coord - x or y * @param {Boolean} isX * @param {Boolean} isMaxOpen - does not limit value to outer bound * @returns {Number} coord - processed x or y */ Item.prototype.getPlaceRectCoord = function( coord, isX, isMaxOpen ) { var measure = isX ? 'Width' : 'Height'; var size = this.size[ 'outer' + measure ]; var segment = this.layout[ isX ? 'columnWidth' : 'rowHeight' ]; var parentSize = this.layout.size[ 'inner' + measure ]; // additional parentSize calculations for Y if ( !isX ) { parentSize = Math.max( parentSize, this.layout.maxY ); // prevent gutter from bumping up height when non-vertical grid if ( !this.layout.rowHeight ) { parentSize -= this.layout.gutter; } } var max; if ( segment ) { segment += this.layout.gutter; // allow for last column to reach the edge parentSize += isX ? this.layout.gutter : 0; // snap to closest segment coord = Math.round( coord / segment ); // contain to outer bound // contain non-growing bound, allow growing bound to grow var mathMethod; if ( this.layout.options.isHorizontal ) { mathMethod = !isX ? 'floor' : 'ceil'; } else { mathMethod = isX ? 'floor' : 'ceil'; } var maxSegments = Math[ mathMethod ]( parentSize / segment ); maxSegments -= Math.ceil( size / segment ); max = maxSegments; } else { max = parentSize - size; } coord = isMaxOpen ? coord : Math.min( coord, max ); coord *= segment || 1; return Math.max( 0, coord ); }; Item.prototype.copyPlaceRectPosition = function() { this.rect.x = this.placeRect.x; this.rect.y = this.placeRect.y; }; // ----- ----- // // remove element from DOM Item.prototype.removeElem = function() { this.element.parentNode.removeChild( this.element ); // add space back to packer this.layout.packer.addSpace( this.rect ); this.emitEvent( 'remove', [ this ] ); }; // ----- ----- // return Item; })); /*! * Packery v1.4.2 * bin-packing layout library * * Licensed GPLv3 for open source use * or Flickity Commercial License for commercial use * * http://packery.metafizzy.co * Copyright 2015 Metafizzy */ ( function( window, factory ) { // universal module definition if ( typeof define == 'function' && define.amd ) { // AMD define( [ 'classie/classie', 'get-size/get-size', 'outlayer/outlayer', 'packery/js/rect', 'packery/js/packer', 'packery/js/item' ], factory ); } else if ( typeof exports == 'object' ) { // CommonJS module.exports = factory( require('desandro-classie'), require('get-size'), require('outlayer'), require('./rect'), require('./packer'), require('./item') ); } else { // browser global window.Packery = factory( window.classie, window.getSize, window.Outlayer, window.Packery.Rect, window.Packery.Packer, window.Packery.Item ); } }( window, function factory( classie, getSize, Outlayer, Rect, Packer, Item ) { // ----- Rect ----- // // allow for pixel rounding errors IE8-IE11 & Firefox; #227 Rect.prototype.canFit = function( rect ) { return this.width >= rect.width - 1 && this.height >= rect.height - 1; }; // -------------------------- Packery -------------------------- // // create an Outlayer layout class var Packery = Outlayer.create('packery'); Packery.Item = Item; Packery.prototype._create = function() { // call super Outlayer.prototype._create.call( this ); // initial properties this.packer = new Packer(); // Left over from v1.0 this.stamp( this.options.stamped ); // create drag handlers var _this = this; this.handleDraggabilly = { dragStart: function() { _this.itemDragStart( this.element ); }, dragMove: function() { _this.itemDragMove( this.element, this.position.x, this.position.y ); }, dragEnd: function() { _this.itemDragEnd( this.element ); } }; this.handleUIDraggable = { start: function handleUIDraggableStart( event, ui ) { // HTML5 may trigger dragstart, dismiss HTML5 dragging if ( !ui ) { return; } _this.itemDragStart( event.currentTarget ); }, drag: function handleUIDraggableDrag( event, ui ) { if ( !ui ) { return; } _this.itemDragMove( event.currentTarget, ui.position.left, ui.position.top ); }, stop: function handleUIDraggableStop( event, ui ) { if ( !ui ) { return; } _this.itemDragEnd( event.currentTarget ); } }; }; // ----- init & layout ----- // /** * logic before any new layout */ Packery.prototype._resetLayout = function() { this.getSize(); this._getMeasurements(); // reset packer var packer = this.packer; // packer settings, if horizontal or vertical if ( this.options.isHorizontal ) { packer.width = Number.POSITIVE_INFINITY; packer.height = this.size.innerHeight + this.gutter; packer.sortDirection = 'rightwardTopToBottom'; } else { packer.width = this.size.innerWidth + this.gutter; packer.height = Number.POSITIVE_INFINITY; packer.sortDirection = 'downwardLeftToRight'; } packer.reset(); // layout this.maxY = 0; this.maxX = 0; }; /** * update columnWidth, rowHeight, & gutter * @private */ Packery.prototype._getMeasurements = function() { this._getMeasurement( 'columnWidth', 'width' ); this._getMeasurement( 'rowHeight', 'height' ); this._getMeasurement( 'gutter', 'width' ); }; Packery.prototype._getItemLayoutPosition = function( item ) { this._packItem( item ); return item.rect; }; /** * layout item in packer * @param {Packery.Item} item */ Packery.prototype._packItem = function( item ) { this._setRectSize( item.element, item.rect ); // pack the rect in the packer this.packer.pack( item.rect ); this._setMaxXY( item.rect ); }; /** * set max X and Y value, for size of container * @param {Packery.Rect} rect * @private */ Packery.prototype._setMaxXY = function( rect ) { this.maxX = Math.max( rect.x + rect.width, this.maxX ); this.maxY = Math.max( rect.y + rect.height, this.maxY ); }; /** * set the width and height of a rect, applying columnWidth and rowHeight * @param {Element} elem * @param {Packery.Rect} rect */ Packery.prototype._setRectSize = function( elem, rect ) { var size = getSize( elem ); var w = size.outerWidth; var h = size.outerHeight; // size for columnWidth and rowHeight, if available // only check if size is non-zero, #177 if ( w || h ) { w = this._applyGridGutter( w, this.columnWidth ); h = this._applyGridGutter( h, this.rowHeight ); } // rect must fit in packer rect.width = Math.min( w, this.packer.width ); rect.height = Math.min( h, this.packer.height ); }; /** * fits item to columnWidth/rowHeight and adds gutter * @param {Number} measurement - item width or height * @param {Number} gridSize - columnWidth or rowHeight * @returns measurement */ Packery.prototype._applyGridGutter = function( measurement, gridSize ) { // just add gutter if no gridSize if ( !gridSize ) { return measurement + this.gutter; } gridSize += this.gutter; // fit item to columnWidth/rowHeight var remainder = measurement % gridSize; var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil'; measurement = Math[ mathMethod ]( measurement / gridSize ) * gridSize; return measurement; }; Packery.prototype._getContainerSize = function() { if ( this.options.isHorizontal ) { return { width: this.maxX - this.gutter }; } else { return { height: this.maxY - this.gutter }; } }; // -------------------------- stamp -------------------------- // /** * makes space for element * @param {Element} elem */ Packery.prototype._manageStamp = function( elem ) { var item = this.getItem( elem ); var rect; if ( item && item.isPlacing ) { rect = item.placeRect; } else { var offset = this._getElementOffset( elem ); rect = new Rect({ x: this.options.isOriginLeft ? offset.left : offset.right, y: this.options.isOriginTop ? offset.top : offset.bottom }); } this._setRectSize( elem, rect ); // save its space in the packer this.packer.placed( rect ); this._setMaxXY( rect ); }; // -------------------------- methods -------------------------- // function verticalSorter( a, b ) { return a.position.y - b.position.y || a.position.x - b.position.x; } function horizontalSorter( a, b ) { return a.position.x - b.position.x || a.position.y - b.position.y; } Packery.prototype.sortItemsByPosition = function() { var sorter = this.options.isHorizontal ? horizontalSorter : verticalSorter; this.items.sort( sorter ); }; /** * Fit item element in its current position * Packery will position elements around it * useful for expanding elements * * @param {Element} elem * @param {Number} x - horizontal destination position, optional * @param {Number} y - vertical destination position, optional */ Packery.prototype.fit = function( elem, x, y ) { var item = this.getItem( elem ); if ( !item ) { return; } // prepare internal properties this._getMeasurements(); // stamp item to get it out of layout this.stamp( item.element ); // required for positionPlaceRect item.getSize(); // set placing flag item.isPlacing = true; // fall back to current position for fitting x = x === undefined ? item.rect.x: x; y = y === undefined ? item.rect.y: y; // position it best at its destination item.positionPlaceRect( x, y, true ); this._bindFitEvents( item ); item.moveTo( item.placeRect.x, item.placeRect.y ); // layout everything else this.layout(); // return back to regularly scheduled programming this.unstamp( item.element ); this.sortItemsByPosition(); // un set placing flag, back to normal item.isPlacing = false; // copy place rect position item.copyPlaceRectPosition(); }; /** * emit event when item is fit and other items are laid out * @param {Packery.Item} item * @private */ Packery.prototype._bindFitEvents = function( item ) { var _this = this; var ticks = 0; function tick() { ticks++; if ( ticks != 2 ) { return; } _this.dispatchEvent( 'fitComplete', null, [ item ] ); } // when item is laid out item.on( 'layout', function() { tick(); return true; }); // when all items are laid out this.on( 'layoutComplete', function() { tick(); return true; }); }; // -------------------------- resize -------------------------- // // debounced, layout on resize Packery.prototype.resize = function() { // don't trigger if size did not change var size = getSize( this.element ); // check that this.size and size are there // IE8 triggers resize on body size change, so they might not be var hasSizes = this.size && size; var innerSize = this.options.isHorizontal ? 'innerHeight' : 'innerWidth'; if ( hasSizes && size[ innerSize ] == this.size[ innerSize ] ) { return; } this.layout(); }; // -------------------------- drag -------------------------- // /** * handle an item drag start event * @param {Element} elem */ Packery.prototype.itemDragStart = function( elem ) { this.stamp( elem ); var item = this.getItem( elem ); if ( item ) { item.dragStart(); } }; /** * handle an item drag move event * @param {Element} elem * @param {Number} x - horizontal change in position * @param {Number} y - vertical change in position */ Packery.prototype.itemDragMove = function( elem, x, y ) { var item = this.getItem( elem ); if ( item ) { item.dragMove( x, y ); } // debounce var _this = this; // debounce triggering layout function delayed() { _this.layout(); delete _this.dragTimeout; } this.clearDragTimeout(); this.dragTimeout = setTimeout( delayed, 40 ); }; Packery.prototype.clearDragTimeout = function() { if ( this.dragTimeout ) { clearTimeout( this.dragTimeout ); } }; /** * handle an item drag end event * @param {Element} elem */ Packery.prototype.itemDragEnd = function( elem ) { var item = this.getItem( elem ); var itemDidDrag; if ( item ) { itemDidDrag = item.didDrag; item.dragStop(); } // if elem didn't move, or if it doesn't need positioning // unignore and unstamp and call it a day if ( !item || ( !itemDidDrag && !item.needsPositioning ) ) { this.unstamp( elem ); return; } // procced with dragged item classie.add( item.element, 'is-positioning-post-drag' ); // save this var, as it could get reset in dragStart var onLayoutComplete = this._getDragEndLayoutComplete( elem, item ); if ( item.needsPositioning ) { item.on( 'layout', onLayoutComplete ); item.moveTo( item.placeRect.x, item.placeRect.y ); } else if ( item ) { // item didn't need placement item.copyPlaceRectPosition(); } this.clearDragTimeout(); this.on( 'layoutComplete', onLayoutComplete ); this.layout(); }; /** * get drag end callback * @param {Element} elem * @param {Packery.Item} item * @returns {Function} onLayoutComplete */ Packery.prototype._getDragEndLayoutComplete = function( elem, item ) { var itemNeedsPositioning = item && item.needsPositioning; var completeCount = 0; var asyncCount = itemNeedsPositioning ? 2 : 1; var _this = this; return function onLayoutComplete() { completeCount++; // don't proceed if not complete if ( completeCount != asyncCount ) { return true; } // reset item if ( item ) { classie.remove( item.element, 'is-positioning-post-drag' ); item.isPlacing = false; item.copyPlaceRectPosition(); } _this.unstamp( elem ); // only sort when item moved _this.sortItemsByPosition(); // emit item drag event now that everything is done if ( itemNeedsPositioning ) { _this.dispatchEvent( 'dragItemPositioned', null, [ item ] ); } // listen once return true; }; }; /** * binds Draggabilly events * @param {Draggabilly} draggie */ Packery.prototype.bindDraggabillyEvents = function( draggie ) { draggie.on( 'dragStart', this.handleDraggabilly.dragStart ); draggie.on( 'dragMove', this.handleDraggabilly.dragMove ); draggie.on( 'dragEnd', this.handleDraggabilly.dragEnd ); }; /** * binds jQuery UI Draggable events * @param {jQuery} $elems */ Packery.prototype.bindUIDraggableEvents = function( $elems ) { $elems .on( 'dragstart', this.handleUIDraggable.start ) .on( 'drag', this.handleUIDraggable.drag ) .on( 'dragstop', this.handleUIDraggable.stop ); }; Packery.Rect = Rect; Packery.Packer = Packer; return Packery; })); /*! * jQuery hashchange event - v1.3 - 7/21/2010 * http://benalman.com/projects/jquery-hashchange-plugin/ * * Copyright (c) 2010 "Cowboy" Ben Alman * Dual licensed under the MIT and GPL licenses. * http://benalman.com/about/license/ */ // Script: jQuery hashchange event // // *Version: 1.3, Last updated: 7/21/2010* // // Project Home - http://benalman.com/projects/jquery-hashchange-plugin/ // GitHub - http://github.com/cowboy/jquery-hashchange/ // Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js // (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped) // // About: License // // Copyright (c) 2010 "Cowboy" Ben Alman, // Dual licensed under the MIT and GPL licenses. // http://benalman.com/about/license/ // // About: Examples // // These working examples, complete with fully commented code, illustrate a few // ways in which this plugin can be used. // // hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/ // document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/ // // About: Support and Testing // // Information about what version or versions of jQuery this plugin has been // tested with, what browsers it has been tested in, and where the unit tests // reside (so you can test it yourself). // // jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2 // Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5, // Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5. // Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/ // // About: Known issues // // While this jQuery hashchange event implementation is quite stable and // robust, there are a few unfortunate browser bugs surrounding expected // hashchange event-based behaviors, independent of any JavaScript // window.onhashchange abstraction. See the following examples for more // information: // // Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/ // Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/ // WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/ // Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/ // // Also note that should a browser natively support the window.onhashchange // event, but not report that it does, the fallback polling loop will be used. // // About: Release History // // 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more // "removable" for mobile-only development. Added IE6/7 document.title // support. Attempted to make Iframe as hidden as possible by using // techniques from http://www.paciellogroup.com/blog/?p=604. Added // support for the "shortcut" format $(window).hashchange( fn ) and // $(window).hashchange() like jQuery provides for built-in events. // Renamed jQuery.hashchangeDelay to and // lowered its default value to 50. Added // and properties plus document-domain.html // file to address access denied issues when setting document.domain in // IE6/7. // 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin // from a page on another domain would cause an error in Safari 4. Also, // IE6/7 Iframe is now inserted after the body (this actually works), // which prevents the page from scrolling when the event is first bound. // Event can also now be bound before DOM ready, but it won't be usable // before then in IE6/7. // 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug // where browser version is incorrectly reported as 8.0, despite // inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag. // 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special // window.onhashchange functionality into a separate plugin for users // who want just the basic event & back button support, without all the // extra awesomeness that BBQ provides. This plugin will be included as // part of jQuery BBQ, but also be available separately. (function($,window,undefined){ '$:nomunge'; // Used by YUI compressor. // Reused string. var str_hashchange = 'hashchange', // Method / object references. doc = document, fake_onhashchange, special = $.event.special, // Does the browser support window.onhashchange? Note that IE8 running in // IE7 compatibility mode reports true for 'onhashchange' in window, even // though the event isn't supported, so also test document.documentMode. doc_mode = doc.documentMode, supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 ); // Get location.hash (or what you'd expect location.hash to be) sans any // leading #. Thanks for making this necessary, Firefox! function get_fragment( url ) { url = url || location.href; return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' ); }; // Method: jQuery.fn.hashchange // // Bind a handler to the window.onhashchange event or trigger all bound // window.onhashchange event handlers. This behavior is consistent with // jQuery's built-in event handlers. // // Usage: // // > jQuery(window).hashchange( [ handler ] ); // // Arguments: // // handler - (Function) Optional handler to be bound to the hashchange // event. This is a "shortcut" for the more verbose form: // jQuery(window).bind( 'hashchange', handler ). If handler is omitted, // all bound window.onhashchange event handlers will be triggered. This // is a shortcut for the more verbose // jQuery(window).trigger( 'hashchange' ). These forms are described in // the section. // // Returns: // // (jQuery) The initial jQuery collection of elements. // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and // $(elem).hashchange() for triggering, like jQuery does for built-in events. $.fn[ str_hashchange ] = function( fn ) { return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange ); }; // Property: jQuery.fn.hashchange.delay // // The numeric interval (in milliseconds) at which the // polling loop executes. Defaults to 50. // Property: jQuery.fn.hashchange.domain // // If you're setting document.domain in your JavaScript, and you want hash // history to work in IE6/7, not only must this property be set, but you must // also set document.domain BEFORE jQuery is loaded into the page. This // property is only applicable if you are supporting IE6/7 (or IE8 operating // in "IE7 compatibility" mode). // // In addition, the property must be set to the // path of the included "document-domain.html" file, which can be renamed or // modified if necessary (note that the document.domain specified must be the // same in both your main JavaScript as well as in this file). // // Usage: // // jQuery.fn.hashchange.domain = document.domain; // Property: jQuery.fn.hashchange.src // // If, for some reason, you need to specify an Iframe src file (for example, // when setting document.domain as in ), you can // do so using this property. Note that when using this property, history // won't be recorded in IE6/7 until the Iframe src file loads. This property // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7 // compatibility" mode). // // Usage: // // jQuery.fn.hashchange.src = 'path/to/file.html'; $.fn[ str_hashchange ].delay = 50; /* $.fn[ str_hashchange ].domain = null; $.fn[ str_hashchange ].src = null; */ // Event: hashchange event // // Fired when location.hash changes. In browsers that support it, the native // HTML5 window.onhashchange event is used, otherwise a polling loop is // initialized, running every milliseconds to // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7 // compatibility" mode), a hidden Iframe is created to allow the back button // and hash-based history to work. // // Usage as described in : // // > // Bind an event handler. // > jQuery(window).hashchange( function(e) { // > var hash = location.hash; // > ... // > }); // > // > // Manually trigger the event handler. // > jQuery(window).hashchange(); // // A more verbose usage that allows for event namespacing: // // > // Bind an event handler. // > jQuery(window).bind( 'hashchange', function(e) { // > var hash = location.hash; // > ... // > }); // > // > // Manually trigger the event handler. // > jQuery(window).trigger( 'hashchange' ); // // Additional Notes: // // * The polling loop and Iframe are not created until at least one handler // is actually bound to the 'hashchange' event. // * If you need the bound handler(s) to execute immediately, in cases where // a location.hash exists on page load, via bookmark or page refresh for // example, use jQuery(window).hashchange() or the more verbose // jQuery(window).trigger( 'hashchange' ). // * The event can be bound before DOM ready, but since it won't be usable // before then in IE6/7 (due to the necessary Iframe), recommended usage is // to bind it inside a DOM ready handler. // Override existing $.event.special.hashchange methods (allowing this plugin // to be defined after jQuery BBQ in BBQ's source code). special[ str_hashchange ] = $.extend( special[ str_hashchange ], { // Called only when the first 'hashchange' event is bound to window. setup: function() { // If window.onhashchange is supported natively, there's nothing to do.. if ( supports_onhashchange ) { return false; } // Otherwise, we need to create our own. And we don't want to call this // until the user binds to the event, just in case they never do, since it // will create a polling loop and possibly even a hidden Iframe. $( fake_onhashchange.start ); }, // Called only when the last 'hashchange' event is unbound from window. teardown: function() { // If window.onhashchange is supported natively, there's nothing to do.. if ( supports_onhashchange ) { return false; } // Otherwise, we need to stop ours (if possible). $( fake_onhashchange.stop ); } }); // fake_onhashchange does all the work of triggering the window.onhashchange // event for browsers that don't natively support it, including creating a // polling loop to watch for hash changes and in IE 6/7 creating a hidden // Iframe to enable back and forward. fake_onhashchange = (function(){ var self = {}, timeout_id, // Remember the initial hash so it doesn't get triggered immediately. last_hash = get_fragment(), fn_retval = function(val){ return val; }, history_set = fn_retval, history_get = fn_retval; // Start the polling loop. self.start = function() { timeout_id || poll(); }; // Stop the polling loop. self.stop = function() { timeout_id && clearTimeout( timeout_id ); timeout_id = undefined; }; // This polling loop checks every $.fn.hashchange.delay milliseconds to see // if location.hash has changed, and triggers the 'hashchange' event on // window when necessary. function poll() { var hash = get_fragment(), history_hash = history_get( last_hash ); if ( hash !== last_hash ) { history_set( last_hash = hash, history_hash ); $(window).trigger( str_hashchange ); } else if ( history_hash !== last_hash ) { location.href = location.href.replace( /#.*/, '' ) + history_hash; } timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay ); }; // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv !supports_onhashchange && (function(){ // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8 // when running in "IE7 compatibility" mode. var iframe, iframe_src; // When the event is bound and polling starts in IE 6/7, create a hidden // Iframe for history handling. self.start = function(){ if ( !iframe ) { iframe_src = $.fn[ str_hashchange ].src; iframe_src = iframe_src && iframe_src + get_fragment(); // Create hidden Iframe. Attempt to make Iframe as hidden as possible // by using techniques from http://www.paciellogroup.com/blog/?p=604. iframe = $('