;(function($){
    /**
     * @class mUtil  Metronic base utilize class that privides helper functions
     */
    //== Polyfill
    // matches polyfill
    this.Element && function(ElementPrototype) {
        ElementPrototype.matches = ElementPrototype.matches ||
            ElementPrototype.matchesSelector ||
            ElementPrototype.webkitMatchesSelector ||
            ElementPrototype.msMatchesSelector ||
            function(selector) {
                var node = this,
                    nodes = (node.parentNode || node.document).querySelectorAll(selector),
                    i = -1;
                while (nodes[++i] && nodes[i] != node);
                return !!nodes[i];
            }
    }(Element.prototype);
    
    // closest polyfill
    this.Element && function(ElementPrototype) {
        ElementPrototype.closest = ElementPrototype.closest ||
            function(selector) {
                var el = this;
                while (el.matches && !el.matches(selector)) el = el.parentNode;
                return el.matches ? el : null;
            }
    }(Element.prototype);
    
    
    // matches polyfill
    this.Element && function(ElementPrototype) {
        ElementPrototype.matches = ElementPrototype.matches ||
            ElementPrototype.matchesSelector ||
            ElementPrototype.webkitMatchesSelector ||
            ElementPrototype.msMatchesSelector ||
            function(selector) {
                var node = this,
                    nodes = (node.parentNode || node.document).querySelectorAll(selector),
                    i = -1;
                while (nodes[++i] && nodes[i] != node);
                return !!nodes[i];
            }
    }(Element.prototype);
    
    //
    // requestAnimationFrame polyfill by Erik Möller.
    //  With fixes from Paul Irish and Tino Zijdel
    //
    //  http://paulirish.com/2011/requestanimationframe-for-smart-animating/
    //  http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
    //
    //  MIT license
    //
    (function() {
        var lastTime = 0;
        var vendors = ['webkit', 'moz'];
        for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
            window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
            window.cancelAnimationFrame =
                window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
        }
    
        if (!window.requestAnimationFrame)
            window.requestAnimationFrame = function(callback) {
                var currTime = new Date().getTime();
                var timeToCall = Math.max(0, 16 - (currTime - lastTime));
                var id = window.setTimeout(function() {
                    callback(currTime + timeToCall);
                }, timeToCall);
                lastTime = currTime + timeToCall;
                return id;
            };
    
        if (!window.cancelAnimationFrame)
            window.cancelAnimationFrame = function(id) {
                clearTimeout(id);
            };
    }());
    
    // Source: https://github.com/jserz/js_piece/blob/master/DOM/ParentNode/prepend()/prepend().md
    (function(arr) {
        arr.forEach(function(item) {
            if (item.hasOwnProperty('prepend')) {
                return;
            }
            Object.defineProperty(item, 'prepend', {
                configurable: true,
                enumerable: true,
                writable: true,
                value: function prepend() {
                    var argArr = Array.prototype.slice.call(arguments),
                        docFrag = document.createDocumentFragment();
    
                    argArr.forEach(function(argItem) {
                        var isNode = argItem instanceof Node;
                        docFrag.appendChild(isNode ? argItem : document.createTextNode(String(argItem)));
                    });
    
                    this.insertBefore(docFrag, this.firstChild);
                }
            });
        });
    })([Element.prototype, Document.prototype, DocumentFragment.prototype]);
    
    //== Global variables 
    window.mUtilElementDataStore = {};
    window.mUtilElementDataStoreID = 0;
    window.mUtilDelegatedEventHandlers = {};
    
    var mUtil = function($) {
    
        var resizeHandlers = [];
    
        /** @type {object} breakpoints The device width breakpoints **/
        var breakpoints = {
            sm: 544, // Small screen / phone           
            md: 768, // Medium screen / tablet            
            lg: 1024, // Large screen / desktop        
            xl: 1200 // Extra large screen / wide desktop
        };
    
        /**
         * Handle window resize event with some 
         * delay to attach event handlers upon resize complete 
         */
        var _windowResizeHandler = function() {
            var _runResizeHandlers = function() {
                // reinitialize other subscribed elements
                for (var i = 0; i < resizeHandlers.length; i++) {
                    var each = resizeHandlers[i];
                    each.call();
                }
            };
    
            var timeout = false; // holder for timeout id
            var delay = 250; // delay after event is "complete" to run callback
    
            window.addEventListener('resize', function() {
                clearTimeout(timeout);
                timeout = setTimeout(function() {
                    _runResizeHandlers();
                }, delay); // wait 50ms until window resize finishes.
            });
        };
    
        return {
            /**
             * Class main initializer.
             * @param {object} options.
             * @returns null
             */
            //main function to initiate the theme
            init: function(options) {
                if (options && options.breakpoints) {
                    breakpoints = options.breakpoints;
                }
    
                _windowResizeHandler();
            },
    
            /**
             * Adds window resize event handler.
             * @param {function} callback function.
             */
            addResizeHandler: function(callback) {
                resizeHandlers.push(callback);
            },
    
            /**
             * Removes window resize event handler.
             * @param {function} callback function.
             */
            removeResizeHandler: function(callback) {
                for (var i = 0; i < resizeHandlers.length; i++) {
                    if (callback === resizeHandlers[i]) {
                        delete resizeHandlers[i];
                    }
                }
            },
    
            /**
             * Trigger window resize handlers.
             */
            runResizeHandlers: function() {
                _runResizeHandlers();
            },
    
            resize: function() {
                if (typeof(Event) === 'function') {
                    // modern browsers
                    window.dispatchEvent(new Event('resize'));
                } else {
                    // for IE and other old browsers
                    // causes deprecation warning on modern browsers
                    var evt = window.document.createEvent('UIEvents'); 
                    evt.initUIEvent('resize', true, false, window, 0); 
                    window.dispatchEvent(evt);
                }
            },
    
            /**
             * Get GET parameter value from URL.
             * @param {string} paramName Parameter name.
             * @returns {string}  
             */
            getURLParam: function(paramName) {
                var searchString = window.location.search.substring(1),
                    i, val, params = searchString.split("&");
    
                for (i = 0; i < params.length; i++) {
                    val = params[i].split("=");
                    if (val[0] == paramName) {
                        return unescape(val[1]);
                    }
                }
    
                return null;
            },
    
            /**
             * Checks whether current device is mobile touch.
             * @returns {boolean}  
             */
            isMobileDevice: function() {
                return (this.getViewPort().width < this.getBreakpoint('lg') ? true : false);
            },
    
            /**
             * Checks whether current device is desktop.
             * @returns {boolean}  
             */
            isDesktopDevice: function() {
                return mUtil.isMobileDevice() ? false : true;
            },
    
            /**
             * Gets browser window viewport size. Ref:
             * http://andylangton.co.uk/articles/javascript/get-viewport-size-javascript/
             * @returns {object}  
             */
            getViewPort: function() {
                var e = window,
                    a = 'inner';
                if (!('innerWidth' in window)) {
                    a = 'client';
                    e = document.documentElement || document.body;
                }
    
                return {
                    width: e[a + 'Width'],
                    height: e[a + 'Height']
                };
            },
    
            /**
             * Checks whether given device mode is currently activated.
             * @param {string} mode Responsive mode name(e.g: desktop,
             *     desktop-and-tablet, tablet, tablet-and-mobile, mobile)
             * @returns {boolean}  
             */
            isInResponsiveRange: function(mode) {
                var breakpoint = this.getViewPort().width;
    
                if (mode == 'general') {
                    return true;
                } else if (mode == 'desktop' && breakpoint >= (this.getBreakpoint('lg') + 1)) {
                    return true;
                } else if (mode == 'tablet' && (breakpoint >= (this.getBreakpoint('md') + 1) && breakpoint < this.getBreakpoint('lg'))) {
                    return true;
                } else if (mode == 'mobile' && breakpoint <= this.getBreakpoint('md')) {
                    return true;
                } else if (mode == 'desktop-and-tablet' && breakpoint >= (this.getBreakpoint('md') + 1)) {
                    return true;
                } else if (mode == 'tablet-and-mobile' && breakpoint <= this.getBreakpoint('lg')) {
                    return true;
                } else if (mode == 'minimal-desktop-and-below' && breakpoint <= this.getBreakpoint('xl')) {
                    return true;
                }
    
                return false;
            },
    
            /**
             * Generates unique ID for give prefix.
             * @param {string} prefix Prefix for generated ID
             * @returns {boolean}  
             */
            getUniqueID: function(prefix) {
                return prefix + Math.floor(Math.random() * (new Date()).getTime());
            },
    
            /**
             * Gets window width for give breakpoint mode.
             * @param {string} mode Responsive mode name(e.g: xl, lg, md, sm)
             * @returns {number}  
             */
            getBreakpoint: function(mode) {
                return breakpoints[mode];
            },
    
            /**
             * Checks whether object has property matchs given key path.
             * @param {object} obj Object contains values paired with given key path
             * @param {string} keys Keys path seperated with dots
             * @returns {object}  
             */
            isset: function(obj, keys) {
                var stone;
    
                keys = keys || '';
    
                if (keys.indexOf('[') !== -1) {
                    throw new Error('Unsupported object path notation.');
                }
    
                keys = keys.split('.');
    
                do {
                    if (obj === undefined) {
                        return false;
                    }
    
                    stone = keys.shift();
    
                    if (!obj.hasOwnProperty(stone)) {
                        return false;
                    }
    
                    obj = obj[stone];
    
                } while (keys.length);
    
                return true;
            },
    
            /**
             * Gets highest z-index of the given element parents
             * @param {object} el jQuery element object
             * @returns {number}  
             */
            getHighestZindex: function(el) {
                var elem = mUtil.get(el),
                    position, value;
    
                while (elem && elem !== document) {
                    // Ignore z-index if position is set to a value where z-index is ignored by the browser
                    // This makes behavior of this function consistent across browsers
                    // WebKit always returns auto if the element is positioned
                    position = mUtil.css(elem, 'position');
    
                    if (position === "absolute" || position === "relative" || position === "fixed") {
                        // IE returns 0 when zIndex is not specified
                        // other browsers return a string
                        // we ignore the case of nested elements with an explicit value of 0
                        // <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
                        value = parseInt(mUtil.css(elem, 'z-index'));
    
                        if (!isNaN(value) && value !== 0) {
                            return value;
                        }
                    }
    
                    elem = elem.parentNode;
                }
    
                return null;
            },
    
            /**
             * Checks whether the element has any parent with fixed positionfreg
             * @param {object} el jQuery element object
             * @returns {boolean}  
             */
            hasFixedPositionedParent: function(el) {
                while (el && el !== document) {
                    position = mUtil.css(el, 'position');
    
                    if (position === "fixed") {
                        return true;
                    }
    
                    el = el.parentNode;
                }
    
                return false;
            },
    
            /**
             * Simulates delay
             */
            sleep: function(milliseconds) {
                var start = new Date().getTime();
                for (var i = 0; i < 1e7; i++) {
                    if ((new Date().getTime() - start) > milliseconds) {
                        break;
                    }
                }
            },
    
            /**
             * Gets randomly generated integer value within given min and max range
             * @param {number} min Range start value
             * @param {number} min Range end value
             * @returns {number}  
             */
            getRandomInt: function(min, max) {
                return Math.floor(Math.random() * (max - min + 1)) + min;
            },
    
            /**
             * Checks whether Angular library is included
             * @returns {boolean}  
             */
            isAngularVersion: function() {
                return window.Zone !== undefined ? true : false;
            },
    
            //== jQuery Workarounds
    
            //== Deep extend:  $.extend(true, {}, objA, objB);
            deepExtend: function(out) {
                out = out || {};
    
                for (var i = 1; i < arguments.length; i++) {
                    var obj = arguments[i];
    
                    if (!obj)
                        continue;
    
                    for (var key in obj) {
                        if (obj.hasOwnProperty(key)) {
                            if (typeof obj[key] === 'object')
                                out[key] = mUtil.deepExtend(out[key], obj[key]);
                            else
                                out[key] = obj[key];
                        }
                    }
                }
    
                return out;
            },
    
            //== extend:  $.extend({}, objA, objB); 
            extend: function(out) {
                out = out || {};
    
                for (var i = 1; i < arguments.length; i++) {
                    if (!arguments[i])
                        continue;
    
                    for (var key in arguments[i]) {
                        if (arguments[i].hasOwnProperty(key))
                            out[key] = arguments[i][key];
                    }
                }
    
                return out;
            },
    
            get: function(query) {
                var el;
    
                if (query === document) {
                    return document;
                }
    
                if (!!(query && query.nodeType === 1)) {
                    return query;
                }
    
                if (el = document.getElementById(query)) {
                    return el;
                } else if (el = document.getElementsByTagName(query)) {
                    return el[0];
                } else if (el = document.getElementsByClassName(query)) {
                    return el[0];
                } else {
                    return null;
                }
            },
    
            getByClass: function(query) {
                var el;
                
                if (el = document.getElementsByClassName(query)) {
                    return el[0];
                } else {
                    return null;
                }
            },
    
            /**
             * Checks whether the element has given classes
             * @param {object} el jQuery element object
             * @param {string} Classes string
             * @returns {boolean}  
             */
            hasClasses: function(el, classes) {
                if (!el) {
                    return;
                }
    
                var classesArr = classes.split(" ");
    
                for (var i = 0; i < classesArr.length; i++) {
                    if (mUtil.hasClass(el, mUtil.trim(classesArr[i])) == false) {
                        return false;
                    }
                }
    
                return true;
            },
    
            hasClass: function(el, className) {
                if (!el) {
                    return;
                }
    
                return el.classList ? el.classList.contains(className) : new RegExp('\\b' + className + '\\b').test(el.className);
            },
    
            addClass: function(el, className) {
                if (!el || typeof className === 'undefined') {
                    return;
                }
    
                var classNames = className.split(' ');
    
                if (el.classList) {
                    for (var i = 0; i < classNames.length; i++) {
                        if (classNames[i] && classNames[i].length > 0) {
                            el.classList.add(mUtil.trim(classNames[i]));
                        }
                    }
                } else if (!mUtil.hasClass(el, className)) {
                    for (var i = 0; i < classNames.length; i++) {
                        el.className += ' ' + mUtil.trim(classNames[i]);
                    }
                }
            },
    
            removeClass: function(el, className) {
                if (!el || typeof className === 'undefined') {
                    return;
                }
    
                var classNames = className.split(' ');
    
                if (el.classList) {
                    for (var i = 0; i < classNames.length; i++) {
                        el.classList.remove(mUtil.trim(classNames[i]));
                    }
                } else if (mUtil.hasClass(el, className)) {
                    for (var i = 0; i < classNames.length; i++) {
                        el.className = el.className.replace(new RegExp('\\b' + mUtil.trim(classNames[i]) + '\\b', 'g'), '');
                    }
                }
            },
    
            triggerCustomEvent: function(el, eventName, data) {
                if (window.CustomEvent) {
                    var event = new CustomEvent(eventName, {
                        detail: data
                    });
                } else {
                    var event = document.createEvent('CustomEvent');
                    event.initCustomEvent(eventName, true, true, data);
                }
    
                el.dispatchEvent(event);
            },
    
            trim: function(string) {
                return string.trim();
            },
    
            eventTriggered: function(e) {
                if (e.currentTarget.dataset.triggered) {
                    return true;
                } else {
                    e.currentTarget.dataset.triggered = true;
    
                    return false;
                }
            },
    
            remove: function(el) {
                if (el && el.parentNode) {
                    el.parentNode.removeChild(el);
                }
            },
    
            find: function(parent, query) {
                parent = mUtil.get(parent);
                if (parent) {
                    return parent.querySelector(query);
                }            
            },
    
            findAll: function(parent, query) {
                parent = mUtil.get(parent);
                if (parent) {
                    return parent.querySelectorAll(query);
                } 
            },
    
            insertAfter: function(el, referenceNode) {
                return referenceNode.parentNode.insertBefore(el, referenceNode.nextSibling);
            },
    
            parents: function(el, query) {
                function collectionHas(a, b) { //helper function (see below)
                    for (var i = 0, len = a.length; i < len; i++) {
                        if (a[i] == b) return true;
                    }
    
                    return false;
                }
    
                function findParentBySelector(el, selector) {
                    var all = document.querySelectorAll(selector);
                    var cur = el.parentNode;
    
                    while (cur && !collectionHas(all, cur)) { //keep going up until you find a match
                        cur = cur.parentNode; //go up
                    }
    
                    return cur; //will return null if not found
                }
    
                return findParentBySelector(el, query);
            },
    
            children: function(el, selector, log) {
                if (!el || !el.childNodes) {
                    return;
                }
    
                var result = [],
                    i = 0,
                    l = el.childNodes.length;
    
                for (var i; i < l; ++i) {
                    if (el.childNodes[i].nodeType == 1 && mUtil.matches(el.childNodes[i], selector, log)) {
                        result.push(el.childNodes[i]);
                    }
                }
    
                return result;
            },
    
            child: function(el, selector, log) {
                var children = mUtil.children(el, selector, log);
    
                return children ? children[0] : null;
            },
    
            matches: function(el, selector, log) {
                var p = Element.prototype;
                var f = p.matches || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || function(s) {
                    return [].indexOf.call(document.querySelectorAll(s), this) !== -1;
                };
    
                if (el && el.tagName) {
                    return f.call(el, selector);
                } else {
                    return false;
                }
            },
    
            data: function(element) {
                element = mUtil.get(element);
    
                return {
                    set: function(name, data) {
                        if (element.customDataTag === undefined) {
                            mUtilElementDataStoreID++;
                            element.customDataTag = mUtilElementDataStoreID;
                        }
    
                        if (mUtilElementDataStore[element.customDataTag] === undefined) {
                            mUtilElementDataStore[element.customDataTag] = {};
                        }
    
                        mUtilElementDataStore[element.customDataTag][name] = data;
                    },
    
                    get: function(name) {
                        return this.has(name) ? mUtilElementDataStore[element.customDataTag][name] : null;
                    },
    
                    has: function(name) {
                        return (mUtilElementDataStore[element.customDataTag] && mUtilElementDataStore[element.customDataTag][name]) ? true : false;
                    },
    
                    remove: function(name) {
                        if (this.has(name)) {
                            delete mUtilElementDataStore[element.customDataTag][name];
                        }
                    }
                };
            },
    
            outerWidth: function(el, margin) {
                var width;
    
                if (margin === true) {
                    var width = parseFloat(el.offsetWidth);
                    width += parseFloat(mUtil.css(el, 'margin-left')) + parseFloat(mUtil.css(el, 'margin-right'));
    
                    return parseFloat(width);
                } else {
                    var width = parseFloat(el.offsetWidth);
    
                    return width;
                }
            },
    
            offset: function(elem) {
                var rect, win;
                elem = mUtil.get(elem);
    
                if ( !elem ) {
                    return;
                }
    
                // Return zeros for disconnected and hidden (display: none) elements (gh-2310)
                // Support: IE <=11 only
                // Running getBoundingClientRect on a
                // disconnected node in IE throws an error
    
                if ( !elem.getClientRects().length ) {
                    return { top: 0, left: 0 };
                }
    
                // Get document-relative position by adding viewport scroll to viewport-relative gBCR
                rect = elem.getBoundingClientRect();
                win = elem.ownerDocument.defaultView;
    
                return {
                    top: rect.top + win.pageYOffset,
                    left: rect.left + win.pageXOffset
                };
            },
    
            height: function(el) {
                return mUtil.css(el, 'height');
            },
    
            visible: function(el) {
                return !(el.offsetWidth === 0 && el.offsetHeight === 0);
            },
    
            attr: function(el, name, value) {
                el = mUtil.get(el);
    
                if (el == undefined) {
                    return;
                }
    
                if (value !== undefined) {
                    el.setAttribute(name, value);
                } else {
                    return el.getAttribute(name);
                }
            },
    
            hasAttr: function(el, name) {
                el = mUtil.get(el);
    
                if (el == undefined) {
                    return;
                }
    
                return el.getAttribute(name) ? true : false;
            },
    
            removeAttr: function(el, name) {
                el = mUtil.get(el);
    
                if (el == undefined) {
                    return;
                }
    
                el.removeAttribute(name);
            },
    
            animate: function(from, to, duration, update, easing, done) {
                /**
                 * TinyAnimate.easings
                 *  Adapted from jQuery Easing
                 */
                var easings = {};
                var easing;
    
                easings.linear = function(t, b, c, d) {
                    return c * t / d + b;
                };
    
                easing = easings.linear;
    
                // Early bail out if called incorrectly
                if (typeof from !== 'number' ||
                    typeof to !== 'number' ||
                    typeof duration !== 'number' ||
                    typeof update !== 'function') {
                    return;
                }
    
                // Create mock done() function if necessary
                if (typeof done !== 'function') {
                    done = function() {};
                }
    
                // Pick implementation (requestAnimationFrame | setTimeout)
                var rAF = window.requestAnimationFrame || function(callback) {
                    window.setTimeout(callback, 1000 / 50);
                };
    
                // Animation loop
                var canceled = false;
                var change = to - from;
    
                function loop(timestamp) {
                    var time = (timestamp || +new Date()) - start;
    
                    if (time >= 0) {
                        update(easing(time, from, change, duration));
                    }
                    if (time >= 0 && time >= duration) {
                        update(to);
                        done();
                    } else {
                        rAF(loop);
                    }
                }
    
                update(from);
    
                // Start animation loop
                var start = window.performance && window.performance.now ? window.performance.now() : +new Date();
    
                rAF(loop);
            },
    
            actualCss: function(el, prop, cache) {
                if (el instanceof HTMLElement === false) {
                    return;
                }
    
                if (!el.getAttribute('m-hidden-' + prop) || cache === false) {
                    var value;
    
                    // the element is hidden so:
                    // making the el block so we can meassure its height but still be hidden
                    el.style.cssText = 'position: absolute; visibility: hidden; display: block;';
    
                    if (prop == 'width') {
                        value = el.offsetWidth;
                    } else if (prop == 'height') {
                        value = el.offsetHeight;
                    }
    
                    el.style.cssText = '';
    
                    // store it in cache
                    el.setAttribute('m-hidden-' + prop, value);
    
                    return parseFloat(value);
                } else {
                    // store it in cache
                    return parseFloat(el.getAttribute('m-hidden-' + prop));
                }
            },
    
            actualHeight: function(el, cache) {
                return mUtil.actualCss(el, 'height', cache);
            },
    
            actualWidth: function(el, cache) {
                return mUtil.actualCss(el, 'width', cache);
            },
    
            getScroll: function(element, method) {
                // The passed in `method` value should be 'Top' or 'Left'
                method = 'scroll' + method;
                return (element == window || element == document) ? (
                    self[(method == 'scrollTop') ? 'pageYOffset' : 'pageXOffset'] ||
                    (browserSupportsBoxModel && document.documentElement[method]) ||
                    document.body[method]
                ) : element[method];
            },
    
            css: function(el, styleProp, value) {
                el = mUtil.get(el);
    
                if (!el) {
                    return;
                }
    
                if (value !== undefined) {
                    el.style[styleProp] = value;
                } else {
                    var value, defaultView = (el.ownerDocument || document).defaultView;
                    // W3C standard way:
                    if (defaultView && defaultView.getComputedStyle) {
                        // sanitize property name to css notation
                        // (hyphen separated words eg. font-Size)
                        styleProp = styleProp.replace(/([A-Z])/g, "-$1").toLowerCase();
                        return defaultView.getComputedStyle(el, null).getPropertyValue(styleProp);
                    } else if (el.currentStyle) { // IE
                        // sanitize property name to camelCase
                        styleProp = styleProp.replace(/\-(\w)/g, function(str, letter) {
                            return letter.toUpperCase();
                        });
                        value = el.currentStyle[styleProp];
                        // convert other units to pixels on IE
                        if (/^\d+(em|pt|%|ex)?$/i.test(value)) {
                            return (function(value) {
                                var oldLeft = el.style.left,
                                    oldRsLeft = el.runtimeStyle.left;
                                el.runtimeStyle.left = el.currentStyle.left;
                                el.style.left = value || 0;
                                value = el.style.pixelLeft + "px";
                                el.style.left = oldLeft;
                                el.runtimeStyle.left = oldRsLeft;
                                return value;
                            })(value);
                        }
                        return value;
                    }
                }
            },
    
            slide: function(el, dir, speed, callback, recalcMaxHeight) {
                if (!el || (dir == 'up' && mUtil.visible(el) === false) || (dir == 'down' && mUtil.visible(el) === true)) {
                    return;
                }
    
                speed = (speed ? speed : 600);
                var calcHeight = mUtil.actualHeight(el);
                var calcPaddingTop = false;
                var calcPaddingBottom = false;
    
                if (mUtil.css(el, 'padding-top') && mUtil.data(el).has('slide-padding-top') !== true) {
                    mUtil.data(el).set('slide-padding-top', mUtil.css(el, 'padding-top'));
                }
    
                if (mUtil.css(el, 'padding-bottom') && mUtil.data(el).has('slide-padding-bottom') !== true) {
                    mUtil.data(el).set('slide-padding-bottom', mUtil.css(el, 'padding-bottom'));
                }
    
                if (mUtil.data(el).has('slide-padding-top')) {
                    calcPaddingTop = parseInt(mUtil.data(el).get('slide-padding-top'));
                }
    
                if (mUtil.data(el).has('slide-padding-bottom')) {
                    calcPaddingBottom = parseInt(mUtil.data(el).get('slide-padding-bottom'));
                }
    
                if (dir == 'up') { // up          
                    el.style.cssText = 'display: block; overflow: hidden;';
    
                    if (calcPaddingTop) {
                        mUtil.animate(0, calcPaddingTop, speed, function(value) {
                            el.style.paddingTop = (calcPaddingTop - value) + 'px';
                        }, 'linear');
                    }
    
                    if (calcPaddingBottom) {
                        mUtil.animate(0, calcPaddingBottom, speed, function(value) {
                            el.style.paddingBottom = (calcPaddingBottom - value) + 'px';
                        }, 'linear');
                    }
    
                    mUtil.animate(0, calcHeight, speed, function(value) {
                        el.style.height = (calcHeight - value) + 'px';
                    }, 'linear', function() {
                        callback();
                        el.style.height = '';
                        el.style.display = 'none';
                    });
    
    
                } else if (dir == 'down') { // down
                    el.style.cssText = 'display: block; overflow: hidden;';
    
                    if (calcPaddingTop) {
                        mUtil.animate(0, calcPaddingTop, speed, function(value) {
                            el.style.paddingTop = value + 'px';
                        }, 'linear', function() {
                            el.style.paddingTop = '';
                        });
                    }
    
                    if (calcPaddingBottom) {
                        mUtil.animate(0, calcPaddingBottom, speed, function(value) {
                            el.style.paddingBottom = value + 'px';
                        }, 'linear', function() {
                            el.style.paddingBottom = '';
                        });
                    }
    
                    mUtil.animate(0, calcHeight, speed, function(value) {
                        el.style.height = value + 'px';
                    }, 'linear', function() {
                        callback();
                        el.style.height = '';
                        el.style.display = '';
                        el.style.overflow = '';
                    });
                }
            },
    
            slideUp: function(el, speed, callback) {
                mUtil.slide(el, 'up', speed, callback);
            },
    
            slideDown: function(el, speed, callback) {
                mUtil.slide(el, 'down', speed, callback);
            },
    
            show: function(el, display) {
                el.style.display = (display ? display : 'block');
            },
    
            hide: function(el) {
                el.style.display = 'none';
            },
    
            addEvent: function(el, type, handler, one) {
                el = mUtil.get(el);
                if (typeof el !== 'undefined') {
                    el.addEventListener(type, handler);
                }
            },
    
            removeEvent: function(el, type, handler) {
                el = mUtil.get(el);
                el.removeEventListener(type, handler);
            },
    
            on: function(element, selector, event, handler) {
                if (!selector) {
                    return;
                }
    
                var eventId = mUtil.getUniqueID('event');
    
                mUtilDelegatedEventHandlers[eventId] = function(e) {
                    var targets = element.querySelectorAll(selector);
                    var target = e.target;
    
                    while (target && target !== element) {
                        for (var i = 0, j = targets.length; i < j; i++) {
                            if (target === targets[i]) {
                                handler.call(target, e);
                            }
                        }
    
                        target = target.parentNode;
                    }
                }
    
                mUtil.addEvent(element, event, mUtilDelegatedEventHandlers[eventId]);
    
                return eventId;
            },
    
            off: function(element, event, eventId) {
                if (!element || !mUtilDelegatedEventHandlers[eventId]) {
                    return;
                }
    
                mUtil.removeEvent(element, event, mUtilDelegatedEventHandlers[eventId]);
    
                delete mUtilDelegatedEventHandlers[eventId];
            },
    
            one: function onetime(el, type, callback) {
                el = mUtil.get(el);
    
                el.addEventListener(type, function(e) {
                    // remove event
                    e.target.removeEventListener(e.type, arguments.callee);
                    // call handler
                    return callback(e);
                });
            },
    
            hash: function(str) {
                var hash = 0,
                    i, chr;
    
                if (str.length === 0) return hash;
                for (i = 0; i < str.length; i++) {
                    chr = str.charCodeAt(i);
                    hash = ((hash << 5) - hash) + chr;
                    hash |= 0; // Convert to 32bit integer
                }
    
                return hash;
            },
    
            animateClass: function(el, animationName, callback) {
                var animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend';
    
                mUtil.addClass(el, 'animated ' + animationName);
    
                mUtil.one(el, animationEnd, function() {
                    mUtil.removeClass(el, 'animated ' + animationName);
                });
    
                if (callback) {
                    mUtil.one(el.animationEnd, callback);
                }
            },
    
            animateDelay: function(el, value) {
                var vendors = ['webkit-', 'moz-', 'ms-', 'o-', ''];
                for (var i = 0; i < vendors.length; i++) {
                    mUtil.css(el, vendors[i] + 'animation-delay', value);
                }
            },
    
            animateDuration: function(el, value) {
                var vendors = ['webkit-', 'moz-', 'ms-', 'o-', ''];
                for (var i = 0; i < vendors.length; i++) {
                    mUtil.css(el, vendors[i] + 'animation-duration', value);
                }
            },
    
            scrollTo: function(target, offset, duration) {
                var duration = duration ? duration : 500;
                var target = mUtil.get(target);
                var targetPos = target ? mUtil.offset(target).top : 0;
                var scrollPos = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
                var from, to;
    
                if (targetPos > scrollPos) {
                    from = targetPos;
                    to = scrollPos;
                } else {
                    from = scrollPos;
                    to = targetPos;
                }
    
                if (offset) {
                    to += offset;
                }
    
                mUtil.animate(from, to, duration, function(value) {
                    document.documentElement.scrollTop = value;
                    document.body.parentNode.scrollTop = value;
                    document.body.scrollTop = value;
                }); //, easing, done
            },
    
            scrollTop: function(offset, duration) {
                mUtil.scrollTo(null, offset, duration);
            },
    
            isArray: function(obj) {
                return obj && Array.isArray(obj);
            },
    
            ready: function(callback) {
                if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading") {
                    callback();
                } else {
                    document.addEventListener('DOMContentLoaded', callback);
                }
            },
    
            isEmpty: function(obj) {
                for (var prop in obj) {
                    if (obj.hasOwnProperty(prop)) {
                        return false;
                    }
                }
    
                return true;
            },
    
            numberString: function(nStr) {
                nStr += '';
                var x = nStr.split('.');
                var x1 = x[0];
                var x2 = x.length > 1 ? '.' + x[1] : '';
                var rgx = /(\d+)(\d{3})/;
                while (rgx.test(x1)) {
                    x1 = x1.replace(rgx, '$1' + ',' + '$2');
                }
                return x1 + x2;
            },
    
            detectIE: function() {
                var ua = window.navigator.userAgent;
    
                // Test values; Uncomment to check result …
    
                // IE 10
                // ua = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)';
    
                // IE 11
                // ua = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';
    
                // Edge 12 (Spartan)
                // ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0';
    
                // Edge 13
                // ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586';
    
                var msie = ua.indexOf('MSIE ');
                if (msie > 0) {
                    // IE 10 or older => return version number
                    return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
                }
    
                var trident = ua.indexOf('Trident/');
                if (trident > 0) {
                    // IE 11 => return version number
                    var rv = ua.indexOf('rv:');
                    return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
                }
    
                var edge = ua.indexOf('Edge/');
                if (edge > 0) {
                    // Edge (IE 12+) => return version number
                    return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
                }
    
                // other browser
                return false;
            },
    
            isRTL: function() {
                return (mUtil.attr(mUtil.get('html'), 'direction') == 'rtl');
            },
    
            //== Scroller
            scrollerInit: function(element, options) {
                //== Define init function
                function init() {
                    var ps;
                    var height;
    
                    if (options.height instanceof Function) {
                        height = parseInt(options.height.call());
                    } else {
                        height = parseInt(options.height);
                    }
    
                    //== Destroy scroll on table and mobile modes
                    if (options.disableForMobile && mUtil.isInResponsiveRange('tablet-and-mobile')) {
                        if (ps = mUtil.data(element).get('ps')) {
                            if (options.resetHeightOnDestroy) {
                                mUtil.css(element, 'height', 'auto');
                            } else {
                                mUtil.css(element, 'overflow', 'auto');
                                if (height > 0) {
                                    mUtil.css(element, 'height', height + 'px');
                                }
                            }
    
                            ps.destroy();
                            ps = mUtil.data(element).remove('ps');
                        } else if (height > 0){
                            mUtil.css(element, 'overflow', 'auto');
                            mUtil.css(element, 'height', height + 'px');
                        }
    
                        return;
                    }
    
                    if (height > 0) {
                        mUtil.css(element, 'height', height + 'px');
                    }
    
                    mUtil.css(element, 'overflow', 'hidden');
    
                    //== Init scroll
                    if (ps = mUtil.data(element).get('ps')) {
                        ps.update();
                    } else {
                        mUtil.addClass(element, 'm-scroller');
                        ps = new PerfectScrollbar(element, {
                            wheelSpeed: 0.5,
                            swipeEasing: true,
                            wheelPropagation: false,
                            minScrollbarLength: 40,
                            suppressScrollX: mUtil.isRTL() ? false : true
                        });
    
                        mUtil.data(element).set('ps', ps);
                    }
                }
    
                //== Init
                init();
    
                //== Handle window resize
                if (options.handleWindowResize) {
                    mUtil.addResizeHandler(function() {
                        init();
                    });
                }
            },
    
            scrollerUpdate: function(element) {
                var ps;
                if (ps = mUtil.data(element).get('ps')) {
                    ps.update();
                }
            },
    
            scrollersUpdate: function(parent) {
                var scrollers = mUtil.findAll(parent, '.ps');
                for (var i = 0, len = scrollers.length; i < len; i++) {
                    mUtil.scrollerUpdate(scrollers[i]);
                }
            },
    
            scrollerTop: function(element) {
                var ps;
                if (ps = mUtil.data(element).get('ps')) {
                    element.scrollTop = 0;
                }
            },
    
            scrollerDestroy: function(element) {
                var ps;
                if (ps = mUtil.data(element).get('ps')) {
                    ps.destroy();
                    ps = mUtil.data(element).remove('ps');
                }
            }
        }
    }();
    
    //== Initialize mUtil class on document ready
    mUtil.ready(function() {
        mUtil.init();
    });
    /**
     * @class mApp  Metronic App class
     */
    
    var mApp = function() {
    
        /** @type {object} colors State colors **/
        var colors = {
            brand:      '#716aca',
            metal:      '#c4c5d6',
            light:      '#ffffff',
            accent:     '#00c5dc',
            primary:    '#5867dd',
            success:    '#34bfa3',
            info:       '#36a3f7',
            warning:    '#ffb822',
            danger:     '#f4516c',
            focus:      '#9816f4'
        }
    
        /**
        * Initializes bootstrap tooltip
        */
        var initTooltip = function(el) {
            var skin = el.data('skin') ? 'm-tooltip--skin-' + el.data('skin') : '';
            var width = el.data('width') == 'auto' ? 'm-tooltop--auto-width' : '';
            var triggerValue = el.data('trigger') ? el.data('trigger') : 'hover';
            var placement = el.data('placement') ? el.data('placement') : 'left';
                        
            el.tooltip({
                trigger: triggerValue,
                template: '<div class="m-tooltip ' + skin + ' ' + width + ' tooltip" role="tooltip">\
                    <div class="arrow"></div>\
                    <div class="tooltip-inner"></div>\
                </div>'
            });
        }
        
        /**
        * Initializes bootstrap tooltips
        */
        var initTooltips = function() {
            // init bootstrap tooltips
            $('[data-toggle="m-tooltip"]').each(function() {
                initTooltip($(this));
            });
        }
    
        /**
        * Initializes bootstrap popover
        */
        var initPopover = function(el) {
            var skin = el.data('skin') ? 'm-popover--skin-' + el.data('skin') : '';
            var triggerValue = el.data('trigger') ? el.data('trigger') : 'hover';
                
            el.popover({
                trigger: triggerValue,
                template: '\
                <div class="m-popover ' + skin + ' popover" role="tooltip">\
                    <div class="arrow"></div>\
                    <h3 class="popover-header"></h3>\
                    <div class="popover-body"></div>\
                </div>'
            });
        }
    
        /**
        * Initializes bootstrap popovers
        */
        var initPopovers = function() {
            // init bootstrap popover
            $('[data-toggle="m-popover"]').each(function() {
                initPopover($(this));
            });
        }
    
        /**
        * Initializes bootstrap file input
        */
        var initFileInput = function() {
            // init bootstrap popover
            $('.custom-file-input').on('change',function(){
                var fileName = $(this).val();
                $(this).next('.custom-file-label').addClass("selected").html(fileName);
            });
        }           
    
        /**
        * Initializes metronic portlet
        */
        var initPortlet = function(el, options) {
            // init portlet tools
            var el = $(el);
            var portlet = new mPortlet(el[0], options);
        }
    
        /**
        * Initializes metronic portlets
        */
        var initPortlets = function() {
            // init portlet tools
            $('[m-portlet="true"]').each(function() {
                var el = $(this);
    
                if ( el.data('portlet-initialized') !== true ) {
                    initPortlet(el, {});
                    el.data('portlet-initialized', true);
                }
            });
        }
    
        /**
        * Initializes scrollable contents
        */
        var initScrollers = function() {
            $('[data-scrollable="true"]').each(function(){
                var el = $(this);
                mUtil.scrollerInit(this, {disableForMobile: true, handleWindowResize: true, height: function() {
                    if (mUtil.isInResponsiveRange('tablet-and-mobile') && el.data('mobile-height')) {
                        return el.data('mobile-height');
                    } else {
                        return el.data('height');
                    }
                }});
            });
        }
    
        /**
        * Initializes bootstrap alerts
        */
        var initAlerts = function() {
            // init bootstrap popover
            $('body').on('click', '[data-close=alert]', function() {
                $(this).closest('.alert').hide();
            });
        }
    
        /**
        * Initializes Metronic custom tabs
        */
        var initCustomTabs = function() {
            // init bootstrap popover
            $('[data-tab-target]').each(function() {
                if ($(this).data('tabs-initialized') == true ) {
                    return;
                }
    
                $(this).click(function(e) {
                    e.preventDefault();
                    
                    var tab = $(this);
                    var tabs = tab.closest('[data-tabs="true"]');
                    var contents = $( tabs.data('tabs-contents') );
                    var content = $( tab.data('tab-target') );
    
                    tabs.find('.m-tabs__item.m-tabs__item--active').removeClass('m-tabs__item--active');
                    tab.addClass('m-tabs__item--active');
    
                    contents.find('.m-tabs-content__item.m-tabs-content__item--active').removeClass('m-tabs-content__item--active');
                    content.addClass('m-tabs-content__item--active');         
                });
    
                $(this).data('tabs-initialized', true);
            });
        }
    
        var hideTouchWarning = function() {
            jQuery.event.special.touchstart = {
                setup: function(_, ns, handle) {
                    if (typeof this === 'function')
                        if (ns.includes('noPreventDefault')) {
                            this.addEventListener('touchstart', handle, {passive: false});
                        } else {
                            this.addEventListener('touchstart', handle, {passive: true});
                        }
                },
            };
            jQuery.event.special.touchmove = {
                setup: function(_, ns, handle) {
                    if (typeof this === 'function')
                        if (ns.includes('noPreventDefault')) {
                            this.addEventListener('touchmove', handle, {passive: false});
                        } else {
                            this.addEventListener('touchmove', handle, {passive: true});
                        }
                },
            };
            jQuery.event.special.wheel = {
                setup: function(_, ns, handle) {
                    if (typeof this === 'function')
                        if (ns.includes('noPreventDefault')) {
                            this.addEventListener('wheel', handle, {passive: false});
                        } else {
                            this.addEventListener('wheel', handle, {passive: true});
                        }
                },
            };
        };
    
        return {
            /**
            * Main class initializer
            */
            init: function(options) {
                if (options && options.colors) {
                    colors = options.colors;
                }
                mApp.initComponents();
            },
    
            /**
            * Initializes components
            */
            initComponents: function() {
                hideTouchWarning();
                initScrollers();
                initTooltips();
                initPopovers();
                initAlerts();
                initPortlets();
                initFileInput();
                initCustomTabs();
            },
    
    
            /**
            * Init custom tabs
            */
            initCustomTabs: function() {
                initCustomTabs();
            },
    
            /**
            * 
            * @param {object} el jQuery element object
            */
            // wrJangoer function to scroll(focus) to an element
            initTooltips: function() {
                initTooltips();
            },
    
            /**
            * 
            * @param {object} el jQuery element object
            */
            // wrJangoer function to scroll(focus) to an element
            initTooltip: function(el) {
                initTooltip(el);
            },
    
            /**
            * 
            * @param {object} el jQuery element object
            */
            // wrJangoer function to scroll(focus) to an element
            initPopovers: function() {
                initPopovers();
            },
    
            /**
            * 
            * @param {object} el jQuery element object
            */
            // wrJangoer function to scroll(focus) to an element
            initPopover: function(el) {
                initPopover(el);
            },
    
            /**
            * 
            * @param {object} el jQuery element object
            */
            // function to init portlet
            initPortlet: function(el, options) {
                initPortlet(el, options);
            },
    
            /**
            * 
            * @param {object} el jQuery element object
            */
            // function to init portlets
            initPortlets: function() {
                initPortlets();
            },
    
            /**
            * Blocks element with loading indiciator using http://malsup.com/jquery/block/
            * @param {object} target jQuery element object
            * @param {object} options 
            */
            block: function(target, options) {
                var el = $(target);
    
                options = $.extend(true, {
                    opacity: 0.03,
                    overlayColor: '#000000',
                    state: 'brand',
                    type: 'loader',
                    size: 'lg',
                    centerX: true,
                    centerY: true,
                    message: '',
                    shadow: true,
                    width: 'auto'
                }, options);
    
                var skin;
                var state;
                var loading;
    
                if (options.type == 'spinner') {
                    skin = options.skin ? 'm-spinner--skin-' + options.skin : '';
                    state = options.state ? 'm-spinner--' + options.state : '';
                    loading = '<div class="m-spinner ' + skin + ' ' + state + '"></div';
                } else {
                    skin = options.skin ? 'm-loader--skin-' + options.skin : '';
                    state = options.state ? 'm-loader--' + options.state : '';
                    size = options.size ? 'm-loader--' + options.size : '';
                    loading = '<div class="m-loader ' + skin + ' ' + state + ' ' + size + '"></div';
                }
    
                if (options.message && options.message.length > 0) {
                    var classes = 'm-blockui ' + (options.shadow === false ? 'm-blockui-no-shadow' : '');
    
                    html = '<div class="' + classes + '"><span>' + options.message + '</span><span>' + loading + '</span></div>';
    
                    var el = document.createElement('div');
                    mUtil.get('body').prepend(el);
                    mUtil.addClass(el, classes);
                    el.innerHTML = '<span>' + options.message + '</span><span>' + loading + '</span>';
                    options.width = mUtil.actualWidth(el) + 10;
                    mUtil.remove(el);
    
                    if (target == 'body') {
                        html = '<div class="' + classes + '" style="margin-left:-'+ (options.width / 2) +'px;"><span>' + options.message + '</span><span>' + loading + '</span></div>';
                    }
                } else {
                    html = loading;
                }
    
                var params = {
                    message: html,
                    centerY: options.centerY,
                    centerX: options.centerX,
                    css: {
                        top: '30%',
                        left: '50%',
                        border: '0',
                        padding: '0',
                        backgroundColor: 'none',
                        width: options.width
                    },
                    overlayCSS: {
                        backgroundColor: options.overlayColor,
                        opacity: options.opacity,
                        cursor: 'wait',
                        zIndex: '10'
                    },
                    onUnblock: function() {
                        if (el && el[0]) {
                            mUtil.css(el[0], 'position', '');
                            mUtil.css(el[0], 'zoom', '');
                        }                    
                    }
                };
    
                if (target == 'body') {
                    params.css.top = '50%';
                    $.blockUI(params);
                } else {
                    var el = $(target);
                    el.block(params);
                }
            },
    
            /**
            * Un-blocks the blocked element 
            * @param {object} target jQuery element object
            */
            unblock: function(target) {
                if (target && target != 'body') {
                    $(target).unblock();
                } else {
                    $.unblockUI();
                }
            },
    
            /**
            * Blocks the page body element with loading indicator
            * @param {object} options 
            */
            blockPage: function(options) {
                return mApp.block('body', options);
            },
    
            /**
            * Un-blocks the blocked page body element
            */
            unblockPage: function() {
                return mApp.unblock('body');
            },
    
            /**
            * Enable loader progress for button and other elements
            * @param {object} target jQuery element object
            * @param {object} options
            */
            progress: function(target, options) {
                var skin = (options && options.skin) ? options.skin : 'light';
                var alignment = (options && options.alignment) ? options.alignment : 'right'; 
                var size = (options && options.size) ? 'm-spinner--' + options.size : ''; 
                var classes = 'm-loader ' + 'm-loader--' + skin + ' m-loader--' + alignment + ' m-loader--' + size;
    
                mApp.unprogress(target);
                
                $(target).addClass(classes);
                $(target).data('progress-classes', classes);
            },
    
            /**
            * Disable loader progress for button and other elements
            * @param {object} target jQuery element object
            */
            unprogress: function(target) {
                $(target).removeClass($(target).data('progress-classes'));
            },
    
            /**
            * Gets state color's hex code by color name
            * @param {string} name Color name
            * @returns {string}  
            */
            getColor: function(name) {
                return colors[name];
            }
        };
    }();
    
    //== Initialize mApp class on document ready
    $(document).ready(function() {
        mApp.init({});
    });
    (function($) {
    
      var pluginName = 'mDatatable';
      var pfx = 'm-';
      var util = mUtil;
      var app = mApp;
    
      if (typeof util === 'undefined') throw new Error('Util class is required and must be included before ' + pluginName);
    
      // plugin setup
      $.fn[pluginName] = function(options) {
        if ($(this).length === 0) {
          console.log('No ' + pluginName + ' element exist.');
          return;
        }
    
        // global variables
        var datatable = this;
    
        // debug enabled?
        // 1) state will be cleared on each refresh
        // 2) enable some logs
        // 3) etc.
        datatable.debug = false;
    
        datatable.API = {
          record: null,
          value: null,
          params: null,
        };
    
        var Plugin = {
          /********************
           ** PRIVATE METHODS
           ********************/
          isInit: false,
          offset: 110,
          stateId: 'meta',
          ajaxParams: {},
    
          init: function(options) {
            var isHtmlTable = false;
            // data source option empty is normal table
            if (options.data.source === null) {
              Plugin.extractTable();
              isHtmlTable = true;
            }
    
            Plugin.setupBaseDOM.call();
            Plugin.setupDOM(datatable.table);
            Plugin.spinnerCallback(true);
    
            // set custom query from options
            Plugin.setDataSourceQuery(Plugin.getOption('data.source.read.params.query'));
    
            // on event after layout had done setup, show datatable
            $(datatable).on(pfx + 'datatable--on-layout-updated', Plugin.afterRender);
    
            if (datatable.debug) Plugin.stateRemove(Plugin.stateId);
    
            // initialize extensions
            $.each(Plugin.getOption('extensions'), function(extName, extOptions) {
              if (typeof $.fn[pluginName][extName] === 'function')
                new $.fn[pluginName][extName](datatable, extOptions);
            });
    
            // get data
            if (options.data.type === 'remote' || options.data.type === 'local') {
              if (options.data.saveState === false
                  || options.data.saveState.cookie === false
                  && options.data.saveState.webstorage === false) {
                Plugin.stateRemove(Plugin.stateId);
              }
              // get data for local datatable and local table
              if (options.data.type === 'local' && typeof options.data.source === 'object') {
                datatable.dataSet = datatable.originalDataSet = Plugin.dataMapCallback(options.data.source);
              }
              Plugin.dataRender();
            }
    
            if (!isHtmlTable) {
              // if not a html table, setup header
              Plugin.setHeadTitle();
              if (Plugin.getOption('layout.footer')) {
                Plugin.setHeadTitle(datatable.tableFoot);
              }
            }
    
            // hide header
            if (typeof options.layout.header !== 'undefined' &&
                options.layout.header === false) {
              $(datatable.table).find('thead').remove();
            }
    
            // hide footer
            if (typeof options.layout.footer !== 'undefined' &&
                options.layout.footer === false) {
              $(datatable.table).find('tfoot').remove();
            }
    
            // for normal and local data type, run layoutUpdate
            if (options.data.type === null ||
                options.data.type === 'local') {
              Plugin.setupCellField.call();
              Plugin.setupTemplateCell.call();
    
              // setup nested datatable, if option enabled
              Plugin.setupSubDatatable.call();
    
              // setup extra system column properties
              Plugin.setupSystemColumn.call();
              Plugin.redraw();
            }
    
            $(window).resize(Plugin.fullRender);
    
            $(datatable).height('');
    
            $(Plugin.getOption('search.input')).on('keyup', function(e) {
              if (Plugin.getOption('search.onEnter') && e.which !== 13) return;
              Plugin.search($(this).val());
            });
    
            return datatable;
          },
    
          /**
           * Extract static HTML table content into datasource
           */
          extractTable: function() {
            var columns = [];
            var headers = $(datatable).find('tr:first-child th').get().map(function(cell, i) {
              var field = $(cell).data('field');
              if (typeof field === 'undefined') {
                field = $(cell).text().trim();
              }
              var column = {field: field, title: field};
              for (var ii in options.columns) {
                if (options.columns[ii].field === field) {
                  column = $.extend(true, {}, options.columns[ii], column);
                }
              }
              columns.push(column);
              return field;
            });
            // auto create columns config
            options.columns = columns;
    
            var rowProp = [];
            var source = [];
    
            $(datatable).find('tr').each(function() {
              if ($(this).find('td').length) {
                rowProp.push($(this).prop('attributes'));
              }
              var td = {};
              $(this).find('td').each(function(i, cell) {
                td[headers[i]] = cell.innerHTML.trim();
              });
              if (!util.isEmpty(td)) {
                source.push(td);
              }
            });
    
            options.data.attr.rowProps = rowProp;
            options.data.source = source;
          },
    
          /**
           * One time layout update on init
           */
          layoutUpdate: function() {
            // setup nested datatable, if option enabled
            Plugin.setupSubDatatable.call();
    
            // setup extra system column properties
            Plugin.setupSystemColumn.call();
    
            // setup cell hover event
            Plugin.setupHover.call();
    
            if (typeof options.detail === 'undefined'
                // temporary disable lock column in subtable
                && Plugin.getDepth() === 1) {
              // lock columns handler
              Plugin.lockTable.call();
            }
    
            Plugin.columnHide.call();
    
            Plugin.resetScroll();
    
            if (!Plugin.isInit) {
              $(datatable).trigger(pfx + 'datatable--on-init', {table: $(datatable.wrap).attr('id'), options: options});
              Plugin.isInit = true;
            }
    
            $(datatable).trigger(pfx + 'datatable--on-layout-updated', {table: $(datatable.wrap).attr('id')});
          },
    
          lockTable: function() {
            // todo; revise lock table responsive
            var lock = {
              lockEnabled: false,
              init: function() {
                // check if table should be locked columns
                lock.lockEnabled = Plugin.lockEnabledColumns();
                if (lock.lockEnabled.left.length === 0 &&
                    lock.lockEnabled.right.length === 0) {
                  return;
                }
                lock.enable();
              },
              enable: function() {
                var enableLock = function(tablePart) {
                  // check if already has lock column
                  if ($(tablePart).find('.' + pfx + 'datatable__lock').length > 0) {
                    Plugin.log('Locked container already exist in: ', tablePart);
                    return;
                  }
                  // check if no rows exists
                  if ($(tablePart).find('.' + pfx + 'datatable__row').length === 0) {
                    Plugin.log('No row exist in: ', tablePart);
                    return;
                  }
    
                  // locked div container
                  var lockLeft = $('<div/>').addClass(pfx + 'datatable__lock ' + pfx + 'datatable__lock--left');
                  var lockScroll = $('<div/>').addClass(pfx + 'datatable__lock ' + pfx + 'datatable__lock--scroll');
                  var lockRight = $('<div/>').addClass(pfx + 'datatable__lock ' + pfx + 'datatable__lock--right');
    
                  $(tablePart).find('.' + pfx + 'datatable__row').each(function() {
                    var rowLeft = $('<tr/>').addClass(pfx + 'datatable__row').appendTo(lockLeft);
                    var rowScroll = $('<tr/>').addClass(pfx + 'datatable__row').appendTo(lockScroll);
                    var rowRight = $('<tr/>').addClass(pfx + 'datatable__row').appendTo(lockRight);
                    $(this).find('.' + pfx + 'datatable__cell').each(function() {
                      var locked = $(this).data('locked');
                      if (typeof locked !== 'undefined') {
                        if (typeof locked.left !== 'undefined' || locked === true) {
                          // default locked to left
                          $(this).appendTo(rowLeft);
                        }
                        if (typeof locked.right !== 'undefined') {
                          $(this).appendTo(rowRight);
                        }
                      } else {
                        $(this).appendTo(rowScroll);
                      }
                    });
                    // remove old row
                    $(this).remove();
                  });
    
                  if (lock.lockEnabled.left.length > 0) {
                    $(datatable.wrap).addClass(pfx + 'datatable--lock');
                    $(lockLeft).appendTo(tablePart);
                  }
                  if (lock.lockEnabled.left.length > 0 || lock.lockEnabled.right.length > 0) {
                    $(lockScroll).appendTo(tablePart);
                  }
                  if (lock.lockEnabled.right.length > 0) {
                    $(datatable.wrap).addClass(pfx + 'datatable--lock');
                    $(lockRight).appendTo(tablePart);
                  }
                };
    
                $(datatable.table).find('thead,tbody,tfoot').each(function() {
                  var tablePart = this;
                  if ($(this).find('.' + pfx + 'datatable__lock').length === 0) {
                    $(this).ready(function() {
                      enableLock(tablePart);
                    });
                  }
                });
              },
            };
            lock.init();
            return lock;
          },
    
          /**
           * Render everything for resize
           */
          fullRender: function() {
            if (Plugin.isLocked()) {
    
              $(datatable.tableHead).empty();
              Plugin.setHeadTitle();
              if (Plugin.getOption('layout.footer')) {
                $(datatable.tableFoot).empty();
                Plugin.setHeadTitle(datatable.tableFoot);
              }
    
              // todo; full render datatable for specific condition only
              Plugin.spinnerCallback(true);
              $(datatable.wrap).removeClass(pfx + 'datatable--loaded');
    
              Plugin.insertData();
            }
          },
    
          lockEnabledColumns: function() {
            var screen = $(window).width();
            var columns = options.columns;
            var enabled = {left: [], right: []};
            $.each(columns, function(i, column) {
              if (typeof column.locked !== 'undefined') {
                if (typeof column.locked.left !== 'undefined') {
                  if (util.getBreakpoint(column.locked.left) <= screen) {
                    enabled['left'].push(column.locked.left);
                  }
                }
                if (typeof column.locked.right !== 'undefined') {
                  if (util.getBreakpoint(column.locked.right) <= screen) {
                    enabled['right'].push(column.locked.right);
                  }
                }
              }
            });
            return enabled;
          },
    
          /**
           * After render event, called by '+pfx+'-datatable--on-layout-updated
           * @param e
           * @param args
           */
          afterRender: function(e, args) {
            if (args.table == $(datatable.wrap).attr('id')) {
              $(datatable).ready(function() {
                if (!Plugin.isLocked()) {
                  Plugin.redraw();
                  // work on non locked columns
                  if (Plugin.getOption('rows.autoHide')) {
                    Plugin.autoHide();
                    // reset row
                    $(datatable.table).find('.' + pfx + 'datatable__row').css('height', '');
                  }
                }
    
                Plugin.rowEvenOdd.call();
    
                // redraw locked columns table
                if (Plugin.isLocked()) Plugin.redraw();
                $(datatable.tableBody).css('visibility', '');
                $(datatable.wrap).addClass(pfx + 'datatable--loaded');
    
                Plugin.scrollbar.call();
    
                // Plugin.hoverColumn.call();
                Plugin.spinnerCallback(false);
                Plugin.sorting.call();
              });
            }
          },
    
          hoverTimer: 0,
          isScrolling: false,
          setupHover: function() {
            $(window).scroll(function(e) {
              // stop hover when scrolling
              clearTimeout(Plugin.hoverTimer);
              Plugin.isScrolling = true;
            });
    
            $(datatable.tableBody).find('.' + pfx + 'datatable__cell').off('mouseenter', 'mouseleave').on('mouseenter', function() {
              // reset scroll timer to hover class
              Plugin.hoverTimer = setTimeout(function() {
                Plugin.isScrolling = false;
              }, 200);
              if (Plugin.isScrolling) return;
    
              // normal table
              var row = $(this).closest('.' + pfx + 'datatable__row').addClass(pfx + 'datatable__row--hover');
              var index = $(row).index() + 1;
    
              // lock table
              $(row).closest('.' + pfx + 'datatable__lock').parent().find('.' + pfx + 'datatable__row:nth-child(' + index + ')').addClass(pfx + 'datatable__row--hover');
            }).on('mouseleave', function() {
              // normal table
              var row = $(this).closest('.' + pfx + 'datatable__row').removeClass(pfx + 'datatable__row--hover');
              var index = $(row).index() + 1;
    
              // look table
              $(row).closest('.' + pfx + 'datatable__lock').parent().find('.' + pfx + 'datatable__row:nth-child(' + index + ')').removeClass(pfx + 'datatable__row--hover');
            });
          },
    
          /**
           * Adjust width of locked table containers by resize handler
           * @returns {number}
           */
          adjustLockContainer: function() {
            if (!Plugin.isLocked()) return 0;
    
            // refer to head dimension
            var containerWidth = $(datatable.tableHead).width();
            var lockLeft = $(datatable.tableHead).find('.' + pfx + 'datatable__lock--left').width();
            var lockRight = $(datatable.tableHead).find('.' + pfx + 'datatable__lock--right').width();
    
            if (typeof lockLeft === 'undefined') lockLeft = 0;
            if (typeof lockRight === 'undefined') lockRight = 0;
    
            var lockScroll = Math.floor(containerWidth - lockLeft - lockRight);
            $(datatable.table).find('.' + pfx + 'datatable__lock--scroll').css('width', lockScroll);
    
            return lockScroll;
          },
    
          /**
           * todo; not in use
           */
          dragResize: function() {
            var pressed = false;
            var start = undefined;
            var startX, startWidth;
            $(datatable.tableHead).find('.' + pfx + 'datatable__cell').mousedown(function(e) {
              start = $(this);
              pressed = true;
              startX = e.pageX;
              startWidth = $(this).width();
              $(start).addClass(pfx + 'datatable__cell--resizing');
    
            }).mousemove(function(e) {
              if (pressed) {
                var i = $(start).index();
                var tableBody = $(datatable.tableBody);
                var ifLocked = $(start).closest('.' + pfx + 'datatable__lock');
    
                if (ifLocked) {
                  var lockedIndex = $(ifLocked).index();
                  tableBody = $(datatable.tableBody).find('.' + pfx + 'datatable__lock').eq(lockedIndex);
                }
    
                $(tableBody).find('.' + pfx + 'datatable__row').each(function(tri, tr) {
                  $(tr).find('.' + pfx + 'datatable__cell').eq(i).width(startWidth + (e.pageX - startX)).children().width(startWidth + (e.pageX - startX));
                });
    
                $(start).children().css('width', startWidth + (e.pageX - startX));
              }
    
            }).mouseup(function() {
              $(start).removeClass(pfx + 'datatable__cell--resizing');
              pressed = false;
            });
    
            $(document).mouseup(function() {
              $(start).removeClass(pfx + 'datatable__cell--resizing');
              pressed = false;
            });
          },
    
          /**
           * To prepare placeholder for table before content is loading
           */
          initHeight: function() {
            if (options.layout.height && options.layout.scroll) {
              var theadHeight = $(datatable.tableHead).find('.' + pfx + 'datatable__row').height();
              var tfootHeight = $(datatable.tableFoot).find('.' + pfx + 'datatable__row').height();
              var bodyHeight = options.layout.height;
              if (theadHeight > 0) {
                bodyHeight -= theadHeight;
              }
              if (tfootHeight > 0) {
                bodyHeight -= tfootHeight;
              }
              $(datatable.tableBody).css('max-height', bodyHeight);
    
              // set scrollable area fixed height
              $(datatable.tableBody).find('.' + pfx + 'datatable__lock--scroll').css('height', bodyHeight);
            }
          },
    
          /**
           * Setup base DOM (table, thead, tbody, tfoot) and create if not exist.
           */
          setupBaseDOM: function() {
            // keep original state before datatable initialize
            datatable.initialDatatable = $(datatable).clone();
    
            // main element
            if ($(datatable).prop('tagName') === 'TABLE') {
              // if main init element is <table>, wrap with div
              datatable.table = $(datatable).removeClass(pfx + 'datatable').addClass(pfx + 'datatable__table');
              if ($(datatable.table).parents('.' + pfx + 'datatable').length === 0) {
                datatable.table.wrap($('<div/>').addClass(pfx + 'datatable').addClass(pfx + 'datatable--' + options.layout.theme));
                datatable.wrap = $(datatable.table).parent();
              }
            } else {
              // create table
              datatable.wrap = $(datatable).addClass(pfx + 'datatable').addClass(pfx + 'datatable--' + options.layout.theme);
              datatable.table = $('<table/>').addClass(pfx + 'datatable__table').appendTo(datatable);
            }
    
            if (typeof options.layout.class !== 'undefined') {
              $(datatable.wrap).addClass(options.layout.class);
            }
    
            $(datatable.table).removeClass(pfx + 'datatable--destroyed').css('display', 'block');
    
            // force disable save state
            if (typeof $(datatable).attr('id') === 'undefined') {
              Plugin.setOption('data.saveState', false);
              $(datatable.table).attr('id', util.getUniqueID(pfx + 'datatable--'));
            }
    
            // predefine table height
            if (Plugin.getOption('layout.minHeight'))
              $(datatable.table).css('min-height', Plugin.getOption('layout.minHeight'));
    
            if (Plugin.getOption('layout.height'))
              $(datatable.table).css('max-height', Plugin.getOption('layout.height'));
    
            // for normal table load
            if (options.data.type === null) {
              $(datatable.table).css('width', '').css('display', '');
            }
    
            // create table head element
            datatable.tableHead = $(datatable.table).find('thead');
            if ($(datatable.tableHead).length === 0) {
              datatable.tableHead = $('<thead/>').prependTo(datatable.table);
            }
    
            // create table head element
            datatable.tableBody = $(datatable.table).find('tbody');
            if ($(datatable.tableBody).length === 0) {
              datatable.tableBody = $('<tbody/>').appendTo(datatable.table);
            }
    
            if (typeof options.layout.footer !== 'undefined' &&
                options.layout.footer) {
              // create table foot element
              datatable.tableFoot = $(datatable.table).find('tfoot');
              if ($(datatable.tableFoot).length === 0) {
                datatable.tableFoot = $('<tfoot/>').appendTo(datatable.table);
              }
            }
          },
    
          /**
           * Set column data before table manipulation.
           */
          setupCellField: function(tableParts) {
            if (typeof tableParts === 'undefined') tableParts = $(datatable.table).children();
            var columns = options.columns;
            $.each(tableParts, function(part, tablePart) {
              $(tablePart).find('.' + pfx + 'datatable__row').each(function(tri, tr) {
                // prepare data
                $(tr).find('.' + pfx + 'datatable__cell').each(function(tdi, td) {
                  if (typeof columns[tdi] !== 'undefined') {
                    $(td).data(columns[tdi]);
                  }
                });
              });
            });
          },
    
          /**
           * Set column template callback
           * @param tablePart
           */
          setupTemplateCell: function(tablePart) {
            if (typeof tablePart === 'undefined') tablePart = datatable.tableBody;
            var columns = options.columns;
            $(tablePart).find('.' + pfx + 'datatable__row').each(function(tri, tr) {
              // row data object, if any
              var obj = $(tr).data('obj') || {};
    
              // @deprecated in v5.0.6
              // obj['getIndex'] = function() {
              // 	return tri;
              // };
              // @deprecated in v5.0.6
              // obj['getDatatable'] = function() {
              // 	return datatable;
              // };
    
              // @deprecated in v5.0.6
              var rowCallback = Plugin.getOption('rows.callback');
              if (typeof rowCallback === 'function') {
                rowCallback($(tr), obj, tri);
              }
              // before template row callback
              var beforeTemplate = Plugin.getOption('rows.beforeTemplate');
              if (typeof beforeTemplate === 'function') {
                beforeTemplate($(tr), obj, tri);
              }
              // if data object is undefined, collect from table
              if (typeof obj === 'undefined') {
                obj = {};
                $(tr).find('.' + pfx + 'datatable__cell').each(function(tdi, td) {
                  // get column settings by field
                  var column = $.grep(columns, function(n, i) {
                    return $(td).data('field') === n.field;
                  })[0];
                  if (typeof column !== 'undefined') {
                    obj[column['field']] = $(td).text();
                  }
                });
              }
    
              $(tr).find('.' + pfx + 'datatable__cell').each(function(tdi, td) {
                // get column settings by field
                var column = $.grep(columns, function(n, i) {
                  return $(td).data('field') === n.field;
                })[0];
                if (typeof column !== 'undefined') {
                  // column template
                  if (typeof column.template !== 'undefined') {
                    var finalValue = '';
                    // template string
                    if (typeof column.template === 'string') {
                      finalValue = Plugin.dataPlaceholder(column.template, obj);
                    }
                    // template callback function
                    if (typeof column.template === 'function') {
                      finalValue = column.template(obj, tri, datatable);
                    }
                    var span = document.createElement('span');
                    span.innerHTML = finalValue;
                    // insert to cell, wrap with span
                    $(td).html(span);
    
                    // set span overflow
                    if (typeof column.overflow !== 'undefined') {
                      $(span).css('overflow', column.overflow);
                      $(span).css('position', 'relative');
                    }
                  }
                }
              });
    
              // after template row callback
              var afterTemplate = Plugin.getOption('rows.afterTemplate');
              if (typeof afterTemplate === 'function') {
                afterTemplate($(tr), obj, tri);
              }
            });
          },
    
          /**
           * Setup extra system column properties
           * Note: selector checkbox, subtable toggle
           */
          setupSystemColumn: function() {
            datatable.dataSet = datatable.dataSet || [];
            // no records available
            if (datatable.dataSet.length === 0) return;
    
            var columns = options.columns;
            $(datatable.tableBody).find('.' + pfx + 'datatable__row').each(function(tri, tr) {
              $(tr).find('.' + pfx + 'datatable__cell').each(function(tdi, td) {
                // get column settings by field
                var column = $.grep(columns, function(n, i) {
                  return $(td).data('field') === n.field;
                })[0];
                if (typeof column !== 'undefined') {
                  var value = $(td).text();
    
                  // enable column selector
                  if (typeof column.selector !== 'undefined' && column.selector !== false) {
                    // check if checkbox exist
                    if ($(td).find('.' + pfx + 'checkbox [type="checkbox"]').length > 0) return;
    
                    $(td).addClass(pfx + 'datatable__cell--check');
    
                    // append checkbox
                    var chk = $('<label/>').
                        addClass(pfx + 'checkbox ' + pfx + 'checkbox--single').
                        append($('<input/>').attr('type', 'checkbox').attr('value', value).on('click', function() {
                          if ($(this).is(':checked')) {
                            // add checkbox active row class
                            Plugin.setActive(this);
                          } else {
                            // add checkbox active row class
                            Plugin.setInactive(this);
                          }
                        })).
                        append('&nbsp;<span></span>');
    
                    // checkbox selector has outline style
                    if (typeof column.selector.class !== 'undefined') {
                      $(chk).addClass(column.selector.class);
                    }
    
                    $(td).children().html(chk);
                  }
    
                  // enable column subtable toggle
                  if (typeof column.subtable !== 'undefined' && column.subtable) {
                    // check if subtable toggle exist
                    if ($(td).find('.' + pfx + 'datatable__toggle-subtable').length > 0) return;
                    // append subtable toggle
                    $(td).
                        children().
                        html($('<a/>').
                            addClass(pfx + 'datatable__toggle-subtable').
                            attr('href', '#').
                            attr('data-value', value).
                            append($('<i/>').addClass(Plugin.getOption('layout.icons.rowDetail.collapse'))));
                  }
                }
              });
            });
    
            // init checkbox for header/footer
            var initCheckbox = function(tr) {
              // get column settings by field
              var column = $.grep(columns, function(n, i) {
                return typeof n.selector !== 'undefined' && n.selector !== false;
              })[0];
    
              if (typeof column !== 'undefined') {
                // enable column selector
                if (typeof column.selector !== 'undefined' && column.selector !== false) {
                  var td = $(tr).find('[data-field="' + column.field + '"]');
                  // check if checkbox exist
                  if ($(td).find('.' + pfx + 'checkbox [type="checkbox"]').length > 0) return;
    
                  $(td).addClass(pfx + 'datatable__cell--check');
    
                  // append checkbox
                  var chk = $('<label/>').
                      addClass(pfx + 'checkbox ' + pfx + 'checkbox--single ' + pfx + 'checkbox--all').
                      append($('<input/>').attr('type', 'checkbox').on('click', function() {
                        if ($(this).is(':checked')) {
                          Plugin.setActiveAll(true);
                        } else {
                          Plugin.setActiveAll(false);
                        }
                      })).
                      append('&nbsp;<span></span>');
    
                  // checkbox selector has outline style
                  if (typeof column.selector.class !== 'undefined') {
                    $(chk).addClass(column.selector.class);
                  }
    
                  $(td).children().html(chk);
                }
              }
            };
    
            if (options.layout.header) {
              initCheckbox($(datatable.tableHead).find('.' + pfx + 'datatable__row').first());
            }
            if (options.layout.footer) {
              initCheckbox($(datatable.tableFoot).find('.' + pfx + 'datatable__row').first());
            }
          },
    
          /**
           * Adjust width to match container size
           */
          adjustCellsWidth: function() {
            // get table width
            var containerWidth = $(datatable.tableHead).width();
    
            // offset reserved for sort icon
            var sortOffset = 20;
    
            // get total number of columns
            var columns = $(datatable.tableHead).find('.' + pfx + 'datatable__row:first-child').find('.' + pfx + 'datatable__cell:visible').length;
            if (columns > 0) {
              //  remove reserved sort icon width
              containerWidth = containerWidth - (sortOffset * columns);
              var minWidth = Math.floor(containerWidth / columns);
    
              // minimum width
              if (minWidth <= Plugin.offset) {
                minWidth = Plugin.offset;
              }
    
              $(datatable.table).find('.' + pfx + 'datatable__row').find('.' + pfx + 'datatable__cell:visible').each(function(tdi, td) {
                var width = minWidth;
                var dataWidth = $(td).data('width');
                if (typeof dataWidth !== 'undefined') {
                  width = dataWidth;
                }
                $(td).children().css('width', parseInt(width));
              });
            }
    
            return datatable;
          },
    
          /**
           * Adjust height to match container size
           */
          adjustCellsHeight: function() {
            $.each($(datatable.table).children(), function(part, tablePart) {
              var totalRows = $(tablePart).find('.' + pfx + 'datatable__row').first().parent().find('.' + pfx + 'datatable__row').length;
              for (var i = 1; i <= totalRows; i++) {
                var rows = $(tablePart).find('.' + pfx + 'datatable__row:nth-child(' + i + ')');
                if ($(rows).length > 0) {
                  var maxHeight = Math.max.apply(null, $(rows).map(function() {
                    return $(this).height();
                  }).get());
                  $(rows).css('height', Math.ceil(parseInt(maxHeight)));
                }
              }
            });
          },
    
          /**
           * Setup table DOM and classes
           */
          setupDOM: function(table) {
            // set table classes
            $(table).find('> thead').addClass(pfx + 'datatable__head');
            $(table).find('> tbody').addClass(pfx + 'datatable__body');
            $(table).find('> tfoot').addClass(pfx + 'datatable__foot');
            $(table).find('tr').addClass(pfx + 'datatable__row');
            $(table).find('tr > th, tr > td').addClass(pfx + 'datatable__cell');
            $(table).find('tr > th, tr > td').each(function(i, td) {
              if ($(td).find('span').length === 0) {
                $(td).wrapInner($('<span/>').css('width', Plugin.offset));
              }
            });
          },
    
          /**
           * Default scrollbar
           * @returns {{tableLocked: null, init: init, onScrolling: onScrolling}}
           */
          scrollbar: function() {
            var scroll = {
              scrollable: null,
              tableLocked: null,
              mcsOptions: {
                scrollInertia: 0,
                autoDraggerLength: true,
                autoHideScrollbar: true,
                autoExpandScrollbar: false,
                alwaysShowScrollbar: 0,
                mouseWheel: {
                  scrollAmount: 120,
                  preventDefault: false,
                },
                advanced: {
                  updateOnContentResize: true,
                  autoExpandHorizontalScroll: true,
                },
                theme: 'minimal-dark',
              },
              init: function() {
                var screen = util.getViewPort().width;
                // setup scrollable datatable
                if (options.layout.scroll) {
                  // add scrollable datatable class
                  $(datatable.wrap).addClass(pfx + 'datatable--scroll');
    
                  var scrollable = $(datatable.tableBody).find('.' + pfx + 'datatable__lock--scroll');
    
                  // check if scrollable area have rows
                  if ($(scrollable).find('.' + pfx + 'datatable__row').length > 0 && $(scrollable).length > 0) {
                    scroll.scrollHead = $(datatable.tableHead).find('> .' + pfx + 'datatable__lock--scroll > .' + pfx + 'datatable__row');
                    scroll.scrollFoot = $(datatable.tableFoot).find('> .' + pfx + 'datatable__lock--scroll > .' + pfx + 'datatable__row');
                    scroll.tableLocked = $(datatable.tableBody).find('.' + pfx + 'datatable__lock:not(.' + pfx + 'datatable__lock--scroll)');
                    if (Plugin.getOption('layout.customScrollbar') && util.detectIE() != 10 && screen > util.getBreakpoint('lg')) {
                      scroll.initCustomScrollbar(scrollable[0]);
                    } else {
                      scroll.initDefaultScrollbar(scrollable);
                    }
                  } else if ($(datatable.tableBody).find('.' + pfx + 'datatable__row').length > 0) {
                    scroll.scrollHead = $(datatable.tableHead).find('> .' + pfx + 'datatable__row');
                    scroll.scrollFoot = $(datatable.tableFoot).find('> .' + pfx + 'datatable__row');
                    if (Plugin.getOption('layout.customScrollbar') && util.detectIE() != 10 && screen > util.getBreakpoint('lg')) {
                      scroll.initCustomScrollbar(datatable.tableBody);
                    } else {
                      scroll.initDefaultScrollbar(datatable.tableBody);
                    }
                  }
                } else {
                  $(datatable.table).// css('height', 'auto').
                  css('overflow-x', 'auto');
                }
              },
              initDefaultScrollbar: function(scrollable) {
                $(scrollable).css('overflow', 'auto').off().on('scroll', scroll.onScrolling);
              },
              onScrolling: function(e) {
                var left = $(this).scrollLeft();
                var top = $(this).scrollTop();
                $(scroll.scrollHead).css('left', -left);
                $(scroll.scrollFoot).css('left', -left);
                $(scroll.tableLocked).each(function(i, table) {
                  $(table).css('top', -top);
                });
              },
              initCustomScrollbar: function(scrollable) {
                scroll.scrollable = scrollable;
                // create a new instance for table body with scrollbar
                Plugin.initScrollbar(scrollable);
                $(scrollable).off().on('scroll', scroll.onScrolling);
              },
            };
            scroll.init();
            return scroll;
          },
    
          /**
           * Init custom scrollbar and reset position
           * @param element
           * @param options
           */
          initScrollbar: function(element, options) {
            $(datatable.tableBody).css('overflow', '');
            if (util.hasClass(element, 'ps')) {
              $(element).data('ps').update();
            } else {
              var ps = new PerfectScrollbar(element);
              $(element).data('ps', ps);
            }
          },
    
          /**
           * Set column title from options.columns settings
           */
          setHeadTitle: function(tablePart) {
            if (typeof tablePart === 'undefined') tablePart = datatable.tableHead;
            tablePart = $(tablePart)[0];
            var columns = options.columns;
            var row = tablePart.getElementsByTagName('tr')[0];
            var ths = tablePart.getElementsByTagName('td');
    
            if (typeof row === 'undefined') {
              row = document.createElement('tr');
              tablePart.appendChild(row);
            }
    
            $.each(columns, function(i, column) {
              var th = ths[i];
              if (typeof th === 'undefined') {
                th = document.createElement('th');
                row.appendChild(th);
              }
    
              // set column title
              if (typeof column['title'] !== 'undefined') {
                th.innerHTML = column.title;
                th.setAttribute('data-field', column.field);
                util.addClass(th, column.class);
                $(th).data(column);
              }
    
              // set header attr option
              if (typeof column.attr !== 'undefined') {
                $.each(column.attr, function(key, val) {
                  th.setAttribute(key, val);
                });
              }
    
              // apply text align to thead/tfoot
              if (typeof column.textAlign !== 'undefined') {
                var align = typeof datatable.textAlign[column.textAlign] !== 'undefined' ? datatable.textAlign[column.textAlign] : '';
                util.addClass(th, align);
              }
            });
            Plugin.setupDOM(tablePart);
          },
    
          /**
           * Initiate to get remote or local data via ajax
           */
          dataRender: function(action) {
            $(datatable.table).siblings('.' + pfx + 'datatable__pager').removeClass(pfx + 'datatable--paging-loaded');
    
            var buildMeta = function() {
              datatable.dataSet = datatable.dataSet || [];
              Plugin.localDataUpdate();
              // local pagination meta
              var meta = Plugin.getDataSourceParam('pagination');
              if (meta.perpage === 0) {
                meta.perpage = options.data.pageSize || 10;
              }
              meta.total = datatable.dataSet.length;
              var start = Math.max(meta.perpage * (meta.page - 1), 0);
              var end = Math.min(start + meta.perpage, meta.total);
              datatable.dataSet = $(datatable.dataSet).slice(start, end);
              return meta;
            };
    
            var afterGetData = function(result) {
              var localPagingCallback = function(ctx, meta) {
                if (!$(ctx.pager).hasClass(pfx + 'datatable--paging-loaded')) {
                  $(ctx.pager).remove();
                  ctx.init(meta);
                }
                $(ctx.pager).off().on(pfx + 'datatable--on-goto-page', function(e) {
                  $(ctx.pager).remove();
                  ctx.init(meta);
                });
    
                var start = Math.max(meta.perpage * (meta.page - 1), 0);
                var end = Math.min(start + meta.perpage, meta.total);
    
                Plugin.localDataUpdate();
                datatable.dataSet = $(datatable.dataSet).slice(start, end);
    
                // insert data into table content
                Plugin.insertData();
              };
    
              $(datatable.wrap).removeClass(pfx + 'datatable--error');
              // pagination enabled
              if (options.pagination) {
                if (options.data.serverPaging && options.data.type !== 'local') {
                  // server pagination
                  var serverMeta = Plugin.getObject('meta', result || null);
                  if (serverMeta !== null) {
                    Plugin.paging(serverMeta);
                  } else {
                    // no meta object from server response, fallback to local pagination
                    Plugin.paging(buildMeta(), localPagingCallback);
                  }
                } else {
                  // local pagination can be used by remote data also
                  Plugin.paging(buildMeta(), localPagingCallback);
                }
              } else {
                // pagination is disabled
                Plugin.localDataUpdate();
              }
              // insert data into table content
              Plugin.insertData();
            };
    
            // get local datasource
            if (options.data.type === 'local'
                // for remote json datasource
                // || typeof options.data.source.read === 'undefined' && datatable.dataSet !== null
                // for remote datasource, server sorting is disabled and data already received from remote
                || options.data.serverSorting === false && action === 'sort'
                || options.data.serverFiltering === false && action === 'search'
            ) {
              afterGetData();
              return;
            }
    
            // getting data from remote only
            Plugin.getData().done(afterGetData);
          },
    
          /**
           * Process ajax data
           */
          insertData: function() {
            datatable.dataSet = datatable.dataSet || [];
            var params = Plugin.getDataSourceParam();
    
            // get row attributes
            var pagination = params.pagination;
            var start = (Math.max(pagination.page, 1) - 1) * pagination.perpage;
            var end = Math.min(pagination.page, pagination.pages) * pagination.perpage;
            var rowProps = {};
            if (typeof options.data.attr.rowProps !== 'undefined' && options.data.attr.rowProps.length) {
              rowProps = options.data.attr.rowProps.slice(start, end);
            }
    
            // todo; fix performance
            var tableBody = document.createElement('tbody');
            tableBody.style.visibility = 'hidden';
            var colLength = options.columns.length;
    
            $.each(datatable.dataSet, function(rowIndex, row) {
              var tr = document.createElement('tr');
              tr.setAttribute('data-row', rowIndex);
              // keep data object to row
              $(tr).data('obj', row);
    
              if (typeof rowProps[rowIndex] !== 'undefined') {
                $.each(rowProps[rowIndex], function() {
                  tr.setAttribute(this.name, this.value);
                });
              }
    
              var cellIndex = 0;
              var tds = [];
              for (var a = 0; a < colLength; a += 1) {
                var column = options.columns[a];
                var classes = [];
                // add sorted class to cells
                if (Plugin.getObject('sort.field', params) === column.field) {
                  classes.push(pfx + 'datatable__cell--sorted');
                }
    
                // apply text align
                if (typeof column.textAlign !== 'undefined') {
                  var align = typeof datatable.textAlign[column.textAlign] !==
                  'undefined' ? datatable.textAlign[column.textAlign] : '';
                  classes.push(align);
                }
    
                // var classAttr = '';
                if (typeof column.class !== 'undefined') {
                  classes.push(column.class);
                }
    
                var td = document.createElement('td');
                util.addClass(td, classes.join(' '));
                td.setAttribute('data-field', column.field);
                td.innerHTML = Plugin.getObject(column.field, row);
                tr.appendChild(td);
              }
    
              tableBody.appendChild(tr);
            });
    
            // display no records message
            if (datatable.dataSet.length === 0) {
              var errorSpan = document.createElement('span');
              util.addClass(errorSpan, pfx + 'datatable--error');
              errorSpan.innerHTML = Plugin.getOption('translate.records.noRecords');
              tableBody.appendChild(errorSpan);
              $(datatable.wrap).addClass(pfx + 'datatable--error ' + pfx + 'datatable--loaded');
              Plugin.spinnerCallback(false);
            }
    
            // replace existing table body
            $(datatable.tableBody).replaceWith(tableBody);
            datatable.tableBody = tableBody;
    
            // layout update
            Plugin.setupDOM(datatable.table);
            Plugin.setupCellField([datatable.tableBody]);
            Plugin.setupTemplateCell(datatable.tableBody);
            Plugin.layoutUpdate();
          },
    
          updateTableComponents: function() {
            datatable.tableHead = $(datatable.table).children('thead');
            datatable.tableBody = $(datatable.table).children('tbody');
            datatable.tableFoot = $(datatable.table).children('tfoot');
          },
    
          /**
           * Call ajax for raw JSON data
           */
          getData: function() {
            Plugin.spinnerCallback(true);
    
            var ajaxParams = {
              dataType: 'json',
              method: 'GET',
              data: {},
              timeout: Plugin.getOption('data.source.read.timeout') || 30000,
            };
    
            if (options.data.type === 'local') {
              ajaxParams.url = options.data.source;
            }
    
            if (options.data.type === 'remote') {
              ajaxParams.url = Plugin.getOption('data.source.read.url');
              if (typeof ajaxParams.url !== 'string') ajaxParams.url = Plugin.getOption('data.source.read');
              if (typeof ajaxParams.url !== 'string') ajaxParams.url = Plugin.getOption('data.source');
              ajaxParams.headers = Plugin.getOption('data.source.read.headers');
              ajaxParams.method = Plugin.getOption('data.source.read.method') || 'POST';
    
              var data = Plugin.getDataSourceParam();
              // remove if server params is not enabled
              if (!Plugin.getOption('data.serverPaging')) {
                delete data['pagination'];
              }
              if (!Plugin.getOption('data.serverSorting')) {
                delete data['sort'];
              }
              ajaxParams.data = $.extend({}, ajaxParams.data, data, Plugin.getOption('data.source.read.params'));
            }
    
            return $.ajax(ajaxParams).done(function(response, textStatus, jqXHR) {
              datatable.lastResponse = response;
              // extendible data map callback for custom datasource
              datatable.dataSet = datatable.originalDataSet = Plugin.dataMapCallback(response);
              Plugin.setAutoColumns();
              $(datatable).trigger(pfx + 'datatable--on-ajax-done', [datatable.dataSet]);
            }).fail(function(jqXHR, textStatus, errorThrown) {
              $(datatable).trigger(pfx + 'datatable--on-ajax-fail', [jqXHR]);
              $(datatable.tableBody).html($('<span/>').addClass(pfx + 'datatable--error').html(Plugin.getOption('translate.records.noRecords')));
              $(datatable.wrap).addClass(pfx + 'datatable--error ' + pfx + 'datatable--loaded');
              Plugin.spinnerCallback(false);
            }).always(function() {
            });
          },
    
          /**
           * Pagination object
           * @param meta if null, local pagination, otherwise remote pagination
           * @param callback for update data when navigating page
           */
          paging: function(meta, callback) {
            var pg = {
              meta: null,
              pager: null,
              paginateEvent: null,
              pagerLayout: {pagination: null, info: null},
              callback: null,
              init: function(meta) {
                pg.meta = meta;
    
                // parse pagination meta to integer
                pg.meta.page = parseInt(pg.meta.page);
                pg.meta.pages = parseInt(pg.meta.pages);
                pg.meta.perpage = parseInt(pg.meta.perpage);
                pg.meta.total = parseInt(pg.meta.total);
    
                // todo; if meta object not exist will cause error
                // always recount total pages
                pg.meta.pages = Math.max(Math.ceil(pg.meta.total / pg.meta.perpage), 1);
    
                // current page must be not over than total pages
                if (pg.meta.page > pg.meta.pages) pg.meta.page = pg.meta.pages;
    
                // set unique event name between tables
                pg.paginateEvent = Plugin.getTablePrefix();
    
                pg.pager = $(datatable.table).siblings('.' + pfx + 'datatable__pager');
                if ($(pg.pager).hasClass(pfx + 'datatable--paging-loaded')) return;
    
                // if class .'+pfx+'datatable--paging-loaded not exist, recreate pagination
                $(pg.pager).remove();
    
                // if no pages available
                if (pg.meta.pages === 0) return;
    
                // update datasource params
                Plugin.setDataSourceParam('pagination', {
                  page: pg.meta.page,
                  pages: pg.meta.pages,
                  perpage: pg.meta.perpage,
                  total: pg.meta.total,
                });
    
                // default callback function, contains remote pagination handler
                pg.callback = pg.serverCallback;
                // custom callback function
                if (typeof callback === 'function') pg.callback = callback;
    
                pg.addPaginateEvent();
                pg.populate();
    
                pg.meta.page = Math.max(pg.meta.page || 1, pg.meta.page);
    
                $(datatable).trigger(pg.paginateEvent, pg.meta);
    
                pg.pagingBreakpoint.call();
                $(window).resize(pg.pagingBreakpoint);
              },
              serverCallback: function(ctx, meta) {
                Plugin.dataRender();
              },
              populate: function() {
                var icons = Plugin.getOption('layout.icons.pagination');
                var title = Plugin.getOption('translate.toolbar.pagination.items.default');
                // pager root element
                pg.pager = $('<div/>').addClass(pfx + 'datatable__pager ' + pfx + 'datatable--paging-loaded clearfix');
                // numbering links
                var pagerNumber = $('<ul/>').addClass(pfx + 'datatable__pager-nav');
                pg.pagerLayout['pagination'] = pagerNumber;
    
                // pager first/previous button
                $('<li/>').
                    append($('<a/>').
                        attr('title', title.first).
                        addClass(pfx + 'datatable__pager-link ' + pfx + 'datatable__pager-link--first').
                        append($('<i/>').addClass(icons.first)).
                        on('click', pg.gotoMorePage).
                        attr('data-page', 1)).
                    appendTo(pagerNumber);
                $('<li/>').
                    append($('<a/>').
                        attr('title', title.prev).
                        addClass(pfx + 'datatable__pager-link ' + pfx + 'datatable__pager-link--prev').
                        append($('<i/>').addClass(icons.prev)).
                        on('click', pg.gotoMorePage)).
                    appendTo(pagerNumber);
    
                // more previous pages
                $('<li/>').
                    append($('<a/>').
                        attr('title', title.more).
                        addClass(pfx + 'datatable__pager-link ' + pfx + 'datatable__pager-link--more-prev').
                        html($('<i/>').addClass(icons.more)).
                        on('click', pg.gotoMorePage)).
                    appendTo(pagerNumber);
    
                $('<li/>').append($('<input/>').attr('type', 'text').addClass(pfx + 'pager-input form-control').attr('title', title.input).on('keyup', function() {
                  // on keyup update [data-page]
                  $(this).attr('data-page', Math.abs($(this).val()));
                }).on('keypress', function(e) {
                  // on keypressed enter button
                  if (e.which === 13) pg.gotoMorePage(e);
                })).appendTo(pagerNumber);
    
                var pagesNumber = Plugin.getOption('toolbar.items.pagination.pages.desktop.pagesNumber');
                var end = Math.ceil(pg.meta.page / pagesNumber) * pagesNumber;
                var start = end - pagesNumber;
                if (end > pg.meta.pages) {
                  end = pg.meta.pages;
                }
                for (var x = start; x < end; x++) {
                  var pageNumber = x + 1;
                  $('<li/>').
                      append($('<a/>').
                          addClass(pfx + 'datatable__pager-link ' + pfx + 'datatable__pager-link-number').
                          text(pageNumber).
                          attr('data-page', pageNumber).
                          attr('title', pageNumber).
                          on('click', pg.gotoPage)).
                      appendTo(pagerNumber);
                }
    
                // more next pages
                $('<li/>').
                    append($('<a/>').
                        attr('title', title.more).
                        addClass(pfx + 'datatable__pager-link ' + pfx + 'datatable__pager-link--more-next').
                        html($('<i/>').addClass(icons.more)).
                        on('click', pg.gotoMorePage)).
                    appendTo(pagerNumber);
    
                // pager next/last button
                $('<li/>').
                    append($('<a/>').
                        attr('title', title.next).
                        addClass(pfx + 'datatable__pager-link ' + pfx + 'datatable__pager-link--next').
                        append($('<i/>').addClass(icons.next)).
                        on('click', pg.gotoMorePage)).
                    appendTo(pagerNumber);
                $('<li/>').
                    append($('<a/>').
                        attr('title', title.last).
                        addClass(pfx + 'datatable__pager-link ' + pfx + 'datatable__pager-link--last').
                        append($('<i/>').addClass(icons.last)).
                        on('click', pg.gotoMorePage).
                        attr('data-page', pg.meta.pages)).
                    appendTo(pagerNumber);
    
                // page info
                if (Plugin.getOption('toolbar.items.info')) {
                  pg.pagerLayout['info'] = $('<div/>').addClass(pfx + 'datatable__pager-info').append($('<span/>').addClass(pfx + 'datatable__pager-detail'));
                }
    
                $.each(Plugin.getOption('toolbar.layout'), function(i, layout) {
                  $(pg.pagerLayout[layout]).appendTo(pg.pager);
                });
    
                // page size select
                var pageSizeSelect = $('<select/>').
                    addClass('selectpicker ' + pfx + 'datatable__pager-size').
                    attr('title', Plugin.getOption('translate.toolbar.pagination.items.default.select')).
                    attr('data-width', '70px').
                    val(pg.meta.perpage).
                    on('change', pg.updatePerpage).
                    prependTo(pg.pagerLayout['info']);
    
                var pageSizes = Plugin.getOption('toolbar.items.pagination.pageSizeSelect');
                // default value here, to fix override option by user
                if (pageSizes.length == 0) pageSizes = [10, 20, 30, 50, 100];
                $.each(pageSizes, function(i, size) {
                  var display = size;
                  if (size === -1) display = 'All';
                  $('<option/>').attr('value', size).html(display).appendTo(pageSizeSelect);
                });
    
                // init selectpicker to dropdown
                $(datatable).ready(function() {
                  $('.selectpicker').selectpicker().siblings('.dropdown-toggle').attr('title', Plugin.getOption(
                      'translate.toolbar.pagination.items.default.select'));
                });
    
                pg.paste();
              },
              paste: function() {
                // insert pagination based on placement position, top|bottom
                $.each($.unique(Plugin.getOption('toolbar.placement')),
                    function(i, position) {
                      if (position === 'bottom') {
                        $(pg.pager).clone(true).insertAfter(datatable.table);
                      }
                      if (position === 'top') {
                        // pager top need some extra space
                        $(pg.pager).clone(true).addClass(pfx + 'datatable__pager--top').insertBefore(datatable.table);
                      }
                    });
              },
              gotoMorePage: function(e) {
                e.preventDefault();
                // $(this) is a link of .'+pfx+'datatable__pager-link
    
                if ($(this).attr('disabled') === 'disabled') return false;
    
                var page = $(this).attr('data-page');
    
                // event from text input
                if (typeof page === 'undefined') {
                  page = $(e.target).attr('data-page');
                }
    
                pg.openPage(parseInt(page));
                return false;
              },
              gotoPage: function(e) {
                e.preventDefault();
                // prevent from click same page number
                if ($(this).hasClass(pfx + 'datatable__pager-link--active')) return;
    
                pg.openPage(parseInt($(this).data('page')));
              },
              openPage: function(page) {
                // currentPage is 1-based index
                pg.meta.page = parseInt(page);
    
                $(datatable).trigger(pg.paginateEvent, pg.meta);
                pg.callback(pg, pg.meta);
    
                // update page callback function
                $(pg.pager).trigger(pfx + 'datatable--on-goto-page', pg.meta);
              },
              updatePerpage: function(e) {
                e.preventDefault();
                // if (Plugin.getOption('layout.height') === null) {
                // fix white space, when perpage is set from many records to less records
                // $('html, body').animate({scrollTop: $(datatable).position().top});
                // }
    
                pg.pager = $(datatable.table).siblings('.' + pfx + 'datatable__pager').removeClass(pfx + 'datatable--paging-loaded');
    
                // on change select page size
                if (e.originalEvent) {
                  pg.meta.perpage = parseInt($(this).val());
                }
    
                $(pg.pager).find('select.' + pfx + 'datatable__pager-size').val(pg.meta.perpage).attr('data-selected', pg.meta.perpage);
    
                // update datasource params
                Plugin.setDataSourceParam('pagination', {
                  page: pg.meta.page,
                  pages: pg.meta.pages,
                  perpage: pg.meta.perpage,
                  total: pg.meta.total,
                });
    
                // update page callback function
                $(pg.pager).trigger(pfx + 'datatable--on-update-perpage', pg.meta);
                $(datatable).trigger(pg.paginateEvent, pg.meta);
                pg.callback(pg, pg.meta);
    
                // update pagination info
                pg.updateInfo.call();
              },
              addPaginateEvent: function(e) {
                // pagination event
                $(datatable).off(pg.paginateEvent).on(pg.paginateEvent, function(e, meta) {
                  Plugin.spinnerCallback(true);
    
                  pg.pager = $(datatable.table).siblings('.' + pfx + 'datatable__pager');
                  var pagerNumber = $(pg.pager).find('.' + pfx + 'datatable__pager-nav');
    
                  // set sync active page class
                  $(pagerNumber).find('.' + pfx + 'datatable__pager-link--active').removeClass(pfx + 'datatable__pager-link--active');
                  $(pagerNumber).find('.' + pfx + 'datatable__pager-link-number[data-page="' + meta.page + '"]').addClass(pfx + 'datatable__pager-link--active');
    
                  // set next and previous link page number
                  $(pagerNumber).find('.' + pfx + 'datatable__pager-link--prev').attr('data-page', Math.max(meta.page - 1, 1));
                  $(pagerNumber).find('.' + pfx + 'datatable__pager-link--next').attr('data-page', Math.min(meta.page + 1, meta.pages));
    
                  // current page input value sync
                  $(pg.pager).each(function() {
                    $(this).find('.' + pfx + 'pager-input[type="text"]').prop('value', meta.page);
                  });
    
                  $(pg.pager).find('.' + pfx + 'datatable__pager-nav').show();
                  if (meta.pages <= 1) {
                    // hide pager if has 1 page
                    $(pg.pager).find('.' + pfx + 'datatable__pager-nav').hide();
                  }
    
                  // update datasource params
                  Plugin.setDataSourceParam('pagination', {
                    page: pg.meta.page,
                    pages: pg.meta.pages,
                    perpage: pg.meta.perpage,
                    total: pg.meta.total,
                  });
    
                  $(pg.pager).find('select.' + pfx + 'datatable__pager-size').val(meta.perpage).attr('data-selected', meta.perpage);
    
                  // clear active rows
                  $(datatable.table).find('.' + pfx + 'checkbox > [type="checkbox"]').prop('checked', false);
                  $(datatable.table).find('.' + pfx + 'datatable__row--active').removeClass(pfx + 'datatable__row--active');
    
                  pg.updateInfo.call();
                  pg.pagingBreakpoint.call();
                  // Plugin.resetScroll();
                });
              },
              updateInfo: function() {
                var start = Math.max(pg.meta.perpage * (pg.meta.page - 1) + 1, 1);
                var end = Math.min(start + pg.meta.perpage - 1, pg.meta.total);
                // page info update
                $(pg.pager).find('.' + pfx + 'datatable__pager-info').find('.' + pfx + 'datatable__pager-detail').html(Plugin.dataPlaceholder(
                    Plugin.getOption('translate.toolbar.pagination.items.info'), {
                      start: start,
                      end: pg.meta.perpage === -1 ? pg.meta.total : end,
                      pageSize: pg.meta.perpage === -1 ||
                      pg.meta.perpage >= pg.meta.total
                          ? pg.meta.total
                          : pg.meta.perpage,
                      total: pg.meta.total,
                    }));
              },
    
              /**
               * Update pagination layout breakpoint
               */
              pagingBreakpoint: function() {
                // keep page links reference
                var pagerNumber = $(datatable.table).siblings('.' + pfx + 'datatable__pager').find('.' + pfx + 'datatable__pager-nav');
                if ($(pagerNumber).length === 0) return;
    
                var currentPage = Plugin.getCurrentPage();
                var pagerInput = $(pagerNumber).find('.' + pfx + 'pager-input').closest('li');
    
                // reset
                $(pagerNumber).find('li').show();
    
                // pagination update
                $.each(Plugin.getOption('toolbar.items.pagination.pages'),
                    function(mode, option) {
                      if (util.isInResponsiveRange(mode)) {
                        switch (mode) {
                          case 'desktop':
                          case 'tablet':
                            var end = Math.ceil(currentPage / option.pagesNumber) *
                                option.pagesNumber;
                            var start = end - option.pagesNumber;
                            $(pagerInput).hide();
                            pg.meta = Plugin.getDataSourceParam('pagination');
                            pg.paginationUpdate();
                            break;
    
                          case 'mobile':
                            $(pagerInput).show();
                            $(pagerNumber).find('.' + pfx + 'datatable__pager-link--more-prev').closest('li').hide();
                            $(pagerNumber).find('.' + pfx + 'datatable__pager-link--more-next').closest('li').hide();
                            $(pagerNumber).find('.' + pfx + 'datatable__pager-link-number').closest('li').hide();
                            break;
                        }
    
                        return false;
                      }
                    });
              },
    
              /**
               * Update pagination number and button display
               */
              paginationUpdate: function() {
                var pager = $(datatable.table).siblings('.' + pfx + 'datatable__pager').find('.' + pfx + 'datatable__pager-nav'),
                    pagerMorePrev = $(pager).find('.' + pfx + 'datatable__pager-link--more-prev'),
                    pagerMoreNext = $(pager).find('.' + pfx + 'datatable__pager-link--more-next'),
                    pagerFirst = $(pager).find('.' + pfx + 'datatable__pager-link--first'),
                    pagerPrev = $(pager).find('.' + pfx + 'datatable__pager-link--prev'),
                    pagerNext = $(pager).find('.' + pfx + 'datatable__pager-link--next'),
                    pagerLast = $(pager).find('.' + pfx + 'datatable__pager-link--last');
    
                // get visible page
                var pagerNumber = $(pager).find('.' + pfx + 'datatable__pager-link-number');
                // get page before of first visible
                var morePrevPage = Math.max($(pagerNumber).first().data('page') - 1,
                    1);
                $(pagerMorePrev).each(function(i, prev) {
                  $(prev).attr('data-page', morePrevPage);
                });
                // show/hide <li>
                if (morePrevPage === 1) {
                  $(pagerMorePrev).parent().hide();
                } else {
                  $(pagerMorePrev).parent().show();
                }
    
                // get page after of last visible
                var moreNextPage = Math.min($(pagerNumber).last().data('page') + 1,
                    pg.meta.pages);
                $(pagerMoreNext).each(function(i, prev) {
                  $(pagerMoreNext).attr('data-page', moreNextPage).show();
                });
    
                // show/hide <li>
                if (moreNextPage === pg.meta.pages
                    // missing dot fix when last hidden page is one left
                    && moreNextPage === $(pagerNumber).last().data('page')) {
                  $(pagerMoreNext).parent().hide();
                } else {
                  $(pagerMoreNext).parent().show();
                }
    
                // begin/end of pages
                if (pg.meta.page === 1) {
                  $(pagerFirst).attr('disabled', true).addClass(pfx + 'datatable__pager-link--disabled');
                  $(pagerPrev).attr('disabled', true).addClass(pfx + 'datatable__pager-link--disabled');
                } else {
                  $(pagerFirst).removeAttr('disabled').removeClass(pfx + 'datatable__pager-link--disabled');
                  $(pagerPrev).removeAttr('disabled').removeClass(pfx + 'datatable__pager-link--disabled');
                }
                if (pg.meta.page === pg.meta.pages) {
                  $(pagerNext).attr('disabled', true).addClass(pfx + 'datatable__pager-link--disabled');
                  $(pagerLast).attr('disabled', true).addClass(pfx + 'datatable__pager-link--disabled');
                } else {
                  $(pagerNext).removeAttr('disabled').removeClass(pfx + 'datatable__pager-link--disabled');
                  $(pagerLast).removeAttr('disabled').removeClass(pfx + 'datatable__pager-link--disabled');
                }
    
                // display more buttons
                var nav = Plugin.getOption('toolbar.items.pagination.navigation');
                if (!nav.first) $(pagerFirst).remove();
                if (!nav.prev) $(pagerPrev).remove();
                if (!nav.next) $(pagerNext).remove();
                if (!nav.last) $(pagerLast).remove();
              },
            };
            pg.init(meta);
            return pg;
          },
    
          /**
           * Hide/show table cell defined by
           * options[columns][i][responsive][visible/hidden]
           */
          columnHide: function() {
            var screen = util.getViewPort().width;
            // foreach columns setting
            $.each(options.columns, function(i, column) {
              if (typeof column.responsive !== 'undefined') {
                var field = column.field;
                var tds = $.grep($(datatable.table).find('.' + pfx + 'datatable__cell'), function(n, i) {
                  return field === $(n).data('field');
                });
                if (util.getBreakpoint(column.responsive.hidden) >= screen) {
                  $(tds).hide();
                } else {
                  $(tds).show();
                }
                if (util.getBreakpoint(column.responsive.visible) <= screen) {
                  $(tds).show();
                } else {
                  $(tds).hide();
                }
              }
            });
          },
    
          /**
           * Setup sub datatable
           */
          setupSubDatatable: function() {
            var subTableCallback = Plugin.getOption('detail.content');
            if (typeof subTableCallback !== 'function') return;
    
            // subtable already exist
            if ($(datatable.table).find('.' + pfx + 'datatable__subtable').length > 0) return;
    
            $(datatable.wrap).addClass(pfx + 'datatable--subtable');
    
            options.columns[0]['subtable'] = true;
    
            // toggle on open sub table
            var toggleSubTable = function(e) {
              e.preventDefault();
              // get parent row of this subtable
              var parentRow = $(this).closest('.' + pfx + 'datatable__row');
    
              // get subtable row for sub table
              var subTableRow = $(parentRow).next('.' + pfx + 'datatable__row-subtable');
              if ($(subTableRow).length === 0) {
                // prepare DOM for sub table, each <tr> as parent and add <tr> as child table
                subTableRow = $('<tr/>').
                    addClass(pfx + 'datatable__row-subtable ' + pfx + 'datatable__row-loading').
                    hide().
                    append($('<td/>').addClass(pfx + 'datatable__subtable').attr('colspan', Plugin.getTotalColumns()));
                $(parentRow).after(subTableRow);
                // add class to even row
                if ($(parentRow).hasClass(pfx + 'datatable__row--even')) {
                  $(subTableRow).addClass(pfx + 'datatable__row-subtable--even');
                }
              }
    
              $(subTableRow).toggle();
    
              var subTable = $(subTableRow).find('.' + pfx + 'datatable__subtable');
    
              // get id from first column of parent row
              var primaryKey = $(this).closest('[data-field]:first-child').find('.' + pfx + 'datatable__toggle-subtable').data('value');
    
              var icon = $(this).find('i').removeAttr('class');
    
              // prevent duplicate datatable init
              if ($(parentRow).hasClass(pfx + 'datatable__row--subtable-expanded')) {
                $(icon).addClass(Plugin.getOption('layout.icons.rowDetail.collapse'));
                // remove expand class from parent row
                $(parentRow).removeClass(pfx + 'datatable__row--subtable-expanded');
                // trigger event on collapse
                $(datatable).trigger(pfx + 'datatable--on-collapse-subtable', [parentRow]);
              } else {
                // expand and run callback function
                $(icon).addClass(Plugin.getOption('layout.icons.rowDetail.expand'));
                // add expand class to parent row
                $(parentRow).addClass(pfx + 'datatable__row--subtable-expanded');
                // trigger event on expand
                $(datatable).trigger(pfx + 'datatable--on-expand-subtable', [parentRow]);
              }
    
              // prevent duplicate datatable init
              if ($(subTable).find('.' + pfx + 'datatable').length === 0) {
                // get data by primary id
                $.map(datatable.dataSet, function(n, i) {
                  // primary id must be at the first column, otherwise e.data will be undefined
                  if (primaryKey === n[options.columns[0].field]) {
                    e.data = n;
                    return true;
                  }
                  return false;
                });
    
                // deprecated in v5.0.6
                e.detailCell = subTable;
    
                e.parentRow = parentRow;
                e.subTable = subTable;
    
                // run callback with event
                subTableCallback(e);
    
                $(subTable).children('.' + pfx + 'datatable').on(pfx + 'datatable--on-init', function(e) {
                  $(subTableRow).removeClass(pfx + 'datatable__row-loading');
                });
                if (Plugin.getOption('data.type') === 'local') {
                  $(subTableRow).removeClass(pfx + 'datatable__row-loading');
                }
              }
            };
    
            var columns = options.columns;
            $(datatable.tableBody).find('.' + pfx + 'datatable__row').each(function(tri, tr) {
              $(tr).find('.' + pfx + 'datatable__cell').each(function(tdi, td) {
                // get column settings by field
                var column = $.grep(columns, function(n, i) {
                  return $(td).data('field') === n.field;
                })[0];
                if (typeof column !== 'undefined') {
                  var value = $(td).text();
                  // enable column subtable toggle
                  if (typeof column.subtable !== 'undefined' && column.subtable) {
                    // check if subtable toggle exist
                    if ($(td).find('.' + pfx + 'datatable__toggle-subtable').length > 0) return;
                    // append subtable toggle
                    $(td).
                        html($('<a/>').
                            addClass(pfx + 'datatable__toggle-subtable').
                            attr('href', '#').
                            attr('data-value', value).
                            attr('title', Plugin.getOption('detail.title')).
                            on('click', toggleSubTable).
                            append($('<i/>').css('width', $(td).data('width')).addClass(Plugin.getOption('layout.icons.rowDetail.collapse'))));
                  }
                }
              });
            });
    
            // $(datatable.tableHead).find('.'+pfx+'-datatable__row').first()
          },
    
          /**
           * Datasource mapping callback
           */
          dataMapCallback: function(raw) {
            // static dataset array
            var dataSet = raw;
            // dataset mapping callback
            if (typeof Plugin.getOption('data.source.read.map') === 'function') {
              return Plugin.getOption('data.source.read.map')(raw);
            } else {
              // default data mapping fallback
              if (typeof raw !== 'undefined' && typeof raw.data !== 'undefined') {
                dataSet = raw.data;
              }
            }
            return dataSet;
          },
    
          isSpinning: false,
          /**
           * BlockUI spinner callback
           * @param block
           */
          spinnerCallback: function(block) {
            if (block) {
              if (!Plugin.isSpinning) {
                // get spinner options
                var spinnerOptions = Plugin.getOption('layout.spinner');
                if (typeof spinnerOptions.message !== 'undefined' && spinnerOptions.message === true) {
                  // use default spinner message from translation
                  spinnerOptions.message = Plugin.getOption('translate.records.processing');
                }
                Plugin.isSpinning = true;
                if (typeof app !== 'undefined') {
                  app.block(datatable, spinnerOptions);
                }
              }
            } else {
              Plugin.isSpinning = false;
              if (typeof app !== 'undefined') {
                app.unblock(datatable);
              }
            }
          },
    
          /**
           * Default sort callback function
           * @param data
           * @param sort
           * @param column
           * @returns {*|Array.<T>|{sort, field}|{asc, desc}}
           */
          sortCallback: function(data, sort, column) {
            var type = column['type'] || 'string';
            var format = column['format'] || '';
            var field = column['field'];
    
            return $(data).sort(function(a, b) {
              var aField = a[field];
              var bField = b[field];
    
              switch (type) {
                case 'date':
                  if (typeof moment === 'undefined') {
                    throw new Error('Moment.js is required.');
                  }
                  var diff = moment(aField, format).diff(moment(bField, format));
                  if (sort === 'asc') {
                    return diff > 0 ? 1 : diff < 0 ? -1 : 0;
                  } else {
                    return diff < 0 ? 1 : diff > 0 ? -1 : 0;
                  }
                  break;
    
                case 'number':
                  if (isNaN(parseFloat(aField)) && aField != null) {
                    aField = Number(aField.replace(/[^0-9\.-]+/g, ''));
                  }
                  if (isNaN(parseFloat(bField)) && bField != null) {
                    bField = Number(bField.replace(/[^0-9\.-]+/g, ''));
                  }
                  aField = parseFloat(aField);
                  bField = parseFloat(bField);
                  if (sort === 'asc') {
                    return aField > bField ? 1 : aField < bField ? -1 : 0;
                  } else {
                    return aField < bField ? 1 : aField > bField ? -1 : 0;
                  }
                  break;
    
                case 'string':
                default:
                  if (sort === 'asc') {
                    return aField > bField ? 1 : aField < bField ? -1 : 0;
                  } else {
                    return aField < bField ? 1 : aField > bField ? -1 : 0;
                  }
                  break;
              }
            });
          },
    
          /**
           * Custom debug log
           * @param text
           * @param obj
           */
          log: function(text, obj) {
            if (typeof obj === 'undefined') obj = '';
            if (datatable.debug) {
              console.log(text, obj);
            }
          },
    
          /**
           * Auto hide columnds overflow in row
           */
          autoHide: function() {
            $(datatable.table).find('.' + pfx + 'datatable__cell').show();
            $(datatable.tableBody).each(function() {
              while ($(this)[0].offsetWidth < $(this)[0].scrollWidth) {
                $(datatable.table).find('.' + pfx + 'datatable__row').each(function(i) {
                  var cell = $(this).find('.' + pfx + 'datatable__cell').not(':hidden').last();
                  $(cell).hide();
                });
                Plugin.adjustCellsWidth.call();
              }
            });
    
            var toggleHiddenColumns = function(e) {
              e.preventDefault();
    
              var row = $(this).closest('.' + pfx + 'datatable__row');
              var detailRow = $(row).next();
    
              if (!$(detailRow).hasClass(pfx + 'datatable__row-detail')) {
                $(this).find('i').removeClass(Plugin.getOption('layout.icons.rowDetail.collapse')).addClass(Plugin.getOption('layout.icons.rowDetail.expand'));
    
                var hidden = $(row).find('.' + pfx + 'datatable__cell:hidden').clone().show();
    
                detailRow = $('<tr/>').addClass(pfx + 'datatable__row-detail').insertAfter(row);
                var detailRowTd = $('<td/>').addClass(pfx + 'datatable__detail').attr('colspan', Plugin.getTotalColumns()).appendTo(detailRow);
    
                var detailSubTable = $('<table/>');
                $(hidden).each(function() {
                  var field = $(this).data('field');
                  var column = $.grep(options.columns, function(n, i) {
                    return field === n.field;
                  })[0];
                  $(detailSubTable).
                      append($('<tr class="' + pfx + 'datatable__row"></tr>').
                          append($('<td class="' + pfx + 'datatable__cell"></td>').append($('<span/>').css('width', Plugin.offset).append(column.title))).
                          append(this));
                });
                $(detailRowTd).append(detailSubTable);
    
              } else {
                $(this).find('i').removeClass(Plugin.getOption('layout.icons.rowDetail.expand')).addClass(Plugin.getOption('layout.icons.rowDetail.collapse'));
                $(detailRow).remove();
              }
            };
    
            // toggle show hidden columns
            $(datatable.tableBody).find('.' + pfx + 'datatable__row').each(function() {
              $(this).
                  prepend($('<td/>').
                      addClass(pfx + 'datatable__cell ' + pfx + 'datatable__toggle--detail').
                      append($('<a/>').
                          addClass(pfx + 'datatable__toggle-detail').
                          attr('href', '').
                          on('click', toggleHiddenColumns).
                          append($('<i/>').css('width', '21px').// maintain width for both icons expand and collapse
                          addClass(Plugin.getOption('layout.icons.rowDetail.collapse')))));
    
              // check if subtable toggle exist
              if ($(datatable.tableHead).find('.' + pfx + 'datatable__toggle-detail').length === 0) {
                $(datatable.tableHead).
                    find('.' + pfx + 'datatable__row').
                    first().
                    prepend('<th class="' + pfx + 'datatable__cell ' + pfx + 'datatable__toggle-detail"><span style="width: 21px"></span></th>');
                $(datatable.tableFoot).
                    find('.' + pfx + 'datatable__row').
                    first().
                    prepend('<th class="' + pfx + 'datatable__cell ' + pfx + 'datatable__toggle-detail"><span style="width: 21px"></span></th>');
              } else {
                $(datatable.tableHead).find('.' + pfx + 'datatable__toggle-detail').find('span').css('width', '21px');
              }
            });
          },
    
          /**
           * todo; implement hover column
           */
          hoverColumn: function() {
            $(datatable.tableBody).on('mouseenter', '.' + pfx + 'datatable__cell', function() {
              var colIdx = $(Plugin.cell(this).nodes()).index();
              $(Plugin.cells().nodes()).removeClass(pfx + 'datatable__cell--hover');
              $(Plugin.column(colIdx).nodes()).addClass(pfx + 'datatable__cell--hover');
            });
          },
    
          /**
           * To enable auto columns features for remote data source
           */
          setAutoColumns: function() {
            if (Plugin.getOption('data.autoColumns')) {
              $.each(datatable.dataSet[0], function(k, v) {
                var found = $.grep(options.columns, function(n, i) {
                  return k === n.field;
                });
                if (found.length === 0) {
                  options.columns.push({field: k, title: k});
                }
              });
              $(datatable.tableHead).find('.' + pfx + 'datatable__row').remove();
              Plugin.setHeadTitle();
              if (Plugin.getOption('layout.footer')) {
                $(datatable.tableFoot).find('.' + pfx + 'datatable__row').remove();
                Plugin.setHeadTitle(datatable.tableFoot);
              }
            }
          },
    
          /********************
           ** HELPERS
           ********************/
    
          /**
           * Check if table is a locked colums table
           */
          isLocked: function() {
            return util.hasClass(datatable.wrap[0], pfx + 'datatable--lock') || false;
          },
    
          /**
           * Get total extra space of an element for width calculation, including
           * padding, margin, border
           * @param element
           * @returns {number}
           */
          getExtraSpace: function(element) {
            var padding = parseInt($(element).css('paddingRight')) +
                parseInt($(element).css('paddingLeft'));
            var margin = parseInt($(element).css('marginRight')) +
                parseInt($(element).css('marginLeft'));
            var border = Math.ceil(
                $(element).css('border-right-width').replace('px', ''));
            return padding + margin + border;
          },
    
          /**
           * Insert data of array into {{ }} template placeholder
           * @param template
           * @param data
           * @returns {*}
           */
          dataPlaceholder: function(template, data) {
            var result = template;
            $.each(data, function(key, val) {
              result = result.replace('{{' + key + '}}', val);
            });
            return result;
          },
    
          /**
           * Get table unique ID
           * Note: table unique change each time refreshed
           * @param suffix
           * @returns {*}
           */
          getTableId: function(suffix) {
            if (typeof suffix === 'undefined') suffix = '';
            var id = $(datatable).attr('id');
            if (typeof id === 'undefined') {
              id = $(datatable).attr('class').split(' ')[0];
            }
            return id + suffix;
          },
    
          /**
           * Get table prefix with depth number
           */
          getTablePrefix: function(suffix) {
            if (typeof suffix !== 'undefined') suffix = '-' + suffix;
            return Plugin.getTableId() + '-' + Plugin.getDepth() + suffix;
          },
    
          /**
           * Get current table depth of sub table
           * @returns {number}
           */
          getDepth: function() {
            var depth = 0;
            var table = datatable.table;
            do {
              table = $(table).parents('.' + pfx + 'datatable__table');
              depth++;
            } while ($(table).length > 0);
            return depth;
          },
    
          /**
           * Keep state item
           * @param key
           * @param value
           */
          stateKeep: function(key, value) {
            key = Plugin.getTablePrefix(key);
            if (Plugin.getOption('data.saveState') === false) return;
            if (Plugin.getOption('data.saveState.webstorage') && localStorage) {
              localStorage.setItem(key, JSON.stringify(value));
            }
            if (Plugin.getOption('data.saveState.cookie')) {
              Cookies.set(key, JSON.stringify(value));
            }
          },
    
          /**
           * Get state item
           * @param key
           * @param defValue
           */
          stateGet: function(key, defValue) {
            key = Plugin.getTablePrefix(key);
            if (Plugin.getOption('data.saveState') === false) return;
            var value = null;
            if (Plugin.getOption('data.saveState.webstorage') && localStorage) {
              value = localStorage.getItem(key);
            } else {
              value = Cookies.get(key);
            }
            if (typeof value !== 'undefined' && value !== null) {
              return JSON.parse(value);
            }
          },
    
          /**
           * Update data in state without clear existing
           * @param key
           * @param value
           */
          stateUpdate: function(key, value) {
            var ori = Plugin.stateGet(key);
            if (typeof ori === 'undefined' || ori === null) ori = {};
            Plugin.stateKeep(key, $.extend({}, ori, value));
          },
    
          /**
           * Remove state item
           * @param key
           */
          stateRemove: function(key) {
            key = Plugin.getTablePrefix(key);
            if (localStorage) {
              localStorage.removeItem(key);
            }
            Cookies.remove(key);
          },
    
          /**
           * Get total columns.
           */
          getTotalColumns: function(tablePart) {
            if (typeof tablePart === 'undefined') tablePart = datatable.tableBody;
            return $(tablePart).find('.' + pfx + 'datatable__row').first().find('.' + pfx + 'datatable__cell').length;
          },
    
          /**
           * Get table row. Useful to get row when current table is in lock mode.
           * Can be used for both lock and normal table mode.
           * By default, returning result will be in a list of <td>.
           * @param tablePart
           * @param row 1-based index
           * @param tdOnly Optional. Default true
           * @returns {*}
           */
          getOneRow: function(tablePart, row, tdOnly) {
            if (typeof tdOnly === 'undefined') tdOnly = true;
            // get list of <tr>
            var result = $(tablePart).find('.' + pfx + 'datatable__row:not(.' + pfx + 'datatable__row-detail):nth-child(' + row + ')');
            if (tdOnly) {
              // get list of <td> or <th>
              result = result.find('.' + pfx + 'datatable__cell');
            }
            return result;
          },
    
          /**
           * Check if element has vertical overflow
           * @param element
           * @returns {boolean}
           */
          hasOverflowY: function(element) {
            var children = $(element).find('.' + pfx + 'datatable__row');
            var maxHeight = 0;
    
            if (children.length > 0) {
              $(children).each(function(tdi, td) {
                maxHeight += Math.floor($(td).innerHeight());
              });
    
              return maxHeight > $(element).innerHeight();
            }
    
            return false;
          },
    
          /**
           * Sort table row at HTML level by column index.
           * todo; Not in use.
           * @param header Header sort clicked
           * @param sort asc|desc. Optional. Default asc
           * @param int Boolean. Optional. Comparison value parse to integer.
           *     Default false
           */
          sortColumn: function(header, sort, int) {
            if (typeof sort === 'undefined') sort = 'asc'; // desc
            if (typeof int === 'undefined') int = false;
    
            var column = $(header).index();
            var rows = $(datatable.tableBody).find('.' + pfx + 'datatable__row');
            var hIndex = $(header).closest('.' + pfx + 'datatable__lock').index();
            if (hIndex !== -1) {
              rows = $(datatable.tableBody).find('.' + pfx + 'datatable__lock:nth-child(' + (hIndex + 1) + ')').find('.' + pfx + 'datatable__row');
            }
    
            var container = $(rows).parent();
            $(rows).sort(function(a, b) {
              var tda = $(a).find('td:nth-child(' + column + ')').text();
              var tdb = $(b).find('td:nth-child(' + column + ')').text();
    
              if (int) {
                // useful for integer type sorting
                tda = parseInt(tda);
                tdb = parseInt(tdb);
              }
    
              if (sort === 'asc') {
                return tda > tdb ? 1 : tda < tdb ? -1 : 0;
              } else {
                return tda < tdb ? 1 : tda > tdb ? -1 : 0;
              }
            }).appendTo(container);
          },
    
          /**
           * Perform sort remote and local
           */
          sorting: function() {
            var sortObj = {
              init: function() {
                if (options.sortable) {
                  $(datatable.tableHead).
                      find('.' + pfx + 'datatable__cell:not(.' + pfx + 'datatable__cell--check)').
                      addClass(pfx + 'datatable__cell--sort').
                      off('click').
                      on('click', sortObj.sortClick);
                  // first init
                  sortObj.setIcon();
                }
              },
              setIcon: function() {
                var meta = Plugin.getDataSourceParam('sort');
                if ($.isEmptyObject(meta)) return;
    
                // sort icon beside column header
                var td = $(datatable.tableHead).find('.' + pfx + 'datatable__cell[data-field="' + meta.field + '"]').attr('data-sort', meta.sort);
                var sorting = $(td).find('span');
                var icon = $(sorting).find('i');
    
                var icons = Plugin.getOption('layout.icons.sort');
                // update sort icon; desc & asc
                if ($(icon).length > 0) {
                  $(icon).removeAttr('class').addClass(icons[meta.sort]);
                } else {
                  $(sorting).append($('<i/>').addClass(icons[meta.sort]));
                }
              },
              sortClick: function(e) {
                var meta = Plugin.getDataSourceParam('sort');
                var field = $(this).data('field');
                var column = Plugin.getColumnByField(field);
                // sort is disabled for this column
                if (typeof column.sortable !== 'undefined' &&
                    column.sortable === false) return;
    
                $(datatable.tableHead).find('.' + pfx + 'datatable__cell > span > i').remove();
    
                if (options.sortable) {
                  Plugin.spinnerCallback(true);
    
                  var sort = 'desc';
                  if (Plugin.getObject('field', meta) === field) {
                    sort = Plugin.getObject('sort', meta);
                  }
    
                  // toggle sort
                  sort = typeof sort === 'undefined' || sort === 'desc'
                      ? 'asc'
                      : 'desc';
    
                  // update field and sort params
                  meta = {field: field, sort: sort};
                  Plugin.setDataSourceParam('sort', meta);
    
                  sortObj.setIcon();
    
                  setTimeout(function() {
                    Plugin.dataRender('sort');
                    $(datatable).trigger(pfx + 'datatable--on-sort', meta);
                  }, 300);
                }
              },
            };
            sortObj.init();
          },
    
          /**
           * Update JSON data list linked with sort, filter and pagination.
           * Call this method, before using dataSet variable.
           * @returns {*|null}
           */
          localDataUpdate: function() {
            // todo; fix twice execution
            var params = Plugin.getDataSourceParam();
            if (typeof datatable.originalDataSet === 'undefined') {
              datatable.originalDataSet = datatable.dataSet;
            }
    
            var field = Plugin.getObject('sort.field', params);
            var sort = Plugin.getObject('sort.sort', params);
            var column = Plugin.getColumnByField(field);
            if (typeof column !== 'undefined' && Plugin.getOption('data.serverSorting') !== true) {
              if (typeof column.sortCallback === 'function') {
                datatable.dataSet = column.sortCallback(datatable.originalDataSet, sort, column);
              } else {
                datatable.dataSet = Plugin.sortCallback(datatable.originalDataSet, sort, column);
              }
            } else {
              datatable.dataSet = datatable.originalDataSet;
            }
    
            // if server filter enable, don't pass local filter
            if (typeof params.query === 'object' && !Plugin.getOption('data.serverFiltering')) {
              params.query = params.query || {};
    
              var nestedSearch = function(obj) {
                for (var field in obj) {
                  if (!obj.hasOwnProperty(field)) continue;
                  if (typeof obj[field] === 'string') {
                    if (obj[field].toLowerCase() == search || obj[field].toLowerCase().indexOf(search) !== -1) {
                      return true;
                    }
                  }
                  else if (typeof obj[field] === 'number') {
                    if (obj[field] === search) {
                      return true;
                    }
                  }
                  else if (typeof obj[field] === 'object') {
                    return nestedSearch(obj[field]);
                  }
                }
                return false;
              };
    
              var search = $(Plugin.getOption('search.input')).val();
              if (typeof search !== 'undefined' && search !== '') {
                search = search.toLowerCase();
                datatable.dataSet = $.grep(datatable.dataSet, nestedSearch);
                // remove generalSearch as we don't need this for next columns filter
                delete params.query[Plugin.getGeneralSearchKey()];
              }
    
              // remove empty element from array
              $.each(params.query, function(k, v) {
                if (v === '') {
                  delete params.query[k];
                }
              });
    
              // filter array by query
              datatable.dataSet = Plugin.filterArray(datatable.dataSet, params.query);
    
              // reset array index
              datatable.dataSet = datatable.dataSet.filter(function() {
                return true;
              });
            }
    
            return datatable.dataSet;
          },
    
          /**
           * Utility helper to filter array by object pair of {key:value}
           * @param list
           * @param args
           * @param operator
           * @returns {*}
           */
          filterArray: function(list, args, operator) {
            if (typeof list !== 'object') {
              return [];
            }
    
            if (typeof operator === 'undefined') operator = 'AND';
    
            if (typeof args !== 'object') {
              return list;
            }
    
            operator = operator.toUpperCase();
    
            if ($.inArray(operator, ['AND', 'OR', 'NOT']) === -1) {
              return [];
            }
    
            var count = Object.keys(args).length;
            var filtered = [];
    
            $.each(list, function(key, obj) {
              var to_match = obj;
    
              var matched = 0;
              $.each(args, function(m_key, m_value) {
                m_value = m_value instanceof Array ? m_value : [m_value];
                if (to_match.hasOwnProperty(m_key)) {
                  var lhs = to_match[m_key].toString().toLowerCase();
                  m_value.forEach(function(item, index) {
                    if (item.toString().toLowerCase() == lhs || lhs.indexOf(item.toString().toLowerCase()) !== -1) {
                      matched++;
                    }
                  });
                }
              });
    
              if (('AND' == operator && matched == count) ||
                  ('OR' == operator && matched > 0) ||
                  ('NOT' == operator && 0 == matched)) {
                filtered[key] = obj;
              }
            });
    
            list = filtered;
    
            return list;
          },
    
          /**
           * Reset lock column scroll to 0 when resize
           */
          resetScroll: function() {
            if (typeof options.detail === 'undefined' && Plugin.getDepth() === 1) {
              $(datatable.table).find('.' + pfx + 'datatable__row').css('left', 0);
              $(datatable.table).find('.' + pfx + 'datatable__lock').css('top', 0);
              $(datatable.tableBody).scrollTop(0);
            }
          },
    
          /**
           * Get column options by field
           * @param field
           * @returns {boolean}
           */
          getColumnByField: function(field) {
            if (typeof field === 'undefined') return;
            var result;
            $.each(options.columns, function(i, column) {
              if (field === column.field) {
                result = column;
                return false;
              }
            });
            return result;
          },
    
          /**
           * Get default sort column
           */
          getDefaultSortColumn: function() {
            var result;
            $.each(options.columns, function(i, column) {
              if (typeof column.sortable !== 'undefined'
                  && $.inArray(column.sortable, ['asc', 'desc']) !== -1) {
                result = {sort: column.sortable, field: column.field};
                return false;
              }
            });
            return result;
          },
    
          /**
           * Helper to get element dimensions, when the element is hidden
           * @param element
           * @param includeMargin
           * @returns {{width: number, height: number, innerWidth: number,
           *     innerHeight: number, outerWidth: number, outerHeight: number}}
           */
          getHiddenDimensions: function(element, includeMargin) {
            var props = {
                  position: 'absolute',
                  visibility: 'hidden',
                  display: 'block',
                },
                dim = {
                  width: 0,
                  height: 0,
                  innerWidth: 0,
                  innerHeight: 0,
                  outerWidth: 0,
                  outerHeight: 0,
                },
                hiddenParents = $(element).parents().addBack().not(':visible');
            includeMargin = (typeof includeMargin === 'boolean')
                ? includeMargin
                : false;
    
            var oldProps = [];
            hiddenParents.each(function() {
              var old = {};
    
              for (var name in props) {
                old[name] = this.style[name];
                this.style[name] = props[name];
              }
    
              oldProps.push(old);
            });
    
            dim.width = $(element).width();
            dim.outerWidth = $(element).outerWidth(includeMargin);
            dim.innerWidth = $(element).innerWidth();
            dim.height = $(element).height();
            dim.innerHeight = $(element).innerHeight();
            dim.outerHeight = $(element).outerHeight(includeMargin);
    
            hiddenParents.each(function(i) {
              var old = oldProps[i];
              for (var name in props) {
                this.style[name] = old[name];
              }
            });
    
            return dim;
          },
    
          getGeneralSearchKey: function() {
            var searchInput = $(Plugin.getOption('search.input'));
            return $(searchInput).prop('name') || $(searchInput).prop('id');
          },
    
          /**
           * Get value by dot notation path string and to prevent undefined errors
           * @param path String Dot notation path in string
           * @param object Object to iterate
           * @returns {*}
           */
          getObject: function(path, object) {
            return path.split('.').reduce(function(obj, i) {
              return obj !== null && typeof obj[i] !== 'undefined' ? obj[i] : null;
            }, object);
          },
    
          /**
           * Extend object
           * @param obj
           * @param path
           * @param value
           * @returns {*}
           */
          extendObj: function(obj, path, value) {
            var levels = path.split('.'),
                i = 0;
    
            function createLevel(child) {
              var name = levels[i++];
              if (typeof child[name] !== 'undefined' && child[name] !== null) {
                if (typeof child[name] !== 'object' &&
                    typeof child[name] !== 'function') {
                  child[name] = {};
                }
              } else {
                child[name] = {};
              }
              if (i === levels.length) {
                child[name] = value;
              } else {
                createLevel(child[name]);
              }
            }
    
            createLevel(obj);
            return obj;
          },
    
          rowEvenOdd: function() {
            // row even class
            $(datatable.tableBody).find('.' + pfx + 'datatable__row').removeClass(pfx + 'datatable__row--even');
            if ($(datatable.wrap).hasClass(pfx + 'datatable--subtable')) {
              $(datatable.tableBody).find('.' + pfx + 'datatable__row:not(.' + pfx + 'datatable__row-detail):even').addClass(pfx + 'datatable__row--even');
            } else {
              $(datatable.tableBody).find('.' + pfx + 'datatable__row:nth-child(even)').addClass(pfx + 'datatable__row--even');
            }
          },
    
          /********************
           ** PUBLIC API METHODS
           ********************/
    
          // delay timer
          timer: 0,
    
          /**
           * Redraw datatable by recalculating its DOM elements, etc.
           * @returns {jQuery}
           */
          redraw: function() {
            Plugin.adjustCellsWidth.call();
            if (Plugin.isLocked()) {
              // fix hiding cell width issue
              Plugin.scrollbar();
              Plugin.resetScroll();
    
              Plugin.adjustCellsHeight.call();
            }
            Plugin.adjustLockContainer.call();
            Plugin.initHeight.call();
            return datatable;
          },
    
          /**
           * Shortcode to reload
           * @returns {jQuery}
           */
          load: function() {
            Plugin.reload();
            return datatable;
          },
    
          /**
           * Datasource reload
           * @returns {jQuery}
           */
          reload: function() {
            var delay = (function() {
              return function(callback, ms) {
                clearTimeout(Plugin.timer);
                Plugin.timer = setTimeout(callback, ms);
              };
            })();
            delay(function() {
              // local only. remote pagination will skip this block
              if (!options.data.serverFiltering) {
                Plugin.localDataUpdate();
              }
              Plugin.dataRender();
              $(datatable).trigger(pfx + 'datatable--on-reloaded');
            }, Plugin.getOption('search.delay'));
            return datatable;
          },
    
          /**
           * Get record by record ID
           * @param id
           * @returns {jQuery}
           */
          getRecord: function(id) {
            if (typeof datatable.tableBody === 'undefined') datatable.tableBody = $(datatable.table).children('tbody');
            $(datatable.tableBody).find('.' + pfx + 'datatable__cell:first-child').each(function(i, cell) {
              if (id == $(cell).text()) {
                var rowNumber = $(cell).closest('.' + pfx + 'datatable__row').index() + 1;
                datatable.API.record = datatable.API.value = Plugin.getOneRow(datatable.tableBody, rowNumber);
                return datatable;
              }
            });
            return datatable;
          },
    
          /**
           * @deprecated in v5.0.6
           * Get column of current record ID
           * @param columnName
           * @returns {jQuery}
           */
          getColumn: function(columnName) {
            Plugin.setSelectedRecords();
            datatable.API.value = $(datatable.API.record).find('[data-field="' + columnName + '"]');
            return datatable;
          },
    
          /**
           * Destroy datatable to original DOM state before datatable was
           * initialized
           * @returns {jQuery}
           */
          destroy: function() {
            $(datatable).parent().find('.' + pfx + 'datatable__pager').remove();
            var initialDatatable = $(datatable.initialDatatable).addClass(pfx + 'datatable--destroyed').show();
            $(datatable).replaceWith(initialDatatable);
            datatable = initialDatatable;
            $(datatable).trigger(pfx + 'datatable--on-destroy');
            Plugin.isInit = false;
            initialDatatable = null;
            return initialDatatable;
          },
    
          /**
           * Sort by column field
           * @param field
           * @param sort
           */
          sort: function(field, sort) {
            // toggle sort
            sort = typeof sort === 'undefined' ? 'asc' : sort;
    
            Plugin.spinnerCallback(true);
    
            // update field and sort params
            var meta = {field: field, sort: sort};
            Plugin.setDataSourceParam('sort', meta);
    
            setTimeout(function() {
              Plugin.dataRender('sort');
              $(datatable).trigger(pfx + 'datatable--on-sort', meta);
              $(datatable.tableHead).find('.' + pfx + 'datatable__cell > span > i').remove();
            }, 300);
    
            return datatable;
          },
    
          /**
           * @deprecated in v5.0.6
           * Get current selected column value
           * @returns {jQuery}
           */
          getValue: function() {
            return $(datatable.API.value).text();
          },
    
          /**
           * Set checkbox active
           * @param cell JQuery selector or checkbox ID
           */
          setActive: function(cell) {
            if (typeof cell === 'string') {
              // set by checkbox id
              cell = $(datatable.tableBody).find('.' + pfx + 'checkbox--single > [type="checkbox"][value="' + cell + '"]');
            }
    
            $(cell).prop('checked', true);
    
            // normal table
            var row = $(cell).closest('.' + pfx + 'datatable__row').addClass(pfx + 'datatable__row--active');
    
            var index = $(row).index() + 1;
            // lock table
            $(row).closest('.' + pfx + 'datatable__lock').parent().find('.' + pfx + 'datatable__row:nth-child(' + index + ')').addClass(pfx + 'datatable__row--active');
    
            var ids = [];
            $(row).each(function(i, td) {
              var id = $(td).find('.' + pfx + 'checkbox--single:not(.' + pfx + 'checkbox--all) > [type="checkbox"]').val();
              if (typeof id !== 'undefined') {
                ids.push(id);
              }
            });
    
            $(datatable).trigger(pfx + 'datatable--on-check', [ids]);
          },
    
          /**
           * Set checkbox inactive
           * @param cell JQuery selector or checkbox ID
           */
          setInactive: function(cell) {
            if (typeof cell === 'string') {
              // set by checkbox id
              cell = $(datatable.tableBody).find('.' + pfx + 'checkbox--single > [type="checkbox"][value="' + cell + '"]');
            }
    
            $(cell).prop('checked', false);
    
            // normal table
            var row = $(cell).closest('.' + pfx + 'datatable__row').removeClass(pfx + 'datatable__row--active');
            var index = $(row).index() + 1;
    
            // lock table
            $(row).closest('.' + pfx + 'datatable__lock').parent().find('.' + pfx + 'datatable__row:nth-child(' + index + ')').removeClass(pfx + 'datatable__row--active');
    
            var ids = [];
            $(row).each(function(i, td) {
              var id = $(td).find('.' + pfx + 'checkbox--single:not(.' + pfx + 'checkbox--all) > [type="checkbox"]').val();
              if (typeof id !== 'undefined') {
                ids.push(id);
              }
            });
    
            $(datatable).trigger(pfx + 'datatable--on-uncheck', [ids]);
          },
    
          /**
           * Set all checkboxes active or inactive
           * @param active
           */
          setActiveAll: function(active) {
            // todo; check if child table also will set active?
            var checkboxes = $(datatable.table).find('> tbody, > thead').find('> tr:not(.' + pfx + 'datatable__row-subtable)').find('.' + pfx + 'datatable__cell--check [type="checkbox"]');
            if (active) {
              Plugin.setActive(checkboxes);
            } else {
              Plugin.setInactive(checkboxes);
            }
          },
    
          /**
           * @deprecated in v5.0.6
           * Get selected rows which are active
           * @returns {jQuery}
           */
          setSelectedRecords: function() {
            datatable.API.record = $(datatable.tableBody).find('.' + pfx + 'datatable__row--active');
            return datatable;
          },
    
          /**
           * Get selected records
           * @returns {null}
           */
          getSelectedRecords: function() {
            // support old method
            Plugin.setSelectedRecords();
            datatable.API.record = datatable.rows('.' + pfx + 'datatable__row--active').nodes();
            return datatable.API.record;
          },
    
          /**
           * Get options by dots notation path
           * @param path String Dot notation path in string
           * @returns {*}
           */
          getOption: function(path) {
            return Plugin.getObject(path, options);
          },
    
          /**
           * Set global options nodes by dots notation path
           * @param path
           * @param object
           */
          setOption: function(path, object) {
            options = Plugin.extendObj(options, path, object);
          },
    
          /**
           * Search filter for local & remote
           * @param value
           * @param columns. Optional list of columns to be filtered.
           */
          search: function(value, columns) {
            if (typeof columns !== 'undefined') columns = $.makeArray(columns);
            var delay = (function() {
              return function(callback, ms) {
                clearTimeout(Plugin.timer);
                Plugin.timer = setTimeout(callback, ms);
              };
            })();
    
            delay(function() {
              // get query parameters
              var query = Plugin.getDataSourceQuery();
    
              // search not by columns
              if (typeof columns === 'undefined' && typeof value !== 'undefined') {
                var key = Plugin.getGeneralSearchKey();
                query[key] = value;
              }
    
              // search by columns, support multiple columns
              if (typeof columns === 'object') {
                $.each(columns, function(k, column) {
                  query[column] = value;
                });
                // remove empty element from arrays
                $.each(query, function(k, v) {
                  if (v === '' || $.isEmptyObject(v)) {
                    delete query[k];
                  }
                });
              }
    
              Plugin.setDataSourceQuery(query);
    
              // local filter only. remote pagination will skip this block
              if (!options.data.serverFiltering) {
                Plugin.localDataUpdate();
              }
              Plugin.dataRender('search');
            }, Plugin.getOption('search.delay'));
          },
    
          /**
           * Set datasource params extract
           * @param param
           * @param value
           */
          setDataSourceParam: function(param, value) {
            datatable.API.params = $.extend({}, {
              pagination: {page: 1, perpage: Plugin.getOption('data.pageSize')},
              sort: Plugin.getDefaultSortColumn(),
              query: {},
            }, datatable.API.params, Plugin.stateGet(Plugin.stateId));
    
            datatable.API.params = Plugin.extendObj(datatable.API.params, param, value);
    
            Plugin.stateKeep(Plugin.stateId, datatable.API.params);
          },
    
          /**
           * Get datasource params
           * @param param
           */
          getDataSourceParam: function(param) {
            datatable.API.params = $.extend({}, {
              pagination: {page: 1, perpage: Plugin.getOption('data.pageSize')},
              sort: Plugin.getDefaultSortColumn(),
              query: {},
            }, datatable.API.params, Plugin.stateGet(Plugin.stateId));
    
            if (typeof param === 'string') {
              return Plugin.getObject(param, datatable.API.params);
            }
    
            return datatable.API.params;
          },
    
          /**
           * Shortcode to datatable.getDataSourceParam('query');
           * @returns {*}
           */
          getDataSourceQuery: function() {
            return Plugin.getDataSourceParam('query') || {};
          },
    
          /**
           * Shortcode to datatable.setDataSourceParam('query', query);
           * @param query
           */
          setDataSourceQuery: function(query) {
            Plugin.setDataSourceParam('query', query);
          },
    
          /**
           * Get current page number
           * @returns {number}
           */
          getCurrentPage: function() {
            return $(datatable.table).
                siblings('.' + pfx + 'datatable__pager').
                last().
                find('.' + pfx + 'datatable__pager-nav').
                find('.' + pfx + 'datatable__pager-link.' + pfx + 'datatable__pager-link--active').
                data('page') || 1;
          },
    
          /**
           * Get selected dropdown page size
           * @returns {*|number}
           */
          getPageSize: function() {
            return $(datatable.table).siblings('.' + pfx + 'datatable__pager').last().find('select.' + pfx + 'datatable__pager-size').val() || 10;
          },
    
          /**
           * Get total rows
           */
          getTotalRows: function() {
            return datatable.API.params.pagination.total;
          },
    
          /**
           * Get full dataset in grid
           * @returns {*|null|Array}
           */
          getDataSet: function() {
            return datatable.originalDataSet;
          },
    
          /**
           * @deprecated in v5.0.6
           * Hide column by column's field name
           * @param fieldName
           */
          hideColumn: function(fieldName) {
            // add hide option for this column
            $.map(options.columns, function(column) {
              if (fieldName === column.field) {
                column.responsive = {hidden: 'xl'};
              }
              return column;
            });
            // hide current displayed column
            var tds = $.grep($(datatable.table).find('.' + pfx + 'datatable__cell'), function(n, i) {
              return fieldName === $(n).data('field');
            });
            $(tds).hide();
          },
    
          /**
           * @deprecated in v5.0.6
           * Show column by column's field name
           * @param fieldName
           */
          showColumn: function(fieldName) {
            // add hide option for this column
            $.map(options.columns, function(column) {
              if (fieldName === column.field) {
                delete column.responsive;
              }
              return column;
            });
            // hide current displayed column
            var tds = $.grep($(datatable.table).find('.' + pfx + 'datatable__cell'), function(n, i) {
              return fieldName === $(n).data('field');
            });
            $(tds).show();
          },
    
          nodeTr: [],
          nodeTd: [],
          nodeCols: [],
          recentNode: [],
    
          table: function() {
            return datatable.table;
          },
    
          /**
           * Select a single row from the table
           * @param selector
           * @returns {jQuery}
           */
          row: function(selector) {
            Plugin.rows(selector);
            Plugin.nodeTr = Plugin.recentNode = $(Plugin.nodeTr).first();
            return datatable;
          },
    
          /**
           * Select multiple rows from the table
           * @param selector
           * @returns {jQuery}
           */
          rows: function(selector) {
            Plugin.nodeTr = Plugin.recentNode = $(datatable.tableBody).find(selector).filter('.' + pfx + 'datatable__row');
            return datatable;
          },
    
          /**
           * Select a single column from the table
           * @param index zero-based index
           * @returns {jQuery}
           */
          column: function(index) {
            Plugin.nodeCols = Plugin.recentNode = $(datatable.tableBody).find('.' + pfx + 'datatable__cell:nth-child(' + (index + 1) + ')');
            return datatable;
          },
    
          /**
           * Select multiple columns from the table
           * @param selector
           * @returns {jQuery}
           */
          columns: function(selector) {
            var context = datatable.table;
            if (Plugin.nodeTr === Plugin.recentNode) {
              context = Plugin.nodeTr;
            }
            var columns = $(context).find('.' + pfx + 'datatable__cell[data-field="' + selector + '"]');
            if (columns.length > 0) {
              Plugin.nodeCols = Plugin.recentNode = columns;
            } else {
              Plugin.nodeCols = Plugin.recentNode = $(context).find(selector).filter('.' + pfx + 'datatable__cell');
            }
            return datatable;
          },
    
          cell: function(selector) {
            Plugin.cells(selector);
            Plugin.nodeTd = Plugin.recentNode = $(Plugin.nodeTd).first();
            return datatable;
          },
    
          cells: function(selector) {
            var cells = $(datatable.tableBody).find('.' + pfx + 'datatable__cell');
            if (typeof selector !== 'undefined') {
              cells = $(cells).filter(selector);
            }
            Plugin.nodeTd = Plugin.recentNode = cells;
            return datatable;
          },
    
          /**
           * Delete the selected row from the table
           * @returns {jQuery}
           */
          remove: function() {
            if ($(Plugin.nodeTr.length) && Plugin.nodeTr === Plugin.recentNode) {
              $(Plugin.nodeTr).remove();
            }
            Plugin.layoutUpdate();
            return datatable;
          },
    
          /**
           * Show or hide the columns or rows
           */
          visible: function(bool) {
            if ($(Plugin.recentNode.length)) {
              var locked = Plugin.lockEnabledColumns();
              if (Plugin.recentNode === Plugin.nodeCols) {
                var index = Plugin.recentNode.index();
    
                if (Plugin.isLocked()) {
                  var scrollColumns = $(Plugin.recentNode).closest('.' + pfx + 'datatable__lock--scroll').length;
                  if (scrollColumns) {
                    // is at center of scrollable area
                    index += locked.left.length + 1;
                  } else if ($(Plugin.recentNode).closest('.' + pfx + 'datatable__lock--right').length) {
                    // is at the right locked table
                    index += locked.left.length + scrollColumns + 1;
                  }
                }
              }
    
              if (bool) {
                if (Plugin.recentNode === Plugin.nodeCols) {
                  delete options.columns[index].responsive;
                }
                $(Plugin.recentNode).show();
              } else {
                if (Plugin.recentNode === Plugin.nodeCols) {
                  Plugin.setOption('columns.' + index + '.responsive', {hidden: 'xl'});
                }
                $(Plugin.recentNode).hide();
              }
              Plugin.redraw();
            }
          },
    
          /**
           * Get the the DOM element for the selected rows or columns
           * @returns {Array}
           */
          nodes: function() {
            return Plugin.recentNode;
          },
    
          /**
           * will be implemented soon
           * @returns {jQuery}
           */
          dataset: function() {
            return datatable;
          },
    
        };
    
        /**
         * Public API methods can be used directly by datatable
         */
        $.each(Plugin, function(funcName, func) {
          datatable[funcName] = func;
        });
    
        // initialize main datatable plugin
        if (typeof options !== 'undefined') {
          if (typeof options === 'string') {
            var method = options;
            datatable = $(this).data(pluginName);
            if (typeof datatable !== 'undefined') {
              options = datatable.options;
              Plugin[method].apply(this, Array.prototype.slice.call(arguments, 1));
            }
          } else {
            if (!datatable.data(pluginName) && !$(this).hasClass(pfx + 'datatable--loaded')) {
              datatable.dataSet = null;
              datatable.textAlign = {
                left: pfx + 'datatable__cell--left',
                center: pfx + 'datatable__cell--center',
                right: pfx + 'datatable__cell--right',
              };
    
              // merge default and user defined options
              options = $.extend(true, {}, $.fn[pluginName].defaults, options);
    
              datatable.options = options;
    
              // init plugin process
              Plugin.init.apply(this, [options]);
    
              $(datatable.wrap).data(pluginName, datatable);
            }
          }
        } else {
          // get existing instance datatable
          datatable = $(this).data(pluginName);
          if (typeof datatable === 'undefined') {
            $.error(pluginName + ' not initialized');
          }
          options = datatable.options;
        }
    
        return datatable;
      };
    
      // default options
      $.fn[pluginName].defaults = {
        // datasource definition
        data: {
          type: 'local',
          source: null,
          pageSize: 10, // display records per page
          saveState: {
            // save datatable state(pagination, filtering, sorting, etc) in cookie or browser webstorage
            cookie: false,
            webstorage: true,
          },
    
          serverPaging: false,
          serverFiltering: false,
          serverSorting: false,
    
          autoColumns: false,
          attr: {
            rowProps: [],
          },
        },
    
        // layout definition
        layout: {
          theme: 'default', // datatable will support multiple themes and designs
          class: pfx + 'datatable--brand', // custom wrapper class
          scroll: false, // enable/disable datatable scroll both horizontal and vertical when needed.
          height: null, // datatable's body's fixed height
          minHeight: 300,
          footer: false, // display/hide footer
          header: true, // display/hide header
          customScrollbar: true, // set false to disable custom scrollbar
    
          // datatable spinner
          spinner: {
            overlayColor: '#000000',
            opacity: 0,
            type: 'loader',
            state: 'brand',
            message: true,
          },
    
          // datatable UI icons
          icons: {
            sort: {asc: 'la la-arrow-up', desc: 'la la-arrow-down'},
            pagination: {
              next: 'la la-angle-right',
              prev: 'la la-angle-left',
              first: 'la la-angle-double-left',
              last: 'la la-angle-double-right',
              more: 'la la-ellipsis-h',
            },
            rowDetail: {expand: 'fa fa-caret-down', collapse: 'fa fa-caret-right'},
          },
        },
    
        // column sorting
        sortable: true,
    
        // resize column size with mouse drag coming soon)
        resizable: false,
    
        // column based filtering (coming soon)
        filterable: false,
    
        pagination: true,
    
        // inline and bactch editing (cooming soon)
        editable: false,
    
        // columns definition
        columns: [],
    
        search: {
          // enable trigger search by keyup enter
          onEnter: false,
          // input text for search
          input: null,
          // search delay in milliseconds
          delay: 400,
        },
    
        rows: {
          // deprecated
          callback: function() {
          },
          // call before row template
          beforeTemplate: function() {
          },
          // call after row template
          afterTemplate: function() {
          },
          // auto hide columns, if rows overflow. work on non locked columns
          autoHide: false,
        },
    
        // toolbar
        toolbar: {
          // place pagination and displayInfo blocks according to the array order
          layout: ['pagination', 'info'],
    
          // toolbar placement can be at top or bottom or both top and bottom repeated
          placement: ['bottom'],  //'top', 'bottom'
    
          // toolbar items
          items: {
            // pagination
            pagination: {
              // pagination type(default or scroll)
              type: 'default',
    
              // number of pages to display by breakpoints
              pages: {
                desktop: {
                  layout: 'default',
                  pagesNumber: 6,
                },
                tablet: {
                  layout: 'default',
                  pagesNumber: 3,
                },
                mobile: {
                  layout: 'compact',
                },
              },
    
              // navigation buttons
              navigation: {
                prev: true, // display prev link
                next: true, // display next link
                first: true, // display first link
                last: true, // display last link
              },
    
              // page size select
              pageSizeSelect: [], // display dropdown to select pagination size. -1 is used for "ALl" option
            },
    
            // records info
            info: true,
          },
        },
    
        // here we will keep all strings and message used by datatable UI so developer can easiliy translate to any language.
        // By default the stirngs will be in the plugin source and here can override it
        translate: {
          records: {
            processing: 'Please wait...',
            noRecords: 'No records found',
          },
          toolbar: {
            pagination: {
              items: {
                default: {
                  first: 'First',
                  prev: 'Previous',
                  next: 'Next',
                  last: 'Last',
                  more: 'More pages',
                  input: 'Page number',
                  select: 'Select page size',
                },
                info: 'Displaying {{start}} - {{end}} of {{total}} records',
              },
            },
          },
        },
    
        extensions: {},
      };
    
    }(jQuery));
    var defaults = {
        layout: {
            icons: {
                pagination: {
                    next: 'la la-angle-right',
                    prev: 'la la-angle-left',
                    first: 'la la-angle-double-left',
                    last: 'la la-angle-double-right',
                    more: 'la la-ellipsis-h',
                },
                rowDetail: {expand: 'fa fa-caret-down', collapse: 'fa fa-caret-right'},
            }
        }
    };
    
    if (mUtil.isRTL()) {
        defaults = {
            layout: {
                icons: {
                    pagination: {
                        next: 'la la-angle-left',
                        prev: 'la la-angle-right',
                        last: 'la la-angle-double-left',
                        first: 'la la-angle-double-right',
                    },
                    rowDetail: {collapse: 'fa fa-caret-down', expand: 'fa fa-caret-right'},
                }
            }
        }
    }
    
    $.extend(true, $.fn.mDatatable.defaults, defaults);
    var mDropdown = function(elementId, options) {
        //== Main object
        var the = this;
        var init = false;
    
        //== Get element object
        var element = mUtil.get(elementId);
        var body = mUtil.get('body');
    
        if (!element) {
            return;
        }
    
        //== Default options
        var defaultOptions = {
            toggle: 'click',
            hoverTimeout: 300,
            skin: 'light',
            height: 'auto',
            maxHeight: false,
            minHeight: false,
            persistent: false,
            mobileOverlay: true
        };
    
        ////////////////////////////
        // ** Private Methods  ** //
        ////////////////////////////
    
        var Plugin = {
            /**
             * Run plugin
             * @returns {mdropdown}
             */
            construct: function(options) {
                if (mUtil.data(element).has('dropdown')) {
                    the = mUtil.data(element).get('dropdown');
                } else {
                    // reset dropdown
                    Plugin.init(options);
    
                    Plugin.setup();
    
                    mUtil.data(element).set('dropdown', the);
                }
    
                return the;
            },
    
            /**
             * Handles subdropdown click toggle
             * @returns {mdropdown}
             */
            init: function(options) {
                // merge default and user defined options
                the.options = mUtil.deepExtend({}, defaultOptions, options);
                the.events = [];
                the.eventHandlers = {};
                the.open = false;
                
                the.layout = {};
                the.layout.close = mUtil.find(element, '.m-dropdown__close');
                the.layout.toggle = mUtil.find(element, '.m-dropdown__toggle');
                the.layout.arrow = mUtil.find(element, '.m-dropdown__arrow');
                the.layout.wrapper = mUtil.find(element, '.m-dropdown__wrapper');
                the.layout.defaultDropPos = mUtil.hasClass(element, 'm-dropdown--up') ? 'up' : 'down';
                the.layout.currentDropPos = the.layout.defaultDropPos;
    
                if (mUtil.attr(element, 'm-dropdown-toggle') == "hover") {
                    the.options.toggle = 'hover';
                }
            },
    
            /**
             * Setup dropdown
             */
            setup: function() {
                if (the.options.placement) {
                    mUtil.addClass(element, 'm-dropdown--' + the.options.placement);
                }
    
                if (the.options.align) {
                    mUtil.addClass(element, 'm-dropdown--align-' + the.options.align);
                }
    
                if (the.options.width) {
                    mUtil.css(the.layout.wrapper, 'width', the.options.width + 'px');
                }
    
                if (mUtil.attr(element, 'm-dropdown-persistent') == '1') {
                    the.options.persistent = true;
                }
    
                if (the.options.toggle == 'hover') {    
                    mUtil.addEvent(element, 'mouseout', Plugin.hideMouseout);
                } 
    
                // set zindex
                Plugin.setZindex();
            },
    
            /**
             * Toggle dropdown
             */
            toggle: function() {
                if (the.open) {
                    return Plugin.hide();
                } else {
                    return Plugin.show();
                }
            },
    
            /**
             * Set content
             */
            setContent: function(content) {
                var content = mUtil.find(element, '.m-dropdown__content').innerHTML = content;
    
                return the;
            },
    
            /**
             * Show dropdown
             */
            show: function() {
                if (the.options.toggle == 'hover' && mUtil.hasAttr(element, 'hover')) {
                    Plugin.clearHovered();
                    return the;
                }
    
                if (the.open) {
                    return the;
                }
    
                if (the.layout.arrow) {
                    Plugin.adjustArrowPos();
                }
    
                Plugin.eventTrigger('beforeShow');
    
                Plugin.hideOpened();
    
                mUtil.addClass(element, 'm-dropdown--open');
    
                if (mUtil.isMobileDevice() && the.options.mobileOverlay) {
                    var zIndex = mUtil.css(element, 'z-index') - 1;
    
                    var dropdownoff = mUtil.insertAfter(document.createElement('DIV'), element );
    
                    mUtil.addClass(dropdownoff, 'm-dropdown__dropoff');
                    mUtil.css(dropdownoff, 'z-index', zIndex);
                    mUtil.data(dropdownoff).set('dropdown', element);
                    mUtil.data(element).set('dropoff', dropdownoff);
    
                    mUtil.addEvent(dropdownoff, 'click', function(e) {
                        Plugin.hide();
                        mUtil.remove(this);
                        e.preventDefault();
                    });
                }
    
                element.focus();
                element.setAttribute('aria-expanded', 'true');
                the.open = true;
    
                //== Update scrollers
                mUtil.scrollersUpdate(element);
    
                Plugin.eventTrigger('afterShow');
    
                return the;
            },
    
            /**
             * Clear dropdown hover
             */
            clearHovered: function() {
                var timeout = mUtil.attr(element, 'timeout');
    
                mUtil.removeAttr(element, 'hover');            
                mUtil.removeAttr(element, 'timeout');
    
                clearTimeout(timeout);
            },
    
            /**
             * Hide hovered dropdown
             */
            hideHovered: function(force) {
                if (force === true) {
                    if (Plugin.eventTrigger('beforeHide') === false) {
                        return;
                    }
    
                    Plugin.clearHovered();
                    mUtil.removeClass(element, 'm-dropdown--open');
                    the.open = false;
                    Plugin.eventTrigger('afterHide');
                } else {
                    if (mUtil.hasAttr(element, 'hover') === true) {
                        return;
                    }
    
                    if (Plugin.eventTrigger('beforeHide') === false) {
                        return;
                    }
    
                    var timeout = setTimeout(function() {
                        if (mUtil.attr(element, 'hover')) {
                            Plugin.clearHovered();
                            mUtil.removeClass(element, 'm-dropdown--open');
                            the.open = false;
                            Plugin.eventTrigger('afterHide');
                        }
                    }, the.options.hoverTimeout);
    
                    mUtil.attr(element, 'hover', '1');            
                    mUtil.attr(element, 'timeout', timeout);
                }
            },
    
            /**
             * Hide clicked dropdown
             */
            hideClicked: function() {
                if (Plugin.eventTrigger('beforeHide') === false) {
                    return;
                }
    
                mUtil.removeClass(element, 'm-dropdown--open');
                mUtil.data(element).remove('dropoff');
                the.open = false;
                Plugin.eventTrigger('afterHide');
            },
    
            /**
             * Hide dropdown
             */
            hide: function(force) {
                if (the.open === false) {
                    return the;
                }
    
                if (mUtil.isDesktopDevice() && the.options.toggle == 'hover') {
                    Plugin.hideHovered(force);
                } else {
                    Plugin.hideClicked();
                }
    
                if (the.layout.defaultDropPos == 'down' && the.layout.currentDropPos == 'up') {
                    mUtil.removeClass(element, 'm-dropdown--up');
                    the.layout.arrow.prependTo(the.layout.wrapper);
                    the.layout.currentDropPos = 'down';
                }
    
                return the;
            },
    
            /**
             * Hide on mouseout
             */
            hideMouseout: function() {
                if (mUtil.isDesktopDevice()) {
                    Plugin.hide();
                }
            },
    
            /**
             * Hide opened dropdowns
             */
            hideOpened: function() {
                var query = mUtil.findAll(body, '.m-dropdown.m-dropdown--open');
                
                for (var i = 0, j = query.length; i < j; i++) {
                    var dropdown = query[i];
                    mUtil.data(dropdown).get('dropdown').hide(true);
                }
            },
    
            /**
             * Adjust dropdown arrow positions
             */
            adjustArrowPos: function() {
                var width = mUtil.outerWidth(element); // ?
    
                var alignment = mUtil.hasClass(the.layout.arrow, 'm-dropdown__arrow--right') ? 'right' : 'left';
                var pos = 0;
    
                if (the.layout.arrow) {
                    if ( mUtil.isInResponsiveRange('mobile') && mUtil.hasClass(element, 'm-dropdown--mobile-full-width') ) {
                        pos = mUtil.offset(element).left + (width / 2) - Math.abs( parseInt(mUtil.css(the.layout.arrow, 'width')) / 2) - parseInt(mUtil.css(the.layout.wrapper, 'left'));
                        
                        mUtil.css(the.layout.arrow, 'right', 'auto');
                        mUtil.css(the.layout.arrow, 'left', pos + 'px');
                        
                        mUtil.css(the.layout.arrow, 'margin-left', 'auto');
                        mUtil.css(the.layout.arrow, 'margin-right', 'auto');
                    } else if (mUtil.hasClass(the.layout.arrow, 'm-dropdown__arrow--adjust')) {
                        pos = width / 2 - Math.abs( parseInt(mUtil.css(the.layout.arrow, 'width')) / 2);
                        if (mUtil.hasClass(element, 'm-dropdown--align-push')) {
                            pos = pos + 20;
                        }
    
                        if (alignment == 'right') {
                            if (mUtil.isRTL()) {
                                mUtil.css(the.layout.arrow, 'right', 'auto');
                                mUtil.css(the.layout.arrow, 'left', pos + 'px');
                            } else {
                                mUtil.css(the.layout.arrow, 'left', 'auto');
                                mUtil.css(the.layout.arrow, 'right', pos + 'px');
                            }
                        } else {
                            if (mUtil.isRTL()) {
                                mUtil.css(the.layout.arrow, 'left', 'auto');
                                mUtil.css(the.layout.arrow, 'right', pos + 'px');
                            } else {
                                mUtil.css(the.layout.arrow, 'right', 'auto');
                                mUtil.css(the.layout.arrow, 'left', pos + 'px');
                            }                       
                        }
                    }
                }
            },
    
            /**
             * Get zindex
             */
            setZindex: function() {
                var zIndex = 101; //mUtil.css(the.layout.wrapper, 'z-index');
                var newZindex = mUtil.getHighestZindex(element);
                if (newZindex >= zIndex) {
                    zIndex = newZindex + 1;
                }
                
                mUtil.css(the.layout.wrapper, 'z-index', zIndex);
            },
    
            /**
             * Check persistent
             */
            isPersistent: function() {
                return the.options.persistent;
            },
    
            /**
             * Check persistent
             */
            isShown: function() {
                return the.open;
            },
    
            /**
             * Trigger events
             */
            eventTrigger: function(name, args) {
                for (var i = 0; i < the.events.length; i++) {
                    var event = the.events[i];
                    if (event.name == name) {
                        if (event.one == true) {
                            if (event.fired == false) {
                                the.events[i].fired = true;
                                event.handler.call(this, the, args);
                            }
                        } else {
                            event.handler.call(this, the, args);
                        }
                    }
                }
            },
    
            addEvent: function(name, handler, one) {
                the.events.push({
                    name: name,
                    handler: handler,
                    one: one,
                    fired: false
                });
            }
        };
    
        //////////////////////////
        // ** Public Methods ** //
        //////////////////////////
    
        /**
         * Set default options 
         */
    
        the.setDefaults = function(options) {
            defaultOptions = options;
        };
    
        /**
         * Show dropdown
         * @returns {mDropdown}
         */
        the.show = function() {
            return Plugin.show();
        };
    
        /**
         * Hide dropdown
         * @returns {mDropdown}
         */
        the.hide = function() {
            return Plugin.hide();
        };
    
        /**
         * Toggle dropdown
         * @returns {mDropdown}
         */
        the.toggle = function() {
            return Plugin.toggle();
        };
    
        /**
         * Toggle dropdown
         * @returns {mDropdown}
         */
        the.isPersistent = function() {
            return Plugin.isPersistent();
        };
    
        /**
         * Check shown state
         * @returns {mDropdown}
         */
        the.isShown = function() {
            return Plugin.isShown();
        };
    
        /**
         * Set dropdown content
         * @returns {mDropdown}
         */
        the.setContent = function(content) {
            return Plugin.setContent(content);
        };
    
        /**
         * Register event
         */
        the.on = function(name, handler) {
            return Plugin.addEvent(name, handler);
        };
    
        /**
         * Register event
         */
        the.one = function(name, handler) {
            return Plugin.addEvent(name, handler, true);
        };
    
        ///////////////////////////////
        // ** Plugin Construction ** //
        ///////////////////////////////
    
        //== Run plugin
        Plugin.construct.apply(the, [options]);
    
        //== Init done
        init = true;
    
        // Return plugin instance
        return the;
    };
    
    //== Plugin global lazy initialization
    mUtil.on(document, '[m-dropdown-toggle="click"] .m-dropdown__toggle', 'click', function(e) {
        var element = this.closest('.m-dropdown');  
        var dropdown;
    
        if (element) {
            if (mUtil.data(element).has('dropdown')) {
                dropdown = mUtil.data(element).get('dropdown');
            } else {                 
                dropdown = new mDropdown(element);
            }             
    
            dropdown.toggle();
    
            e.preventDefault();
        } 
    });
    
    mUtil.on(document, '[m-dropdown-toggle="hover"] .m-dropdown__toggle', 'click', function(e) {
        if (mUtil.isDesktopDevice()) {
            if (mUtil.attr(this, 'href') == '#') {
                e.preventDefault();
            }
        } else if (mUtil.isMobileDevice()) {
            var element = this.closest('.m-dropdown');
            var dropdown;
    
            if (element) {
                if (mUtil.data(element).has('dropdown')) {
                    dropdown = mUtil.data(element).get('dropdown');
                } else {                        
                    dropdown = new mDropdown(element);
                }  
    
                dropdown.toggle();
    
                e.preventDefault();
            }
        }
    });
    
    mUtil.on(document, '[m-dropdown-toggle="hover"]', 'mouseover', function(e) {
        if (mUtil.isDesktopDevice()) {
            var element = this;
            var dropdown;
    
            if (element) {
                if (mUtil.data(element).has('dropdown')) {
                    dropdown = mUtil.data(element).get('dropdown');
                } else {                        
                    dropdown = new mDropdown(element);
                }              
    
                dropdown.show();
    
                e.preventDefault();
            }
        }
    });
    
    document.addEventListener("click", function(e) {
        var query;
        var body = mUtil.get('body');
        var target = e.target;
    
        //== Handle dropdown close
        if (query = body.querySelectorAll('.m-dropdown.m-dropdown--open')) {
            for (var i = 0, len = query.length; i < len; i++) {
                var element = query[i];
                if (mUtil.data(element).has('dropdown') === false) {
                    return;
                }
    
                var the = mUtil.data(element).get('dropdown');
                var toggle = mUtil.find(element, '.m-dropdown__toggle');
    
                if (mUtil.hasClass(element, 'm-dropdown--disable-close')) {
                    e.preventDefault();
                    e.stopPropagation();
                    //return;
                }
    
                if (toggle && target !== toggle && toggle.contains(target) === false && target.contains(toggle) === false) {
                    if (the.isPersistent() === false) {
                        the.hide();
                    } 
                } else if (element.contains(target) === false) {
                    the.hide();
                }
            }
        }
    });
    var mHeader = function(elementId, options) {
        //== Main object
        var the = this;
        var init = false;
    
        //== Get element object
        var element = mUtil.get(elementId);
        var body = mUtil.get('body');
    
        if (element === undefined) {
            return;
        }
    
        //== Default options
        var defaultOptions = {
            classic: false,
            offset: {
                mobile: 150,
                desktop: 200
            },
            minimize: {
                mobile: false,
                desktop: false
            }
        };
    
        ////////////////////////////
        // ** Private Methods  ** //
        ////////////////////////////
    
        var Plugin = {
            /**
             * Run plugin
             * @returns {mHeader}
             */
            construct: function(options) {
                if (mUtil.data(element).has('header')) {
                    the = mUtil.data(element).get('header');
                } else {
                    // reset header
                    Plugin.init(options);
    
                    // build header
                    Plugin.build();
    
                    mUtil.data(element).set('header', the);
                }
    
                return the;
            },
    
            /**
             * Handles subheader click toggle
             * @returns {mHeader}
             */
            init: function(options) {
                the.events = [];
    
                // merge default and user defined options
                the.options = mUtil.deepExtend({}, defaultOptions, options);
            },
    
            /**
             * Reset header
             * @returns {mHeader}
             */
            build: function() {
                var lastScrollTop = 0;
    
                if (the.options.minimize.mobile === false && the.options.minimize.desktop === false) {
                    return;
                }
    
                window.addEventListener('scroll', function() {
                    var offset = 0, on, off, st;
    
                    if (mUtil.isInResponsiveRange('desktop')) {
                        offset = the.options.offset.desktop;
                        on = the.options.minimize.desktop.on;
                        off = the.options.minimize.desktop.off;
                    } else if (mUtil.isInResponsiveRange('tablet-and-mobile')) {
                        offset = the.options.offset.mobile;
                        on = the.options.minimize.mobile.on;
                        off = the.options.minimize.mobile.off;
                    }
    
                    st = window.pageYOffset;
    
                    if (
                        (mUtil.isInResponsiveRange('tablet-and-mobile') && the.options.classic && the.options.classic.mobile) ||
                        (mUtil.isInResponsiveRange('desktop') && the.options.classic && the.options.classic.desktop)
    
                    ) {
                        if (st > offset) { // down scroll mode
                            mUtil.addClass(body, on);
                            mUtil.removeClass(body, off);
                        } else { // back scroll mode
                            mUtil.addClass(body, off);
                            mUtil.removeClass(body, on);
                        }
                    } else {
                        if (st > offset && lastScrollTop < st) { // down scroll mode
                            mUtil.addClass(body, on);
                            mUtil.removeClass(body, off);
                        } else { // back scroll mode
                            mUtil.addClass(body, off);
                            mUtil.removeClass(body, on);
                        }
    
                        lastScrollTop = st;
                    }
                });
            },
    
            /**
             * Trigger events
             */
            eventTrigger: function(name, args) {
                for (var i = 0; i < the.events.length; i++) {
                    var event = the.events[i];
                    if (event.name == name) {
                        if (event.one == true) {
                            if (event.fired == false) {
                                the.events[i].fired = true;
                                event.handler.call(this, the, args);
                            }
                        } else {
                            event.handler.call(this, the, args);
                        }
                    }
                }
            },
    
            addEvent: function(name, handler, one) {
                the.events.push({
                    name: name,
                    handler: handler,
                    one: one,
                    fired: false
                });
            }
        };
    
        //////////////////////////
        // ** Public Methods ** //
        //////////////////////////
    
        /**
         * Set default options 
         */
    
        the.setDefaults = function(options) {
            defaultOptions = options;
        };
    
        /**
         * Register event
         */
        the.on = function(name, handler) {
            return Plugin.addEvent(name, handler);
        };
    
        ///////////////////////////////
        // ** Plugin Construction ** //
        ///////////////////////////////
    
        //== Run plugin
        Plugin.construct.apply(the, [options]);
    
        //== Init done
        init = true;
    
        // Return plugin instance
        return the;
    };
    var mMenu = function(elementId, options) {
        //== Main object
        var the = this;
        var init = false;
    
        //== Get element object
        var element = mUtil.get(elementId);
        var body = mUtil.get('body');  
    
        if (!element) {
            return;
        }
    
        //== Default options
        var defaultOptions = {       
            // accordion submenu mode
            accordion: {
                slideSpeed: 200, // accordion toggle slide speed in milliseconds
                autoScroll: false, // enable auto scrolling(focus) to the clicked menu item
                autoScrollSpeed: 1200,
                expandAll: true // allow having multiple expanded accordions in the menu
            },
    
            // dropdown submenu mode
            dropdown: {
                timeout: 500 // timeout in milliseconds to show and hide the hoverable submenu dropdown
            }
        };
    
        ////////////////////////////
        // ** Private Methods  ** //
        ////////////////////////////
    
        var Plugin = {
            /**
             * Run plugin
             * @returns {mMenu}
             */
            construct: function(options) {
                if (mUtil.data(element).has('menu')) {
                    the = mUtil.data(element).get('menu');
                } else {
                    // reset menu
                    Plugin.init(options);
    
                    // reset menu
                    Plugin.reset();
    
                    // build menu
                    Plugin.build();
    
                    mUtil.data(element).set('menu', the);
                }
    
                return the;
            },
    
            /**
             * Handles submenu click toggle
             * @returns {mMenu}
             */
            init: function(options) {
                the.events = [];
    
                the.eventHandlers = {};
    
                // merge default and user defined options
                the.options = mUtil.deepExtend({}, defaultOptions, options);
    
                // pause menu
                the.pauseDropdownHoverTime = 0;
    
                the.uid = mUtil.getUniqueID();
            },
    
            update: function(options) {
                // merge default and user defined options
                the.options = mUtil.deepExtend({}, defaultOptions, options);
    
                // pause menu
                the.pauseDropdownHoverTime = 0;
    
                 // reset menu
                Plugin.reset();
    
                the.eventHandlers = {};
    
                // build menu
                Plugin.build();
    
                mUtil.data(element).set('menu', the);
            },
    
            reload: function() {
                 // reset menu
                Plugin.reset();
    
                // build menu
                Plugin.build();
            },
    
            /**
             * Reset menu
             * @returns {mMenu}
             */
            build: function() {
                //== General accordion submenu toggle
                if ( $(window).width() < 1024 ) {
                    the.eventHandlers['event_1'] = mUtil.on( element, '.m-menu__toggle', 'click', Plugin.handleSubmenuAccordion);
                }
                //== Dropdown mode(hoverable)
                if (Plugin.getSubmenuMode() === 'dropdown' || Plugin.isConditionalSubmenuDropdown()) {
                    // dropdown submenu - hover toggle
                    the.eventHandlers['event_2'] = mUtil.on( element, '[m-menu-submenu-toggle="hover"]', 'mouseover', Plugin.handleSubmenuDrodownHoverEnter);
                    the.eventHandlers['event_3'] = mUtil.on( element, '[m-menu-submenu-toggle="hover"]', 'mouseout', Plugin.handleSubmenuDrodownHoverExit);
    
                    // dropdown submenu - click toggle
                    the.eventHandlers['event_4'] = mUtil.on( element, '[m-menu-submenu-toggle="click"] > .m-menu__toggle, [m-menu-submenu-toggle="click"] > .m-menu__link .m-menu__toggle', 'click', Plugin.handleSubmenuDropdownClick);
                    the.eventHandlers['event_5'] = mUtil.on( element, '[m-menu-submenu-toggle="tab"] > .m-menu__toggle, [m-menu-submenu-toggle="tab"] > .m-menu__link .m-menu__toggle', 'click', Plugin.handleSubmenuDropdownTabClick);
                }
    
                //== General link click
                the.eventHandlers['event_6'] = mUtil.on(element, '.m-menu__item:not(.m-menu__item--submenu) > .m-menu__link:not(.m-menu__toggle):not(.m-menu__link--toggle-skip)', 'click', Plugin.handleLinkClick);
    
                //== Init scrollable menu
                if (the.options.scroll && the.options.scroll.height) {
                    Plugin.scrollerInit();
                }
            },
    
            /**
             * Reset menu
             * @returns {mMenu}
             */
            reset: function() { 
                mUtil.off( element, 'click', the.eventHandlers['event_1']);
    
                // dropdown submenu - hover toggle
                mUtil.off( element, 'mouseover', the.eventHandlers['event_2']);
                mUtil.off( element, 'mouseout', the.eventHandlers['event_3']);
    
                // dropdown submenu - click toggle
                mUtil.off( element, 'click', the.eventHandlers['event_4']);
                mUtil.off( element, 'click', the.eventHandlers['event_5']);
                
                mUtil.off(element, 'click', the.eventHandlers['event_6']);
            },
    
            /**
             * Init scroll menu
             *
            */
            scrollerInit: function() {
                if ( the.options.scroll && the.options.scroll.height ) {
                    mUtil.scrollerDestroy(element);
                    mUtil.scrollerInit(element, {disableForMobile: true, resetHeightOnDestroy: true, handleWindowResize: true, height: the.options.scroll.height});
                }            
            },
    
            /**
             * Update scroll menu
            */
            scrollerUpdate: function() {
                if ( the.options.scroll && the.options.scroll.height ) {
                    mUtil.scrollerUpdate(element);
                } else {
                    mUtil.scrollerDestroy(element);
                }
            },
    
            /**
             * Scroll top
            */
            scrollerTop: function() {
                if ( the.options.scroll && the.options.scroll.height ) {
                    mUtil.scrollerTop(element);
                }
            },
    
            /**
             * Get submenu mode for current breakpoint and menu state
             * @returns {mMenu}
             */
            getSubmenuMode: function(el) {
                if ( mUtil.isInResponsiveRange('desktop') ) {
                    if (el && mUtil.hasAttr(el, 'm-menu-submenu-toggle')) {
                        return mUtil.attr(el, 'm-menu-submenu-toggle');
                    }
    
                    if ( mUtil.isset(the.options.submenu, 'desktop.state.body') ) {
                        if ( mUtil.hasClass(body, the.options.submenu.desktop.state.body) ) {
                            return the.options.submenu.desktop.state.mode;
                        } else {
                            return the.options.submenu.desktop.default;
                        }
                    } else if ( mUtil.isset(the.options.submenu, 'desktop') ) {
                        return the.options.submenu.desktop;
                    }
                } else if ( mUtil.isInResponsiveRange('tablet') && mUtil.isset(the.options.submenu, 'tablet') ) {
                    return the.options.submenu.tablet;
                } else if ( mUtil.isInResponsiveRange('mobile') && mUtil.isset(the.options.submenu, 'mobile') ) {
                    return the.options.submenu.mobile;
                } else {
                    return false;
                }
            },
    
            /**
             * Get submenu mode for current breakpoint and menu state
             * @returns {mMenu}
             */
            isConditionalSubmenuDropdown: function() {
                if ( mUtil.isInResponsiveRange('desktop') && mUtil.isset(the.options.submenu, 'desktop.state.body') ) {
                    return true;
                } else {
                    return false;
                }
            },
    
            /**
             * Handles menu link click
             * @returns {mMenu}
             */
            handleLinkClick: function(e) {
                if ( Plugin.eventTrigger('linkClick', this) === false ) {
                    e.preventDefault();
                };
    
                if ( Plugin.getSubmenuMode(this) === 'dropdown' || Plugin.isConditionalSubmenuDropdown() ) {
                    Plugin.handleSubmenuDropdownClose(e, this);
                }
            },
    
            /**
             * Handles submenu hover toggle
             * @returns {mMenu}
             */
            handleSubmenuDrodownHoverEnter: function(e) {
                if ( Plugin.getSubmenuMode(this) === 'accordion' ) {
                    return;
                }
    
                if ( the.resumeDropdownHover() === false ) {
                    return;
                }
    
                var item = this;
    
                if ( item.getAttribute('data-hover') == '1' ) {
                    item.removeAttribute('data-hover');
                    clearTimeout( item.getAttribute('data-timeout') );
                    item.removeAttribute('data-timeout');
                    //Plugin.hideSubmenuDropdown(item, false);
                }
    
                Plugin.showSubmenuDropdown(item);
            },
    
            /**
             * Handles submenu hover toggle
             * @returns {mMenu}
             */
            handleSubmenuDrodownHoverExit: function(e) {
                if ( the.resumeDropdownHover() === false ) {
                    return;
                }
    
                if ( Plugin.getSubmenuMode(this) === 'accordion' ) {
                    return;
                }
    
                var item = this;
                var time = the.options.dropdown.timeout;
    
                var timeout = setTimeout(function() {
                    if ( item.getAttribute('data-hover') == '1' ) {
                        Plugin.hideSubmenuDropdown(item, true);
                    } 
                }, time);
    
                item.setAttribute('data-hover', '1');
                item.setAttribute('data-timeout', timeout);  
            },
    
            /**
             * Handles submenu click toggle
             * @returns {mMenu}
             */
            handleSubmenuDropdownClick: function(e) {
                if ( Plugin.getSubmenuMode(this) === 'accordion' ) {
                    return;
                }
     
                var item = this.closest('.m-menu__item'); 
    
                if ( item.getAttribute('m-menu-submenu-mode') == 'accordion' ) {
                    return;
                }
    
                if ( mUtil.hasClass(item, 'm-menu__item--hover') === false ) {
                    mUtil.addClass(item, 'm-menu__item--open-dropdown');
                    Plugin.showSubmenuDropdown(item);
                } else {
                    mUtil.removeClass(item, 'm-menu__item--open-dropdown' );
                    Plugin.hideSubmenuDropdown(item, true);
                }
    
                e.preventDefault();
            },
    
            /**
             * Handles tab click toggle
             * @returns {mMenu}
             */
            handleSubmenuDropdownTabClick: function(e) {
                if (Plugin.getSubmenuMode(this) === 'accordion') {
                    return;
                }
    
                var item = this.closest('.m-menu__item');
    
                if (item.getAttribute('m-menu-submenu-mode') == 'accordion') {
                    return;
                }
    
                if (mUtil.hasClass(item, 'm-menu__item--hover') == false) {
                    mUtil.addClass(item, 'm-menu__item--open-dropdown');
                    Plugin.showSubmenuDropdown(item);
                }
    
                e.preventDefault();
            },
    
            /**
             * Handles submenu dropdown close on link click
             * @returns {mMenu}
             */
            handleSubmenuDropdownClose: function(e, el) {
                // exit if its not submenu dropdown mode
                if (Plugin.getSubmenuMode(el) === 'accordion') {
                    return;
                }
    
                var shown = element.querySelectorAll('.m-menu__item.m-menu__item--submenu.m-menu__item--hover:not(.m-menu__item--tabs)');
    
                // check if currently clicked link's parent item ha
                if (shown.length > 0 && mUtil.hasClass(el, 'm-menu__toggle') === false && el.querySelectorAll('.m-menu__toggle').length === 0) {
                    // close opened dropdown menus
                    for (var i = 0, len = shown.length; i < len; i++) {
                        Plugin.hideSubmenuDropdown(shown[0], true);
                    }
                }
            },
    
            /**
             * helper functions
             * @returns {mMenu}
             */
            handleSubmenuAccordion: function(e, el) {
                var query;
                var item = el ? el : this;
    
                if ( Plugin.getSubmenuMode(el) === 'dropdown' && (query = item.closest('.m-menu__item') ) ) {
                    if (query.getAttribute('m-menu-submenu-mode') != 'accordion' ) {
                        e.preventDefault();
                        return;
                    }
                }
    
                var li = item.closest('.m-menu__item');
                var submenu = mUtil.child(li, '.m-menu__submenu, .m-menu__inner');
    
                if (mUtil.hasClass(item.closest('.m-menu__item'), 'm-menu__item--open-always')) {
                    return;
                }
    
                if ( li && submenu ) {
                    e.preventDefault();
                    var speed = the.options.accordion.slideSpeed;
                    var hasClosables = false;
    
                    if ( mUtil.hasClass(li, 'm-menu__item--open') === false ) {
                        // hide other accordions                    
                        if ( the.options.accordion.expandAll === false ) {
                            var subnav = item.closest('.m-menu__nav, .m-menu__subnav');
                            var closables = mUtil.children(subnav, '.m-menu__item.m-menu__item--open.m-menu__item--submenu:not(.m-menu__item--expanded):not(.m-menu__item--open-always)');
    
                            if ( subnav && closables ) {
                                for (var i = 0, len = closables.length; i < len; i++) {
                                    var el_ = closables[0];
                                    var submenu_ = mUtil.child(el_, '.m-menu__submenu');
                                    if ( submenu_ ) {
                                        mUtil.slideUp(submenu_, speed, function() {
                                            Plugin.scrollerUpdate();
                                            mUtil.removeClass(el_, 'm-menu__item--open');
                                        });                    
                                    }
                                }
                            }
                        }
    
                        mUtil.slideDown(submenu, speed, function() {
                            Plugin.scrollToItem(item);
                            Plugin.scrollerUpdate();
                            
                            Plugin.eventTrigger('submenuToggle', submenu);
                        });
                    
                        mUtil.addClass(li, 'm-menu__item--open');
    
                    } else {
                        mUtil.slideUp(submenu, speed, function() {
                            Plugin.scrollToItem(item);
                            Plugin.eventTrigger('submenuToggle', submenu);
                        });
    
                        mUtil.removeClass(li, 'm-menu__item--open');       
                    }
                }
            },
    
            /**
             * scroll to item function
             * @returns {mMenu}
             */
            scrollToItem: function(item) {
                // handle auto scroll for accordion submenus
                if ( mUtil.isInResponsiveRange('desktop') && the.options.accordion.autoScroll && element.getAttribute('m-menu-scrollable') !== '1' ) {
                    mUtil.scrollTo(item, the.options.accordion.autoScrollSpeed);
                }
            },
    
            /**
             * helper functions
             * @returns {mMenu}
             */
            hideSubmenuDropdown: function(item, classAlso) {
                // remove submenu activation class
                if ( classAlso ) {
                    mUtil.removeClass(item, 'm-menu__item--hover');
                    mUtil.removeClass(item, 'm-menu__item--active-tab');
                }
    
                // clear timeout
                item.removeAttribute('data-hover');
    
                if ( item.getAttribute('m-menu-dropdown-toggle-class') ) {
                    mUtil.removeClass(body, item.getAttribute('m-menu-dropdown-toggle-class'));
                }
    
                var timeout = item.getAttribute('data-timeout');
                item.removeAttribute('data-timeout');
                clearTimeout(timeout);
            },
    
            /**
             * helper functions
             * @returns {mMenu}
             */
            showSubmenuDropdown: function(item) {
                // close active submenus
                var list = element.querySelectorAll('.m-menu__item--submenu.m-menu__item--hover, .m-menu__item--submenu.m-menu__item--active-tab');
    
                if ( list ) {
                    for (var i = 0, len = list.length; i < len; i++) {
                        var el = list[i];
                        if ( item !== el && el.contains(item) === false && item.contains(el) === false ) {
                            Plugin.hideSubmenuDropdown(el, true);
                        }
                    }
                } 
    
                // adjust submenu position
                Plugin.adjustSubmenuDropdownArrowPos(item);
    
                // add submenu activation class
                mUtil.addClass(item, 'm-menu__item--hover');
                
                if ( item.getAttribute('m-menu-dropdown-toggle-class') ) {
                    mUtil.addClass(body, item.getAttribute('m-menu-dropdown-toggle-class'));
                }
            },
    
            /**
             * Handles submenu slide toggle
             * @returns {mMenu}
             */
            createSubmenuDropdownClickDropoff: function(el) {
                var query;
                var zIndex = (query = mUtil.child(el, '.m-menu__submenu') ? mUtil.css(query, 'z-index') : 0) - 1;
    
                var dropoff = document.createElement('<div class="m-menu__dropoff" style="background: transparent; position: fixed; top: 0; bottom: 0; left: 0; right: 0; z-index: ' + zIndex + '"></div>');
    
                body.appendChild(dropoff);
    
                mUtil.addEvent(dropoff, 'click', function(e) {
                    e.stopPropagation();
                    e.preventDefault();
                    mUtil.remove(this);
                    Plugin.hideSubmenuDropdown(el, true);
                });
            },
    
            /**
             * Handles submenu click toggle
             * @returns {mMenu}
             */
            adjustSubmenuDropdownArrowPos: function(item) {
                var submenu = mUtil.child(item, '.m-menu__submenu');
                var arrow = mUtil.child( submenu, '.m-menu__arrow.m-menu__arrow--adjust');
                var subnav = mUtil.child( submenu, '.m-menu__subnav');
    
                if ( arrow ) { 
                    var pos = 0; 
                    var link = mUtil.child(item, '.m-menu__link');
    
                    if ( mUtil.hasClass(submenu, 'm-menu__submenu--classic') || mUtil.hasClass(submenu, 'm-menu__submenu--fixed') ) {
                        if ( mUtil.hasClass(submenu, 'm-menu__submenu--right')) {
                            pos = mUtil.outerWidth(item) / 2;
                            if (mUtil.hasClass(submenu, 'm-menu__submenu--pull')) {
                                if (mUtil.isRTL()) {
                                    pos = pos + Math.abs( parseFloat(mUtil.css(submenu, 'margin-left')) );
                                } else {
                                    pos = pos + Math.abs( parseFloat(mUtil.css(submenu, 'margin-right')) );
                                }
                            }
                            pos = parseInt(mUtil.css(submenu, 'width')) - pos;
                        } else if ( mUtil.hasClass(submenu, 'm-menu__submenu--left') ) {
                            pos = mUtil.outerWidth(item) / 2;
                            if ( mUtil.hasClass(submenu, 'm-menu__submenu--pull')) {
                                if (mUtil.isRTL()) {
                                    pos = pos + Math.abs( parseFloat(mUtil.css(submenu, 'margin-right')) );
                                } else {
                                    pos = pos + Math.abs( parseFloat(mUtil.css(submenu, 'margin-left')) );
                                }
                            }
                        }
    
                        if (mUtil.isRTL()) {
                            mUtil.css(arrow, 'right', pos + 'px');  
                        } else {
                            mUtil.css(arrow, 'left', pos + 'px');  
                        }
                    } else {
                        if ( mUtil.hasClass(submenu, 'm-menu__submenu--center') || mUtil.hasClass(submenu, 'm-menu__submenu--full') ) {
                            pos = mUtil.offset(item).left - ((mUtil.getViewPort().width - parseInt(mUtil.css(submenu, 'width'))) / 2);
                            pos = pos + (mUtil.outerWidth(item) / 2);
    
                            mUtil.css(arrow, 'left', pos + 'px');
                            if (mUtil.isRTL()) {
                                mUtil.css(arrow, 'right', 'auto');
                            }                        
                        }
                    }
                }
            },
    
            /**
             * Handles submenu hover toggle
             * @returns {mMenu}
             */
            pauseDropdownHover: function(time) {
                var date = new Date();
    
                the.pauseDropdownHoverTime = date.getTime() + time;
            },
    
            /**
             * Handles submenu hover toggle
             * @returns {mMenu}
             */
            resumeDropdownHover: function() {
                var date = new Date();
    
                return (date.getTime() > the.pauseDropdownHoverTime ? true : false);
            },
    
            /**
             * Reset menu's current active item
             * @returns {mMenu}
             */
            resetActiveItem: function(item) {
                var list;
                var parents;
    
                list = element.querySelectorAll('.m-menu__item--active');
                
                for (var i = 0, len = list.length; i < len; i++) {
                    var el = list[0];
                    mUtil.removeClass(el, 'm-menu__item--active');
                    mUtil.hide( mUtil.child(el, '.m-menu__submenu') );
                    parents = mUtil.parents(el, '.m-menu__item--submenu');
    
                    for (var i_ = 0, len_ = parents.length; i_ < len_; i_++) {
                        var el_ = parents[i];
                        mUtil.removeClass(el_, 'm-menu__item--open');
                        mUtil.hide( mUtil.child(el_, '.m-menu__submenu') );
                    }
                }
    
                // close open submenus
                if ( the.options.accordion.expandAll === false ) {
                    if ( list = element.querySelectorAll('.m-menu__item--open') ) {
                        for (var i = 0, len = list.length; i < len; i++) {
                            mUtil.removeClass(parents[0], 'm-menu__item--open');
                        }
                    }
                }
            },
    
            /**
             * Sets menu's active item
             * @returns {mMenu}
             */
            setActiveItem: function(item) {
                // reset current active item
                Plugin.resetActiveItem();
    
                mUtil.addClass(item, 'm-menu__item--active');
                
                var parents = mUtil.parents(item, '.m-menu__item--submenu');
                for (var i = 0, len = parents.length; i < len; i++) {
                    mUtil.addClass(parents[i], 'm-menu__item--open');
                }
            },
    
            /**
             * Returns page breadcrumbs for the menu's active item
             * @returns {mMenu}
             */
            getBreadcrumbs: function(item) {
                var query;
                var breadcrumbs = [];
                var link = mUtil.child(item, '.m-menu__link');
    
                breadcrumbs.push({
                    text: (query = mUtil.child(link, '.m-menu__link-text') ? query.innerHTML : ''),
                    title: link.getAttribute('title'),
                    href: link.getAttribute('href')
                });
    
                var parents = mUtil.parents(item, '.m-menu__item--submenu');
                for (var i = 0, len = parents.length; i < len; i++) {
                    var submenuLink = mUtil.child(parents[i], '.m-menu__link');
    
                    breadcrumbs.push({
                        text: (query = mUtil.child(submenuLink, '.m-menu__link-text') ? query.innerHTML : ''),
                        title: submenuLink.getAttribute('title'),
                        href: submenuLink.getAttribute('href')
                    });
                }
    
                return  breadcrumbs.reverse();
            },
    
            /**
             * Returns page title for the menu's active item
             * @returns {mMenu}
             */
            getPageTitle: function(item) {
                var query;
    
                return (query = mUtil.child(item, '.m-menu__link-text') ? query.innerHTML : '');
            },
    
            /**
             * Trigger events
             */
            eventTrigger: function(name, args) {
                for (var i = 0; i < the.events.length; i++ ) {
                    var event = the.events[i];
                    if ( event.name == name ) {
                        if ( event.one == true ) {
                            if ( event.fired == false ) {
                                the.events[i].fired = true;
                                event.handler.call(this, the, args);
                            }
                        } else {
                            event.handler.call(this, the, args);
                        }
                    }
                }
            },
    
            addEvent: function(name, handler, one) {
                the.events.push({
                    name: name,
                    handler: handler,
                    one: one,
                    fired: false
                });
            },
    
            removeEvent: function(name) {
                if (the.events[name]) {
                    delete the.events[name];
                }
            }
        };
    
        //////////////////////////
        // ** Public Methods ** //
        //////////////////////////
    
        /**
         * Set default options 
         */
    
        the.setDefaults = function(options) {
            defaultOptions = options;
        };
    
        /**
         * Set active menu item
         */
        the.scrollerUpdate = function() {
            return Plugin.scrollerUpdate();
        };
    
        /**
         * Set active menu item
         */
        the.scrollerTop = function() {
            return Plugin.scrollerTop();
        };
    
        /**
         * Set active menu item
         */
        the.setActiveItem = function(item) {
            return Plugin.setActiveItem(item);
        };
    
        the.reload = function() {
            return Plugin.reload();
        };
    
        the.update = function(options) {
            return Plugin.update(options);
        };
    
        /**
         * Set breadcrumb for menu item
         */
        the.getBreadcrumbs = function(item) {
            return Plugin.getBreadcrumbs(item);
        };
    
        /**
         * Set page title for menu item
         */
        the.getPageTitle = function(item) {
            return Plugin.getPageTitle(item);
        };
    
        /**
         * Get submenu mode
         */
        the.getSubmenuMode = function(el) {
            return Plugin.getSubmenuMode(el);
        };
    
        /**
         * Hide dropdown submenu
         * @returns {jQuery}
         */
        the.hideDropdown = function(item) {
            Plugin.hideSubmenuDropdown(item, true);
        };
    
        /**
         * Disable menu for given time
         * @returns {jQuery}
         */
        the.pauseDropdownHover = function(time) {
            Plugin.pauseDropdownHover(time);
        };
    
        /**
         * Disable menu for given time
         * @returns {jQuery}
         */
        the.resumeDropdownHover = function() {
            return Plugin.resumeDropdownHover();
        };
    
        /**
         * Register event
         */
        the.on = function(name, handler) {
            return Plugin.addEvent(name, handler);
        };
    
        the.off = function(name) {
            return Plugin.removeEvent(name);
        };
    
        the.one = function(name, handler) {
            return Plugin.addEvent(name, handler, true);
        };
    
        ///////////////////////////////
        // ** Plugin Construction ** //
        ///////////////////////////////
    
        //== Run plugin
        Plugin.construct.apply(the, [options]);
    
        //== Handle plugin on window resize
        mUtil.addResizeHandler(function() {
            if (init) {
                the.reload();
            }  
        });
    
        //== Init done
        init = true;
    
        // Return plugin instance
        return the;
    };
    
    // Plugin global lazy initialization
    document.addEventListener("click", function (e) {
        var body = mUtil.get('body');
        var query;
        if ( query = body.querySelectorAll('.m-menu__nav .m-menu__item.m-menu__item--submenu.m-menu__item--hover:not(.m-menu__item--tabs)[m-menu-submenu-toggle="click"]') ) {
            for (var i = 0, len = query.length; i < len; i++) {
                var element = query[i].closest('.m-menu__nav').parentNode;
    
                if ( element ) {
                    var the = mUtil.data(element).get('menu');
    
                    if ( !the ) {
                        break;
                    }
    
                    if ( !the || the.getSubmenuMode() !== 'dropdown' ) {
                        break;
                    }
    
                    if ( e.target !== element && element.contains(e.target) === false ) {
                        var items;
                        if ( items = element.querySelectorAll('.m-menu__item--submenu.m-menu__item--hover:not(.m-menu__item--tabs)[m-menu-submenu-toggle="click"]') ) {
                            for (var j = 0, cnt = items.length; j < cnt; j++) {
                                the.hideDropdown(items[j]);
                            }
                        }
                    }
                }            
            }
        } 
    });
    var mOffcanvas = function(elementId, options) {
        //== Main object
        var the = this;
        var init = false;
    
        //== Get element object
        var element = mUtil.get(elementId);
        var body = mUtil.get('body');
    
        if (!element) {
            return;
        }
    
        //== Default options
        var defaultOptions = {};
    
        ////////////////////////////
        // ** Private Methods  ** //
        ////////////////////////////
    
        var Plugin = {
            /**
             * Run plugin
             * @returns {moffcanvas}
             */
            construct: function(options) {
                if (mUtil.data(element).has('offcanvas')) {
                    the = mUtil.data(element).get('offcanvas');
                } else {
                    // reset offcanvas
                    Plugin.init(options);
                    
                    // build offcanvas
                    Plugin.build();
    
                    mUtil.data(element).set('offcanvas', the);
                }
    
                return the;
            },
    
            /**
             * Handles suboffcanvas click toggle
             * @returns {moffcanvas}
             */
            init: function(options) {
                the.events = [];
    
                // merge default and user defined options
                the.options = mUtil.deepExtend({}, defaultOptions, options);
                the.overlay;
    
                the.classBase = the.options.baseClass;
                the.classShown = the.classBase + '--on';
                the.classOverlay = the.classBase + '-overlay';
    
                the.state = mUtil.hasClass(element, the.classShown) ? 'shown' : 'hidden';
            },
    
            build: function() {
                //== offcanvas toggle
                if (the.options.toggleBy) {
                    if (typeof the.options.toggleBy === 'string') { 
                        mUtil.addEvent( the.options.toggleBy, 'click', Plugin.toggle); 
                    } else if (the.options.toggleBy && the.options.toggleBy[0] && the.options.toggleBy[0].target) {
                        for (var i in the.options.toggleBy) { 
                            mUtil.addEvent( the.options.toggleBy[i].target, 'click', Plugin.toggle); 
                        }
                    } else if (the.options.toggleBy && the.options.toggleBy.target) {
                        mUtil.addEvent( the.options.toggleBy.target, 'click', Plugin.toggle); 
                    } 
                }
    
                //== offcanvas close
                var closeBy = mUtil.get(the.options.closeBy);
                if (closeBy) {
                    mUtil.addEvent(closeBy, 'click', Plugin.hide);
                }
            },
    
    
            /**
             * Handles offcanvas toggle
             */
            toggle: function() {;
                Plugin.eventTrigger('toggle'); 
    
                if (the.state == 'shown') {
                    Plugin.hide(this);
                } else {
                    Plugin.show(this);
                }
            },
    
            /**
             * Handles offcanvas show
             */
            show: function(target) {
                if (the.state == 'shown') {
                    return;
                }
    
                Plugin.eventTrigger('beforeShow');
    
                Plugin.togglerClass(target, 'show');
    
                //== Offcanvas panel
                mUtil.addClass(body, the.classShown);
                mUtil.addClass(element, the.classShown);
    
                the.state = 'shown';
    
                if (the.options.overlay) {
                    the.overlay = mUtil.insertAfter(document.createElement('DIV') , element );
                    mUtil.addClass(the.overlay, the.classOverlay);
                    mUtil.addEvent(the.overlay, 'click', function(e) {
                        e.stopPropagation();
                        e.preventDefault();
                        Plugin.hide(target);       
                    });
                }
    
                Plugin.eventTrigger('afterShow');
            },
    
            /**
             * Handles offcanvas hide
             */
            hide: function(target) {
                if (the.state == 'hidden') {
                    return;
                }
    
                Plugin.eventTrigger('beforeHide');
    
                Plugin.togglerClass(target, 'hide');
    
                mUtil.removeClass(body, the.classShown);
                mUtil.removeClass(element, the.classShown);
    
                the.state = 'hidden';
    
                if (the.options.overlay && the.overlay) {
                    mUtil.remove(the.overlay);
                }
    
                Plugin.eventTrigger('afterHide');
            },
    
            /**
             * Handles toggler class
             */
            togglerClass: function(target, mode) {
                //== Toggler
                var id = mUtil.attr(target, 'id');
                var toggleBy;
    
                if (the.options.toggleBy && the.options.toggleBy[0] && the.options.toggleBy[0].target) {
                    for (var i in the.options.toggleBy) {
                        if (the.options.toggleBy[i].target === id) {
                            toggleBy = the.options.toggleBy[i];
                        }        
                    }
                } else if (the.options.toggleBy && the.options.toggleBy.target) {
                    toggleBy = the.options.toggleBy;
                }
    
                if (toggleBy) {                
                    var el = mUtil.get(toggleBy.target);
                    
                    if (mode === 'show') {
                        mUtil.addClass(el, toggleBy.state);
                    }
    
                    if (mode === 'hide') {
                        mUtil.removeClass(el, toggleBy.state);
                    }
                }
            },
    
            /**
             * Trigger events
             */
            eventTrigger: function(name, args) {
                for (var i = 0; i < the.events.length; i++) {
                    var event = the.events[i];
                    if (event.name == name) {
                        if (event.one == true) {
                            if (event.fired == false) {
                                the.events[i].fired = true;
                                event.handler.call(this, the, args);
                            }
                        } else {
                            event.handler.call(this, the, args);
                        }
                    }
                }
            },
    
            addEvent: function(name, handler, one) {
                the.events.push({
                    name: name,
                    handler: handler,
                    one: one,
                    fired: false
                });
            }
        };
    
        //////////////////////////
        // ** Public Methods ** //
        //////////////////////////
    
        /**
         * Set default options 
         */
    
        the.setDefaults = function(options) {
            defaultOptions = options;
        };
    
        /**
         * Hide 
         */
        the.hide = function() {
            return Plugin.hide();
        };
    
        /**
         * Show 
         */
        the.show = function() {
            return Plugin.show();
        };
    
        /**
         * Get suboffcanvas mode
         */
        the.on = function(name, handler) {
            return Plugin.addEvent(name, handler);
        };
    
        /**
         * Set offcanvas content
         * @returns {mOffcanvas}
         */
        the.one = function(name, handler) {
            return Plugin.addEvent(name, handler, true);
        };
    
        ///////////////////////////////
        // ** Plugin Construction ** //
        ///////////////////////////////
    
        //== Run plugin
        Plugin.construct.apply(the, [options]);
    
        //== Init done
        init = true;
    
        // Return plugin instance
        return the;
    };
    // plugin setup
    var mPortlet = function(elementId, options) {
        //== Main object
        var the = this;
        var init = false;
    
        //== Get element object
        var element = mUtil.get(elementId);
        var body = mUtil.get('body');
    
        if (!element) {
            return;
        }
    
        //== Default options
        var defaultOptions = {
            bodyToggleSpeed: 400,
            tooltips: true,
            tools: {
                toggle: {
                    collapse: 'Collapse',
                    expand: 'Expand'
                },
                reload: 'Reload',
                remove: 'Remove',
                fullscreen: {
                    on: 'Fullscreen',
                    off: 'Exit Fullscreen'
                }
            },
            sticky: {
                offset: 300,
                zIndex: 98
            }
        };
    
        ////////////////////////////
        // ** Private Methods  ** //
        ////////////////////////////
    
        var Plugin = {
            /**
             * Construct
             */
    
            construct: function(options) {
                if (mUtil.data(element).has('portlet')) {
                    the = mUtil.data(element).get('portlet');
                } else {
                    // reset menu
                    Plugin.init(options);
    
                    // build menu
                    Plugin.build();
    
                    mUtil.data(element).set('portlet', the);
                }
    
                return the;
            },
    
            /**
             * Init portlet
             */
            init: function(options) {
                the.element = element;
                the.events = [];
    
                // merge default and user defined options
                the.options = mUtil.deepExtend({}, defaultOptions, options);
                the.head = mUtil.child(element, '.m-portlet__head');
                the.foot = mUtil.child(element, '.m-portlet__foot');
    
                if (mUtil.child(element, '.m-portlet__body')) {
                    the.body = mUtil.child(element, '.m-portlet__body');
                } else if (mUtil.child(element, '.m-form').length !== 0) {
                    the.body = mUtil.child(element, '.m-form');
                }
            },
    
            /**
             * Build Form Wizard
             */
            build: function() {
                //== Remove
                var remove = mUtil.find(the.head, '[m-portlet-tool=remove]');
                if (remove) {
                    mUtil.addEvent(remove, 'click', function(e) {
                        e.preventDefault();
                        Plugin.remove();
                    });
                }
    
                //== Reload
                var reload = mUtil.find(the.head, '[m-portlet-tool=reload]');
                if (reload) {
                    mUtil.addEvent(reload, 'click', function(e) {
                        e.preventDefault();
                        Plugin.reload();
                    });
                }
    
                //== Toggle
                var toggle = mUtil.find(the.head, '[m-portlet-tool=toggle]');
                if (toggle) {
                    mUtil.addEvent(toggle, 'click', function(e) {
                        e.preventDefault();
                        Plugin.toggle();
                    });
                }
    
                //== Fullscreen
                var fullscreen = mUtil.find(the.head, '[m-portlet-tool=fullscreen]');
                if (fullscreen) {
                    mUtil.addEvent(fullscreen, 'click', function(e) {
                        e.preventDefault();
                        Plugin.fullscreen();
                    });
                }
    
                Plugin.setupTooltips();
            },
    
            /**
             * Window scroll handle event for sticky portlet
             */
            onScrollSticky: function() {
                var st = window.pageYOffset;
                var offset = the.options.sticky.offset;
    
    
                if (st > offset) {
                    if (mUtil.hasClass(body, 'm-portlet--sticky') === false) {
                        Plugin.eventTrigger('stickyOn');
    
                        mUtil.addClass(body, 'm-portlet--sticky');
                        mUtil.addClass(element, 'm-portlet--sticky');
    
                        Plugin.updateSticky();
                    }
                } else { // back scroll mode
                    if (mUtil.hasClass(body, 'm-portlet--sticky')) {
                        Plugin.eventTrigger('stickyOff');
    
                        mUtil.removeClass(body, 'm-portlet--sticky');
                        mUtil.removeClass(element, 'm-portlet--sticky');
    
                        Plugin.resetSticky();
                    }
                }
            },
    
            /**
             * Init sticky portlet
             */
            initSticky: function() {
                if (!the.head) {
                    return;
                }
    
                window.addEventListener('scroll', Plugin.onScrollSticky);
            },
    
            /**
             * Update sticky portlet positions
             */
            updateSticky: function() {
                if (!the.head) {
                    return;
                }
    
                var top;
    
                if (mUtil.hasClass(body, 'm-portlet--sticky')) {
                    if (the.options.sticky.position.top instanceof Function) {
                        top = parseInt(the.options.sticky.position.top.call());
                    } else {
                        top = parseInt(the.options.sticky.position.top);
                    }
    
                    var left;
                    if (the.options.sticky.position.left instanceof Function) {
                        left = parseInt(the.options.sticky.position.left.call());
                    } else {
                        left = parseInt(the.options.sticky.position.left);
                    }
    
                    var right;
                    if (the.options.sticky.position.right instanceof Function) {
                        right = parseInt(the.options.sticky.position.right.call());
                    } else {
                        right = parseInt(the.options.sticky.position.right);
                    }
    
                    mUtil.css(the.head, 'z-index', the.options.sticky.zIndex);
                    mUtil.css(the.head, 'top', top + 'px');
    
                    if (mUtil.isRTL()) {
                        mUtil.css(the.head, 'left', right + 'px');
                        mUtil.css(the.head, 'right',left  + 'px');
                    } else {
                        mUtil.css(the.head, 'left', left + 'px');
                        mUtil.css(the.head, 'right', right + 'px');
                    }
                    
                }
            },
    
            /**
             * Reset sticky portlet positions
             */
            resetSticky: function() {
                if (!the.head) {
                    return;
                }
    
                if (mUtil.hasClass(body, 'm-portlet--sticky') === false) {
                    mUtil.css(the.head, 'z-index', '');
                    mUtil.css(the.head, 'top', '');
                    mUtil.css(the.head, 'left', '');
                    mUtil.css(the.head, 'right', '');
                }
            },
    
            /**
             * Destroy sticky portlet
             */
            destroySticky: function() {
                if (!the.head) {
                    return;
                }
    
                Plugin.resetSticky();
    
                window.removeEventListener('scroll', Plugin.onScrollSticky);
            },
    
            /**
             * Remove portlet
             */
            remove: function() {
                if (Plugin.eventTrigger('beforeRemove') === false) {
                    return;
                }
    
                if (mUtil.hasClass(body, 'm-portlet--fullscreen') && mUtil.hasClass(element, 'm-portlet--fullscreen')) {
                    Plugin.fullscreen('off');
                }
    
                Plugin.removeTooltips();
    
                mUtil.remove(element);
    
                Plugin.eventTrigger('afterRemove');
            },
    
            /**
             * Set content
             */
            setContent: function(html) {
                if (html) {
                    the.body.innerHTML = html;
                }
            },
    
            /**
             * Get body
             */
            getBody: function() {
                return the.body;
            },
    
            /**
             * Get self
             */
            getSelf: function() {
                return element;
            },
    
            /**
             * Setup tooltips
             */
            setupTooltips: function() {
                if (the.options.tooltips) {
                    var collapsed = mUtil.hasClass(element, 'm-portlet--collapse') || mUtil.hasClass(element, 'm-portlet--collapsed');
                    var fullscreenOn = mUtil.hasClass(body, 'm-portlet--fullscreen') && mUtil.hasClass(element, 'm-portlet--fullscreen');
    
                    //== Remove
                    var remove = mUtil.find(the.head, '[m-portlet-tool=remove]');
                    if (remove) {
                        var placement = (fullscreenOn ? 'bottom' : 'top');
                        var tip = new Tooltip(remove, {
                            title: the.options.tools.remove,
                            placement: placement,
                            offset: (fullscreenOn ? '0,10px,0,0' : '0,5px'),
                            trigger: 'hover',
                            template: '<div class="m-tooltip m-tooltip--portlet tooltip bs-tooltip-' + placement + '" role="tooltip">\
                                <div class="tooltip-arrow arrow"></div>\
                                <div class="tooltip-inner"></div>\
                            </div>'
                        });
    
                        mUtil.data(remove).set('tooltip', tip);
                    }
    
                    //== Reload
                    var reload = mUtil.find(the.head, '[m-portlet-tool=reload]');
                    if (reload) {
                        var placement = (fullscreenOn ? 'bottom' : 'top');
                        var tip = new Tooltip(reload, {
                            title: the.options.tools.reload,
                            placement: placement,
                            offset: (fullscreenOn ? '0,10px,0,0' : '0,5px'),
                            trigger: 'hover',
                            template: '<div class="m-tooltip m-tooltip--portlet tooltip bs-tooltip-' + placement + '" role="tooltip">\
                                <div class="tooltip-arrow arrow"></div>\
                                <div class="tooltip-inner"></div>\
                            </div>'
                        });
    
                        mUtil.data(reload).set('tooltip', tip);
                    }
    
                    //== Toggle
                    var toggle = mUtil.find(the.head, '[m-portlet-tool=toggle]');
                    if (toggle) {
                        var placement = (fullscreenOn ? 'bottom' : 'top');
                        var tip = new Tooltip(toggle, {
                            title: (collapsed ? the.options.tools.toggle.expand : the.options.tools.toggle.collapse),
                            placement: placement,
                            offset: (fullscreenOn ? '0,10px,0,0' : '0,5px'),
                            trigger: 'hover',
                            template: '<div class="m-tooltip m-tooltip--portlet tooltip bs-tooltip-' + placement + '" role="tooltip">\
                                <div class="tooltip-arrow arrow"></div>\
                                <div class="tooltip-inner"></div>\
                            </div>'
                        });
    
                        mUtil.data(toggle).set('tooltip', tip);
                    }
    
                    //== Fullscreen
                    var fullscreen = mUtil.find(the.head, '[m-portlet-tool=fullscreen]');
                    if (fullscreen) {
                        var placement = (fullscreenOn ? 'bottom' : 'top');
                        var tip = new Tooltip(fullscreen, {
                            title: (fullscreenOn ? the.options.tools.fullscreen.off : the.options.tools.fullscreen.on),
                            placement: placement,
                            offset: (fullscreenOn ? '0,10px,0,0' : '0,5px'),
                            trigger: 'hover',
                            template: '<div class="m-tooltip m-tooltip--portlet tooltip bs-tooltip-' + placement + '" role="tooltip">\
                                <div class="tooltip-arrow arrow"></div>\
                                <div class="tooltip-inner"></div>\
                            </div>'
                        });
    
                        mUtil.data(fullscreen).set('tooltip', tip);
                    }
                }
            },
    
            /**
             * Setup tooltips
             */
            removeTooltips: function() {
                if (the.options.tooltips) {
                    //== Remove
                    var remove = mUtil.find(the.head, '[m-portlet-tool=remove]');
                    if (remove && mUtil.data(remove).has('tooltip')) {
                        mUtil.data(remove).get('tooltip').dispose();
                    }
    
                    //== Reload
                    var reload = mUtil.find(the.head, '[m-portlet-tool=reload]');
                    if (reload && mUtil.data(reload).has('tooltip')) {
                        mUtil.data(reload).get('tooltip').dispose();
                    }
    
                    //== Toggle
                    var toggle = mUtil.find(the.head, '[m-portlet-tool=toggle]');
                    if (toggle && mUtil.data(toggle).has('tooltip')) {
                        mUtil.data(toggle).get('tooltip').dispose();
                    }
    
                    //== Fullscreen
                    var fullscreen = mUtil.find(the.head, '[m-portlet-tool=fullscreen]');
                    if (fullscreen && mUtil.data(fullscreen).has('tooltip')) {
                        mUtil.data(fullscreen).get('tooltip').dispose();
                    }
                }
            },
    
            /**
             * Reload
             */
            reload: function() {
                Plugin.eventTrigger('reload');
            },
    
            /**
             * Toggle
             */
            toggle: function() {
                if (mUtil.hasClass(element, 'm-portlet--collapse') || mUtil.hasClass(element, 'm-portlet--collapsed')) {
                    Plugin.expand();
                } else {
                    Plugin.collapse();
                }
            },
    
            /**
             * Collapse
             */
            collapse: function() {
                if (Plugin.eventTrigger('beforeCollapse') === false) {
                    return;
                }
    
                mUtil.slideUp(the.body, the.options.bodyToggleSpeed, function() {
                    Plugin.eventTrigger('afterCollapse');
                });
    
                mUtil.addClass(element, 'm-portlet--collapse');
    
                var toggle = mUtil.find(the.head, '[m-portlet-tool=toggle]');
                if (toggle && mUtil.data(toggle).has('tooltip')) {
                    mUtil.data(toggle).get('tooltip').updateTitleContent(the.options.tools.toggle.expand);
                }
            },
    
            /**
             * Expand
             */
            expand: function() {
                if (Plugin.eventTrigger('beforeExpand') === false) {
                    return;
                }
    
                mUtil.slideDown(the.body, the.options.bodyToggleSpeed, function() {
                    Plugin.eventTrigger('afterExpand');
                });
    
                mUtil.removeClass(element, 'm-portlet--collapse');
                mUtil.removeClass(element, 'm-portlet--collapsed');
    
                var toggle = mUtil.find(the.head, '[m-portlet-tool=toggle]');
                if (toggle && mUtil.data(toggle).has('tooltip')) {
                    mUtil.data(toggle).get('tooltip').updateTitleContent(the.options.tools.toggle.collapse);
                }
            },
    
            /**
             * Toggle
             */
            fullscreen: function(mode) {
                var d = {};
                var speed = 300;
    
                if (mode === 'off' || (mUtil.hasClass(body, 'm-portlet--fullscreen') && mUtil.hasClass(element, 'm-portlet--fullscreen'))) {
                    Plugin.eventTrigger('beforeFullscreenOff');
    
                    mUtil.removeClass(body, 'm-portlet--fullscreen');
                    mUtil.removeClass(element, 'm-portlet--fullscreen');
    
                    Plugin.removeTooltips();
                    Plugin.setupTooltips();
    
                    if (the.foot) {
                        mUtil.css(the.body, 'margin-bottom', '');
                        mUtil.css(the.foot, 'margin-top', '');
                    }
    
                    Plugin.eventTrigger('afterFullscreenOff');
                } else {
                    Plugin.eventTrigger('beforeFullscreenOn');
    
                    mUtil.addClass(element, 'm-portlet--fullscreen');
                    mUtil.addClass(body, 'm-portlet--fullscreen');
    
                    Plugin.removeTooltips();
                    Plugin.setupTooltips();
    
    
                    if (the.foot) {
                        var height1 = parseInt(mUtil.css(the.foot, 'height'));
                        var height2 = parseInt(mUtil.css(the.foot, 'height')) + parseInt(mUtil.css(the.head, 'height'));
                        mUtil.css(the.body, 'margin-bottom', height1 + 'px');
                        mUtil.css(the.foot, 'margin-top', '-' + height2 + 'px');
                    }
    
                    Plugin.eventTrigger('afterFullscreenOn');
                }
            },
    
            /**
             * Trigger events
             */
            eventTrigger: function(name) {
                //mUtil.triggerCustomEvent(name);
                for (i = 0; i < the.events.length; i++) {
                    var event = the.events[i];
                    if (event.name == name) {
                        if (event.one == true) {
                            if (event.fired == false) {
                                the.events[i].fired = true;
                                event.handler.call(this, the);
                            }
                        } else {
                            event.handler.call(this, the);
                        }
                    }
                }
            },
    
            addEvent: function(name, handler, one) {
                the.events.push({
                    name: name,
                    handler: handler,
                    one: one,
                    fired: false
                });
    
                return the;
            }
        };
    
        //////////////////////////
        // ** Public Methods ** //
        //////////////////////////
    
        /**
         * Set default options 
         */
    
        the.setDefaults = function(options) {
            defaultOptions = options;
        };
    
        /**
         * Remove portlet
         * @returns {mPortlet}
         */
        the.remove = function() {
            return Plugin.remove(html);
        };
    
        /**
         * Init sticky portlet
         * @returns {mPortlet}
         */
        the.initSticky = function() {
            return Plugin.initSticky();
        };
    
        /**
         * Update sticky portlet scroll event
         * @returns {mPortlet}
         */
        the.updateSticky = function() {
            return Plugin.updateSticky();
        };
    
        /**
         * Reset sticky portlet positions
         * @returns {mPortlet}
         */
        the.resetSticky = function() {
            return Plugin.resetSticky();
        };
    
        /**
         * Destroy sticky portlet scroll event
         * @returns {mPortlet}
         */
        the.destroySticky = function() {
            return Plugin.destroySticky();
        };
    
        /**
         * Reload portlet
         * @returns {mPortlet}
         */
        the.reload = function() {
            return Plugin.reload();
        };
    
        /**
         * Set portlet content
         * @returns {mPortlet}
         */
        the.setContent = function(html) {
            return Plugin.setContent(html);
        };
    
        /**
         * Toggle portlet
         * @returns {mPortlet}
         */
        the.toggle = function() {
            return Plugin.toggle();
        };
    
        /**
         * Collapse portlet
         * @returns {mPortlet}
         */
        the.collapse = function() {
            return Plugin.collapse();
        };
    
        /**
         * Expand portlet
         * @returns {mPortlet}
         */
        the.expand = function() {
            return Plugin.expand();
        };
    
        /**
         * Fullscreen portlet
         * @returns {mPortlet}
         */
        the.fullscreen = function() {
            return Plugin.fullscreen('on');
        };
    
        /**
         * Fullscreen portlet
         * @returns {mPortlet}
         */
        the.unFullscreen = function() {
            return Plugin.fullscreen('off');
        };
    
        /**
         * Get portletbody 
         * @returns {jQuery}
         */
        the.getBody = function() {
            return Plugin.getBody();
        };
    
        /**
         * Get portletbody 
         * @returns {jQuery}
         */
        the.getSelf = function() {
            return Plugin.getSelf();
        };
    
        /**
         * Attach event
         */
        the.on = function(name, handler) {
            return Plugin.addEvent(name, handler);
        };
    
        /**
         * Attach event that will be fired once
         */
        the.one = function(name, handler) {
            return Plugin.addEvent(name, handler, true);
        };
    
        //== Construct plugin
        Plugin.construct.apply(the, [options]);
    
        return the;
    };
    // plugin setup
    var mQuicksearch = function(elementId, options) {
        //== Main object
        var the = this;
        var init = false;
    
        //== Get element object
        var element = mUtil.get(elementId);
        var body = mUtil.get('body');  
    
        if (!element) {
            return;
        }
    
        //== Default options
        var defaultOptions = {
            mode: 'default', //'default/dropdown'
            minLength: 1,
            maxHeight: 300,
            requestTimeout: 200, // ajax request fire timeout in milliseconds 
            inputTarget: 'm_quicksearch_input',
            iconCloseTarget: 'm_quicksearch_close',
            iconCancelTarget: 'm_quicksearch_cancel',
            iconSearchTarget: 'm_quicksearch_search',
            
            spinnerClass: 'm-loader m-loader--skin-light m-loader--right',
            hasResultClass: 'm-list-search--has-result',
            
            templates: {
                error: '<div class="m-search-results m-search-results--skin-light"><span class="m-search-result__message">{{message}}</div></div>'
            }
        };
    
        ////////////////////////////
        // ** Private Methods  ** //
        ////////////////////////////
    
        var Plugin = {
            /**
             * Construct
             */
    
            construct: function(options) {
                if (mUtil.data(element).has('quicksearch')) {
                    the = mUtil.data(element).get('quicksearch');
                } else {
                    // reset menu
                    Plugin.init(options);
    
                    // build menu
                    Plugin.build();
    
                    mUtil.data(element).set('quicksearch', the);
                }
    
                return the;
            },
    
            init: function(options) {
                the.element = element;
                the.events = [];
    
                // merge default and user defined options
                the.options = mUtil.deepExtend({}, defaultOptions, options);
    
                // search query
                the.query = '';
    
                // form
                the.form = mUtil.find(element, 'form');
    
                // input element
                the.input = mUtil.get(the.options.inputTarget);
    
                // close icon
                the.iconClose = mUtil.get(the.options.iconCloseTarget);
    
                if (the.options.mode == 'default') {
                    // search icon
                    the.iconSearch = mUtil.get(the.options.iconSearchTarget);
    
                    // cancel icon
                    the.iconCancel = mUtil.get(the.options.iconCancelTarget);
                }
    
                // dropdown
                the.dropdown = new mDropdown(element, {
                    mobileOverlay: false
                });
    
                // cancel search timeout
                the.cancelTimeout;
    
                // ajax processing state
                the.processing = false;
    
                // ajax request fire timeout
                the.requestTimeout = false;
            },
    
            /**
             * Build plugin
             */
            build: function() {
                // attach input keyup handler
                mUtil.addEvent(the.input, 'keyup', Plugin.search);
    
                // Prevent enter click
                mUtil.find(element, "form").onkeypress = function(e) {
                    var key = e.charCode || e.keyCode || 0;     
                    if (key == 13) {
                        e.preventDefault();
                    }
                }
    
                if (the.options.mode == 'default') {
                    mUtil.addEvent(the.input, 'focus', Plugin.showDropdown);
                    mUtil.addEvent(the.iconCancel, 'click', Plugin.handleCancel);
    
                    mUtil.addEvent(the.iconSearch, 'click', function() {
                        if (mUtil.isInResponsiveRange('tablet-and-mobile')) {
                            mUtil.addClass(body, 'm-header-search--mobile-expanded');
                            the.input.focus();
                        }
                    });
    
                    mUtil.addEvent(the.iconClose, 'click', function() {
                        if (mUtil.isInResponsiveRange('tablet-and-mobile')) {
                            mUtil.removeClass(body, 'm-header-search--mobile-expanded');
                            Plugin.closeDropdown();
                        }
                    });
                } else if (the.options.mode == 'dropdown') {
                    the.dropdown.on('afterShow', function() {
                        the.input.focus();
                    });
    
                    mUtil.addEvent(the.iconClose, 'click', Plugin.closeDropdown);
                }
            },
    
            showProgress: function() {
                the.processing = true;
                mUtil.addClass(the.form, the.options.spinnerClass);
                Plugin.handleCancelIconVisibility('off');
    
                return the;
            },
    
            hideProgress: function() {
                the.processing = false;
                mUtil.removeClass(the.form, the.options.spinnerClass);
                Plugin.handleCancelIconVisibility('on');
                mUtil.addClass(element, the.options.hasResultClass);
    
                return the;
            },
    
            /**
             * Search handler
             */
            search: function(e) {
                the.query = the.input.value;
    
                if (the.query.length === 0) {
                    Plugin.handleCancelIconVisibility('on');
                    mUtil.removeClass(element, the.options.hasResultClass);
                    mUtil.removeClass(the.form, the.options.spinnerClass);
                }
    
                if (the.query.length < the.options.minLength || the.processing == true) {
                    return;
                }
    
                if (the.requestTimeout) {
                    clearTimeout(the.requestTimeout);
                }
    
                the.requestTimeout = false;
    
                the.requestTimeout = setTimeout(function() {
                    Plugin.eventTrigger('search');
                }, the.options.requestTimeout);            
    
                return the;
            },
    
            /**
             * Handle cancel icon visibility
             */
            handleCancelIconVisibility: function(status) {
                if (status == 'on') {
                    if (the.input.value.length === 0) {
                        if (the.iconCancel) mUtil.css(the.iconCancel, 'visibility', 'hidden');
                        if (the.iconClose) mUtil.css(the.iconClose, 'visibility', 'visible');
                    } else {
                        clearTimeout(the.cancelTimeout);
                        the.cancelTimeout = setTimeout(function() {
                            if (the.iconCancel) mUtil.css(the.iconCancel, 'visibility', 'visible');
                            if (the.iconClose) mUtil.css(the.iconClose, 'visibility', 'visible');
                        }, 500);
                    }
                } else {
                    if (the.iconCancel) mUtil.css(the.iconCancel, 'visibility', 'hidden');
                    if (the.iconClose) mUtil.css(the.iconClose, 'visibility', 'hidden');
                }
            },
    
            /**
             * Cancel handler
             */
            handleCancel: function(e) {
                the.input.value = '';
                mUtil.css(the.iconCancel, 'visibility', 'hidden');
                mUtil.removeClass(element, the.options.hasResultClass);
    
                Plugin.closeDropdown();
            },
    
            /**
             * Cancel handler
             */
            closeDropdown: function() {
                the.dropdown.hide();
            },
    
            /**
             * Show dropdown
             */
            showDropdown: function(e) {
                if (the.dropdown.isShown() == false && the.input.value.length > the.options.minLength && the.processing == false) {
                    console.log('show!!!');
                    the.dropdown.show();
                    if (e) {
                        e.preventDefault();
                        e.stopPropagation();
                    }                
                }
            },
    
            /**
             * Trigger events
             */
            eventTrigger: function(name) {
                //mUtil.triggerCustomEvent(name);
                for (i = 0; i < the.events.length; i++) {
                    var event = the.events[i];
                    if (event.name == name) {
                        if (event.one == true) {
                            if (event.fired == false) {
                                the.events[i].fired = true;
                                event.handler.call(this, the);
                            }
                        } else {
                            event.handler.call(this, the);
                        }
                    }
                }
            },
    
            addEvent: function(name, handler, one) {
                the.events.push({
                    name: name,
                    handler: handler,
                    one: one,
                    fired: false
                });
    
                return the;
            }
        };
    
        //////////////////////////
        // ** Public Methods ** //
        //////////////////////////
    
        /**
         * Set default options 
         */
    
        the.setDefaults = function(options) {
            defaultOptions = options;
        };
    
        /**
         * quicksearch off 
         */
        the.search = function() {
            return Plugin.handleSearch();
        };
    
        the.showResult = function(res) {
            the.dropdown.setContent(res);
            Plugin.showDropdown();
    
            return the;
        };
    
        the.showError = function(text) {
            var msg = the.options.templates.error.replace('{{message}}', text);
            the.dropdown.setContent(msg);
            Plugin.showDropdown();
    
            return the;
        };
    
        /**
         *  
         */
        the.showProgress = function() {
            return Plugin.showProgress();
        };
    
        the.hideProgress = function() {
            return Plugin.hideProgress();
        };
    
        /**
         * quicksearch off 
         */
        the.search = function() {
            return Plugin.search();
        };
    
        /**
         * Attach event
         * @returns {mQuicksearch}
         */
        the.on = function(name, handler) {
            return Plugin.addEvent(name, handler);
        };
    
        /**
         * Attach event that will be fired once
         * @returns {mQuicksearch}
         */
        the.one = function(name, handler) {
            return Plugin.addEvent(name, handler, true);
        };
    
        //== Construct plugin
        Plugin.construct.apply(the, [options]);
    
        return the;
    };
    var mScrollTop = function(elementId, options) {
        //== Main object
        var the = this;
        var init = false;
    
        //== Get element object
        var element = mUtil.get(elementId);
        var body = mUtil.get('body');
    
        if (!element) {
            return;
        }
    
        //== Default options
        var defaultOptions = {
            offset: 300,
            speed: 600
        };
    
        ////////////////////////////
        // ** Private Methods  ** //
        ////////////////////////////
    
        var Plugin = {
            /**
             * Run plugin
             * @returns {mscrolltop}
             */
            construct: function(options) {
                if (mUtil.data(element).has('scrolltop')) {
                    the = mUtil.data(element).get('scrolltop');
                } else {
                    // reset scrolltop
                    Plugin.init(options);
    
                    // build scrolltop
                    Plugin.build();
    
                    mUtil.data(element).set('scrolltop', the);
                }
    
                return the;
            },
    
            /**
             * Handles subscrolltop click toggle
             * @returns {mscrolltop}
             */
            init: function(options) {
                the.events = [];
    
                // merge default and user defined options
                the.options = mUtil.deepExtend({}, defaultOptions, options);
            },
    
            build: function() {
                // handle window scroll
                if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
                    window.addEventListener('touchend', function() {
                        Plugin.handle();
                    });
    
                    window.addEventListener('touchcancel', function() {
                        Plugin.handle();
                    });
    
                    window.addEventListener('touchleave', function() {
                        Plugin.handle();
                    });
                } else {
                    window.addEventListener('scroll', function() { 
                        Plugin.handle();
                    });
                }
    
                // handle button click 
                mUtil.addEvent(element, 'click', Plugin.scroll);
            },
    
            /**
             * Handles scrolltop click scrollTop
             */
            handle: function() {
                var pos = window.pageYOffset; // current vertical position
                if (pos > the.options.offset) {
                    mUtil.addClass(body, 'm-scroll-top--shown');
                } else {
                    mUtil.removeClass(body, 'm-scroll-top--shown');
                }
            },
    
            /**
             * Handles scrolltop click scrollTop
             */
            scroll: function(e) {
                e.preventDefault();
    
                mUtil.scrollTop(0, the.options.speed);
            },
    
    
            /**
             * Trigger events
             */
            eventTrigger: function(name, args) {
                for (var i = 0; i < the.events.length; i++) {
                    var event = the.events[i];
                    if (event.name == name) {
                        if (event.one == true) {
                            if (event.fired == false) {
                                the.events[i].fired = true;
                                event.handler.call(this, the, args);
                            }
                        } else {
                            event.handler.call(this, the, args);
                        }
                    }
                }
            },
    
            addEvent: function(name, handler, one) {
                the.events.push({
                    name: name,
                    handler: handler,
                    one: one,
                    fired: false
                });
            }
        };
    
        //////////////////////////
        // ** Public Methods ** //
        //////////////////////////
    
        /**
         * Set default options 
         */
    
        the.setDefaults = function(options) {
            defaultOptions = options;
        };
    
        /**
         * Get subscrolltop mode
         */
        the.on = function(name, handler) {
            return Plugin.addEvent(name, handler);
        };
    
        /**
         * Set scrolltop content
         * @returns {mscrolltop}
         */
        the.one = function(name, handler) {
            return Plugin.addEvent(name, handler, true);
        };
    
        ///////////////////////////////
        // ** Plugin Construction ** //
        ///////////////////////////////
    
        //== Run plugin
        Plugin.construct.apply(the, [options]);
    
        //== Init done
        init = true;
    
        // Return plugin instance
        return the;
    };
    // plugin setup
    var mToggle = function(elementId, options) {
        //== Main object
        var the = this;
        var init = false;
    
        //== Get element object
        var element = mUtil.get(elementId);
        var body = mUtil.get('body');  
    
        if (!element) {
            return;
        }
    
        //== Default options
        var defaultOptions = {
            togglerState: '',
            targetState: ''
        };    
    
        ////////////////////////////
        // ** Private Methods  ** //
        ////////////////////////////
    
        var Plugin = {
            /**
             * Construct
             */
    
            construct: function(options) {
                if (mUtil.data(element).has('toggle')) {
                    the = mUtil.data(element).get('toggle');
                } else {
                    // reset menu
                    Plugin.init(options);
    
                    // build menu
                    Plugin.build();
    
                    mUtil.data(element).set('toggle', the);
                }
    
                return the;
            },
    
            /**
             * Handles subtoggle click toggle
             */
            init: function(options) {
                the.element = element;
                the.events = [];
    
                // merge default and user defined options
                the.options = mUtil.deepExtend({}, defaultOptions, options);
    
                the.target = mUtil.get(the.options.target);
                the.targetState = the.options.targetState;
                the.togglerState = the.options.togglerState;
    
                the.state = mUtil.hasClasses(the.target, the.targetState) ? 'on' : 'off';
            },
    
            /**
             * Setup toggle
             */
            build: function() {
                mUtil.addEvent(element, 'mouseup', Plugin.toggle);
            },
            
            /**
             * Handles offcanvas click toggle
             */
            toggle: function() {
                Plugin.eventTrigger('beforeToggle');
                
                if (the.state == 'off') {
                    Plugin.toggleOn();
                } else {
                    Plugin.toggleOff();
                }
    
                return the;
            },
    
            /**
             * Handles toggle click toggle
             */
            toggleOn: function() {
                Plugin.eventTrigger('beforeOn');
    
                mUtil.addClass(the.target, the.targetState);
    
                if (the.togglerState) {
                    mUtil.addClass(element, the.togglerState);
                }
    
                the.state = 'on';
    
                Plugin.eventTrigger('afterOn');
    
                Plugin.eventTrigger('toggle');
    
                return the;
            },
    
            /**
             * Handles toggle click toggle
             */
            toggleOff: function() {
                Plugin.eventTrigger('beforeOff');
    
                mUtil.removeClass(the.target, the.targetState);
    
                if (the.togglerState) {
                    mUtil.removeClass(element, the.togglerState);
                }
    
                the.state = 'off';
    
                Plugin.eventTrigger('afterOff');
    
                Plugin.eventTrigger('toggle');
    
                return the;
            },
    
            /**
             * Trigger events
             */
            eventTrigger: function(name) {
                for (i = 0; i < the.events.length; i++) {
                    var event = the.events[i];
    
                    if (event.name == name) {
                        if (event.one == true) {
                            if (event.fired == false) {
                                the.events[i].fired = true;                            
                                event.handler.call(this, the);
                            }
                        } else {
                            event.handler.call(this, the);
                        }
                    }
                }
            },
    
            addEvent: function(name, handler, one) {
                the.events.push({
                    name: name,
                    handler: handler,
                    one: one,
                    fired: false
                });
    
                return the;
            }
        };
    
        //////////////////////////
        // ** Public Methods ** //
        //////////////////////////
    
        /**
         * Set default options 
         */
    
        the.setDefaults = function(options) {
            defaultOptions = options;
        };
    
        /**
         * Get toggle state 
         */
        the.getState = function() {
            return the.state;
        };
    
        /**
         * Toggle 
         */
        the.toggle = function() {
            return Plugin.toggle();
        };
    
        /**
         * Toggle on 
         */
        the.toggleOn = function() {
            return Plugin.toggleOn();
        };
    
        /**
         * Toggle off 
         */
        the.toggle = function() {
            return Plugin.toggleOff();
        };
    
        /**
         * Attach event
         * @returns {mToggle}
         */
        the.on = function(name, handler) {
            return Plugin.addEvent(name, handler);
        };
    
        /**
         * Attach event that will be fired once
         * @returns {mToggle}
         */
        the.one = function(name, handler) {
            return Plugin.addEvent(name, handler, true);
        };
    
        //== Construct plugin
        Plugin.construct.apply(the, [options]);
    
        return the;
    };
    // plugin setup
    var mWizard = function(elementId, options) {
        //== Main object
        var the = this;
        var init = false;
    
        //== Get element object
        var element = mUtil.get(elementId);
        var body = mUtil.get('body');
    
        if (!element) {
            return; 
        }
    
        //== Default options
        var defaultOptions = {
            startStep: 1,
            manualStepForward: false
        };
    
        ////////////////////////////
        // ** Private Methods  ** //
        ////////////////////////////
    
        var Plugin = {
            /**
             * Construct
             */
    
            construct: function(options) {
                if (mUtil.data(element).has('wizard')) {
                    the = mUtil.data(element).get('wizard');
                } else {
                    // reset menu
                    Plugin.init(options);
    
                    // build menu
                    Plugin.build();
    
                    mUtil.data(element).set('wizard', the);
                }
    
                return the;
            },
    
            /**
             * Init wizard
             */
            init: function(options) {
                the.element = element;
                the.events = [];
    
                // merge default and user defined options
                the.options = mUtil.deepExtend({}, defaultOptions, options);
    
                //== Elements
                the.steps = mUtil.findAll(element, '.m-wizard__step');
    
                the.progress = mUtil.find(element, '.m-wizard__progress .progress-bar');
                the.btnSubmit = mUtil.find(element, '[data-wizard-action="submit"]');
                the.btnNext = mUtil.find(element, '[data-wizard-action="next"]');
                the.btnPrev = mUtil.find(element, '[data-wizard-action="prev"]');
                the.btnLast = mUtil.find(element, '[data-wizard-action="last"]');
                the.btnFirst = mUtil.find(element, '[data-wizard-action="first"]');
    
                //== Variables
                the.events = [];
                the.currentStep = 1;
                the.stopped = false;
                the.totalSteps = the.steps.length;
    
                //== Init current step
                if (the.options.startStep > 1) {
                    Plugin.goTo(the.options.startStep);
                }
    
                //== Init UI
                Plugin.updateUI();
            },
    
            /**
             * Build Form Wizard
             */
            build: function() {
                //== Next button event handler
                mUtil.addEvent(the.btnNext, 'click', function(e) {
                    e.preventDefault();
                    Plugin.goNext();
                });
    
                //== Prev button event handler
                mUtil.addEvent(the.btnPrev, 'click', function(e) {
                    e.preventDefault();
                    Plugin.goPrev();
                });
    
                //== First button event handler
                mUtil.addEvent(the.btnFirst, 'click', function(e) {
                    e.preventDefault();
                    Plugin.goFirst();
                });
    
                //== Last button event handler
                mUtil.addEvent(the.btnLast, 'click', function(e) {
                    e.preventDefault();
                    Plugin.goLast();
                });
    
                mUtil.on(element, '.m-wizard__step a.m-wizard__step-number', 'click', function() {
                    var step = this.closest('.m-wizard__step');
                    var steps = mUtil.parents(this, '.m-wizard__steps')
                    var find = mUtil.findAll(steps, '.m-wizard__step');
                    var num;
    
                    for (var i = 0, j = find.length; i < j; i++) {
                        if (step === find[i]) {
                            num = (i + 1);
                            break;
                        }
                    }
    
                    if (num) {
                        if (the.options.manualStepForward === false) {
                            if (num < the.currentStep) {
                                Plugin.goTo(num);
                            }
                        } else {
                            Plugin.goTo(num);
                        }                    
                    }
                });
            },
    
            /**
             * Handles wizard click wizard
             */
            goTo: function(number) {
                //== Skip if this step is already shown
                if (number === the.currentStep || number > the.totalSteps || number < 0) {
                    return;
                }
    
                //== Validate step number
                if (number) {
                    number = parseInt(number);
                } else {
                    number = Plugin.getNextStep();
                }
    
                //== Before next and prev events
                var callback;
    
                if (number > the.currentStep) {
                    callback = Plugin.eventTrigger('beforeNext');
                } else {
                    callback = Plugin.eventTrigger('beforePrev');
                }
                
                //== Skip if stopped
                if (the.stopped === true) {
                    the.stopped = false;
                    return;
                }
    
                //== Continue if no exit
                if (callback !== false) {
                    //== Before change
                    Plugin.eventTrigger('beforeChange');
    
                    //== Set current step 
                    the.currentStep = number;
    
                    //== Update UI
                    Plugin.updateUI();
    
                    //== Trigger change event
                    Plugin.eventTrigger('change');
                }
    
                //== After next and prev events
                if (number > the.startStep) {
                    Plugin.eventTrigger('afterNext');
                } else {
                    Plugin.eventTrigger('afterPrev');
                }
    
                return the;
            },
    
            /**
             * Set step class
             */
            setStepClass: function() {
                if (Plugin.isLastStep()) {
                    mUtil.addClass(element, 'm-wizard--step-last');
                } else {
                    mUtil.removeClass(element, 'm-wizard--step-last');
                }
    
                if (Plugin.isFirstStep()) {
                    mUtil.addClass(element, 'm-wizard--step-first');
                } else {
                    mUtil.removeClass(element, 'm-wizard--step-first');
                }
    
                if (Plugin.isBetweenStep()) {
                    mUtil.addClass(element, 'm-wizard--step-between');
                } else {
                    mUtil.removeClass(element, 'm-wizard--step-between');
                }
            },
    
            updateUI: function(argument) {
                //== Update progress bar
                Plugin.updateProgress();
    
                //== Show current target content
                Plugin.handleTarget();
    
                //== Set classes
                Plugin.setStepClass();
    
                //== Apply nav step classes
                for (var i = 0, j = the.steps.length; i < j; i++) {
                    mUtil.removeClass(the.steps[i], 'm-wizard__step--current m-wizard__step--done');
                }
    
                for (var i = 1; i < the.currentStep; i++) {
                    mUtil.addClass(the.steps[i - 1], 'm-wizard__step--done');
                }
                
                mUtil.addClass(the.steps[the.currentStep - 1], 'm-wizard__step--current');
            },
    
            /**
             * Cancel
             */
            stop: function() {
                the.stopped = true;
            },
    
            /**
             * Resume
             */
            start: function() {
                the.stopped = false;
            },
    
            /**
             * Check last step
             */
            isLastStep: function() {
                return the.currentStep === the.totalSteps;
            },
    
            /**
             * Check first step
             */
            isFirstStep: function() {
                return the.currentStep === 1;
            },
    
            /**
             * Check between step
             */
            isBetweenStep: function() {
                return Plugin.isLastStep() === false && Plugin.isFirstStep() === false;
            },
    
            /**
             * Go to the next step
             */
            goNext: function() {
                return Plugin.goTo(Plugin.getNextStep());
            },
    
            /**
             * Go to the prev step
             */
            goPrev: function() {
                return Plugin.goTo(Plugin.getPrevStep());
            },
    
            /**
             * Go to the last step
             */
            goLast: function() {
                return Plugin.goTo(the.totalSteps);
            },
    
            /**
             * Go to the first step
             */
            goFirst: function() {
                return Plugin.goTo(1);
            },
    
            /**
             * Set progress
             */
            updateProgress: function() {
                //== Calculate progress position
                if (!the.progress) {
                    return;
                }
    
                //== Update progress
                if (mUtil.hasClass(element, 'm-wizard--1')) {
                    var width = 100 * ((the.currentStep - 1) / (the.totalSteps));
                    var number = mUtil.find(element, '.m-wizard__step-number');
                    var offset = parseInt(mUtil.css(number, 'width'));
                    mUtil.css(the.progress, 'width', 'calc(' + width + '% + ' + (offset / 2) + 'px)');
                } else if (mUtil.hasClass(element, 'm-wizard--2')) {
                    if (the.currentStep === 1) {
                        //return;
                    }
    
                    var progress = (the.currentStep - 1) * (100 * (1 / (the.totalSteps - 1)));
    
                    if (mUtil.isInResponsiveRange('minimal-desktop-and-below')) {
                        mUtil.css(the.progress, 'height', progress + '%');
                    } else {
                        mUtil.css(the.progress, 'width', progress + '%');
                    }
                } else {
                    var width = 100 * ((the.currentStep) / (the.totalSteps));
                    mUtil.css(the.progress, 'width', width + '%');
                }
            },
    
            /**
             * Show/hide target content
             */
            handleTarget: function() {
                var step = the.steps[the.currentStep - 1];
                var target = mUtil.get(mUtil.attr(step, 'm-wizard-target'));
                var current = mUtil.find(element, '.m-wizard__form-step--current');
                
                mUtil.removeClass(current, 'm-wizard__form-step--current');
                mUtil.addClass(target, 'm-wizard__form-step--current');
            },
    
            /**
             * Get next step
             */
            getNextStep: function() {
                if (the.totalSteps >= (the.currentStep + 1)) {
                    return the.currentStep + 1;
                } else {
                    return the.totalSteps;
                }
            },
    
            /**
             * Get prev step
             */
            getPrevStep: function() {
                if ((the.currentStep - 1) >= 1) {
                    return the.currentStep - 1;
                } else {
                    return 1;
                }
            },
    
            /**
             * Trigger events
             */
            eventTrigger: function(name) {
                //mUtil.triggerCustomEvent(name);
                for (i = 0; i < the.events.length; i++) {
                    var event = the.events[i];
                    if (event.name == name) {
                        if (event.one == true) {
                            if (event.fired == false) {
                                the.events[i].fired = true;
                                event.handler.call(this, the);
                            }
                        } else {
                            event.handler.call(this, the);
                        }
                    }
                }
            },
    
            addEvent: function(name, handler, one) {
                the.events.push({
                    name: name,
                    handler: handler,
                    one: one,
                    fired: false
                });
    
                return the;
            }
        };
    
        //////////////////////////
        // ** Public Methods ** //
        //////////////////////////
    
        /**
         * Set default options 
         */
    
        the.setDefaults = function(options) {
            defaultOptions = options;
        };
    
        /**
         * Go to the next step 
         */
        the.goNext = function() {
            return Plugin.goNext();
        };
    
        /**
         * Go to the prev step 
         */
        the.goPrev = function() {
            return Plugin.goPrev();
        };
    
        /**
         * Go to the last step 
         */
        the.goLast = function() {
            return Plugin.goLast();
        };
    
        /**
         * Cancel step 
         */
        the.stop = function() {
            return Plugin.stop();
        };
    
        /**
         * Resume step 
         */
        the.start = function() {
            return Plugin.start();
        };
    
        /**
         * Go to the first step 
         */
        the.goFirst = function() {
            return Plugin.goFirst();
        };
    
        /**
         * Go to a step
         */
        the.goTo = function(number) {
            return Plugin.goTo(number);
        };
    
        /**
         * Get current step number 
         */
        the.getStep = function() {
            return the.currentStep;
        };
    
        /**
         * Check last step 
         */
        the.isLastStep = function() {
            return Plugin.isLastStep();
        };
    
        /**
         * Check first step 
         */
        the.isFirstStep = function() {
            return Plugin.isFirstStep();
        };
        
        /**
         * Attach event
         */
        the.on = function(name, handler) {
            return Plugin.addEvent(name, handler);
        };
    
        /**
         * Attach event that will be fired once
         */
        the.one = function(name, handler) {
            return Plugin.addEvent(name, handler, true);
        };
    
        //== Construct plugin
        Plugin.construct.apply(the, [options]);
    
        return the;
    };
    (function($) {
    
        var pluginName = 'mDatatable';
        var pfx = 'm-';
    
        $.fn[pluginName] = $.fn[pluginName] || {};
    
        /**
         * @param datatable Main datatable plugin instance
         * @param options Extension options
         * @returns {*}
         */
        $.fn[pluginName].checkbox = function(datatable, options) {
            var Extension = {
                selectedAllRows: false,
                selectedRows: [],
                unselectedRows: [],
    
                init: function() {
                    if (Extension.selectorEnabled()) {
                        // reset
                        datatable.setDataSourceParam(options.vars.selectedAllRows, false);
                        datatable.stateRemove('checkbox');
    
                        // requestIds is not null
                        if (options.vars.requestIds) {
                            // request ids in response
                            datatable.setDataSourceParam(options.vars.requestIds, true);
                        }
    
                        // remove selected checkbox on datatable reload
                        $(datatable).on(pfx + 'datatable--on-reloaded', function() {
                            datatable.stateRemove('checkbox');
                            datatable.setDataSourceParam(options.vars.selectedAllRows, false);
                            Extension.selectedAllRows = false;
                            Extension.selectedRows = [];
                            Extension.unselectedRows = [];
                        });
    
                        // select all on extension init
                        Extension.selectedAllRows = datatable.getDataSourceParam(options.vars.selectedAllRows);
    
                        $(datatable).on(pfx + 'datatable--on-layout-updated', function(e, args) {
                            if (args.table != $(datatable.wrap).attr('id')) {
                                return;
                            }
                            datatable.ready(function() {
                                Extension.initVars();
                                Extension.initEvent();
                                Extension.initSelect();
                            });
                        });
    
                        $(datatable).on(pfx + 'datatable--on-check', function(e, ids) {
                            ids.forEach(function(id) {
                                Extension.selectedRows.push(id);
                                // // remove from unselected rows
                                Extension.unselectedRows = Extension.remove(Extension.unselectedRows, id);
                            });
                            var storage = {};
                            storage['selectedRows'] = $.unique(Extension.selectedRows);
                            storage['unselectedRows'] = $.unique(Extension.unselectedRows);
                            datatable.stateKeep('checkbox', storage);
                        });
                        $(datatable).on(pfx + 'datatable--on-uncheck', function(e, ids) {
                            ids.forEach(function(id) {
                                Extension.unselectedRows.push(id);
                                // // remove from selected rows
                                Extension.selectedRows = Extension.remove(Extension.selectedRows, id);
                            });
                            var storage = {};
                            storage['selectedRows'] = $.unique(Extension.selectedRows);
                            storage['unselectedRows'] = $.unique(Extension.unselectedRows);
                            datatable.stateKeep('checkbox', storage);
                        });
                    }
                },
    
                /**
                 * Init checkbox clicks event
                 */
                initEvent: function() {
                    // select all checkbox click
                    $(datatable.tableHead).find('.' + pfx + 'checkbox--all > [type="checkbox"]').click(function(e) {
                        // clear selected and unselected rows
                        Extension.selectedRows = Extension.unselectedRows = [];
                        datatable.stateRemove('checkbox');
    
                        // select all rows
                        if ($(this).is(':checked')) {
                            Extension.selectedAllRows = true;
                        }
                        else {
                            Extension.selectedAllRows = false;
                        }
    
                        // local select all current page rows
                        if (!options.vars.requestIds) {
                            if ($(this).is(':checked')) {
                                Extension.selectedRows = $.makeArray($(datatable.tableBody).find('.' + pfx + 'checkbox--single > [type="checkbox"]').map(function(i, chk) {
                                    return $(chk).val();
                                }));
                            }
                            var storage = {};
                            storage['selectedRows'] = $.unique(Extension.selectedRows);
                            datatable.stateKeep('checkbox', storage);
                        }
    
                        // keep selectedAllRows in datasource params
                        datatable.setDataSourceParam(options.vars.selectedAllRows, Extension.selectedAllRows);
    
                        $(datatable).trigger(pfx + 'datatable--on-click-checkbox', [$(this)]);
                    });
    
                    // single row checkbox click
                    $(datatable.tableBody).find('.' + pfx + 'checkbox--single > [type="checkbox"]').click(function(e) {
                        var id = $(this).val();
                        if ($(this).is(':checked')) {
                            Extension.selectedRows.push(id);
                            // remove from unselected rows
                            Extension.unselectedRows = Extension.remove(Extension.unselectedRows, id);
                        }
                        else {
                            Extension.unselectedRows.push(id);
                            // remove from selected rows
                            Extension.selectedRows = Extension.remove(Extension.selectedRows, id);
                        }
    
                        // local checkbox header check
                        if (!options.vars.requestIds && Extension.selectedRows.length < 1) {
                            // remove select all checkbox, if there is no checked checkbox left
                            $(datatable.tableHead).find('.' + pfx + 'checkbox--all > [type="checkbox"]').prop('checked', false);
                        }
    
                        var storage = {};
                        storage['selectedRows'] = $.unique(Extension.selectedRows);
                        storage['unselectedRows'] = $.unique(Extension.unselectedRows);
                        datatable.stateKeep('checkbox', storage);
    
                        $(datatable).trigger(pfx + 'datatable--on-click-checkbox', [$(this)]);
                    });
                },
    
                initSelect: function() {
                    // selected all rows from server
                    if (Extension.selectedAllRows && options.vars.requestIds) {
                        if (!datatable.hasClass(pfx + 'datatable--error')) {
                            // set header select all checkbox checked
                            $(datatable.tableHead).find('.' + pfx + 'checkbox--all > [type="checkbox"]').prop('checked', true);
                        }
    
                        // set all checkbox in table body
                        datatable.setActiveAll(true);
    
                        // remove unselected rows
                        Extension.unselectedRows.forEach(function(id) {
                            datatable.setInactive(id);
                        });
    
                    }
                    else {
                        // single check for server and local
                        Extension.selectedRows.forEach(function(id) {
                            datatable.setActive(id);
                        });
    
                        // local checkbox; check if all checkboxes of currect page are checked
                        if (!datatable.hasClass(pfx + 'datatable--error') && $(datatable.tableBody).find('.' + pfx + 'checkbox--single > [type="checkbox"]').not(':checked').length < 1) {
                            // set header select all checkbox checked
                            $(datatable.tableHead).find('.' + pfx + 'checkbox--all > [type="checkbox"]').prop('checked', true);
                        }
                    }
                },
    
                /**
                 * Check if selector is enabled from options
                 */
                selectorEnabled: function() {
                    return $.grep(datatable.options.columns, function(n, i) {
                        return n.selector || false;
                    })[0];
                },
    
                initVars: function() {
                    // get single select/unselect from localstorage
                    var storage = datatable.stateGet('checkbox');
                    if (typeof storage !== 'undefined') {
                        Extension.selectedRows = storage['selectedRows'] || [];
                        Extension.unselectedRows = storage['unselectedRows'] || [];
                    }
                },
    
                getSelectedId: function(path) {
                    Extension.initVars();
    
                    // server selected all rows
                    if (Extension.selectedAllRows && options.vars.requestIds) {
                        if (typeof path === 'undefined') {
                            path = options.vars.rowIds;
                        }
    
                        // if selected all rows, return id from response meta
                        var selectedAllRows = datatable.getObject(path, datatable.lastResponse) || [];
    
                        if (selectedAllRows.length > 0) {
                            // remove single unselected rows from selectedAllRows ids from server response emta
                            Extension.unselectedRows.forEach(function(id) {
                                selectedAllRows = Extension.remove(selectedAllRows, parseInt(id));
                            });
                        }
                        return selectedAllRows;
                    }
    
                    // else return single checked selected rows
                    return Extension.selectedRows;
                },
    
                remove: function(array, element) {
                    return array.filter(function(e) {
                        return e !== element;
                    });
                },
            };
    
            // make the extension accessible from datatable init
            datatable.checkbox = function() {
                return Extension;
            };
    
            if (typeof options === 'object') {
                options = $.extend(true, {}, $.fn[pluginName].checkbox.default, options);
                Extension.init.apply(this, [options]);
            }
    
            return datatable;
        };
    
        $.fn[pluginName].checkbox.default = {
            vars: {
                // select all rows flag to be sent to the server
                selectedAllRows: 'selectedAllRows',
                // request id parameter's name
                requestIds: 'requestIds',
                // response path to all rows id
                rowIds: 'meta.rowIds',
            },
        };
    
    }(jQuery));
    var mLayout = function() {
        var header;
        var horMenu;
        var asideMenu;
        var asideMenuOffcanvas;
        var horMenuOffcanvas;
        var asideLeftToggle;
        var asideLeftHide;
        var scrollTop;
        var quicksearch;
        var mainPortlet;
    
        //== Header
        var initStickyHeader = function() {
            var tmp;
            var headerEl = mUtil.get('m_header');
            var options = {
                offset: {},
                minimize: {}
            };
    
            if (mUtil.attr(headerEl, 'm-minimize-mobile') == 'hide') {
                options.minimize.mobile = {};
                options.minimize.mobile.on = 'm-header--hide';
                options.minimize.mobile.off = 'm-header--show';
            } else {
                options.minimize.mobile = false;
            }
    
            if (mUtil.attr(headerEl, 'm-minimize') == 'hide') {
                options.minimize.desktop = {};
                options.minimize.desktop.on = 'm-header--hide';
                options.minimize.desktop.off = 'm-header--show';
            } else {
                options.minimize.desktop = false;
            }
    
            if (tmp = mUtil.attr(headerEl, 'm-minimize-offset')) {
                options.offset.desktop = tmp;
            }
    
            if (tmp = mUtil.attr(headerEl, 'm-minimize-mobile-offset')) {
                options.offset.mobile = tmp;
            }
    
            header = new mHeader('m_header', options);
        }
    
        //== Hor menu
        var initHorMenu = function() {
            // init aside left offcanvas
            horMenuOffcanvas = new mOffcanvas('m_header_menu', {
                overlay: true,
                baseClass: 'm-aside-header-menu-mobile',
                closeBy: 'm_aside_header_menu_mobile_close_btn',
                toggleBy: {
                    target: 'm_aside_header_menu_mobile_toggle',
                    state: 'm-brand__toggler--active'
                }
            });
    
            horMenu = new mMenu('m_header_menu', {
                submenu: {
                    desktop: 'dropdown',
                    tablet: 'accordion',
                    mobile: 'accordion'
                },
                accordion: {
                    slideSpeed: 200, // accordion toggle slide speed in milliseconds
                    expandAll: false // allow having multiple expanded accordions in the menu
                }
            });
        }
    
        //== Aside menu
        var initLeftAsideMenu = function() {
            //== Init aside menu
            var menu = mUtil.get('m_ver_menu');
            var menuDesktopMode = (mUtil.attr(menu, 'm-menu-dropdown') === '1' ? 'dropdown' : 'accordion');
    
            var scroll;
            if (mUtil.attr(menu, 'm-menu-scrollable') === '1') {
                scroll = {
                    height: function() {
                        if (mUtil.isInResponsiveRange('desktop')) {
                            return mUtil.getViewPort().height - parseInt(mUtil.css('m_header', 'height'));
                        }                   
                    }
                };
            }
    
            asideMenu = new mMenu('m_ver_menu', {
                // vertical scroll
                scroll: scroll,
    
                // submenu setup
                submenu: {
                    desktop: {
                        // by default the menu mode set to accordion in desktop mode
                        default: menuDesktopMode,
                        // whenever body has this class switch the menu mode to dropdown
                        state: {
                            body: 'm-aside-left--minimize',
                            mode: 'dropdown'
                        }
                    },
                    tablet: 'accordion', // menu set to accordion in tablet mode
                    mobile: 'accordion' // menu set to accordion in mobile mode
                },
    
                //accordion setup
                accordion: {
                    autoScroll: false, // enable auto scrolling(focus) to the clicked menu item
                    expandAll: false // allow having multiple expanded accordions in the menu
                }
            });
        }
    
        //== Aside
        var initLeftAside = function() {
            // init aside left offcanvas
            var body = mUtil.get('body');
            var asideLeft = mUtil.get('m_aside_left');
            var asideOffcanvasClass = mUtil.hasClass(asideLeft, 'm-aside-left--offcanvas-default') ? 'm-aside-left--offcanvas-default' : 'm-aside-left';
    
            asideMenuOffcanvas = new mOffcanvas('m_aside_left', {
                baseClass: asideOffcanvasClass,
                overlay: true,
                closeBy: 'm_aside_left_close_btn',
                toggleBy: {
                    target: 'm_aside_left_offcanvas_toggle',
                    state: 'm-brand__toggler--active'
                }
            });
    
            //== Handle minimzied aside hover
            if (mUtil.hasClass(body, 'm-aside-left--fixed')) {
                var insideTm;
                var outsideTm;
    
                mUtil.addEvent(asideLeft, 'mouseenter', function() {
                    if (outsideTm) {
                        clearTimeout(outsideTm);
                        outsideTm = null;
                    }
    
                    insideTm = setTimeout(function() {
                        if (mUtil.hasClass(body, 'm-aside-left--minimize') && mUtil.isInResponsiveRange('desktop')) {
                            mUtil.removeClass(body, 'm-aside-left--minimize');
                            mUtil.addClass(body, 'm-aside-left--minimize-hover');
                            asideMenu.scrollerUpdate();
                            asideMenu.scrollerTop();
                        }
                    }, 300);
                });
    
                mUtil.addEvent(asideLeft, 'mouseleave', function() {
                    if (insideTm) {
                        clearTimeout(insideTm);
                        insideTm = null;
                    }
    
                    outsideTm = setTimeout(function() {
                        if (mUtil.hasClass(body, 'm-aside-left--minimize-hover') && mUtil.isInResponsiveRange('desktop')) {
                            mUtil.removeClass(body, 'm-aside-left--minimize-hover');
                            mUtil.addClass(body, 'm-aside-left--minimize');
                            asideMenu.scrollerUpdate();
                            asideMenu.scrollerTop();
                        }
                    }, 500);
                });
            }
        }
    
        //== Sidebar toggle
        var initLeftAsideToggle = function() {
            if ($('#m_aside_left_minimize_toggle').length === 0) {
                return;
            }
    
            asideLeftToggle = new mToggle('m_aside_left_minimize_toggle', {
                target: 'body',
                targetState: 'm-brand--minimize m-aside-left--minimize',
                togglerState: 'm-brand__toggler--active'
            }); 
    
            asideLeftToggle.on('toggle', function(toggle) {     
                if (mUtil.get('main_portlet')) {
                    mainPortlet.updateSticky();      
                } 
                
                horMenu.pauseDropdownHover(800);
                asideMenu.pauseDropdownHover(800);
    
                //== Remember state in cookie
                Cookies.set('sidebar_toggle_state', toggle.getState());
                // to set default minimized left aside use this cookie value in your 
                // server side code and add "m-brand--minimize m-aside-left--minimize" classes to 
                // the body tag in order to initialize the minimized left aside mode during page loading.
            });
    
            asideLeftToggle.on('beforeToggle', function(toggle) {   
                var body = mUtil.get('body'); 
                if (mUtil.hasClass(body, 'm-aside-left--minimize') === false && mUtil.hasClass(body, 'm-aside-left--minimize-hover')) {
                    mUtil.removeClass(body, 'm-aside-left--minimize-hover');
                }
            });
        }
    
        //== Sidebar hide
        var initLeftAsideHide = function() {
            if ($('#m_aside_left_hide_toggle').length === 0 ) {
                return;
            }
    
            initLeftAsideHide = new mToggle('m_aside_left_hide_toggle', {
                target: 'body',
                targetState: 'm-aside-left--hide',
                togglerState: 'm-brand__toggler--active'
            });
    
            initLeftAsideHide.on('toggle', function(toggle) {
                horMenu.pauseDropdownHover(800);
                asideMenu.pauseDropdownHover(800);
    
                //== Remember state in cookie
                Cookies.set('sidebar_hide_state', toggle.getState());
                // to set default minimized left aside use this cookie value in your 
                // server side code and add "m-brand--minimize m-aside-left--minimize" classes to 
                // the body tag in order to initialize the minimized left aside mode during page loading.
            });
        }
    
        //== Topbar
        var initTopbar = function() {
            $('#m_aside_header_topbar_mobile_toggle').click(function() {
                $('body').toggleClass('m-topbar--on');
            });
    
            // Animated Notification Icon 
            /*
            setInterval(function() {
                $('#m_topbar_notification_icon .m-nav__link-icon').addClass('m-animate-shake');
                $('#m_topbar_notification_icon .m-nav__link-badge').addClass('m-animate-blink');
            }, 3000);
    
            setInterval(function() {
                $('#m_topbar_notification_icon .m-nav__link-icon').removeClass('m-animate-shake');
                $('#m_topbar_notification_icon .m-nav__link-badge').removeClass('m-animate-blink');
            }, 6000);
            */
        }
    
        //== Quicksearch
        var initQuicksearch = function() {
            if ($('#m_quicksearch').length === 0) {
                return;
            }
    
            quicksearch = new mQuicksearch('m_quicksearch', {
                mode: mUtil.attr('m_quicksearch', 'm-quicksearch-mode'), // quick search type
                minLength: 1
            });
    
            //<div class="m-search-results m-search-results--skin-light"><span class="m-search-result__message">Something went wrong</div></div>
    
            quicksearch.on('search', function(the) {
                the.showProgress();
    
                $.ajax({
                    url: 'https://keenthemes.com/metronic/themes/themes/metronic/dist/preview/inc/api/quick_search.php',
                    data: {
                        query: the.query
                    },
                    dataType: 'html',
                    success: function(res) {
                        the.hideProgress();
                        the.showResult(res);
                    },
                    error: function(res) {
                        the.hideProgress();
                        the.showError('Connection error. Pleae try again later.');
                    }
                });
            });
        }
    
        //== Scrolltop
        var initScrollTop = function() {
            var scrollTop = new mScrollTop('m_scroll_top', {
                offset: 300,
                speed: 600
            });
        }
    
        //== Main portlet(sticky portlet)
        var createMainPortlet = function() {
            return new mPortlet('main_portlet', {
                sticky: {
                    offset: parseInt(mUtil.css( mUtil.get('m_header'), 'height')),
                    zIndex: 90,
                    position: {
                        top: function() {
                            return parseInt(mUtil.css( mUtil.get('m_header'), 'height') );
                        },
                        left: function() {
                            var left = parseInt(mUtil.css( mUtil.getByClass('m-content'), 'paddingLeft'));
                            
                            if (mUtil.isInResponsiveRange('desktop')) {
                                //left += parseInt(mUtil.css(mUtil.get('m_aside_left'), 'width') );
                                if (mUtil.hasClass(mUtil.get('body'), 'm-aside-left--minimize')) {
                                    left += 78; // need to use hardcoded width of the minimize aside
                                } else {
                                    left += 255; // need to use hardcoded width of the aside
                                }
                            } 
    
                            return left; 
                        },
                        right: function() {
                            return parseInt(mUtil.css( mUtil.getByClass('m-content'), 'paddingRight') );
                        }
                    }
                }
            });
        }
    
        return {
            init: function() {
                this.initHeader();
                this.initAside();
                this.initMainPortlet();
            },
    
            initMainPortlet: function() {
                if (!mUtil.get('main_portlet')) {
                    return;
                }
                
                mainPortlet = createMainPortlet();
                mainPortlet.initSticky();
                
                mUtil.addResizeHandler(function(){
                    mainPortlet.updateSticky();
                });
            },
    
            resetMainPortlet: function() {
                mainPortlet.destroySticky();
                mainPortlet = createMainPortlet();
                mainPortlet.initSticky();
            },
    
            initHeader: function() {
                initStickyHeader();
                initHorMenu();
                initTopbar();
                initQuicksearch();
                initScrollTop();
            },
    
            initAside: function() { 
                initLeftAside();
                initLeftAsideMenu();
                initLeftAsideToggle();
                initLeftAsideHide();
    
                this.onLeftSidebarToggle(function(e) {
                    //== Update sticky portlet
                    if (mainPortlet) {
                        mainPortlet.updateSticky();
                    }
    
                    //== Reload datatable
                    var datatables = $('.m-datatable');
                    if (datatables) {
                        datatables.each(function() {
                            $(this).mDatatable('redraw');
                        });
                    }                
                });
            },
    
            getAsideMenu: function() {
                return asideMenu;
            },
    
            onLeftSidebarToggle: function(handler) {
                if (asideLeftToggle) {
                    asideLeftToggle.on('toggle', handler);
                }
            },
    
            closeMobileAsideMenuOffcanvas: function() {
                if (mUtil.isMobileDevice()) {
                    asideMenuOffcanvas.hide();
                }
            },
    
            closeMobileHorMenuOffcanvas: function() {
                if (mUtil.isMobileDevice()) {
                    horMenuOffcanvas.hide();
                }
            }
        };
    }();
    
    $(document).ready(function() {
        if (mUtil.isAngularVersion() === false) {
            mLayout.init();
        }
    });
    
    var mQuickSidebar = function() {
        var topbarAside = $('#m_quick_sidebar');
        var topbarAsideTabs = $('#m_quick_sidebar_tabs');    
        var topbarAsideContent = topbarAside.find('.m-quick-sidebar__content');
    
        var initMessages = function() {
            var messages = mUtil.find( mUtil.get('m_quick_sidebar_tabs_messenger'),  '.m-messenger__messages'); 
            var form = $('#m_quick_sidebar_tabs_messenger .m-messenger__form');
    
            mUtil.scrollerInit(messages, {
                disableForMobile: true, 
                resetHeightOnDestroy: false, 
                handleWindowResize: true, 
                height: function() {
                    var height = topbarAside.outerHeight(true) - 
                        topbarAsideTabs.outerHeight(true) - 
                        form.outerHeight(true) - 120;
    
                    return height;                    
                }
            });
        }
    
        var initSettings = function() { 
            var settings = mUtil.find( mUtil.get('m_quick_sidebar_tabs_settings'),  '.m-list-settings'); 
    
            if (!settings) {
                return;
            }
    
            mUtil.scrollerInit(settings, {
                disableForMobile: true, 
                resetHeightOnDestroy: false, 
                handleWindowResize: true, 
                height: function() {
                    return mUtil.getViewPort().height - topbarAsideTabs.outerHeight(true) - 60;            
                }
            });
        }
    
        var initLogs = function() {
            var logs = mUtil.find( mUtil.get('m_quick_sidebar_tabs_logs'),  '.m-list-timeline'); 
    
            if (!logs) {
                return;
            }
    
            mUtil.scrollerInit(logs, {
                disableForMobile: true, 
                resetHeightOnDestroy: false, 
                handleWindowResize: true, 
                height: function() {
                    return mUtil.getViewPort().height - topbarAsideTabs.outerHeight(true) - 60;            
                }
            });
        }
    
        var initOffcanvasTabs = function() {
            initMessages();
            initSettings();
            initLogs();
        }
    
        var initOffcanvas = function() {
            var topbarAsideObj = new mOffcanvas('m_quick_sidebar', {
                overlay: true,  
                baseClass: 'm-quick-sidebar',
                closeBy: 'm_quick_sidebar_close',
                toggleBy: 'm_quick_sidebar_toggle'
            });   
    
            // run once on first time dropdown shown
            topbarAsideObj.one('afterShow', function() {
                mApp.block(topbarAside);
    
                setTimeout(function() {
                    mApp.unblock(topbarAside);
                    
                    topbarAsideContent.removeClass('m--hide');
    
                    initOffcanvasTabs();
                }, 1000);                         
            });
        }
    
        return {     
            init: function() {  
                if (topbarAside.length === 0) {
                    return;
                }
    
                initOffcanvas(); 
            }
        };
    }();
    
    $(document).ready(function() {
        mQuickSidebar.init();
    });
})(jQuery);