/*!

* jQuery & Zepto Lazy - v1.7.7
* http://jquery.eisbehr.de/lazy/
*
* Copyright 2012 - 2017, Daniel 'Eisbehr' Kern
*
* Dual licensed under the MIT and GPL-2.0 licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl-2.0.html
*
* $("img.lazy").lazy();
*/

;(function(window, undefined) {

"use strict";
// noinspection JSUnresolvedVariable
/**
 * library instance - here and not in construct to be shorter in minimization
 * @return void
 */
var $ = window.jQuery || window.Zepto,
/**
 * unique plugin instance id counter
 * @type {number}
 */
lazyInstanceId = 0,
/**
 * helper to register window load for jQuery 3
 * @type {boolean}
 */    
windowLoaded = false;
/**
 * make lazy available to jquery - and make it a bit more case-insensitive :)
 * @access public
 * @type {function}
 * @param {object} settings
 * @return void
 */
$.fn.Lazy = $.fn.lazy = function(settings) {
    return new LazyPlugin(this, settings);
};
/**
 * helper to add plugins to lazy prototype configuration
 * @access public
 * @type {function}
 * @param {string|Array} names
 * @param {string|Array} [elements]
 * @param {function} loader
 * @return void
 */
$.Lazy = $.lazy = function(names, elements, loader) {
    // make second parameter optional
    if( $.isFunction(elements) ) {
        loader = elements;
        elements = [];
    }
    // exit here if parameter is not a callable function
    if( !$.isFunction(loader) ) {
        return;
    }
    // make parameters an array of names to be sure
    names = $.isArray(names) ? names : [names];
    elements = $.isArray(elements) ? elements : [elements];
    var config = LazyPlugin.prototype.config,
        forced = config._f || (config._f = {});
    // add the loader plugin for every name
    for( var i = 0, l = names.length; i < l; i++ ) {
        if( config[names[i]] === undefined || $.isFunction(config[names[i]]) ) {
            config[names[i]] = loader;
        }
    }
    // add forced elements loader
    for( var c = 0, a = elements.length; c < a; c++ ) {
        forced[elements[c]] = names[0];
    }
};
/**
 * contains all logic and the whole element handling
 * is packed in a private function outside class to reduce memory usage, because it will not be created on every plugin instance
 * @access private
 * @type {function}
 * @param {LazyPlugin} instance
 * @param {object} config
 * @param {object|Array} items
 * @param {object} events
 * @param {string} namespace
 * @return void
 */
function _executeLazy(instance, config, items, events, namespace) {
    /**
     * a helper to trigger the 'onFinishedAll' callback after all other events
     * @access private
     * @type {number}
     */
    var _awaitingAfterLoad = 0,
    /**
     * visible content width
     * @access private
     * @type {number}
     */
    _actualWidth = -1,
    /**
     * visible content height
     * @access private
     * @type {number}
     */
    _actualHeight = -1,
    /**
     * determine possibly detected high pixel density
     * @access private
     * @type {boolean}
     */
    _isRetinaDisplay = false, 
    /**
     * dictionary entry for better minimization
     * @access private
     * @type {string}
     */
    _afterLoad = "afterLoad",
    /**
     * dictionary entry for better minimization
     * @access private
     * @type {string}
     */
    _load = "load",
    /**
     * dictionary entry for better minimization
     * @access private
     * @type {string}
     */
    _error = "error",
    /**
     * dictionary entry for better minimization
     * @access private
     * @type {string}
     */
    _img = "img",
    /**
     * dictionary entry for better minimization
     * @access private
     * @type {string}
     */
    _src = "src",
    /**
     * dictionary entry for better minimization
     * @access private
     * @type {string}
     */
    _srcset = "srcset",
    /**
     * dictionary entry for better minimization
     * @access private
     * @type {string}
     */
    _sizes = "sizes",
    /**
     * dictionary entry for better minimization
     * @access private
     * @type {string}
     */
    _backgroundImage = "background-image";
    /**
     * initialize plugin
     * bind loading to events or set delay time to load all items at once
     * @access private
     * @return void
     */
    function _initialize() {
        // detect actual device pixel ratio
        // noinspection JSUnresolvedVariable
        _isRetinaDisplay = window.devicePixelRatio > 1;
        // prepare all initial items
        items = _prepareItems(items);
        // if delay time is set load all items at once after delay time
        if( config.delay >= 0 ) {
            setTimeout(function() {
                _lazyLoadItems(true);
            }, config.delay);
        }
        // if no delay is set or combine usage is active bind events
        if( config.delay < 0 || config.combined ) {
            // create unique event function
            events.e = _throttle(config.throttle, function(event) {
                // reset detected window size on resize event
                if( event.type === "resize" ) {
                    _actualWidth = _actualHeight = -1;
                }
                // execute 'lazy magic'
                _lazyLoadItems(event.all);
            });
            // create function to add new items to instance
            events.a = function(additionalItems) {
                additionalItems = _prepareItems(additionalItems);
                items.push.apply(items, additionalItems);
            };
            // create function to get all instance items left
            events.g = function() {
                // filter loaded items before return in case internal filter was not running until now
                return (items = $(items).filter(function() {
                    return !$(this).data(config.loadedName);
                }));
            };
            // create function to force loading elements
            events.f = function(forcedItems) {
                for( var i = 0; i < forcedItems.length; i++ ) {
                    // only handle item if available in current instance
                    // use a compare function, because Zepto can't handle object parameter for filter
                    // var item = items.filter(forcedItems[i]);
                    /* jshint loopfunc: true */
                    var item = items.filter(function() {
                        return this === forcedItems[i];
                    });
                    if( item.length ) {
                        _lazyLoadItems(false, item);   
                    }
                }
            };
            // load initial items
            _lazyLoadItems();
            // bind lazy load functions to scroll and resize event
            // noinspection JSUnresolvedVariable
            $(config.appendScroll).on("scroll." + namespace + " resize." + namespace, events.e);
        }
    }
    /**
     * prepare items before handle them
     * @access private
     * @param {Array|object|jQuery} items
     * @return {Array|object|jQuery}
     */
    function _prepareItems(items) {
        // fetch used configurations before loops
        var defaultImage = config.defaultImage,
            placeholder = config.placeholder,
            imageBase = config.imageBase,
            srcsetAttribute = config.srcsetAttribute,
            loaderAttribute = config.loaderAttribute,
            forcedTags = config._f || {};
        // filter items and only add those who not handled yet and got needed attributes available
        items = $(items).filter(function() {
            var element = $(this),
                tag = _getElementTagName(this);
            return !element.data(config.handledName) && 
                   (element.attr(config.attribute) || element.attr(srcsetAttribute) || element.attr(loaderAttribute) || forcedTags[tag] !== undefined);
        })
        // append plugin instance to all elements
        .data("plugin_" + config.name, instance);
        for( var i = 0, l = items.length; i < l; i++ ) {
            var element = $(items[i]),
                tag = _getElementTagName(items[i]),
                elementImageBase = element.attr(config.imageBaseAttribute) || imageBase;
            // generate and update source set if an image base is set
            if( tag === _img && elementImageBase && element.attr(srcsetAttribute) ) {
                element.attr(srcsetAttribute, _getCorrectedSrcSet(element.attr(srcsetAttribute), elementImageBase));
            }
            // add loader to forced element types
            if( forcedTags[tag] !== undefined && !element.attr(loaderAttribute) ) {
                element.attr(loaderAttribute, forcedTags[tag]);
            }
            // set default image on every element without source
            if( tag === _img && defaultImage && !element.attr(_src) ) {
                element.attr(_src, defaultImage);
            }
            // set placeholder on every element without background image
            else if( tag !== _img && placeholder && (!element.css(_backgroundImage) || element.css(_backgroundImage) === "none") ) {
                element.css(_backgroundImage, "url('" + placeholder + "')");
            }
        }
        return items;
    }
    /**
     * the 'lazy magic' - check all items
     * @access private
     * @param {boolean} [allItems]
     * @param {object} [forced]
     * @return void
     */
    function _lazyLoadItems(allItems, forced) {
        // skip if no items where left
        if( !items.length ) {
            // destroy instance if option is enabled
            if( config.autoDestroy ) {
                // noinspection JSUnresolvedFunction
                instance.destroy();
            }
            return;
        }
        var elements = forced || items,
            loadTriggered = false,
            imageBase = config.imageBase || "",
            srcsetAttribute = config.srcsetAttribute,
            handledName = config.handledName;
        // loop all available items
        for( var i = 0; i < elements.length; i++ ) {
            // item is at least in loadable area
            if( allItems || forced || _isInLoadableArea(elements[i]) ) {
                var element = $(elements[i]),
                    tag = _getElementTagName(elements[i]),
                    attribute = element.attr(config.attribute),
                    elementImageBase = element.attr(config.imageBaseAttribute) || imageBase,
                    customLoader = element.attr(config.loaderAttribute);
                    // is not already handled 
                if( !element.data(handledName) &&
                    // and is visible or visibility doesn't matter
                    (!config.visibleOnly || element.is(":visible")) && (
                    // and image source or source set attribute is available
                    (attribute || element.attr(srcsetAttribute)) && (
                        // and is image tag where attribute is not equal source or source set
                        (tag === _img && (elementImageBase + attribute !== element.attr(_src) || element.attr(srcsetAttribute) !== element.attr(_srcset))) ||
                        // or is non image tag where attribute is not equal background
                        (tag !== _img && elementImageBase + attribute !== element.css(_backgroundImage))
                    ) ||
                    // or custom loader is available
                    customLoader ))
                {
                    // mark element always as handled as this point to prevent double handling
                    loadTriggered = true;
                    element.data(handledName, true);
                    // load item
                    _handleItem(element, tag, elementImageBase, customLoader);
                }
            }
        }
        // when something was loaded remove them from remaining items
        if( loadTriggered ) {
            items = $(items).filter(function() {
                return !$(this).data(handledName);
            });
        }
    }
    /**
     * load the given element the lazy way
     * @access private
     * @param {object} element
     * @param {string} tag
     * @param {string} imageBase
     * @param {function} [customLoader]
     * @return void
     */
    function _handleItem(element, tag, imageBase, customLoader) {
        // increment count of items waiting for after load
        ++_awaitingAfterLoad;
        // extended error callback for correct 'onFinishedAll' handling
        var errorCallback = function() {
            _triggerCallback("onError", element);
            _reduceAwaiting();
            // prevent further callback calls
            errorCallback = $.noop;
        };
        // trigger function before loading image
        _triggerCallback("beforeLoad", element);
        // fetch all double used data here for better code minimization
        var srcAttribute = config.attribute,
            srcsetAttribute = config.srcsetAttribute,
            sizesAttribute = config.sizesAttribute,
            retinaAttribute = config.retinaAttribute,
            removeAttribute = config.removeAttribute,
            loadedName = config.loadedName,
            elementRetina = element.attr(retinaAttribute);
        // handle custom loader
        if( customLoader ) {
            // on load callback
            var loadCallback = function() {
                // remove attribute from element
                if( removeAttribute ) {
                    element.removeAttr(config.loaderAttribute);
                }
                // mark element as loaded
                element.data(loadedName, true);
                // call after load event
                _triggerCallback(_afterLoad, element);
                // remove item from waiting queue and possibly trigger finished event
                // it's needed to be asynchronous to run after filter was in _lazyLoadItems
                setTimeout(_reduceAwaiting, 1);
                // prevent further callback calls
                loadCallback = $.noop;
            };
            // bind error event to trigger callback and reduce waiting amount
            element.off(_error).one(_error, errorCallback)
            // bind after load callback to element
            .one(_load, loadCallback);
            // trigger custom loader and handle response
            if( !_triggerCallback(customLoader, element, function(response) {
                if( response ) {
                    element.off(_load);
                    loadCallback();
                }
                else {
                    element.off(_error);
                    errorCallback();
                }
            })) element.trigger(_error);
        }
        // handle images
        else {
            // create image object
            var imageObj = $(new Image());
            // bind error event to trigger callback and reduce waiting amount
            imageObj.one(_error, errorCallback)
            // bind after load callback to image
            .one(_load, function() {
                // remove element from view
                element.hide();
                // set image back to element
                // do it as single 'attr' calls, to be sure 'src' is set after 'srcset'
                if( tag === _img ) {
                    element.attr(_sizes, imageObj.attr(_sizes))
                           .attr(_srcset, imageObj.attr(_srcset))
                           .attr(_src, imageObj.attr(_src));
                }
                else {
                    element.css(_backgroundImage, "url('" + imageObj.attr(_src) + "')");
                }
                // bring it back with some effect!
                element[config.effect](config.effectTime);
                // remove attribute from element
                if( removeAttribute ) {
                    element.removeAttr(srcAttribute + " " + srcsetAttribute + " " + retinaAttribute + " " + config.imageBaseAttribute);
                    // only remove 'sizes' attribute, if it was a custom one
                    if( sizesAttribute !== _sizes ) {
                        element.removeAttr(sizesAttribute);
                    }
                }
                // mark element as loaded
                element.data(loadedName, true);
                // call after load event
                _triggerCallback(_afterLoad, element);
                // cleanup image object
                imageObj.remove();
                // remove item from waiting queue and possibly trigger finished event
                _reduceAwaiting();
            });
            // set sources
            // do it as single 'attr' calls, to be sure 'src' is set after 'srcset'
            var imageSrc = (_isRetinaDisplay && elementRetina ? elementRetina : element.attr(srcAttribute)) || "";
            imageObj.attr(_sizes, element.attr(sizesAttribute))
                    .attr(_srcset, element.attr(srcsetAttribute))
                    .attr(_src, imageSrc ? imageBase + imageSrc : null);
            // call after load even on cached image
            imageObj.complete && imageObj.trigger(_load); // jshint ignore : line
        }
    }
    /**
     * check if the given element is inside the current viewport or threshold
     * @access private
     * @param {object} element
     * @return {boolean}
     */
    function _isInLoadableArea(element) {
        var elementBound = element.getBoundingClientRect(),
            direction    = config.scrollDirection,
            threshold    = config.threshold,
            vertical     = // check if element is in loadable area from top
                           ((_getActualHeight() + threshold) > elementBound.top) &&
                           // check if element is even in loadable are from bottom
                           (-threshold < elementBound.bottom),
            horizontal   = // check if element is in loadable area from left
                           ((_getActualWidth() + threshold) > elementBound.left) &&
                           // check if element is even in loadable area from right
                           (-threshold < elementBound.right);
        if( direction === "vertical" ) {
            return vertical;
        }
        else if( direction === "horizontal" ) {
            return horizontal;
        }
        return vertical && horizontal;
    }
    /**
     * receive the current viewed width of the browser
     * @access private
     * @return {number}
     */
    function _getActualWidth() {
        return _actualWidth >= 0 ? _actualWidth : (_actualWidth = $(window).width());
    }
    /**
     * receive the current viewed height of the browser
     * @access private
     * @return {number}
     */
    function _getActualHeight() {
        return _actualHeight >= 0 ? _actualHeight : (_actualHeight = $(window).height());
    }
    /**
     * get lowercase tag name of an element
     * @access private
     * @param {object} element
     * @returns {string}
     */
    function _getElementTagName(element) {
        return element.tagName.toLowerCase();
    }
    /**
     * prepend image base to all srcset entries
     * @access private
     * @param {string} srcset
     * @param {string} imageBase
     * @returns {string}
     */
    function _getCorrectedSrcSet(srcset, imageBase) {
        if( imageBase ) {
            // trim, remove unnecessary spaces and split entries
            var entries = srcset.split(",");
            srcset = "";
            for( var i = 0, l = entries.length; i < l; i++ ) {
                srcset += imageBase + entries[i].trim() + (i !== l - 1 ? "," : "");
            }
        }
        return srcset;
    }
    /**
     * helper function to throttle down event triggering
     * @access private
     * @param {number} delay
     * @param {function} callback
     * @return {function}
     */
    function _throttle(delay, callback) {
        var timeout,
            lastExecute = 0;
        return function(event, ignoreThrottle) {
            var elapsed = +new Date() - lastExecute;
            function run() {
                lastExecute = +new Date();
                // noinspection JSUnresolvedFunction
                callback.call(instance, event);
            }
            timeout && clearTimeout(timeout); // jshint ignore : line
            if( elapsed > delay || !config.enableThrottle || ignoreThrottle ) {
                run();
            }
            else {
                timeout = setTimeout(run, delay - elapsed);
            }
        };
    }
    /**
     * reduce count of awaiting elements to 'afterLoad' event and fire 'onFinishedAll' if reached zero
     * @access private
     * @return void
     */
    function _reduceAwaiting() {
        --_awaitingAfterLoad;
        // if no items were left trigger finished event
        if( !items.length && !_awaitingAfterLoad ) {
            _triggerCallback("onFinishedAll");
        }
    }
    /**
     * single implementation to handle callbacks, pass element and set 'this' to current instance
     * @access private
     * @param {string|function} callback
     * @param {object} [element]
     * @param {*} [args]
     * @return {boolean}
     */
    function _triggerCallback(callback, element, args) {
        if( (callback = config[callback]) ) {
            // jQuery's internal '$(arguments).slice(1)' are causing problems at least on old iPads
            // below is shorthand of 'Array.prototype.slice.call(arguments, 1)'
            callback.apply(instance, [].slice.call(arguments, 1));
            return true;
        }
        return false;
    }
    // if event driven or window is already loaded don't wait for page loading
    if( config.bind === "event" || windowLoaded ) {
        _initialize();
    }
    // otherwise load initial items and start lazy after page load
    else {
        // noinspection JSUnresolvedVariable
        $(window).on(_load + "." + namespace, _initialize);
    }  
}
/**
 * lazy plugin class constructor
 * @constructor
 * @access private
 * @param {object} elements
 * @param {object} settings
 * @return {object|LazyPlugin}
 */
function LazyPlugin(elements, settings) {
    /**
     * this lazy plugin instance
     * @access private
     * @type {object|LazyPlugin|LazyPlugin.prototype}
     */
    var _instance = this,
    /**
     * this lazy plugin instance configuration
     * @access private
     * @type {object}
     */
    _config = $.extend({}, _instance.config, settings),
    /**
     * instance generated event executed on container scroll or resize
     * packed in an object to be referenceable and short named because properties will not be minified
     * @access private
     * @type {object}
     */
    _events = {},
    /**
     * unique namespace for instance related events
     * @access private
     * @type {string}
     */
    _namespace = _config.name + "-" + (++lazyInstanceId);
    // noinspection JSUndefinedPropertyAssignment
    /**
     * wrapper to get or set an entry from plugin instance configuration
     * much smaller on minify as direct access
     * @access public
     * @type {function}
     * @param {string} entryName
     * @param {*} [value]
     * @return {LazyPlugin|*}
     */
    _instance.config = function(entryName, value) {
        if( value === undefined ) {
            return _config[entryName];
        }
        _config[entryName] = value;
        return _instance;
    };
    // noinspection JSUndefinedPropertyAssignment
    /**
     * add additional items to current instance
     * @access public
     * @param {Array|object|string} items
     * @return {LazyPlugin}
     */
    _instance.addItems = function(items) {
        _events.a && _events.a($.type(items) === "string" ? $(items) : items); // jshint ignore : line
        return _instance;
    };
    // noinspection JSUndefinedPropertyAssignment
    /**
     * get all left items of this instance
     * @access public
     * @returns {object}
     */
    _instance.getItems = function() {
        return _events.g ? _events.g() : {};
    };
    // noinspection JSUndefinedPropertyAssignment
    /**
     * force lazy to load all items in loadable area right now
     * by default without throttle
     * @access public
     * @type {function}
     * @param {boolean} [useThrottle]
     * @return {LazyPlugin}
     */
    _instance.update = function(useThrottle) {
        _events.e && _events.e({}, !useThrottle); // jshint ignore : line
        return _instance;
    };
    // noinspection JSUndefinedPropertyAssignment
    /**
     * force element(s) to load directly, ignoring the viewport
     * @access public
     * @param {Array|object|string} items
     * @return {LazyPlugin}
     */
    _instance.force = function(items) {
        _events.f && _events.f($.type(items) === "string" ? $(items) : items); // jshint ignore : line
        return _instance;
    };
    // noinspection JSUndefinedPropertyAssignment
    /**
     * force lazy to load all available items right now
     * this call ignores throttling
     * @access public
     * @type {function}
     * @return {LazyPlugin}
     */
    _instance.loadAll = function() {
        _events.e && _events.e({all: true}, true); // jshint ignore : line
        return _instance;
    };
    // noinspection JSUndefinedPropertyAssignment
    /**
     * destroy this plugin instance
     * @access public
     * @type {function}
     * @return undefined
     */
    _instance.destroy = function() {
        // unbind instance generated events
        // noinspection JSUnresolvedFunction, JSUnresolvedVariable
        $(_config.appendScroll).off("." + _namespace, _events.e);
        // noinspection JSUnresolvedVariable
        $(window).off("." + _namespace);
        // clear events
        _events = {};
        return undefined;
    };
    // start using lazy and return all elements to be chainable or instance for further use
    // noinspection JSUnresolvedVariable
    _executeLazy(_instance, _config, elements, _events, _namespace);
    return _config.chainable ? elements : _instance;
}
/**
 * settings and configuration data
 * @access public
 * @type {object}
 */
LazyPlugin.prototype.config = {
    // general
    name               : "lazy",
    chainable          : true,
    autoDestroy        : true,
    bind               : "load",
    threshold          : 500,
    visibleOnly        : false,
    appendScroll       : window,
    scrollDirection    : "both",
    imageBase          : null,
    defaultImage       : "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==",
    placeholder        : null,
    delay              : -1,
    combined           : false,
    // attributes
    attribute          : "data-src",
    srcsetAttribute    : "data-srcset",
    sizesAttribute     : "data-sizes",
    retinaAttribute    : "data-retina",
    loaderAttribute    : "data-loader",
    imageBaseAttribute : "data-imagebase",
    removeAttribute    : true,
    handledName        : "handled",
    loadedName         : "loaded",
    // effect
    effect             : "show",
    effectTime         : 0,
    // throttle
    enableThrottle     : true,
    throttle           : 250,
    // callbacks
    beforeLoad         : undefined,
    afterLoad          : undefined,
    onError            : undefined,
    onFinishedAll      : undefined
};
// register window load event globally to prevent not loading elements
// since jQuery 3.X ready state is fully async and may be executed after 'load' 
$(window).on("load", function() {
    windowLoaded = true;
});

})(window);