PageSourceSearch

https://portal.compostforlifemiami.com/static/management/assets/de…/base/scripts.bundle.1d17853ebcf0.js

js compostforlifemiami.com collected 2026-10-05 09:39:56 UTC 273,061 bytes, 8,599 lines download raw bytes

1/**
2 * @class mApp  Metronic App class
3 */
4
5var mApp = function() {
6
7    /**
8    * Initializes bootstrap tooltip
9    */
10    var initTooltip = function(el) {
11        var skin = el.data('skin') ? 'm-tooltip--skin-' + el.data('skin') : '';
12        var width = el.data('width') == 'auto' ? 'm-tooltop--auto-width' : '';
13        var triggerValue = el.data('trigger') ? el.data('trigger') : 'hover';
14            
15        el.tooltip({
16            trigger: triggerValue,
17            template: '<div class="m-tooltip ' + skin + ' ' + width + ' tooltip" role="tooltip">\
18                <div class="arrow"></div>\
19                <div class="tooltip-inner"></div>\
20            </div>'
21        });
22    }
23    
24    /**
25    * Initializes bootstrap tooltips
26    */
27    var initTooltips = function() {
28        // init bootstrap tooltips
29        $('[data-toggle="m-tooltip"]').each(function() {
30            initTooltip($(this));
31        });
32    }
33
34    /**
35    * Initializes bootstrap popover
36    */
37    var initPopover = function(el) {
38        var skin = el.data('skin') ? 'm-popover--skin-' + el.data('skin') : '';
39        var triggerValue = el.data('trigger') ? el.data('trigger') : 'hover';
40            
41        el.popover({
42            trigger: triggerValue,
43            html:true,
44            template: '\
45            <div class="m-popover ' + skin + ' popover" role="tooltip">\
46                <div class="arrow"></div>\
47                <h3 class="popover-header"></h3>\
48                <div class="popover-body"></div>\
49            </div>'
50        });
51    }
52
53    /**
54    * Initializes bootstrap popovers
55    */
56    var initPopovers = function() {
57        // init bootstrap popover
58        $('[data-toggle="m-popover"]').each(function() {
59            initPopover($(this));
60        });
61    }
62
63    /**
64    * Initializes bootstrap file input
65    */
66    var initFileInput = function() {
67        // init bootstrap popover
68        $('.custom-file-input').on('change',function(){
69            var fileName = $(this).val();
70            $(this).next('.custom-file-label').addClass("selected").html(fileName);
71        });
72    }           
73
74    /**
75    * Initializes metronic portlet
76    */
77    var initPortlet = function(el, options) {
78        // init portlet tools
79        el.mPortlet(options);
80    }
81
82    /**
83    * Initializes metronic portlets
84    */
85    var initPortlets = function() {
86        // init portlet tools
87        $('[data-portlet="true"]').each(function() {
88            var el = $(this);
89
90            if ( el.data('portlet-initialized') !== true ) {
91                initPortlet(el, {});
92                el.data('portlet-initialized', true);
93            }
94        });
95    }
96
97    /**
98    * Initializes scrollable contents
99    */
100    var initScrollables = function() {
101        $('[data-scrollable="true"]').each(function(){
102            var maxHeight;
103            var height;
104            var el = $(this);
105
106            if (mUtil.isInResponsiveRange('tablet-and-mobile')) {
107                if (el.data('mobile-max-height')) {
108                    maxHeight = el.data('mobile-max-height');
109                } else {
110                    maxHeight = el.data('max-height');
111                }
112
113                if (el.data('mobile-height')) {
114                    height = el.data('mobile-height');
115                } else {
116                    height = el.data('height');
117                }
118            } else {
119                maxHeight = el.data('max-height');
120                height = el.data('max-height');
121            }
122
123            if (maxHeight) {
124                el.css('max-height', maxHeight);
125            }
126            if (height) {
127                el.css('height', height);
128            }
129
130            mApp.initScroller(el, {});
131        });
132    }
133
134    /**
135    * Initializes bootstrap alerts
136    */
137    var initAlerts = function() {
138        // init bootstrap popover
139        $('body').on('click', '[data-close=alert]', function() {
140            $(this).closest('.alert').hide();
141        });
142    }
143
144    /**
145    * Initializes Metronic custom tabs
146    */
147    var initCustomTabs = function() {
148        // init bootstrap popover
149        $('[data-tab-target]').each(function() {
150            if ($(this).data('tabs-initialized') == true ) {
151                return;
152            }
153
154            $(this).click(function(e) {
155                e.preventDefault();
156                
157                var tab = $(this);
158                var tabs = tab.closest('[data-tabs="true"]');
159                var contents = $( tabs.data('tabs-contents') );
160                var content = $( tab.data('tab-target') );
161
162                tabs.find('.m-tabs__item.m-tabs__item--active').removeClass('m-tabs__item--active');
163                tab.addClass('m-tabs__item--active');
164
165                contents.find('.m-tabs-content__item.m-tabs-content__item--active').removeClass('m-tabs-content__item--active');
166                content.addClass('m-tabs-content__item--active');         
167            });
168
169            $(this).data('tabs-initialized', true);
170        });
171    }
172
173    /**
174    * Initializes bootstrap collapse for Metronic's accordion feature
175    */
176    var initAccordions = function(el) {
177       
178    }
179
180	var hideTouchWarning = function() {
181		jQuery.event.special.touchstart = {
182			setup: function(_, ns, handle) {
183				if (typeof this === 'function')
184					if (ns.includes('noPreventDefault')) {
185						this.addEventListener('touchstart', handle, {passive: false});
186					} else {
187						this.addEventListener('touchstart', handle, {passive: true});
188					}
189			},
190		};
191		jQuery.event.special.touchmove = {
192			setup: function(_, ns, handle) {
193				if (typeof this === 'function')
194					if (ns.includes('noPreventDefault')) {
195						this.addEventListener('touchmove', handle, {passive: false});
196					} else {
197						this.addEventListener('touchmove', handle, {passive: true});
198					}
199			},
200		};
201		jQuery.event.special.wheel = {
202			setup: function(_, ns, handle) {
203				if (typeof this === 'function')
204					if (ns.includes('noPreventDefault')) {
205						this.addEventListener('wheel', handle, {passive: false});
206					} else {
207						this.addEventListener('wheel', handle, {passive: true});
208					}
209			},
210		};
211	};
212
213    return {
214        /**
215        * Main class initializer
216        */
217        init: function() {
218            mApp.initComponents();
219        },
220
221        /**
222        * Initializes components
223        */
224        initComponents: function() {
225            hideTouchWarning();
226            initScrollables();
227            initTooltips();
228            initPopovers();
229            initAlerts();
230            initPortlets();
231            initFileInput();
232            initAccordions();
233            initCustomTabs();
234        },
235
236
237        /**
238        * Init custom tabs
239        */
240        initCustomTabs: function() {
241            initCustomTabs();
242        },
243
244        /**
245        * 
246        * @param {object} el jQuery element object
247        */
248        // wrJangoer function to scroll(focus) to an element
249        initTooltips: function() {
250            initTooltips();
251        },
252
253        /**
254        * 
255        * @param {object} el jQuery element object
256        */
257        // wrJangoer function to scroll(focus) to an element
258        initTooltip: function(el) {
259            initTooltip(el);
260        },
261
262        /**
263        * 
264        * @param {object} el jQuery element object
265        */
266        // wrJangoer function to scroll(focus) to an element
267        initPopovers: function() {
268            initPopovers();
269        },
270
271        /**
272        * 
273        * @param {object} el jQuery element object
274        */
275        // wrJangoer function to scroll(focus) to an element
276        initPopover: function(el) {
277            initPopover(el);
278        },
279
280        /**
281        * 
282        * @param {object} el jQuery element object
283        */
284        // function to init portlet
285        initPortlet: function(el, options) {
286            initPortlet(el, options);
287        },
288
289        /**
290        * 
291        * @param {object} el jQuery element object
292        */
293        // function to init portlets
294        initPortlets: function() {
295            initPortlets();
296        },
297
298        /**
299        * Scrolls to an element with animation
300        * @param {object} el jQuery element object
301        * @param {number} offset Offset to element scroll position
302        */
303        scrollTo: function(target, offset) {
304            el = $(target);
305
306            var pos = (el && el.length > 0) ? el.offset().top : 0;
307            pos = pos + (offset ? offset : 0);
308
309            jQuery('html,body').animate({
310                scrollTop: pos
311            }, 'slow');
312        },
313
314        /**
315        * Scrolls until element is centered in the viewport 
316        * @param {object} el jQuery element object
317        */
318        // wrJangoer function to scroll(focus) to an element
319        scrollToViewport: function(el) {
320            var elOffset = $(el).offset().top;
321            var elHeight = el.height();
322            var windowHeight = mUtil.getViewPort().height;
323            var offset = elOffset - ((windowHeight / 2) - (elHeight / 2));
324
325            jQuery('html,body').animate({
326                scrollTop: offset
327            }, 'slow');
328        },
329
330        /**
331        * Scrolls to the top of the page
332        */
333        // function to scroll to the top
334        scrollTop: function() {
335            mApp.scrollTo();
336        },
337
338        /**
339        * Initializes scrollable content using mCustomScrollbar plugin
340        * @param {object} el jQuery element object
341        * @param {object} options mCustomScrollbar plugin options(refer: http://manos.malihu.gr/jquery-custom-content-scroller/)
342        */
343        initScroller: function(el, options, doNotDestroy) {
344            if (mUtil.isMobileDevice()) {
345                el.css('overflow', 'auto');
346            } else {
347                if (doNotDestroy !== true) {
348                     mApp.destroyScroller(el); 
349                }               
350                el.mCustomScrollbar({
351                    scrollInertia: 0,
352                    autoDraggerLength: true,
353                    autoHideScrollbar: true,
354                    autoExpandScrollbar: false,
355                    alwaysShowScrollbar: 0,
356                    axis: el.data('axis') ? el.data('axis') : 'y',
357                    mouseWheel: {
358                        scrollAmount: 120,
359                        preventDefault: true
360                    },
361                    setHeight: (options.height ? options.height : ''),
362                    theme:"minimal-dark"
363                });
364            }           
365        },
366
367        /**
368        * Destroys scrollable content's mCustomScrollbar plugin instance
369        * @param {object} el jQuery element object
370        */
371        destroyScroller: function(el) {
372            el.mCustomScrollbar("destroy");
373            el.removeClass('mCS_destroyed');
374        },
375
376        /**
377        * Shows bootstrap alert
378        * @param {object} options
379        * @returns {string} ID attribute of the created alert
380        */
381        alert: function(options) {
382            options = $.extend(true, {
383                container: "", // alerts parent container(by default placed after the page breadcrumbs)
384                place: "append", // "append" or "prepend" in container 
385                type: 'success', // alert's type
386                message: "", // alert's message
387                close: true, // make alert closable
388                reset: true, // close all previouse alerts first
389                focus: true, // auto scroll to the alert after shown
390                closeInSeconds: 0, // auto close after defined seconds
391                icon: "" // put icon before the message
392            }, options);
393
394            var id = mUtil.getUniqueID("App_alert");
395
396            var html = '<div id="' + id + '" class="custom-alerts alert alert-' + options.type + ' fade in">' + (options.close ? '<button type="button" class="close" data-dismiss="alert" aria-hidden="true"></button>' : '') + (options.icon !== "" ? '<i class="fa-lg fa fa-' + options.icon + '"></i>  ' : '') + options.message + '</div>';
397
398            if (options.reset) {
399                $('.custom-alerts').remove();
400            }
401
402            if (!options.container) {
403                if ($('.page-fixed-main-content').size() === 1) {
404                    $('.page-fixed-main-content').prepend(html);
405                } else if (($('body').hasClass("page-container-bg-solid") || $('body').hasClass("page-content-white")) && $('.page-head').size() === 0) {
406                    $('.page-title').after(html);
407                } else {
408                    if ($('.page-bar').size() > 0) {
409                        $('.page-bar').after(html);
410                    } else {
411                        $('.page-breadcrumb, .breadcrumbs').after(html);
412                    }
413                }
414            } else {
415                if (options.place == "append") {
416                    $(options.container).append(html);
417                } else {
418                    $(options.container).prepend(html);
419                }
420            }
421
422            if (options.focus) {
423                mApp.scrollTo($('#' + id));
424            }
425
426            if (options.closeInSeconds > 0) {
427                setTimeout(function() {
428                    $('#' + id).remove();
429                }, options.closeInSeconds * 1000);
430            }
431
432            return id;
433        },
434
435        /**
436        * Blocks element with loading indiciator using http://malsup.com/jquery/block/
437        * @param {object} target jQuery element object
438        * @param {object} options 
439        */
440        block: function(target, options) {
441            var el = $(target);
442
443            options = $.extend(true, {
444                opacity: 0.03,
445                overlayColor: '#000000',
446                state: 'brand',
447                type: 'loader',
448                size: 'lg',
449                centerX: true,
450                centerY: true,
451                message: '',
452                shadow: true,
453                width: 'auto'
454            }, options);
455
456            var skin;
457            var state;
458            var loading;
459
460            if (options.type == 'spinner') {
461                skin = options.skin ? 'm-spinner--skin-' + options.skin : '';
462                state = options.state ? 'm-spinner--' + options.state : '';
463                loading = '<div class="m-spinner ' + skin + ' ' + state 
463+ '"></div';
464            } else {
465                skin = options.skin ? 'm-loader--skin-' + options.skin : '';
466                state = options.state ? 'm-loader--' + options.state : '';
467                size = options.size ? 'm-loader--' + options.size : '';
468                loading = '<div class="m-loader ' + skin + ' ' + state + ' ' + size + '"></div';
469            }
470
471            if (options.message && options.message.length > 0) {
472                var classes = 'm-blockui ' + (options.shadow === false ? 'm-blockui-no-shadow' : '');
473
474                html = '<div class="' + classes + '"><span>' + options.message + '</span><span>' + loading + '</span></div>';
475                options.width = mUtil.realWidth(html) + 10;
476                if (target == 'body') {
477                    html = '<div class="' + classes + '" style="margin-left:-'+ (options.width / 2) +'px;"><span>' + options.message + '</span><span>' + loading + '</span></div>';
478                }
479            } else {
480                html = loading;
481            }
482
483            var params = {
484                message: html,
485                centerY: options.centerY,
486                centerX: options.centerX,
487                css: {
488                    top: '30%',
489                    left: '50%',
490                    border: '0',
491                    padding: '0',
492                    backgroundColor: 'none',
493                    width: options.width
494                },
495                overlayCSS: {
496                    backgroundColor: options.overlayColor,
497                    opacity: options.opacity,
498                    cursor: 'wait',
499                    zIndex: '10'
500                },
501                onUnblock: function() {
502                    if (el) {
503                        el.css('position', '');
504                        el.css('zoom', '');
505                    }                    
506                }
507            };
508
509            if (target == 'body') {
510                params.css.top = '50%';
511                $.blockUI(params);
512            } else {
513                var el = $(target);
514                el.block(params);
515            }
516        },
517
518        /**
519        * Un-blocks the blocked element 
520        * @param {object} target jQuery element object
521        */
522        unblock: function(target) {
523            if (target && target != 'body') {
524                $(target).unblock();
525            } else {
526                $.unblockUI();
527            }
528        },
529
530        /**
531        * Blocks the page body element with loading indicator
532        * @param {object} options 
533        */
534        blockPage: function(options) {
535            return mApp.block('body', options);
536        },
537
538        /**
539        * Un-blocks the blocked page body element
540        */
541        unblockPage: function() {
542            return mApp.unblock('body');
543        },
544
545        /**
546        * Enable loader progress for button and other elements
547        * @param {object} target jQuery element object
548        * @param {object} options
549        */
550        progress: function(target, options) {
551            var skin = (options && options.skin) ? options.skin : 'light';
552            var alignment = (options && options.alignment) ? options.alignment : 'right'; 
553            var size = (options && options.size) ? 'm-spinner--' + options.size : ''; 
554            var classes = 'm-loader ' + 'm-loader--' + skin + ' m-loader--' + alignment + ' m-loader--' + size;
555
556            mApp.unprogress(target);
557            
558            $(target).addClass(classes);
559            $(target).data('progress-classes', classes);
560        },
561
562        /**
563        * Disable loader progress for button and other elements
564        * @param {object} target jQuery element object
565        */
566        unprogress: function(target) {
567            $(target).removeClass($(target).data('progress-classes'));
568        }
569    };
570}();
571
572//== Initialize mApp class on document ready
573$(document).ready(function() {
574    mApp.init();
575});
576/**
577 * @class mUtil  Metronic base utilize class that privides helper functions
578 */
579
580var mUtil = function() {
581    var resizeHandlers = [];
582
583    /** @type {object} breakpoints The device width breakpoints **/
584    var breakpoints = {        
585        sm: 544, // Small screen / phone           
586        md: 768, // Medium screen / tablet            
587        lg: 992, // Large screen / desktop        
588        xl: 1200 // Extra large screen / wide desktop
589    };
590
591    /** @type {object} colors State colors **/
592    var colors = {
593        brand:      '#716aca',
594        metal:      '#c4c5d6',
595        light:      '#ffffff',
596        accent:     '#00c5dc',
597        primary:    '#5867dd',
598        success:    '#34bfa3',
599        info:       '#36a3f7',
600        warning:    '#ffb822',
601        danger:     '#f4516c'
602    };
603
604    /**
605    * Handle window resize event with some 
606    * delay to attach event handlers upon resize complete 
607    */
608    var _windowResizeHandler = function() {
609        var _runResizeHandlers = function() {
610            // reinitialize other subscribed elements
611            for (var i = 0; i < resizeHandlers.length; i++) {
612                var each = resizeHandlers[i];
613                each.call();
614            }
615        };
616
617        var timeout = false; // holder for timeout id
618        var delay = 250; // delay after event is "complete" to run callback
619
620        window.addEventListener('resize', function() {
621            clearTimeout(timeout);
622            timeout = setTimeout(function() {
623                _runResizeHandlers();
624            }, delay); // wait 50ms until window resize finishes.
625        });
626    };
627
628    return {
629        /**
630        * Class main initializer.
631        * @param {object} options.
632        * @returns null
633        */
634        //main function to initiate the theme
635        init: function(options) {
636            if (options && options.breakpoints) {
637                breakpoints = options.breakpoints;
638            }
639
640            if (options && options.colors) {
641                colors = options.colors;
642            }
643
644            _windowResizeHandler();
645        },
646
647        /**
648        * Adds window resize event handler.
649        * @param {function} callback function.
650        */
651        addResizeHandler: function(callback) {
652            resizeHandlers.push(callback);
653        },
654
655        /**
656        * Trigger window resize handlers.
657        */
658        runResizeHandlers: function() {
659            _runResizeHandlers();
660        },        
661
662        /**
663        * Get GET parameter value from URL.
664        * @param {string} paramName Parameter name.
665        * @returns {string}  
666        */
667        getURLParam: function(paramName) {
668            var searchString = window.location.search.substring(1),
669                i, val, params = searchString.split("&");
670
671            for (i = 0; i < params.length; i++) {
672                val = params[i].split("=");
673                if (val[0] == paramName) {
674                    return unescape(val[1]);
675                }
676            }
677
678            return null;
679        },
680
681        /**
682        * Checks whether current device is mobile touch.
683        * @returns {boolean}  
684        */
685        isMobileDevice: function() {
686            return (this.getViewPort().width < this.getBreakpoint('lg') ? true : false);
687        },
688
689        /**
690        * Checks whether current device is desktop.
691        * @returns {boolean}  
692        */
693        isDesktopDevice: function() {
694            return mUtil.isMobileDevice() ? false : true;
695        },
696
697        /**
698        * Gets browser window viewport size. Ref: http://andylangton.co.uk/articles/javascript/get-viewport-size-javascript/
699        * @returns {object}  
700        */
701        getViewPort: function() {
702            var e = window,
703                a = 'inner';
704            if (!('innerWidth' in window)) {
705                a = 'client';
706                e = document.documentElement || document.body;
707            }
708
709            return {
710                width: e[a + 'Width'],
711                height: e[a + 'Height']
712            };
713        },
714
715        /**
716        * Checks whether given device mode is currently activated.
717        * @param {string} mode Responsive mode name(e.g: desktop, desktop-and-tablet, tablet, tablet-and-mobile, mobile)
718        * @returns {boolean}  
719        */
720        isInResponsiveRange: function(mode) {
721            var breakpoint = this.getViewPort().width;
722
723            if (mode == 'general') {
724                return true;
725            } else if (mode == 'desktop' && breakpoint >
725= (this.getBreakpoint('lg') + 1)) {
726                return true;
727            } else if (mode == 'tablet' && (breakpoint >= (this.getBreakpoint('md') + 1) && breakpoint < this.getBreakpoint('lg'))) {
728                return true;
729            } else if (mode == 'mobile' && breakpoint <= this.getBreakpoint('md')) {
730                return true;
731            } else if (mode == 'desktop-and-tablet' && breakpoint >= (this.getBreakpoint('md') + 1)) {
732                return true;
733            } else if (mode == 'tablet-and-mobile' && breakpoint <= this.getBreakpoint('lg')) {
734                return true;
735            } else if (mode == 'minimal-desktop-and-below' && breakpoint <= this.getBreakpoint('xl')) {
736                return true;
737            }
738
739            return false;
740        },
741
742        /**
743        * Generates unique ID for give prefix.
744        * @param {string} prefix Prefix for generated ID
745        * @returns {boolean}  
746        */
747        getUniqueID: function(prefix) {
748            return prefix + Math.floor(Math.random() * (new Date()).getTime());
749        },
750
751        /**
752        * Gets window width for give breakpoint mode.
753        * @param {string} mode Responsive mode name(e.g: xl, lg, md, sm)
754        * @returns {number}  
755        */
756        getBreakpoint: function(mode) {
757            if ($.inArray(mode, breakpoints)) {
758                return breakpoints[mode];
759            }
760        },
761
762        /**
763        * Checks whether object has property matchs given key path.
764        * @param {object} obj Object contains values paired with given key path
765        * @param {string} keys Keys path seperated with dots
766        * @returns {object}  
767        */
768        isset: function(obj, keys) {
769            var stone;
770
771            keys = keys || '';
772
773            if (keys.indexOf('[') !== -1) {
774                throw new Error('Unsupported object path notation.');
775            }
776
777            keys = keys.split('.');
778
779            do {
780                if (obj === undefined) {
781                    return false;
782                }
783
784                stone = keys.shift();
785
786                if (!obj.hasOwnProperty(stone)) {
787                    return false;
788                }
789
790                obj = obj[stone];
791
792            } while (keys.length);
793
794            return true;
795        },
796
797        /**
798        * Gets highest z-index of the given element parents
799        * @param {object} el jQuery element object
800        * @returns {number}  
801        */
802        getHighestZindex: function(el) {
803            var elem = $(el),
804                position, value;
805
806            while (elem.length && elem[0] !== document) {
807                // Ignore z-index if position is set to a value where z-index is ignored by the browser
808                // This makes behavior of this function consistent across browsers
809                // WebKit always returns auto if the element is positioned
810                position = elem.css("position");
811
812                if (position === "absolute" || position === "relative" || position === "fixed") {
813                    // IE returns 0 when zIndex is not specified
814                    // other browsers return a string
815                    // we ignore the case of nested elements with an explicit value of 0
816                    // <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
817                    value = parseInt(elem.css("zIndex"), 10);
818                    if (!isNaN(value) && value !== 0) {
819                        return value;
820                    }
821                }
822                elem = elem.parent();
823            }
824        },
825
826        /**
827        * Checks whether the element has given classes
828        * @param {object} el jQuery element object
829        * @param {string} Classes string
830        * @returns {boolean}  
831        */
832        hasClasses: function(el, classes) {
833            var classesArr = classes.split(" ");
834
835            for ( var i = 0; i < classesArr.length; i++ ) {
836                if ( el.hasClass( classesArr[i] ) == false ) {
837                    return false;
838                }
839            }                
840
841            return true;
842        },
843
844        /**
845        * Gets element actual/real width
846        * @param {object} el jQuery element object
847        * @returns {number}  
848        */
849        realWidth: function(el){
850            var clone = $(el).clone();
851            clone.css("visibility","hidden");
852            clone.css('overflow', 'hidden');
853            clone.css("height","0");
854            $('body').append(clone);
855            var width = clone.outerWidth();
856            clone.remove();
857
858            return width;
859        },
860
861        /**
862        * Checks whether the element has any parent with fixed position
863        * @param {object} el jQuery element object
864        * @returns {boolean}  
865        */
866        hasFixedPositionedParent: function(el) {
867            var result = false;
868            
869            el.parents().each(function () {
870                if ($(this).css('position') == 'fixed') {
871                    result = true;
872                    return;
873                }
874            });
875
876            return result;
877        },
878
879        /**
880        * Simulates delay
881        */
882        sleep: function(milliseconds) {
883            var start = new Date().getTime();
884            for (var i = 0; i < 1e7; i++) {
885                if ((new Date().getTime() - start) > milliseconds){
886                    break;
887                }
888            }
889        },
890
891        /**
892        * Gets randomly generated integer value within given min and max range
893        * @param {number} min Range start value
894        * @param {number} min Range end value
895        * @returns {number}  
896        */
897        getRandomInt: function(min, max) {
898            return Math.floor(Math.random() * (max - min + 1)) + min;
899        },
900
901        /**
902        * Gets state color's hex code by color name
903        * @param {string} name Color name
904        * @returns {string}  
905        */
906        getColor: function(name) {
907            return colors[name];
908        },
909
910        /**
911        * Checks whether Angular library is included
912        * @returns {boolean}  
913        */
914        isAngularVersion: function() {
915            return window.Zone !== undefined  ? true : false;
916        }
917    }
918}();
919
920//== Initialize mUtil class on document ready
921$(document).ready(function() {
922    mUtil.init();
923});
924(function($) {
925
926	if (typeof mUtil === 'undefined') throw new Error('mUtil is required and must be included before mDatatable.');
927
928	// plugin setup
929	$.fn.mDatatable = function(options) {
930		if ($(this).length === 0) throw new Error('No mDatatable element exist.');
931
932		// global variables
933		var datatable = this;
934
935		// debug enabled?
936		// 1) state will be cleared on each refresh
937		// 2) enable some logs
938		// 3) etc.
939		datatable.debug = false;
940
941		datatable.API = {
942			record: null,
943			value: null,
944			params: null,
945		};
946
947		var Plugin = {
948			/********************
949			 ** PRIVATE METHODS
950			 ********************/
951			isInit: false,
952			offset: 110,
953			stateId: 'meta',
954			ajaxParams: {},
955
956			init: function(options) {
957				// data source option empty is normal table
958				if (options.data.source === null) {
959					Plugin.extractTable();
960				}
961
962				Plugin.setupBaseDOM.call();
963				Plugin.setupDOM(datatable.table);
964				Plugin.spinnerCallback(true);
965
966				// set custom query from options
967				Plugin.setDataSourceQuery(Plugin.getOption('data.source.read.params.query'));
968
969				// on event after layout had done setup, show datatable
970				$(datatable).on('m-datatable--on-layout-updated', Plugin.afterRender);
971
972				if (datatable.debug) Plugin.stateRemove(Plugin.stateId);
973
974				// initialize extensions
975				$.each(Plugin.getOption('extensions'), function(extName, extOptions) {
976					if (typeof $.fn.mDatatable[extName] === 'function')
977						new $.fn.mDatatable[extName](datatable, extOptions);
978				});
979
980				// get data
981				if (options.data.type === 'remote' || options.data.type === 'local') {
982					if (options.data.saveState === false
983						|| options.data.saveState.cookie === false
984						&& options.data.saveState.webstorage === false) {
985						Plugin.stateRemove(Plugin.stateId);
986					}
987					// get data for local datatable and local table
988					if (options.data.type === 'local' && typeof options.data.source === 'object') {
989						datatable.dataSet = datatable.originalDataSet = Plugin.dataMapCallback(options.data.source);
990					}
991					Plugin.dataRender();
992				}
993
994				Plugin.setHeadTitle();
995				Plugin.setHeadTitle(datatable.tableFoot);
996
997				// for normal table, setup layout right away
998				if (options.data.type === null) {
999					Plugin.setupCellField.call();
1000					Plugin.setupTemplateCell.call();
1001					// setup extra system column properties
1002					Plugin.setupSystemColumn.call();
1003				}
1004
1005				// hide header
1006				if (typeof options.layout.header !== 'undefined' &&
1007					options.layout.header === false) {
1008					$(datatable.table).find('thead').remove();
1009				}
1010
1011				// hide footer
1012				if (typeof options.layout.footer !== 'undefined' &&
1013					options.layout.footer === false) {
1014					$(datatable.table).find('tfoot').remove();
1015				}
1016
1017				// for normal and local data type, run layoutUpdate
1018				if (options.data.type === null ||
1019					options.data.type === 'local') {
1020					// setup nested datatable, if option enabled
1021					Plugin.setupSubDatatable.call();
1022					// setup extra system column properties
1023					Plugin.setupSystemColumn.call();
1024					Plugin.redraw();
1025				}
1026
1027				$(window).resize(Plugin.fullRender);
1028
1029				$(datatable).height('');
1030
1031				$(Plugin.getOption('search.input')).on('keyup', function(e) {
1032					if (Plugin.getOption('search.onEnter') && e.which !== 13) return;
1033					Plugin.search($(this).val().toLowerCase());
1034				});
1035
1036				return datatable;
1037			},
1038
1039			/**
1040			 * Extract static HTML table content into datasource
1041			 */
1042			extractTable: function() {
1043				var columns = [];
1044				var headers = $(datatable).
1045					find('tr:first-child th').
1046					get().
1047					map(function(cell, i) {
1048						var field = $(cell).data('field');
1049						if (typeof field === 'undefined') {
1050							field = $(cell).text().trim();
1051						}
1052						var column = {field: field, title: field};
1053						for (var ii in options.columns) {
1054							if (options.columns[ii].field === field) {
1055								column = $.extend(true, {}, options.columns[ii], column);
1056							}
1057						}
1058						columns.push(column);
1059						return field;
1060					});
1061				// auto create columns config
1062				options.columns = columns;
1063
1064				var data = $(datatable).find('tr').get().map(function(row) {
1065					return $(row).find('td').get().map(function(cell, i) {
1066						return $(cell).html();
1067					});
1068				});
1069				var source = [];
1070				$.each(data, function(i, row) {
1071					if (row.length === 0) return;
1072					var td = {};
1073					$.each(row, function(index, value) {
1074						td[headers[index]] = $.trim(value);
1075					});
1076					source.push(td);
1077				});
1078				options.data.source = source;
1079			},
1080
1081			/**
1082			 * One time layout update on init
1083			 */
1084			layoutUpdate: function() {
1085				// setup nested datatable, if option enabled
1086				Plugin.setupSubDatatable.call();
1087
1088				// setup extra system column properties
1089				Plugin.setupSystemColumn.call();
1090
1091				Plugin.columnHide.call();
1092
1093				Plugin.sorting.call();
1094
1095				// setup cell hover event
1096				Plugin.setupHover.call();
1097
1098				if (typeof options.detail === 'undefined'
1099					// temporary disable lock column in subtable
1100					&& Plugin.getDepth() === 1) {
1101					// lock columns handler
1102					Plugin.lockTable.call();
1103				}
1104
1105				Plugin.resetScroll();
1106
1107				if (!Plugin.isInit) {
1108					$(datatable).trigger('m-datatable--on-init', {table: $(datatable.wrap).attr('id'), options: options});
1109					Plugin.isInit = true;
1110				}
1111
1112				$(datatable).trigger('m-datatable--on-layout-updated', {table: $(datatable.wrap).attr('id')});
1113			},
1114
1115			lockTable: function() {
1116				// todo; revise lock table responsive
1117				var lock = {
1118					lockEnabled: false,
1119					init: function() {
1120						// check if table should be locked columns
1121						lock.lockEnabled = Plugin.lockEnabledColumns();
1122						if (lock.lockEnabled.left.length === 0 &&
1123							lock.lockEnabled.right.length === 0) {
1124							return;
1125						}
1126						lock.enable();
1127					},
1128					enable: function() {
1129						var enableLock = function(tablePart) {
1130							// check if already has lock column
1131							if ($(tablePart).find('.m-datatable__lock').length > 0) {
1132								Plugin.log('Locked container already exist in: ', tablePart);
1133								return;
1134							}
1135							// check if no rows exists
1136							if ($(tablePart).find('.m-datatable__row').length === 0) {
1137								Plugin.log('No row exist in: ', tablePart);
1138								return;
1139							}
1140
1141							// locked div container
1142							var lockLeft = $('<div/>').
1143								addClass('m-datatable__lock m-datatable__lock--left');
1144							var lockScroll = $('<div/>').
1145								addClass('m-datatable__lock m-datatable__lock--scroll');
1146							var lockRight = $('<div/>').
1147								addClass('m-datatable__lock m-datatable__lock--right');
1148
1149							$(tablePart).find('.m-datatable__row').each(function() {
1150								var rowLeft = $('<tr/>').
1151									addClass('m-datatable__row').
1152									appendTo(lockLeft);
1153								var rowScroll = $('<tr/>').
1154									addClass('m-datatable__row').
1155									appendTo(lockScroll);
1156								var rowRight = $('<tr/>').
1157									addClass('m-datatable__row').
1158									appendTo(lockRight);
1159								$(this).find('.m-datatable__cell').each(function() {
1160									var locked = $(this).data('locked');
1161									if (typeof locked !== 'undefined') {
1162										if (typeof locked.left !== 'undefined' || locked === true) {
1163											// default locked to left
1164											$(this).appendTo(rowLeft);
1165										}
1166										if (typeof locked.right !== 'undefined') {
1167											$(this).appendTo(rowRight);
1168										}
1169									} else {
1170										$(this).appendTo(rowScroll);
1171									}
1172								});
1173								// remove old row
1174								$(this).remove();
1175							});
1176
1177							if (lock.lockEnabled.left.length > 0) {
1178								$(datatable.wrap).addClass('m-datatable--lock');
1179								$(lockLeft).appendTo(tablePart);
1180							}
1181							if (lock.lockEnabled.left.length > 0 || lock.lockEnabled.right.length > 0) {
1182								$(lockScroll).appendTo(tablePart);
1183							}
1184							if (lock.lockEnabled.right.length > 0) {
1185								$(datatable.wrap).addClass('m-datatable--lock');
1186								$(lockRight).appendTo(tablePart);
1187							}
1188						};
1189
1190						$(datatable.table).find('thead,tbody,tfoot').each(function() {
1191							var tablePart = this;
1192							if ($(this).find('.m-datatable__lock').length === 0) {
1193								$(this).ready(function() {
1194									enableLock(tablePart);
1195								});
1196							}
1197						});
1198					},
1199				};
1200				lock.init();
1201				return lock;
1202			},
1203
1204			/**
1205			 * Render everything for resize
1206			 */
1207			fullRender: function() {
1208				// todo; full render datatable for specific condition only
1209				Plugin.spinnerCallback(true);
1210				$(datatable.wrap).removeClass('m-datatable--loaded');
1211
1212				Plugin.insertData();
1213			},
1214
1215			lockEnabledColumns: function() {
1216				var screen = $(window).width();
1217				var columns = options.columns;
1218				var enabled = {left: [], right: []};
1219				$.each(columns, function(i, column) {
1220					if (typeof column.locked !== 'undefined') {
1221						if (typeof column.locked.left !== 'undefined') {
1222							if (mUtil.getBreakpoint(column.locked.left) <= screen) {
1223								enabled['left'].push(column.locked.left);
1224							}
1225						}
1226						if (typeof column.locked.right !== 'undefined') {
1227							if (mUtil.getBreakpoint(column.locked.right) <= screen) {
1228								enabled['right'].push(column.locked.right);
1229							}
1230						}
1231					}
1232				});
1233				return enabled;
1234			},
1235
1236			/**
1237			 * After render event, called by m-datatable--on-layout-updated
1238			 * @param e
1239			 * @param args
1240			 */
1241			afterRender: function(e, args) {
1242				if (args.table == $(datatable.wrap).attr('id')) {
1243					$(datatable).ready(function() {
1244						if (!Plugin.isLocked()) {
1245							Plugin.redraw();
1246							// work on non locked columns
1247							if (Plugin.getOption('rows.autoHide')) {
1248								Plugin.autoHide();
1249								// reset row
1250								$(datatable.table).find('.m-datatable__row').css('height', '');
1251							}
1252						}
1253
1254							// row even class
1255						$(datatable.tableBody).find('.m-datatable__row').removeClass('m-datatable__row--even');
1256						if ($(datatable.wrap).hasClass('m-datatable--subtable')) {
1257							$(datatable.tableBody).find('.m-datatable__row:not(.m-datatable__row-detail):even').addClass('m-datatable__row--even');
1258						} else {
1259							$(datatable.tableBody).find('.m-datatable__row:nth-child(even)').addClass('m-datatable__row--even');
1260						}
1261
1262						// redraw locked columns table
1263						if (Plugin.isLocked()) Plugin.redraw();
1264						$(datatable.tableBody).css('visibility', '');
1265						$(datatable.wrap).addClass('m-datatable--loaded');
1266						Plugin.scrollbar.call();
1267						// Plugin.hoverColumn.call();
1268						Plugin.spinnerCallback(false);
1269					});
1270				}
1271			},
1272
1273			hoverTimer: 0,
1274			isScrolling: false,
1275			setupHover: function() {
1276				$(window).scroll(function(e) {
1277					// stop hover when scrolling
1278					clearTimeout(Plugin.hoverTimer);
1279					Plugin.isScrolling = true;
1280				});
1281
1282				$(datatable.tableBody).
1283					find('.m-datatable__cell').
1284					off('mouseenter', 'mouseleave').
1285					on('mouseenter', function() {
1286						// reset scroll timer to hover class
1287						Plugin.hoverTimer = setTimeout(function() {
1288							Plugin.isScrolling = false;
1289						}, 200);
1290						if (Plugin.isScrolling) return;
1291
1292						// normal table
1293						var row = $(this).
1294							closest('.m-datatable__row').
1295							addClass('m-datatable__row--hover');
1296						var index = $(row).index() + 1;
1297
1298						// lock table
1299						$(row).
1300							closest('.m-datatable__lock').
1301							parent().
1302							find('.m-datatable__row:nth-child(' + index + ')').
1303							addClass('m-datatable__row--hover');
1304					}).
1305					on('mouseleave', function() {
1306						// normal table
1307						var row = $(this).
1308							closest('.m-datatable__row').
1309							removeClass('m-datatable__row--hover');
1310						var index = $(row).index() + 1;
1311
1312						// look table
1313						$(row).
1314							closest('.m-datatable__lock').
1315							parent().
1316							find('.m-datatable__row:nth-child(' + index + ')').
1317							removeClass('m-datatable__row--hover');
1318					});
1319			},
1320
1321			/**
1322			 * Adjust width of locked table containers by resize handler
1323			 * @returns {number}
1324			 */
1325			adjustLockContainer: function() {
1326				if (!Plugin.isLocked()) return 0;
1327
1328				// refer to head dimension
1329				var containerWidth = $(datatable.tableHead).width();
1330				var lockLeft = $(datatable.tableHead).
1331					find('.m-datatable__lock--left').
1332					width();
1333				var lockRight = $(datatable.tableHead).
1334					find('.m-datatable__lock--right').
1335					width();
1336
1337				if (typeof lockLeft === 'undefined') lockLeft = 0;
1338				if (typeof lockRight === 'undefined') lockRight = 0;
1339
1340				var lockScroll = Math.floor(containerWidth - lockLeft - lockRight);
1341				$(datatable.table).
1342					find('.m-datatable__lock--scroll').
1343					css('width', lockScroll);
1344
1345				return lockScroll;
1346			},
1347
1348			/**
1349			 * todo; not in use
1350			 */
1351			dragResize: function() {
1352				var pressed = false;
1353				var start = undefined;
1354				var startX, startWidth;
1355				$(datatable.tableHead).
1356					find('.m-datatable__cell').
1357					mousedown(function(e) {
1358						start = $(this);
1359						pressed = true;
1360						startX = e.pageX;
1361						startWidth = $(this).width();
1362						$(start).addClass('m-datatable__cell--resizing');
1363
1364					}).
1365					mousemove(function(e) {
1366						if (pressed) {
1367							var i = $(start).index();
1368							var tableBody = $(datatable.tableBody);
1369							var ifLocked = $(start).closest('.m-datatable__lock');
1370
1371							if (ifLocked) {
1372								var lockedIndex = $(ifLocked).index();
1373								tableBody = $(datatable.tableBody).
1374									find('.m-datatable__lock').
1375									eq(lockedIndex);
1376							}
1377
1378							$(tableBody).find('.m-datatable__row').each(function(tri, tr) {
1379								$(tr).
1380									find('.m-datatable__cell').
1381									eq(i).
1382									width(startWidth + (e.pageX - startX)).
1383									children().
1384									width(startWidth + (e.pageX - startX));
1385							});
1386
1387							$(start).children().css('width', startWidth + (e.pageX - startX));
1388						}
1389
1390					}).
1391					mouseup(function() {
1392						$(start).removeClass('m-datatable__cell--resizing');
1393						pressed = false;
1394					});
1395
1396				$(document).mouseup(function() {
1397					$(start).removeClass('m-datatable__cell--resizing');
1398					pressed = false;
1399				});
1400			},
1401
1402			/**
1403			 * To prepare placeholder for table before content is loading
1404			 */
1405			initHeight: function() {
1406				if (options.layout.height && options.layout.scroll) {
1407					var theadHeight = $(datatable.tableHead).find('.m-datatable__row').height();
1408					var tfootHeight = $(datatable.tableFoot).find('.m-datatable__row').height();
1409					var bodyHeight = options.layout.height;
1410					if (theadHeight > 0) {
1411						bodyHeight -= theadHeight;
1412					}
1413					if (tfootHeight > 0) {
1414						bodyHeight -= tfootHeight;
1415					}
1416					$(datatable.tableBody).css('max-height', bodyHeight);
1417				}
1418			},
1419
1420			/**
1421			 * Setup base DOM (table, thead, tbody, tfoot) and create if not exist.
1422			 */
1423			setupBaseDOM: function() {
1424				// keep original state before mDatatable initialize
1425				datatable.initialDatatable = $(datatable).clone();
1426
1427				// main element
1428				if ($(datatable).prop('tagName') === 'TABLE') {
1429					// if main init element is <table>, wrap with div
1430					datatable.table = $(datatable).
1431						removeClass('m-datatable').
1432						addClass('m-datatable__table');
1433					if ($(datatable.table).parents('.m-datatable').length === 0) {
1434						datatable.table.wrap($('<div/>').
1435							addClass('m-datatable').
1436							addClass('m-datatable--' + options.layout.theme));
1437						datatable.wrap = $(datatable.table).parent();
1438					}
1439				} else {
1440					// create table
1441					datatable.wrap = $(datatable).
1442						addClass('m-datatable').
1443						addClass('m-datatable--' + options.layout.theme);
1444					datatable.table = $('<table/>').
1445						addClass('m-datatable__table').
1446						appendTo(datatable);
1447				}
1448
1449				if (typeof options.layout.class !== 'undefined') {
1450					$(datatable.wrap).addClass(options.layout.class);
1451				}
1452
1453				$(datatable.table).
1454					removeClass('m-datatable--destroyed').
1455					css('display', 'block');
1456
1457				// force disable save state
1458				if (typeof $(datatable).attr('id') === 'undefined') {
1459					Plugin.setOption('data.saveState', false);
1460					$(datatable.table).attr('id', mUtil.getUniqueID('m-datatable--'));
1461				}
1462
1463				// predefine table height
1464				if (Plugin.getOption('layout.minHeight'))
1465					$(datatable.table).css('min-height', Plugin.getOption('layout.minHeight'));
1466
1467				if (Plugin.getOption('layout.height'))
1468					$(datatable.table).css('max-height', Plugin.getOption('layout.height'));
1469
1470				// for normal table load
1471				if (options.data.type === null) {
1472					$(datatable.table).css('width', '').css('display', '');
1473				}
1474
1475				// create table head element
1476				datatable.tableHead = $(datatable.table).find('thead');
1477				if ($(datatable.tableHead).length === 0) {
1478					datatable.tableHead = $('<thead/>').prependTo(datatable.table);
1479				}
1480
1481				// create table head element
1482				datatable.tableBody = $(datatable.table).find('tbody');
1483				if ($(datatable.tableBody).length === 0) {
1484					datatable.tableBody = $('<tbody/>').appendTo(datatable.table);
1485				}
1486
1487				if (typeof options.layout.footer !== 'undefined' &&
1488					options.layout.footer) {
1489					// create table foot element
1490					datatable.tableFoot = $(datatable.table).find('tfoot');
1491					if ($(datatable.tableFoot).length === 0) {
1492						datatable.tableFoot = $('<tfoot/>').appendTo(datatable.table);
1493					}
1494				}
1495			},
1496
1497			/**
1498			 * Set column data before table manipulation.
1499			 */
1500			setupCellField: function(tableParts) {
1501				if (typeof tableParts === 'undefined') tableParts = $(datatable.table).children();
1502				var columns = options.columns;
1503				$.each(tableParts, function(part, tablePart) {
1504					$(tablePart).find('.m-datatable__row').each(function(tri, tr) {
1505						// prepare data
1506						$(tr).find('.m-datatable__cell').each(function(tdi, td) {
1507							if (typeof columns[tdi] !== 'undefined') {
1508								$(td).data(columns[tdi]);
1509							}
1510						});
1511					});
1512				});
1513			},
1514
1515			/**
1516			 * Set column template callback
1517			 * @param tablePart
1518			 */
1519			setupTemplateCell: function(tablePart) {
1520				if (typeof tablePart === 'undefined') tablePart = datatable.tableBody;
1521				var columns = options.columns;
1522				$(tablePart).find('.m-datatable__row').each(function(tri, tr) {
1523					// row data object, if any
1524					var obj = $(tr).data('obj') || {};
1525
1526					// @deprecated in v5.0.6
1527					obj['getIndex'] = function() {
1528						return tri;
1529					};
1530					// @deprecated in v5.0.6
1531					obj['getDatatable'] = function() {
1532						return datatable;
1533					};
1534
1535					// @deprecated in v5.0.6
1536					var rowCallback = Plugin.getOption('rows.callback');
1537					if (typeof rowCallback === 'function') {
1538						rowCallback($(tr), obj, tri);
1539					}
1540					// before template row callback
1541					var beforeTemplate = Plugin.getOption('rows.beforeTemplate');
1542					if (typeof beforeTemplate === 'function') {
1543						beforeTemplate($(tr), obj, tri);
1544					}
1545					// if data object is undefined, collect from table
1546					if (typeof obj === 'undefined') {
1547						obj = {};
1548						$(tr).find('.m-datatable__cell').each(function(tdi, td) {
1549							// get column settings by field
1550							var column = $.grep(columns, function(n, i) {
1551								return $(td).data('field') === n.field;
1552							})[0];
1553							if (typeof column !== 'undefined') {
1554								obj[column['field']] = $(td).text();
1555							}
1556						});
1557					}
1558
1559					$(tr).find('.m-datatable__cell').each(function(tdi, td) {
1560						// get column settings by field
1561						var column = $.grep(columns, function(n, i) {
1562							return $(td).data('field') === n.field;
1563						})[0];
1564						if (typeof column !== 'undefined') {
1565							// column template
1566							if (typeof column.template !== 'undefined') {
1567								var finalValue = '';
1568								// template string
1569								if (typeof column.template === 'string') {
1570									finalValue = Plugin.dataPlaceholder(column.template, obj);
1571								}
1572								// template callback function
1573								if (typeof column.template === 'function') {
1574									finalValue = column.template(obj, tri, datatable);
1575								}
1576								var span = $('<span/>').append(finalValue);
1577								// insert to cell, wrap with span
1578								$(td).html(span);
1579
1580								// set span overflow
1581								if (typeof column.overflow !== 'undefined') {
1582									$(span).css('overflow', column.overflow);
1583								}
1584							}
1585						}
1586					});
1587
1588					// after template row callback
1589					var afterTemplate = Plugin.getOption('rows.afterTemplate');
1590					if (typeof afterTemplate === 'function') {
1591						afterTemplate($(tr), obj, tri);
1592					}
1593				});
1594			},
1595
1596			/**
1597			 * Setup extra system column properties
1598			 * Note: selector checkbox, subtable toggle
1599			 */
1600			setupSystemColumn: function() {
1601				datatable.dataSet = datatable.dataSet || [];
1602				// no records available
1603				if (datatable.dataSet.length === 0) return;
1604
1605				var columns = options.columns;
1606				$(datatable.tableBody).
1607					find('.m-datatable__row').
1608					each(function(tri, tr) {
1609						$(tr).find('.m-datatable__cell').each(function(tdi, td) {
1610							// get column settings by field
1611							var column = $.grep(columns, function(n, i) {
1612								return $(td).data('field') === n.field;
1613							})[0];
1614							if (typeof column !== 'undefined') {
1615								var value = $(td).text();
1616
1617								// enable column selector
1618								if (typeof column.selector !== 'undefined' &&
1619									column.selector !== false) {
1620									// check if checkbox exist
1621									if ($(td).find('.m-checkbox [type="checkbox"]').length > 0) return;
1622									$(td).addClass('m-datatable__cell--check');
1623									// append checkbox
1624									var chk = $('<label/>').
1625										addClass('m-checkbox m-checkbox--single').
1626										append($('<input/>').
1627											attr('type', 'checkbox').
1628											attr('value', value).
1629											on('click', function() {
1630												if ($(this).is(':checked')) {
1631													// add checkbox active row class
1632													Plugin.setActive(this);
1633												} else {
1634													// add checkbox active row class
1635													Plugin.setInactive(this);
1636												}
1637											})).
1638										append($('<span/>'));
1639
1640									// checkbox selector has outline style
1641									if (typeof column.selector.class !== 'undefined') {
1642										$(chk).addClass(column.selector.class);
1643									}
1644
1645									$(td).children().html(chk);
1646								}
1647
1648								// enable column subtable toggle
1649								if (typeof column.subtable !== 'undefined' && column.subtable) {
1650									// check if subtable toggle exist
1651									if ($(td).find('.m-datatable__toggle-subtable').length > 0) return;
1652									// append subtable toggle
1653									$(td).
1654										children().
1655										html($('<a/>').
1656											addClass('m-datatable__toggle-subtable').
1657											attr('href', '#').
1658											attr('data-value', value).
1659											append($('<i/>').
1660												addClass(Plugin.getOption('layout.icons.rowDetail.collapse'))));
1661								}
1662							}
1663						});
1664					});
1665
1666				// init checkbox for header/footer
1667				var initCheckbox = function(tr) {
1668					// get column settings by field
1669					var column = $.grep(columns, function(n, i) {
1670						return typeof n.selector !== 'undefined' && n.selector !== false;
1671					})[0];
1672
1673					if (typeof column !== 'undefined') {
1674						// enable column selector
1675						if (typeof column.selector !== 'undefined' && column.selector !== false) {
1676							var td = $(tr).find('[data-field="' + column.field + '"]');
1677							// check if checkbox exist
1678							if ($(td).find('.m-checkbox [type="checkbox"]').length > 0) return;
1679							$(td).addClass('m-datatable__cell--check');
1680
1681							// todo; check all, for server pagination
1682							// append checkbox
1683							var chk = $('<label/>').
1684								addClass('m-checkbox m-checkbox--single m-checkbox--all').
1685								append($('<input/>').
1686									attr('type', 'checkbox').
1687									on('click', function() {
1688										if ($(this).is(':checked')) {
1689											Plugin.setActiveAll(true);
1690										} else {
1691											Plugin.setActiveAll(false);
1692										}
1693									})).
1694								append($('<span/>'));
1695
1696							// checkbox selector has outline style
1697							if (typeof column.selector.class !== 'undefined') {
1698								$(chk).addClass(column.selector.class);
1699							}
1700
1701							$(td).children().html(chk);
1702						}
1703					}
1704				};
1705
1706				if (options.layout.header) {
1707					initCheckbox($(datatable.tableHead).find('.m-datatable__row').first());
1708				}
1709				if (options.layout.footer) {
1710					initCheckbox($(datatable.tableFoot).find('.m-datatable__row').first());
1711				}
1712			},
1713
1714			/**
1715			 * Adjust width to match container size
1716			 */
1717			adjustCellsWidth: function() {
1718				// get table width
1719				var containerWidth = $(datatable.tableHead).width();
1720
1721				// offset reserved for sort icon
1722				var sortOffset = 20;
1723
1724				// get total number of columns
1725				var columns = Plugin.getOneRow(datatable.tableHead, 1).length;
1726				if (columns > 0) {
1727					//  remove reserved sort icon width
1728					containerWidth = containerWidth - (sortOffset * columns);
1729					var minWidth = Math.floor(containerWidth / columns);
1730
1731					// minimum width
1732					if (minWidth <= Plugin.offset) {
1733						minWidth = Plugin.offset;
1734					}
1735
1736					$(datatable.table).
1737						find('.m-datatable__row').
1738						find('.m-datatable__cell').
1739						each(function(tdi, td) {
1740							var width = minWidth;
1741							var dataWidth = $(td).data('width');
1742							if (typeof dataWidth !== 'undefined') {
1743								width = dataWidth;
1744							}
1745							$(td).children().css('width', width);
1746						});
1747				}
1748			},
1749
1750			/**
1751			 * Adjust height to match container size
1752			 */
1753			adjustCellsHeight: function() {
1754				$.each($(datatable.table).children(), function(part, tablePart) {
1755					var totalRows = $(tablePart).find('.m-datatable__row').first().parent().find('.m-datatable__row').length;
1756					for (var i = 1; i <= totalRows; i++) {
1757						var rows = $(tablePart).find('.m-datatable__row:nth-child(' + i + ')');
1758						if ($(rows).length > 0) {
1759							var maxHeight = Math.max.apply(null, $(rows).map(function() {
1760								return $(this).height();
1761							}).get());
1762							$(rows).css('height', Math.ceil(parseInt(maxHeight)));
1763						}
1764					}
1765				});
1766			},
1767
1768			/**
1769			 * Setup table DOM and classes
1770			 */
1771			setupDOM: function(table) {
1772				// set table classes
1773				$(table).find('> thead').addClass('m-datatable__head');
1774				$(table).find('> tbody').addClass('m-datatable__body');
1775				$(table).find('> tfoot').addClass('m-datatable__foot');
1776				$(table).find('tr').addClass('m-datatable__row');
1777				$(table).find('tr > th, tr > td').addClass('m-datatable__cell');
1778				$(table).find('tr > th, tr > td').each(function(i, td) {
1779					if ($(td).find('span').length === 0) {
1780						$(td).wrapInner($('<span/>').css('width', Plugin.offset));
1781					}
1782				});
1783			},
1784
1785			/**
1786			 * Default scrollbar
1787			 * @returns {{tableLocked: null, init: init, onScrolling: onScrolling}}
1788			 */
1789			scrollbar: function() {
1790				var scroll = {
1791					scrollable: null,
1792					tableLocked: null,
1793					mcsOptions: {
1794						scrollInertia: 0,
1795						autoDraggerLength: true,
1796						autoHideScrollbar: true,
1797						autoExpandScrollbar: false,
1798						alwaysShowScrollbar: 0,
1799						mouseWheel: {
1800							scrollAmount: 120,
1801							preventDefault: false,
1802						},
1803						advanced: {
1804							updateOnContentResize: true,
1805							autoExpandHorizontalScroll: true,
1806						},
1807						theme: 'minimal-dark',
1808					},
1809					init: function() {
1810						// destroy previous custom scrollbar
1811						Plugin.destroyScroller(scroll.scrollable);
1812						var screen = mUtil.getViewPort().width;
1813						// setup scrollable datatable
1814						if (options.layout.scroll) {
1815							// add scrollable datatable class
1816							$(datatable.wrap).addClass('m-datatable--scroll');
1817
1818							var scrollable = $(datatable.tableBody).find('.m-datatable__lock--scroll');
1819
1820							// check if scrollable area have rows
1821							if ($(scrollable).find('.m-datatable__row').length > 0 && $(scrollable).length > 0) {
1822								scroll.scrollHead = $(datatable.tableHead).find('> .m-datatable__lock--scroll > .m-datatable__row');
1823								scroll.scrollFoot = $(datatable.tableFoot).find('> .m-datatable__lock--scroll > .m-datatable__row');
1824								scroll.tableLocked = $(datatable.tableBody).find('.m-datatable__lock:not(.m-datatable__lock--scroll)');
1825								if (screen > mUtil.getBreakpoint('lg')) {
1826									scroll.mCustomScrollbar(scrollable);
1827								} else {
1828									scroll.defaultScrollbar(scrollable);
1829								}
1830							} else if ($(datatable.tableBody).find('.m-datatable__row').length > 0) {
1831								scroll.scrollHead = $(datatable.tableHead).find('> .m-datatable__row');
1832								scroll.scrollFoot = $(datatable.tableFoot).find('> .m-datatable__row');
1833								if (screen > mUtil.getBreakpoint('lg')) {
1834									scroll.mCustomScrollbar(datatable.tableBody);
1835								} else {
1836									scroll.defaultScrollbar(datatable.tableBody);
1837								}
1838							}
1839						} else {
1840							$(datatable.table).
1841								// css('height', 'auto').
1842								css('overflow-x', 'auto');
1843						}
1844					},
1845					defaultScrollbar: function(scrollable) {
1846						$(scrollable).
1847							css('overflow', 'auto').
1848							css('max-height', Plugin.getOption('layout.height')).
1849							on('scroll', scroll.onScrolling);
1850					},
1851					onScrolling: function(e) {
1852						var left = $(this).scrollLeft();
1853						var top = $(this).scrollTop();
1854						$(scroll.scrollHead).css('left', -left);
1855						$(scroll.scrollFoot).css('left', -left);
1856						$(scroll.tableLocked).each(function(i, table) {
1857							$(table).css('top', -top);
1858						});
1859					},
1860					mCustomScrollbar: function(scrollable) {
1861						scroll.scrollable = scrollable;
1862						var height = Plugin.getOption('layout.height');
1863						// vertical and horizontal scrollbar
1864						var axis = 'xy';
1865						if (height === null) {
1866							// horizontal scrollbar
1867							axis = 'x';
1868						}
1869						var mcsOptions = $.extend({}, scroll.mcsOptions, {
1870							axis: axis,
1871							setHeight: $(datatable.tableBody).height(),
1872							callbacks: {
1873								whileScrolling: function() {
1874									var mcs = this.mcs;
1875									$(scroll.scrollHead).css('left', mcs.left);
1876									$(scroll.scrollFoot).css('left', mcs.left);
1877									$(scroll.tableLocked).each(function(i, table) {
1878										$(table).css('top', mcs.top);
1879									});
1880									// stop hover when scrolling
1881									clearTimeout(Plugin.hoverTimer);
1882									Plugin.isScrolling = true;
1883								},
1884							},
1885						});
1886
1887						if (Plugin.getOption('layout.smoothScroll.scrollbarShown') === true) {
1888							$(scrollable).attr('data-scrollbar-shown', 'true');
1889						}
1890
1891						// create a new instance for table body with scrollbar
1892						Plugin.mCustomScrollbar(scrollable, mcsOptions);
1893					},
1894				};
1895				scroll.init();
1896				return scroll;
1897			},
1898
1899			/**
1900			 * Init custom scrollbar and reset position
1901			 * @param element
1902			 * @param options
1903			 */
1904			mCustomScrollbar: function(element, options) {
1905				$(datatable.tableBody).css('overflow', '');
1906				// check if any custom scrollbar exist in the element
1907				Plugin.destroyScroller($(datatable.table).find('.mCustomScrollbar'));
1908				$(element).mCustomScrollbar(options);
1909			},
1910
1911			/**
1912			 * Set column title from options.columns settings
1913			 */
1914			setHeadTitle: function(tablePart) {
1915				if (typeof tablePart === 'undefined') tablePart = datatable.tableHead;
1916				var columns = options.columns;
1917				var row = $(tablePart).find('.m-datatable__row');
1918				var ths = $(tablePart).find('.m-datatable__cell');
1919				if ($(row).length === 0) {
1920					row = $('<tr/>').appendTo(tablePart);
1921				}
1922				$.each(columns, function(i, column) {
1923					var th = $(ths).eq(i);
1924					if ($(th).length === 0) {
1925						th = $('<th/>').appendTo(row);
1926					}
1927					// set column title
1928					if (typeof column['title'] !== 'undefined') {
1929						$(th).
1930							html(column['title']).
1931							attr('data-field', column.field).
1932							data(column);
1933					}
1934					// apply text align to thead/tfoot
1935					if (typeof column.textAlign !== 'undefined') {
1936						var align = typeof datatable.textAlign[column.textAlign] !==
1937						'undefined' ? datatable.textAlign[column.textAlign] : '';
1938						$(th).addClass(align);
1939					}
1940				});
1941				Plugin.setupDOM(tablePart);
1942			},
1943
1944			/**
1945			 * Initiate to get remote or local data via ajax
1946			 */
1947			dataRender: function(action) {
1948				$(datatable.table).
1949					siblings('.m-datatable__pager').
1950					removeClass('m-datatable--paging-loaded');
1951
1952				var buildMeta = function() {
1953					datatable.dataSet = datatable.dataSet || [];
1954					Plugin.localDataUpdate();
1955					// local pagination meta
1956					var meta = Plugin.getDataSourceParam('pagination');
1957					if (meta.perpage === 0) {
1958						meta.perpage = options.data.pageSize || 10;
1959					}
1960					meta.total = datatable.dataSet.length;
1961					var start = Math.max(meta.perpage * (meta.page - 1), 0);
1962					var end = Math.min(start + meta.perpage, meta.total);
1963					datatable.dataSet = $(datatable.dataSet).slice(start, end);
1964					return meta;
1965				};
1966
1967				var afterGetData = function(result) {
1968					var localPagingCallback = function(ctx, meta) {
1969						if (!$(ctx.pager).hasClass('m-datatable--paging-loaded')) {
1970							$(ctx.pager).remove();
1971							ctx.init(meta);
1972						}
1973						$(ctx.pager).off().on('m-datatable--on-goto-page', function(e) {
1974							$(ctx.pager).remove();
1975							ctx.init(meta);
1976						});
1977
1978						var start = Math.max(meta.perpage * (meta.page - 1), 0);
1979						var end = Math.min(start + meta.perpage, meta.total);
1980
1981						Plugin.localDataUpdate();
1982						datatable.dataSet = $(datatable.dataSet).slice(start, end);
1983
1984						// insert data into table content
1985						Plugin.insertData();
1986					};
1987
1988					$(datatable.wrap).removeClass('m-datatable--error');
1989					// pagination enabled
1990					if (options.pagination) {
1991						if (options.data.serverPaging && options.data.type !== 'local') {
1992							// server pagination
1993							var serverMeta = Plugin.getObject('meta', result || null);
1994							if (serverMeta !== null) {
1995								Plugin.paging(serverMeta);
1996							} else {
1997								// no meta object from server response, fallback to local pagination
1998								Plugin.paging(buildMeta(), localPagingCallback);
1999							}
2000						} else {
2001							// local pagination can be used by remote data also
2002							Plugin.paging(buildMeta(), localPagingCallback);
2003						}
2004					} else {
2005						// pagination is disabled
2006						Plugin.localDataUpdate();
2007					}
2008					// insert data into table content
2009					Plugin.insertData();
2010				};
2011
2012				// get local datasource
2013				if (options.data.type === 'local'
2014					// for remote json datasource
2015					|| typeof options.data.source.read === 'undefined' &&
2016					datatable.dataSet !== null
2017					// for remote datasource, server sorting is disabled and data already received from remote
2018					|| options.data.serverSorting === false && action === 'sort'
2019				) {
2020					afterGetData();
2021					return;
2022				}
2023
2024				// getting data from remote only
2025				Plugin.getData().done(afterGetData);
2026			},
2027
2028			/**
2029			 * Process ajax data
2030			 */
2031			insertData: function() {
2032				datatable.dataSet = datatable.dataSet || [];
2033				var params = Plugin.getDataSourceParam();
2034
2035				// todo; fix performance
2036				var tableBody = $('<tbody/>').
2037					addClass('m-datatable__body').
2038					css('visibility', 'hidden');
2039				var colLength = options.columns.length;
2040
2041				$.each(datatable.dataSet, function(i, row) {
2042					// keep data object to row
2043					var tr = $('<tr/>').attr('data-row', i).data('obj', row);
2044					var i = 0;
2045					var tds = [];
2046					for (var a = 0; a < colLength; a += 1) {
2047						var column = options.columns[a];
2048						var classes = [];
2049						// add sorted class to cells
2050						if (Plugin.getObject('sort.field', params) === column.field) {
2051							classes.push('m-datatable__cell--sorted');
2052						}
2053
2054						// apply text align
2055						if (typeof column.textAlign !== 'undefined') {
2056							var align = typeof datatable.textAlign[column.textAlign] !==
2057							'undefined' ? datatable.textAlign[column.textAlign] : '';
2058							classes.push(align);
2059						}
2060
2061						tds[i++] = '<td data-field="' + column.field + '"';
2062						tds[i++] = ' class="' + classes.join(' ') + '"';
2063						tds[i++] = '>';
2064						tds[i++] = Plugin.getObject(column.field, row);
2065						tds[i++] = '</td>';
2066					}
2067					$(tr).append(tds.join(''));
2068					$(tableBody).append(tr);
2069				});
2070
2071				// display no records message
2072				if (datatable.dataSet.length === 0) {
2073					Plugin.destroyScroller($(datatable.table).find('.mCustomScrollbar'));
2074					$(tableBody).html($('<span/>').
2075						addClass('m-datatable--error').
2076						html(Plugin.getOption('translate.records.noRecords')));
2077					$(datatable.wrap).addClass('m-datatable--error m-datatable--loaded');
2078					Plugin.spinnerCallback(false);
2079				}
2080
2081				// replace existing table body
2082				$(datatable.tableBody).replaceWith(tableBody);
2083				datatable.tableBody = tableBody;
2084
2085				// layout update
2086				Plugin.setupDOM(datatable.table);
2087				Plugin.setupCellField([datatable.tableBody]);
2088				Plugin.setupTemplateCell(datatable.tableBody);
2089				Plugin.layoutUpdate();
2090			},
2091
2092			updateTableComponents: function() {
2093				datatable.tableHead = $(datatable.table).children('thead');
2094				datatable.tableBody = $(datatable.table).children('tbody');
2095				datatable.tableFoot = $(datatable.table).children('tfoot');
2096			},
2097
2098			/**
2099			 * Call ajax for raw JSON data
2100			 */
2101			getData: function() {
2102				Plugin.spinnerCallback(true);
2103
2104				var ajaxParams = {
2105					dataType: 'json',
2106					method: 'GET',
2107					data: {},
2108					timeout: 60000,
2109				};
2110
2111				if (options.data.type === 'local') {
2112					ajaxParams.url = options.data.source;
2113				}
2114
2115				if (options.data.type === 'remote') {
2116					ajaxParams.url = Plugin.getOption('data.source.read.url');
2117					if (typeof ajaxParams.url !== 'string') ajaxParams.url = Plugin.getOption('data.source.read');
2118					if (typeof ajaxParams.url !== 'string') ajaxParams.url = Plugin.getOption('data.source');
2119					ajaxParams.headers = Plugin.getOption('data.source.read.headers');
2120					ajaxParams.method = Plugin.getOption('data.source.read.method') || 'POST';
2121
2122					var data = Plugin.getDataSourceParam();
2123					// remove if server params is not enabled
2124					if (!Plugin.getOption('data.serverPaging')) {
2125						delete data['pagination'];
2126					}
2127					if (!Plugin.getOption('data.serverSorting')) {
2128						delete data['sort'];
2129					}
2130					// deprecated in v5.0.7
2131					ajaxParams.data['datatable'] = data;
2132					ajaxParams.data = $.extend(true, ajaxParams.data, data, Plugin.getOption('data.source.read.params'));
2133				}
2134
2135				return $.ajax(ajaxParams).done(function(response, textStatus, jqXHR) {
2136					datatable.lastResponse = response;
2137					// extendible data map callback for custom datasource
2138					datatable.dataSet = datatable.originalDataSet = Plugin.dataMapCallback(response);
2139					Plugin.setAutoColumns();
2140					$(datatable).trigger('m-datatable--on-ajax-done', [datatable.dataSet]);
2141				}).fail(function(jqXHR, textStatus, errorThrown) {
2142					Plugin.destroyScroller($(datatable.table).find('.mCustomScrollbar'));
2143					$(datatable).trigger('m-datatable--on-ajax-fail', [jqXHR]);
2144					$(datatable.tableBody).html($('<span/>').
2145						addClass('m-datatable--error').
2146						html(Plugin.getOption('translate.records.noRecords')));
2147					$(datatable.wrap).addClass('m-datatable--error m-datatable--loaded');
2148					Plugin.spinnerCallback(false);
2149				}).always(function() {
2150				});
2151			},
2152
2153			/**
2154			 * Pagination object
2155			 * @param meta if null, local pagination, otherwise remote pagination
2156			 * @param callback for update data when navigating page
2157			 */
2158			paging: function(meta, callback) {
2159				var pg = {
2160					meta: null,
2161					pager: null,
2162					paginateEvent: null,
2163					pagerLayout: {pagination: null, info: null},
2164					callback: null,
2165					init: function(meta) {
2166						pg.meta = meta;
2167
2168						// todo; if meta object not exist will cause error
2169						// always recount total pages
2170						pg.meta.pages = Math.max(Math.ceil(pg.meta.total / pg.meta.perpage), 1);
2171
2172						// current page must be not over than total pages
2173						if (pg.meta.page > pg.meta.pages) pg.meta.page = pg.meta.pages;
2174
2175						// set unique event name between tables
2176						pg.paginateEvent = Plugin.getTablePrefix();
2177
2178						pg.pager = $(datatable.table).siblings('.m-datatable__pager');
2179						if ($(pg.pager).hasClass('m-datatable--paging-loaded')) return;
2180
2181						// if class .m-datatable--paging-loaded not exist, recreate pagination
2182						$(pg.pager).remove();
2183
2184						// if no pages available
2185						if (pg.meta.pages === 0) return;
2186
2187						// update datasource params
2188						Plugin.setDataSourceParam('pagination', {
2189							page: pg.meta.page,
2190							pages: pg.meta.pages,
2191							perpage: pg.meta.perpage,
2192							total: pg.meta.total,
2193						});
2194
2195						// default callback function, contains remote pagination handler
2196						pg.callback = pg.serverCallback;
2197						// custom callback function
2198						if (typeof callback === 'function') pg.callback = callback;
2199
2200						pg.addPaginateEvent();
2201						pg.populate();
2202
2203						pg.meta.page = Math.max(pg.meta.page || 1, pg.meta.page);
2204
2205						$(datatable).trigger(pg.paginateEvent, pg.meta);
2206
2207						pg.pagingBreakpoint.call();
2208						$(window).resize(pg.pagingBreakpoint);
2209					},
2210					serverCallback: function(ctx, meta) {
2211						Plugin.dataRender();
2212					},
2213					populate: function() {
2214						var icons = Plugin.getOption('layout.icons.pagination');
2215						var title = Plugin.getOption('translate.toolbar.pagination.items.default');
2216						// pager root element
2217						pg.pager = $('<div/>').addClass('m-datatable__pager m-datatable--paging-loaded clearfix');
2218						// numbering links
2219						var pagerNumber = $('<ul/>').addClass('m-datatable__pager-nav');
2220						pg.pagerLayout['pagination'] = pagerNumber;
2221
2222						// pager first/previous button
2223						$('<li/>').
2224							append($('<a/>').
2225								attr('title', title.first).
2226								addClass('m-datatable__pager-link m-datatable__pager-link--first').
2227								append($('<i/>').addClass(icons.first)).
2228								on('click', pg.gotoMorePage).
2229								attr('data-page', 1)).
2230							appendTo(pagerNumber);
2231						$('<li/>').
2232							append($('<a/>').
2233								attr('title', title.prev).
2234								addClass('m-datatable__pager-link m-datatable__pager-link--prev').
2235								append($('<i/>').addClass(icons.prev)).
2236								on('click', pg.gotoMorePage)).
2237							appendTo(pagerNumber);
2238
2239						// more previous pages
2240						$('<li/>').
2241							append($('<a/>').
2242								attr('title', title.more).
2243								addClass('m-datatable__pager-link m-datatable__pager-link--more-prev').
2244								html($('<i/>').addClass(icons.more)).
2245								on('click', pg.gotoMorePage)).
2246							appendTo(pagerNumber);
2247
2248						$('<li/>').
2249							append($('<input/>').
2250								attr('type', 'text').
2251								addClass('m-pager-input form-control').
2252								attr('title', title.input).
2253								on('keyup', function() {
2254									// on keyup update [data-page]
2255									$(this).attr('data-page', Math.abs($(this).val()));
2256								}).
2257								on('keypress', function(e) {
2258									// on keypressed enter button
2259									if (e.which === 13) pg.gotoMorePage(e);
2260								})).
2261							appendTo(pagerNumber);
2262
2263						var pagesNumber = Plugin.getOption('toolbar.items.pagination.pages.desktop.pagesNumber');
2264						var end = Math.ceil(pg.meta.page / pagesNumber) * pagesNumber;
2265						var start = end - pagesNumber;
2266						if (end > pg.meta.pages) {
2267							end = pg.meta.pages;
2268						}
2269						for (var x = start; x < end; x++) {
2270							var pageNumber = x + 1;
2271							$('<li/>').
2272								append($('<a/>').
2273									addClass('m-datatable__pager-link m-datatable__pager-link-number').
2274									text(pageNumber).
2275									attr('data-page', pageNumber).
2276									attr('title', pageNumber).
2277									on('click', pg.gotoPage)).
2278								appendTo(pagerNumber);
2279						}
2280
2281						// more next pages
2282						$('<li/>').
2283							append($('<a/>').
2284								attr('title', title.more).
2285								addClass('m-datatable__pager-link m-datatable__pager-link--more-next').
2286								html($('<i/>').addClass(icons.more)).
2287								on('click', pg.gotoMorePage)).
2288							appendTo(pagerNumber);
2289
2290						// pager next/last button
2291						$('<li/>').
2292							append($('<a/>').
2293								attr('title', title.next).
2294								addClass('m-datatable__pager-link m-datatable__pager-link--next').
2295								append($('<i/>').addClass(icons.next)).
2296								on('click', pg.gotoMorePage)).
2297							appendTo(pagerNumber);
2298						$('<li/>').
2299							append($('<a/>').
2300								attr('title', title.last).
2301								addClass('m-datatable__pager-link m-datatable__pager-link--last').
2302								append($('<i/>').addClass(icons.last)).
2303								on('click', pg.gotoMorePage).
2304								attr('data-page', pg.meta.pages)).
2305							appendTo(pagerNumber);
2306
2307						// page info
2308						if (Plugin.getOption('toolbar.items.info')) {
2309							pg.pagerLayout['info'] = $('<div/>').
2310								addClass('m-datatable__pager-info').
2311								append($('<span/>').addClass('m-datatable__pager-detail'));
2312						}
2313
2314						$.each(Plugin.getOption('toolbar.layout'), function(i, layout) {
2315							$(pg.pagerLayout[layout]).appendTo(pg.pager);
2316						});
2317
2318						// page size select
2319						var pageSizeSelect = $('<select/>').
2320							addClass('selectpicker m-datatable__pager-size').
2321							attr('title', Plugin.getOption('translate.toolbar.pagination.items.default.select')).
2322							attr('data-width', '70px').
2323							val(pg.meta.perpage).
2324							on('change', pg.updatePerpage).
2325							prependTo(pg.pagerLayout['info']);
2326
2327						var pageSizes = Plugin.getOption('toolbar.items.pagination.pageSizeSelect');
2328						// default value here, to fix override option by user
2329						if (pageSizes.length == 0) pageSizes = [10, 20, 30, 50, 100];
2330						$.each(pageSizes, function(i, size) {
2331							var display = size;
2332							if (size === -1) display = 'All';
2333							$('<option/>').
2334								attr('value', size).
2335								html(display).
2336								appendTo(pageSizeSelect);
2337						});
2338
2339						// init selectpicker to dropdown
2340						$(datatable).ready(function() {
2341							$('.selectpicker').
2342								selectpicker().
2343								siblings('.dropdown-toggle').
2344								attr('title', Plugin.getOption(
2345									'translate.toolbar.pagination.items.default.select'));
2346						});
2347
2348						pg.paste();
2349					},
2350					paste: function() {
2351						// insert pagination based on placement position, top|bottom
2352						$.each($.unique(Plugin.getOption('toolbar.placement')),
2353							function(i, position) {
2354								if (position === 'bottom') {
2355									$(pg.pager).clone(true).insertAfter(datatable.table);
2356								}
2357								if (position === 'top') {
2358									// pager top need some extra space
2359									$(pg.pager).
2360										clone(true).
2361										addClass('m-datatable__pager--top').
2362										insertBefore(datatable.table);
2363								}
2364							});
2365					},
2366					gotoMorePage: function(e) {
2367						e.preventDefault();
2368						// $(this) is a link of .m-datatable__pager-link
2369
2370						if ($(this).attr('disabled') === 'disabled') return false;
2371
2372						var page = $(this).attr('data-page');
2373
2374						// event from text input
2375						if (typeof page === 'undefined') {
2376							page = $(e.target).attr('data-page');
2377						}
2378
2379						pg.openPage(parseInt(page));
2380						return false;
2381					},
2382					gotoPage: function(e) {
2383						e.preventDefault();
2384						// prevent from click same page number
2385						if ($(this).hasClass('m-datatable__pager-link--active')) return;
2386
2387						pg.openPage(parseInt($(this).data('page')));
2388					},
2389					openPage: function(page) {
2390						// currentPage is 1-based index
2391						pg.meta.page = parseInt(page);
2392
2393						$(datatable).trigger(pg.paginateEvent, pg.meta);
2394						pg.callback(pg, pg.meta);
2395
2396						// update page callback function
2397						$(pg.pager).trigger('m-datatable--on-goto-page', pg.meta);
2398					},
2399					updatePerpage: function(e) {
2400						e.preventDefault();
2401						if (Plugin.getOption('layout.height') === null) {
2402							// fix white space, when perpage is set from many records to less records
2403							$('html, body').animate({scrollTop: $(datatable).position().top});
2404						}
2405
2406						pg.pager = $(datatable.table).
2407							siblings('.m-datatable__pager').
2408							removeClass('m-datatable--paging-loaded');
2409
2410						// on change select page size
2411						if (e.originalEvent) {
2412							pg.meta.perpage = parseInt($(this).val());
2413						}
2414
2415						$(pg.pager).
2416							find('select.m-datatable__pager-size').
2417							val(pg.meta.perpage).
2418							attr('data-selected', pg.meta.perpage);
2419
2420						// update datasource params
2421						Plugin.setDataSourceParam('pagination', {
2422							page: pg.meta.page,
2423							pages: pg.meta.pages,
2424							perpage: pg.meta.perpage,
2425							total: pg.meta.total,
2426						});
2427
2428						// update page callback function
2429						$(pg.pager).trigger('m-datatable--on-update-perpage', pg.meta);
2430						$(datatable).trigger(pg.paginateEvent, pg.meta);
2431						pg.callback(pg, pg.meta);
2432
2433						// update pagination info
2434						pg.updateInfo.call();
2435					},
2436					addPaginateEvent: function(e) {
2437						// pagination event
2438						$(datatable).
2439							off(pg.paginateEvent).
2440							on(pg.paginateEvent, function(e, meta) {
2441								Plugin.spinnerCallback(true);
2442
2443								pg.pager = $(datatable.table).siblings('.m-datatable__pager');
2444								var pagerNumber = $(pg.pager).find('.m-datatable__pager-nav');
2445
2446								// set sync active page class
2447								$(pagerNumber).
2448									find('.m-datatable__pager-link--active').
2449									removeClass('m-datatable__pager-link--active');
2450								$(pagerNumber).
2451									find('.m-datatable__pager-link-number[data-page="' + meta.page + '"]').
2452									addClass('m-datatable__pager-link--active');
2453
2454								// set next and previous link page number
2455								$(pagerNumber).
2456									find('.m-datatable__pager-link--prev').
2457									attr('data-page', Math.max(meta.page - 1, 1));
2458								$(pagerNumber).
2459									find('.m-datatable__pager-link--next').
2460									attr('data-page', Math.min(meta.page + 1, meta.pages));
2461
2462								// current page input value sync
2463								$(pg.pager).each(function() {
2464									$(this).
2465										find('.m-pager-input[type="text"]').
2466										prop('value', meta.page);
2467								});
2468
2469								$(pg.pager).find('.m-datatable__pager-nav').show();
2470								if (meta.pages <= 1) {
2471									// hide pager if has 1 page
2472									$(pg.pager).find('.m-datatable__pager-nav').hide();
2473								}
2474
2475								// update datasource params
2476								Plugin.setDataSourceParam('pagination', {
2477									page: pg.meta.page,
2478									pages: pg.meta.pages,
2479									perpage: pg.meta.perpage,
2480									total: pg.meta.total,
2481								});
2482
2483								$(pg.pager).
2484									find('select.m-datatable__pager-size').
2485									val(meta.perpage).
2486									attr('data-selected', meta.perpage);
2487
2488								// clear active rows
2489								$(datatable.table).
2490									find('.m-checkbox > [type="checkbox"]').
2491									prop('checked', false);
2492								$(datatable.table).
2493									find('.m-datatable__row--active').
2494									removeClass('m-datatable__row--active');
2495
2496								pg.updateInfo.call();
2497								pg.pagingBreakpoint.call();
2498								// Plugin.resetScroll();
2499							});
2500					},
2501					updateInfo: function() {
2502						var start = Math.max(pg.meta.perpage * (pg.meta.page - 1) + 1, 1);
2503						var end = Math.min(start + pg.meta.perpage - 1, pg.meta.total);
2504						// page info update
2505						$(pg.pager).
2506							find('.m-datatable__pager-info').
2507							find('.m-datatable__pager-detail').
2508							html(Plugin.dataPlaceholder(
2509								Plugin.getOption('translate.toolbar.pagination.items.info'), {
2510									start: start,
2511									end: pg.meta.perpage === -1 ? pg.meta.total : end,
2512									pageSize: pg.meta.perpage === -1 ||
2513									pg.meta.perpage >= pg.meta.total
2514										? pg.meta.total
2515										: pg.meta.perpage,
2516									total: pg.meta.total,
2517								}));
2518					},
2519
2520					/**
2521					 * Update pagination layout breakpoint
2522					 */
2523					pagingBreakpoint: function() {
2524						// keep page links reference
2525						var pagerNumber = $(datatable.table).
2526							siblings('.m-datatable__pager').
2527							find('.m-datatable__pager-nav');
2528						if ($(pagerNumber).length === 0) return;
2529
2530						var currentPage = Plugin.getCurrentPage();
2531						var pagerInput = $(pagerNumber).
2532							find('.m-pager-input').
2533							closest('li');
2534
2535						// reset
2536						$(pagerNumber).find('li').show();
2537
2538						// pagination update
2539						$.each(Plugin.getOption('toolbar.items.pagination.pages'),
2540							function(mode, option) {
2541								if (mUtil.isInResponsiveRange(mode)) {
2542									switch (mode) {
2543										case 'desktop':
2544										case 'tablet':
2545											var end = Math.ceil(currentPage / option.pagesNumber) *
2546												option.pagesNumber;
2547											var start = end - option.pagesNumber;
2548											$(pagerInput).hide();
2549											pg.meta = Plugin.getDataSourceParam('pagination');
2550											pg.paginationUpdate();
2551											break;
2552
2553										case 'mobile':
2554											$(pagerInput).show();
2555											$(pagerNumber).
2556												find('.m-datatable__pager-link--more-prev').
2557												closest('li').
2558												hide();
2559											$(pagerNumber).
2560												find('.m-datatable__pager-link--more-next').
2561												closest('li').
2562												hide();
2563											$(pagerNumber).
2564												find('.m-datatable__pager-link-number').
2565												closest('li').
2566												hide();
2567											break;
2568									}
2569
2570									return false;
2571								}
2572							});
2573					},
2574
2575					/**
2576					 * Update pagination number and button display
2577					 */
2578					paginationUpdate: function() {
2579						var pager = $(datatable.table).
2580								siblings('.m-datatable__pager').
2581								find('.m-datatable__pager-nav'),
2582							pagerMorePrev = $(pager).
2583								find('.m-datatable__pager-link--more-prev'),
2584							pagerMoreNext = $(pager).
2585								find('.m-datatable__pager-link--more-next'),
2586							pagerFirst = $(pager).find('.m-datatable__pager-link--first'),
2587							pagerPrev = $(pager).find('.m-datatable__pager-link--prev'),
2588							pagerNext = $(pager).find('.m-datatable__pager-link--next'),
2589							pagerLast = $(pager).find('.m-datatable__pager-link--last');
2590
2591						// get visible page
2592						var pagerNumber = $(pager).find('.m-datatable__pager-link-number');
2593						// get page before of first visible
2594						var morePrevPage = Math.max($(pagerNumber).first().data('page') - 1,
2595							1);
2596						$(pagerMorePrev).each(function(i, prev) {
2597							$(prev).attr('data-page', morePrevPage);
2598						});
2599						// show/hide <li>
2600						if (morePrevPage === 1) {
2601							$(pagerMorePrev).parent().hide();
2602						} else {
2603							$(pagerMorePrev).parent().show();
2604						}
2605
2606						// get page after of last visible
2607						var moreNextPage = Math.min($(pagerNumber).last().data('page') + 1,
2608							pg.meta.pages);
2609						$(pagerMoreNext).each(function(i, prev) {
2610							$(pagerMoreNext).attr('data-page', moreNextPage).show();
2611						});
2612
2613						// show/hide <li>
2614						if (moreNextPage === pg.meta.pages
2615							// missing dot fix when last hidden page is one left
2616							&& moreNextPage === $(pagerNumber).last().data('page')) {
2617							$(pagerMoreNext).parent().hide();
2618						} else {
2619							$(pagerMoreNext).parent().show();
2620						}
2621
2622						// begin/end of pages
2623						if (pg.meta.page === 1) {
2624							$(pagerFirst).
2625								attr('disabled', true).
2626								addClass('m-datatable__pager-link--disabled');
2627							$(pagerPrev).
2628								attr('disabled', true).
2629								addClass('m-datatable__pager-link--disabled');
2630						} else {
2631							$(pagerFirst).
2632								removeAttr('disabled').
2633								removeClass('m-datatable__pager-link--disabled');
2634							$(pagerPrev).
2635								removeAttr('disabled').
2636								removeClass('m-datatable__pager-link--disabled');
2637						}
2638						if (pg.meta.page === pg.meta.pages) {
2639							$(pagerNext).
2640								attr('disabled', true).
2641								addClass('m-datatable__pager-link--disabled');
2642							$(pagerLast).
2643								attr('disabled', true).
2644								addClass('m-datatable__pager-link--disabled');
2645						} else {
2646							$(pagerNext).
2647								removeAttr('disabled').
2648								removeClass('m-datatable__pager-link--disabled');
2649							$(pagerLast).
2650								removeAttr('disabled').
2651								removeClass('m-datatable__pager-link--disabled');
2652						}
2653
2654						// display more buttons
2655						var nav = Plugin.getOption('toolbar.items.pagination.navigation');
2656						if (!nav.first) $(pagerFirst).remove();
2657						if (!nav.prev) $(pagerPrev).remove();
2658						if (!nav.next) $(pagerNext).remove();
2659						if (!nav.last) $(pagerLast).remove();
2660					},
2661				};
2662				pg.init(meta);
2663				return pg;
2664			},
2665
2666			/**
2667			 * Hide/show table cell defined by options[columns][i][responsive][visible/hidden]
2668			 */
2669			columnHide: function() {
2670				var screen = mUtil.getViewPort().width;
2671				// foreach columns setting
2672				$.each(options.columns, function(i, column) {
2673					if (typeof column.responsive !== 'undefined') {
2674						var field = column.field;
2675						var tds = $.grep($(datatable.table).find('.m-datatable__cell'), function(n, i) {
2676							return field === $(n).data('field');
2677						});
2678						if (mUtil.getBreakpoint(column.responsive.hidden) >= screen) {
2679							$(tds).hide();
2680						} else {
2681							$(tds).show();
2682						}
2683						if (mUtil.getBreakpoint(column.responsive.visible) <= screen) {
2684							$(tds).show();
2685						} else {
2686							$(tds).hide();
2687						}
2688					}
2689				});
2690			},
2691
2692			/**
2693			 * Setup sub datatable
2694			 */
2695			setupSubDatatable: function() {
2696				var subTableCallback = Plugin.getOption('detail.content');
2697				if (typeof subTableCallback !== 'function') return;
2698
2699				// subtable already exist
2700				if ($(datatable.table).find('.m-datatable__subtable').length > 0) return;
2701
2702				$(datatable.wrap).addClass('m-datatable--subtable');
2703
2704				options.columns[0]['subtable'] = true;
2705
2706				// toggle on open sub table
2707				var toggleSubTable = function(e) {
2708					e.preventDefault();
2709					// get parent row of this subtable
2710					var parentRow = $(this).closest('.m-datatable__row');
2711
2712					// get subtable row for sub table
2713					var subTableRow = $(parentRow).next('.m-datatable__row-subtable');
2714					if ($(subTableRow).length === 0) {
2715						// prepare DOM for sub table, each <tr> as parent and add <tr> as child table
2716						subTableRow = $('<tr/>').
2717							addClass('m-datatable__row-subtable m-datatable__row-loading').
2718							hide().
2719							append($('<td/>').
2720								addClass('m-datatable__subtable').
2721								attr('colspan', Plugin.getTotalColumns()));
2722						$(parentRow).after(subTableRow);
2723						// add class to even row
2724						if ($(parentRow).hasClass('m-datatable__row--even')) {
2725							$(subTableRow).addClass('m-datatable__row-subtable--even');
2726						}
2727					}
2728
2729					$(subTableRow).toggle();
2730
2731					var subTable = $(subTableRow).find('.m-datatable__subtable');
2732
2733					// get id from first column of parent row
2734					var primaryKey = $(this).
2735						closest('[data-field]:first-child').
2736						find('.m-datatable__toggle-subtable').
2737						data('value');
2738
2739					var icon = $(this).find('i').removeAttr('class');
2740
2741					// prevent duplicate datatable init
2742					if ($(parentRow).hasClass('m-datatable__row--subtable-expanded')) {
2743						$(icon).addClass(Plugin.getOption('layout.icons.rowDetail.collapse'));
2744						// remove expand class from parent row
2745						$(parentRow).removeClass('m-datatable__row--subtable-expanded');
2746						// trigger event on collapse
2747						$(datatable).trigger('m-datatable--on-collapse-subtable', [parentRow]);
2748					} else {
2749						// expand and run callback function
2750						$(icon).addClass(Plugin.getOption('layout.icons.rowDetail.expand'));
2751						// add expand class to parent row
2752						$(parentRow).addClass('m-datatable__row--subtable-expanded');
2753						// trigger event on expand
2754						$(datatable).trigger('m-datatable--on-expand-subtable', [parentRow]);
2755					}
2756
2757					// prevent duplicate datatable init
2758					if ($(subTable).find('.m-datatable').length === 0) {
2759						// get data by primary id
2760						$.map(datatable.dataSet, function(n, i) {
2761							if (primaryKey === n[options.columns[0].field]) {
2762								e.data = n;
2763								return true;
2764							}
2765							return false;
2766						});
2767
2768						// deprecated in v5.0.6
2769						e.detailCell = subTable;
2770
2771						e.parentRow = parentRow;
2772						e.subTable = subTable;
2773
2774						// run callback with event
2775						subTableCallback(e);
2776
2777						$(subTable).children('.m-datatable').on('m-datatable--on-init', function(e) {
2778							$(subTableRow).removeClass('m-datatable__row-loading');
2779						});
2780						if (Plugin.getOption('data.type') === 'local') {
2781							$(subTableRow).removeClass('m-datatable__row-loading');
2782						}
2783					}
2784				};
2785
2786				var columns = options.columns;
2787				$(datatable.tableBody).
2788					find('.m-datatable__row').
2789					each(function(tri, tr) {
2790						$(tr).find('.m-datatable__cell').each(function(tdi, td) {
2791							// get column settings by field
2792							var column = $.grep(columns, function(n, i) {
2793								return $(td).data('field') === n.field;
2794							})[0];
2795							if (typeof column !== 'undefined') {
2796								var value = $(td).text();
2797								// enable column subtable toggle
2798								if (typeof column.subtable !== 'undefined' && column.subtable) {
2799									// check if subtable toggle exist
2800									if ($(td).find('.m-datatable__toggle-subtable').length > 0) return;
2801									// append subtable toggle
2802									$(td).html($('<a/>').
2803										addClass('m-datatable__toggle-subtable').
2804										attr('href', '#').
2805										attr('data-value', value).
2806										attr('title', Plugin.getOption('detail.title')).
2807										on('click', toggleSubTable).
2808										append($('<i/>').
2809											css('width', $(td).data('width')).
2810											addClass(Plugin.getOption('layout.icons.rowDetail.collapse'))));
2811								}
2812							}
2813						});
2814					});
2815
2816				// $(datatable.tableHead).find('.m-datatable__row').first()
2817			},
2818
2819			/**
2820			 * Datasource mapping callback
2821			 */
2822			dataMapCallback: function(raw) {
2823				// static dataset array
2824				var dataSet = raw;
2825				// dataset mapping callback
2826				if (typeof Plugin.getOption('data.source.read.map') === 'function') {
2827					return Plugin.getOption('data.source.read.map')(raw);
2828				} else {
2829					// default data mapping fallback
2830					if (typeof raw.data !== 'undefined') {
2831						dataSet = raw.data;
2832					}
2833				}
2834				return dataSet;
2835			},
2836
2837			isSpinning: false,
2838			/**
2839			 * BlockUI spinner callback
2840			 * @param block
2841			 */
2842			spinnerCallback: function(block) {
2843				if (block) {
2844					if (!Plugin.isSpinning) {
2845						// get spinner options
2846						var spinnerOptions = Plugin.getOption('layout.spinner');
2847						if (spinnerOptions.message === true) {
2848							// use default spinner message from translation
2849							spinnerOptions.message = Plugin.getOption('translate.records.processing');
2850						}
2851						Plugin.isSpinning = true;
2852						if (typeof mApp !== 'undefined') {
2853                            mApp.blockPage()
2854//							mApp.block(datatable, spinnerOptions);
2855//							mApp.block("#advancedSearch", spinnerOptions);
2856						}
2857					}
2858				} else {
2859					Plugin.isSpinning = false;
2860					if (typeof mApp !== 'undefined') {
2861						mApp.unblockPage(datatable);
2862//	        			mApp.unblock("#advancedSearch");
2863					}
2864				}
2865			},
2866
2867			/**
2868			 * Default sort callback function
2869			 * @param data
2870			 * @param sort
2871			 * @param column
2872			 * @returns {*|Array.<T>|{sort, field}|{asc, desc}}
2873			 */
2874			sortCallback: function(data, sort, column) {
2875				var type = column['type'] || 'string';
2876				var format = column['format'] || '';
2877				var field = column['field'];
2878
2879				if (type === 'date' && typeof moment === 'undefined') {
2880					throw new Error('Moment.js is required.');
2881				}
2882
2883				return $(data).sort(function(a, b) {
2884					var aField = a[field];
2885					var bField = b[field];
2886
2887					switch (type) {
2888						case 'date':
2889							var diff = moment(aField, format).diff(moment(bField, format));
2890							if (sort === 'asc') {
2891								return diff > 0 ? 1 : diff < 0 ? -1 : 0;
2892							} else {
2893								return diff < 0 ? 1 : diff > 0 ? -1 : 0;
2894							}
2895							break;
2896
2897						case 'number':
2898							if (isNaN(parseFloat(aField)) && aField != null) {
2899								aField = Number(aField.replace(/[^0-9\.-]+/g, ''));
2900							}
2901							if (isNaN(parseFloat(bField)) && bField != null) {
2902								bField = Number(bField.replace(/[^0-9\.-]+/g, ''));
2903							}
2904							aField = parseFloat(aField);
2905							bField = parseFloat(bField);
2906							if (sort === 'asc') {
2907								return aField > bField ? 1 : aField < bField ? -1 : 0;
2908							} else {
2909								return aField < bField ? 1 : aField > bField ? -1 : 0;
2910							}
2911							break;
2912
2913						case 'string':
2914						default:
2915							if (sort === 'asc') {
2916								return aField > bField ? 1 : aField < bField ? -1 : 0;
2917							} else {
2918								return aField < bField ? 1 : aField > bField ? -1 : 0;
2919							}
2920							break;
2921					}
2922				});
2923			},
2924
2925			/**
2926			 * Custom debug log
2927			 * @param text
2928			 * @param obj
2929			 */
2930			log: function(text, obj) {
2931				if (typeof obj === 'undefined') obj = '';
2932				if (datatable.debug) {
2933					console.log(text, obj);
2934				}
2935			},
2936
2937			/**
2938			 * Auto hide columnds overflow in row
2939			 */
2940			autoHide: function() {
2941				$(datatable.table).find('.m-datatable__cell').show();
2942				$(datatable.tableBody).each(function() {
2943					while ($(this)[0].offsetWidth < $(this)[0].scrollWidth) {
2944						$(this).find('.m-datatable__row').each(function(i) {
2945							var cell = $(this).find('.m-datatable__cell').not(':hidden').last();
2946							$(cell).hide();
2947							if (i === 0) {
2948								$(datatable.tableHead).find('.m-datatable__cell').eq($(cell).index()).hide();
2949								$(datatable.tableFoot).find('.m-datatable__cell').eq($(cell).index()).hide();
2950							}
2951						});
2952					}
2953				});
2954
2955				var toggleHiddenColumns = function(e) {
2956					e.preventDefault();
2957
2958					var row = $(this).closest('.m-datatable__row');
2959					var detailRow = $(row).next();
2960
2961					if (!$(detailRow).hasClass('m-datatable__row-detail')) {
2962						$(this).find('i').
2963							removeClass(Plugin.getOption('layout.icons.rowDetail.collapse')).
2964							addClass(Plugin.getOption('layout.icons.rowDetail.expand'));
2965
2966						var hidden = $(row).find('.m-datatable__cell:hidden').clone().show();
2967
2968						detailRow = $('<tr/>').addClass('m-datatable__row-detail').insertAfter(row);
2969						var detailRowTd = $('<td/>').
2970							addClass('m-datatable__detail').
2971							attr('colspan', Plugin.getTotalColumns()).appendTo(detailRow);
2972
2973						var detailSubTable = $('<table/>');
2974						$(hidden).each(function() {
2975							var field = $(this).data('field');
2976							var column = $.grep(options.columns, function(n, i) {
2977								return field === n.field;
2978							})[0];
2979							$(detailSubTable).
2980								append($('<tr class="m-datatable__row"></tr>').
2981									append($('<td class="m-datatable__cell"></td>').
2982										append($('<span/>').
2983											css('width', Plugin.offset).
2984											append(column.title))).
2985									append(this));
2986						});
2987						$(detailRowTd).append(detailSubTable);
2988
2989					} else {
2990						$(this).find('i').
2991							removeClass(Plugin.getOption('layout.icons.rowDetail.expand')).
2992							addClass(Plugin.getOption('layout.icons.rowDetail.collapse'));
2993						$(detailRow).remove();
2994					}
2995				};
2996
2997				// toggle show hidden columns
2998				$(datatable.tableBody).find('.m-datatable__row').each(function() {
2999					$(this).prepend($('<td/>').addClass('m-datatable__cell m-datatable__toggle--detail').
3000						append($('<a/>').
3001							addClass('m-datatable__toggle-detail').
3002							attr('href', '#').
3003							on('click', toggleHiddenColumns).
3004							append($('<i/>').
3005								css('width', '21px').// maintain width for both icons expand and collapse
3006								addClass(Plugin.getOption('layout.icons.rowDetail.collapse')))));
3007
3008					// check if subtable toggle exist
3009					if ($(datatable.tableHead).find('.m-datatable__toggle-detail').length === 0) {
3010						$(datatable.tableHead).
3011							find('.m-datatable__row').
3012							first().
3013							prepend('<th class="m-datatable__cell m-datatable__toggle-detail"><span style="width: 21px"></span></th>');
3014						$(datatable.tableFoot).
3015							find('.m-datatable__row').
3016							first().
3017							prepend('<th class="m-datatable__cell m-datatable__toggle-detail"><span style="width: 21px"></span></th>');
3018					} else {
3019						$(datatable.tableHead).find('.m-datatable__toggle-detail').find('span').css('width', '21px');
3020					}
3021				});
3022			},
3023
3024			/**
3025			 * todo; implement hover column
3026			 */
3027			hoverColumn: function() {
3028				$(datatable.tableBody).on('mouseenter', '.m-datatable__cell', function() {
3029					var colIdx = $(Plugin.cell(this).nodes()).index();
3030					$(Plugin.cells().nodes()).removeClass('m-datatable__cell--hover');
3031					$(Plugin.column(colIdx).nodes()).addClass('m-datatable__cell--hover');
3032				});
3033			},
3034
3035			/**
3036			 * To enable auto columns features for remote data source
3037			 */
3038			setAutoColumns: function() {
3039				if (Plugin.getOption('data.autoColumns')) {
3040					$.each(datatable.dataSet[0], function(k, v) {
3041						var found = $.grep(options.columns, function(n, i) {
3042							return k === n.field;
3043						});
3044						if (found.length === 0) {
3045							options.columns.push({field: k, title: k});
3046						}
3047					});
3048					$(datatable.tableHead).find('.m-datatable__row').remove();
3049					Plugin.setHeadTitle();
3050					if (Plugin.getOption('layout.footer')) {
3051						$(datatable.tableFoot).find('.m-datatable__row').remove();
3052						Plugin.setHeadTitle(datatable.tableFoot);
3053					}
3054				}
3055			},
3056
3057			/********************
3058			 ** HELPERS
3059			 ********************/
3060
3061			/**
3062			 * Check if table is a locked colums table
3063			 */
3064			isLocked: function() {
3065				return $(datatable.wrap).hasClass('m-datatable--lock') || false;
3066			},
3067
3068			/**
3069			 * Insert html into table content, take count mCustomScrollbar DOM to prevent replace
3070			 * @param html
3071			 * @param tablePart
3072			 */
3073			replaceTableContent: function(html, tablePart) {
3074				if (typeof tablePart === 'undefined') tablePart = datatable.tableBody;
3075				if ($(tablePart).hasClass('mCustomScrollbar')) {
3076					$(tablePart).find('.mCSB_container').html(html);
3077				} else {
3078					$(tablePart).html(html);
3079				}
3080			},
3081
3082			/**
3083			 * Get total extra space of an element for width calculation, including padding, margin, border
3084			 * @param element
3085			 * @returns {number}
3086			 */
3087			getExtraSpace: function(element) {
3088				var padding = parseInt($(element).css('paddingRight')) +
3089					parseInt($(element).css('paddingLeft'));
3090				var margin = parseInt($(element).css('marginRight')) +
3091					parseInt($(element).css('marginLeft'));
3092				var border = Math.ceil(
3093					$(element).css('border-right-width').replace('px', ''));
3094				return padding + margin + border;
3095			},
3096
3097			/**
3098			 * Insert data of array into {{ }} template placeholder
3099			 * @param template
3100			 * @param data
3101			 * @returns {*}
3102			 */
3103			dataPlaceholder: function(template, data) {
3104				var result = template;
3105				$.each(data, function(key, val) {
3106					result = result.replace('{{' + key + '}}', val);
3107				});
3108				return result;
3109			},
3110
3111			/**
3112			 * Get table unique ID
3113			 * Note: table unique change each time refreshed
3114			 * @param suffix
3115			 * @returns {*}
3116			 */
3117			getTableId: function(suffix) {
3118				if (typeof suffix === 'undefined') suffix = '';
3119				var id = $(datatable).attr('id');
3120				if (typeof id === 'undefined') {
3121					id = $(datatable).attr('class').split(' ')[0];
3122				}
3123				return id + suffix;
3124			},
3125
3126			/**
3127			 * Get table prefix with depth number
3128			 */
3129			getTablePrefix: function(suffix) {
3130				if (typeof suffix !== 'undefined') suffix = '-' + suffix;
3131				return Plugin.getTableId() + '-' + Plugin.getDepth() + suffix;
3132			},
3133
3134			/**
3135			 * Get current table depth of sub table
3136			 * @returns {number}
3137			 */
3138			getDepth: function() {
3139				var depth = 0;
3140				var table = datatable.table;
3141				do {
3142					table = $(table).parents('.m-datatable__table');
3143					depth++;
3144				} while ($(table).length > 0);
3145				return depth;
3146			},
3147
3148			/**
3149			 * Keep state item
3150			 * @param key
3151			 * @param value
3152			 */
3153			stateKeep: function(key, value) {
3154				key = Plugin.getTablePrefix(key);
3155				if (Plugin.getOption('data.saveState') === false) return;
3156				if (Plugin.getOption('data.saveState.webstorage') && localStorage) {
3157					localStorage.setItem(key, JSON.stringify(value));
3158				}
3159				if (Plugin.getOption('data.saveState.cookie')) {
3160					Cookies.set(key, JSON.stringify(value));
3161				}
3162			},
3163
3164			/**
3165			 * Get state item
3166			 * @param key
3167			 * @param defValue
3168			 */
3169			stateGet: function(key, defValue) {
3170				key = Plugin.getTablePrefix(key);
3171				if (Plugin.getOption('data.saveState') === false) return;
3172				var value = null;
3173				if (Plugin.getOption('data.saveState.webstorage') && localStorage) {
3174					value = localStorage.getItem(key);
3175				} else {
3176					value = Cookies.get(key);
3177				}
3178				if (typeof value !== 'undefined' && value !== null) {
3179					return JSON.parse(value);
3180				}
3181			},
3182
3183			/**
3184			 * Update data in state without clear existing
3185			 * @param key
3186			 * @param value
3187			 */
3188			stateUpdate: function(key, value) {
3189				var ori = Plugin.stateGet(key);
3190				if (typeof ori === 'undefined' || ori === null) ori = {};
3191				Plugin.stateKeep(key, $.extend({}, ori, value));
3192			},
3193
3194			/**
3195			 * Remove state item
3196			 * @param key
3197			 */
3198			stateRemove: function(key) {
3199				key = Plugin.getTablePrefix(key);
3200				if (localStorage) {
3201					localStorage.removeItem(key);
3202				}
3203				Cookies.remove(key);
3204			},
3205
3206			/**
3207			 * Get total columns.
3208			 */
3209			getTotalColumns: function(tablePart) {
3210				if (typeof tablePart === 'undefined') tablePart = datatable.tableBody;
3211				return $(tablePart).
3212					find('.m-datatable__row').
3213					first().
3214					find('.m-datatable__cell').length;
3215			},
3216
3217			/**
3218			 * Get table row. Useful to get row when current table is in lock mode.
3219			 * Can be used for both lock and normal table mode.
3220			 * By default, returning result will be in a list of <td>.
3221			 * @param tablePart
3222			 * @param row 1-based index
3223			 * @param tdOnly Optional. Default true
3224			 * @returns {*}
3225			 */
3226			getOneRow: function(tablePart, row, tdOnly) {
3227				if (typeof tdOnly === 'undefined') tdOnly = true;
3228				// get list of <tr>
3229				var result = $(tablePart).find('.m-datatable__row:not(.m-datatable__row-detail):nth-child(' + row + ')');
3230				if (tdOnly) {
3231					// get list of <td> or <th>
3232					result = result.find('.m-datatable__cell');
3233				}
3234				return result;
3235			},
3236
3237			/**
3238			 * Check if element has vertical overflow
3239			 * @param element
3240			 * @returns {boolean}
3241			 */
3242			hasOverflowY: function(element) {
3243				var children = $(element).find('.m-datatable__row');
3244				var maxHeight = 0;
3245
3246				if (children.length > 0) {
3247					$(children).each(function(tdi, td) {
3248						maxHeight += Math.floor($(td).innerHeight());
3249					});
3250
3251					return maxHeight > $(element).innerHeight();
3252				}
3253
3254				return false;
3255			},
3256
3257			/**
3258			 * Sort table row at HTML level by column index.
3259			 * todo; Not in use.
3260			 * @param header Header sort clicked
3261			 * @param sort asc|desc. Optional. Default asc
3262			 * @param int Boolean. Optional. Comparison value parse to integer. Default false
3263			 */
3264			sortColumn: function(header, sort, int) {
3265				if (typeof sort === 'undefined') sort = 'asc'; // desc
3266				if (typeof int === 'undefined') int = false;
3267
3268				var column = $(header).index();
3269				var rows = $(datatable.tableBody).find('.m-datatable__row');
3270				var hIndex = $(header).closest('.m-datatable__lock').index();
3271				if (hIndex !== -1) {
3272					rows = $(datatable.tableBody).
3273						find('.m-datatable__lock:nth-child(' + (hIndex + 1) + ')').
3274						find('.m-datatable__row');
3275				}
3276
3277				var container = $(rows).parent();
3278				$(rows).sort(function(a, b) {
3279					var tda = $(a).find('td:nth-child(' + column + ')').text();
3280					var tdb = $(b).find('td:nth-child(' + column + ')').text();
3281
3282					if (int) {
3283						// useful for integer type sorting
3284						tda = parseInt(tda);
3285						tdb = parseInt(tdb);
3286					}
3287
3288					if (sort === 'asc') {
3289						return tda > tdb ? 1 : tda < tdb ? -1 : 0;
3290					} else {
3291						return tda < tdb ? 1 : tda > tdb ? -1 : 0;
3292					}
3293				}).appendTo(container);
3294			},
3295
3296			/**
3297			 * Perform sort remote and local
3298			 */
3299			sorting: function() {
3300				var sortObj = {
3301					init: function() {
3302						if (options.sortable) {
3303							$(datatable.tableHead).
3304								find('.m-datatable__cell:not(.m-datatable__cell--check)').
3305								addClass('m-datatable__cell--sort').
3306								off('click').
3307								on('click', sortObj.sortClick);
3308							// first init
3309							sortObj.setIcon();
3310						}
3311					},
3312					setIcon: function() {
3313						var meta = Plugin.getDataSourceParam('sort');
3314						if ($.isEmptyObject(meta)) return;
3315
3316						// sort icon beside column header
3317						var td = $(datatable.tableHead).
3318							find('.m-datatable__cell[data-field="' + meta.field + '"]').
3319							attr('data-sort', meta.sort);
3320						var sorting = $(td).find('span');
3321						var icon = $(sorting).find('i');
3322
3323						var icons = Plugin.getOption('layout.icons.sort');
3324						// update sort icon; desc & asc
3325						if ($(icon).length > 0) {
3326							$(icon).removeAttr('class').addClass(icons[meta.sort]);
3327						} else {
3328							$(sorting).append($('<i/>').addClass(icons[meta.sort]));
3329						}
3330					},
3331					sortClick: function(e) {
3332						var meta = Plugin.getDataSourceParam('sort');
3333						var field = $(this).data('field');
3334						var column = Plugin.getColumnByField(field);
3335						// sort is disabled for this column
3336						if (typeof column.sortable !== 'undefined' &&
3337							column.sortable === false) return;
3338
3339						$(datatable.tableHead).
3340							find('.m-datatable__cell > span > i').
3341							remove();
3342
3343						if (options.sortable) {
3344							Plugin.spinnerCallback(true);
3345
3346							var sort = 'desc';
3347							if (Plugin.getObject('field', meta) === field) {
3348								sort = Plugin.getObject('sort', meta);
3349							}
3350
3351							// toggle sort
3352							sort = typeof sort === 'undefined' || sort === 'desc'
3353								? 'asc'
3354								: 'desc';
3355
3356							// update field and sort params
3357							meta = {field: field, sort: sort};
3358							Plugin.setDataSourceParam('sort', meta);
3359
3360							sortObj.setIcon();
3361
3362							setTimeout(function() {
3363								Plugin.dataRender('sort');
3364								$(datatable).trigger('m-datatable--on-sort', meta);
3365							}, 300);
3366						}
3367					},
3368				};
3369				sortObj.init();
3370			},
3371
3372			/**
3373			 * Update JSON data list linked with sort, filter and pagination.
3374			 * Call this method, before using dataSet variable.
3375			 * @returns {*|null}
3376			 */
3377			localDataUpdate: function() {
3378				// todo; fix twice execution
3379				var params = Plugin.getDataSourceParam();
3380				if (typeof datatable.originalDataSet === 'undefined') {
3381					datatable.originalDataSet = datatable.dataSet;
3382				}
3383
3384				var field = Plugin.getObject('sort.field', params);
3385				var sort = Plugin.getObject('sort.sort', params);
3386				var column = Plugin.getColumnByField(field);
3387				if (typeof column !== 'undefined' && Plugin.getOption('data.serverSorting') !== true) {
3388					if (typeof column.sortCallback === 'function') {
3389						datatable.dataSet = column.sortCallback(datatable.originalDataSet, sort, column);
3390					} else {
3391						datatable.dataSet = Plugin.sortCallback(datatable.originalDataSet, sort, column);
3392					}
3393				} else {
3394					datatable.dataSet = datatable.originalDataSet;
3395				}
3396
3397				// if server filter enable, don't pass local filter
3398				if (typeof params.query === 'object' && !Plugin.getOption('data.serverFiltering')) {
3399					params.query = params.query || {};
3400
3401					var search = $(Plugin.getOption('search.input')).val();
3402					if (typeof search !== 'undefined' && search !== '') {
3403						search = search.toLowerCase();
3404						datatable.dataSet = $.grep(datatable.dataSet, function(obj) {
3405							for (var field in obj) {
3406								if (!obj.hasOwnProperty(field)) continue;
3407								if (typeof obj[field] === 'string') {
3408									if (obj[field].toLowerCase().indexOf(search) > -1) {
3409										return true;
3410									}
3411								}
3412							}
3413							return false;
3414						});
3415						// remove generalSearch as we don't need this for next columns filter
3416						delete params.query[Plugin.getGeneralSearchKey()];
3417					}
3418
3419					// remove empty element from array
3420					$.each(params.query, function(k, v) {
3421						if (v === '') {
3422							delete params.query[k];
3423						}
3424					});
3425
3426					// filter array by query
3427					datatable.dataSet = Plugin.filterArray(datatable.dataSet, params.query);
3428
3429					// reset array index
3430					datatable.dataSet = datatable.dataSet.filter(function() {
3431						return true;
3432					});
3433				}
3434
3435				return datatable.dataSet;
3436			},
3437
3438			/**
3439			 * Utility helper to filter array by object pair of {key:value}
3440			 * @param list
3441			 * @param args
3442			 * @param operator
3443			 * @returns {*}
3444			 */
3445			filterArray: function(list, args, operator) {
3446				if (typeof list !== 'object') {
3447					return [];
3448				}
3449
3450				if (typeof operator === 'undefined') operator = 'AND';
3451
3452				if (typeof args !== 'object') {
3453					return list;
3454				}
3455
3456				operator = operator.toUpperCase();
3457
3458				if ($.inArray(operator, ['AND', 'OR', 'NOT']) === -1) {
3459					return [];
3460				}
3461
3462				var count = Object.keys(args).length;
3463				var filtered = [];
3464
3465				$.each(list, function(key, obj) {
3466					var to_match = obj;
3467
3468					var matched = 0;
3469					$.each(args, function(m_key, m_value) {
3470						if (to_match.hasOwnProperty(m_key) && m_value == to_match[m_key].toLowerCase()) {
3471							matched++;
3472						}
3473					});
3474
3475					if (('AND' == operator && matched == count) ||
3476						('OR' == operator && matched > 0) ||
3477						('NOT' == operator && 0 == matched)) {
3478						filtered[key] = obj;
3479					}
3480				});
3481
3482				list = filtered;
3483
3484				return list;
3485			},
3486
3487			/**
3488			 * Reset lock column scroll to 0 when resize
3489			 */
3490			resetScroll: function() {
3491				if (typeof options.detail === 'undefined' && Plugin.getDepth() === 1) {
3492					$(datatable.table).find('.m-datatable__row').css('left', 0);
3493					$(datatable.table).find('.m-datatable__lock').css('top', 0);
3494					$(datatable.tableBody).scrollTop(0);
3495				}
3496			},
3497
3498			/**
3499			 * Get column options by field
3500			 * @param field
3501			 * @returns {boolean}
3502			 */
3503			getColumnByField: function(field) {
3504				if (typeof field === 'undefined') return;
3505				var result;
3506				$.each(options.columns, function(i, column) {
3507					if (field === column.field) {
3508						result = column;
3509						return false;
3510					}
3511				});
3512				return result;
3513			},
3514
3515			/**
3516			 * Get default sort column
3517			 */
3518			getDefaultSortColumn: function() {
3519				var result;
3520				$.each(options.columns, function(i, column) {
3521					if (typeof column.sortable !== 'undefined'
3522						&& $.inArray(column.sortable, ['asc', 'desc']) !== -1) {
3523						result = {sort: column.sortable, field: column.field};
3524						return false;
3525					}
3526				});
3527				return result;
3528			},
3529
3530			/**
3531			 * Helper to get element dimensions, when the element is hidden
3532			 * @param element
3533			 * @param includeMargin
3534			 * @returns {{width: number, height: number, innerWidth: number, innerHeight: number, outerWidth: number, outerHeight: number}}
3535			 */
3536			getHiddenDimensions: function(element, includeMargin) {
3537				var props = {
3538						position: 'absolute',
3539						visibility: 'hidden',
3540						display: 'block',
3541					},
3542					dim = {
3543						width: 0,
3544						height: 0,
3545						innerWidth: 0,
3546						innerHeight: 0,
3547						outerWidth: 0,
3548						outerHeight: 0,
3549					},
3550					hiddenParents = $(element).parents().addBack().not(':visible');
3551				includeMargin = (typeof includeMargin === 'boolean')
3552					? includeMargin
3553					: false;
3554
3555				var oldProps = [];
3556				hiddenParents.each(function() {
3557					var old = {};
3558
3559					for (var name in props) {
3560						old[name] = this.style[name];
3561						this.style[name] = props[name];
3562					}
3563
3564					oldProps.push(old);
3565				});
3566
3567				dim.width = $(element).width();
3568				dim.outerWidth = $(element).outerWidth(includeMargin);
3569				dim.innerWidth = $(element).innerWidth();
3570				dim.height = $(element).height();
3571				dim.innerHeight = $(element).innerHeight();
3572				dim.outerHeight = $(element).outerHeight(includeMargin);
3573
3574				hiddenParents.each(function(i) {
3575					var old = oldProps[i];
3576					for (var name in props) {
3577						this.style[name] = old[name];
3578					}
3579				});
3580
3581				return dim;
3582			},
3583
3584			getGeneralSearchKey: function() {
3585				var searchInput = $(Plugin.getOption('search.input'));
3586				return $(searchInput).prop('name') || $(searchInput).prop('id');
3587			},
3588
3589			/**
3590			 * Get value by dot notation path string and to prevent undefined errors
3591			 * @param path String Dot notation path in string
3592			 * @param object Object to iterate
3593			 * @returns {*}
3594			 */
3595			getObject: function(path, object) {
3596				return path.split('.').reduce(function(obj, i) {
3597					return obj !== null && typeof obj[i] !== 'undefined' ? obj[i] : null;
3598				}, object);
3599			},
3600
3601			/**
3602			 * Extend object
3603			 * @param obj
3604			 * @param path
3605			 * @param value
3606			 * @returns {*}
3607			 */
3608			extendObj: function(obj, path, value) {
3609				var levels = path.split('.'),
3610					i = 0;
3611
3612				function createLevel(child) {
3613					var name = levels[i++];
3614					if (typeof child[name] !== 'undefined' && child[name] !== null) {
3615						if (typeof child[name] !== 'object' &&
3616							typeof child[name] !== 'function') {
3617							child[name] = {};
3618						}
3619					} else {
3620						child[name] = {};
3621					}
3622					if (i === levels.length) {
3623						child[name] = value;
3624					} else {
3625						createLevel(child[name]);
3626					}
3627				}
3628
3629				createLevel(obj);
3630				return obj;
3631			},
3632
3633			/********************
3634			 ** PUBLIC API METHODS
3635			 ********************/
3636
3637			// delay timer
3638			timer: 0,
3639
3640			/**
3641			 * Redraw datatable by recalculating its DOM elements, etc.
3642			 * @returns {jQuery}
3643			 */
3644			redraw: function() {
3645				Plugin.adjustCellsWidth.call();
3646				if (Plugin.isLocked()) {
3647					Plugin.adjustCellsHeight.call();
3648				}
3649				Plugin.adjustLockContainer.call();
3650				Plugin.initHeight.call();
3651				return datatable;
3652			},
3653
3654			/**
3655			 * Shortcode to reload
3656			 * @returns {jQuery}
3657			 */
3658			load: function() {
3659				Plugin.reload();
3660				return datatable;
3661			},
3662
3663			/**
3664			 * Datasource reload
3665			 * @returns {jQuery}
3666			 */
3667			reload: function() {
3668				var delay = (function() {
3669					return function(callback, ms) {
3670						clearTimeout(Plugin.timer);
3671						Plugin.timer = setTimeout(callback, ms);
3672					};
3673				})();
3674				delay(function() {
3675					// local only. remote pagination will skip this block
3676					if (!options.data.serverFiltering) {
3677						Plugin.localDataUpdate();
3678					}
3679					Plugin.dataRender();
3680					$(datatable).trigger('m-datatable--on-reloaded');
3681				}, Plugin.getOption('search.delay'));
3682				return datatable;
3683			},
3684
3685			/**
3686			 * Get record by record ID
3687			 * @param id
3688			 * @returns {jQuery}
3689			 */
3690			getRecord: function(id) {
3691				if (typeof datatable.tableBody === 'undefined') datatable.tableBody = $(datatable.table).children('tbody');
3692				$(datatable.tableBody).find('.m-datatable__cell:first-child').each(function(i, cell) {
3693					if (id == $(cell).text()) {
3694						var rowNumber = $(cell).closest('.m-datatable__row').index() + 1;
3695						datatable.API.record = datatable.API.value = Plugin.getOneRow(datatable.tableBody, rowNumber);
3696						return datatable;
3697					}
3698				});
3699				return datatable;
3700			},
3701
3702			/**
3703			 * @deprecated in v5.0.6
3704			 * Get column of current record ID
3705			 * @param columnName
3706			 * @returns {jQuery}
3707			 */
3708			getColumn: function(columnName) {
3709				Plugin.setSelectedRecords();
3710				datatable.API.value = $(datatable.API.record).find('[data-field="' + columnName + '"]');
3711				return datatable;
3712			},
3713
3714			/**
3715			 * Destroy datatable to original DOM state before datatable was initialized
3716			 * @returns {jQuery}
3717			 */
3718			destroy: function() {
3719				$(datatable).parent().find('.m-datatable__pager').remove();
3720				var initialDatatable = $(datatable.initialDatatable).addClass('m-datatable--destroyed').show();
3721				$(datatable).replaceWith(initialDatatable);
3722				datatable = initialDatatable;
3723				$(datatable).trigger('m-datatable--on-destroy');
3724				Plugin.isInit = false;
3725				initialDatatable = null;
3726				return initialDatatable;
3727			},
3728
3729			/**
3730			 * Sort by column field
3731			 * @param field
3732			 * @param sort
3733			 */
3734			sort: function(field, sort) {
3735				if (typeof sort === 'undefined') sort = 'asc';
3736				$(datatable.tableHead).
3737					find('.m-datatable__cell[data-field="' + field + '"]').
3738					trigger('click');
3739				return datatable;
3740			},
3741
3742			/**
3743			 * @deprecated in v5.0.6
3744			 * Get current selected column value
3745			 * @returns {jQuery}
3746			 */
3747			getValue: function() {
3748				return $(datatable.API.value).text();
3749			},
3750
3751			/**
3752			 * Set checkbox active
3753			 * @param cell JQuery selector or checkbox ID
3754			 */
3755			setActive: function(cell) {
3756				if (typeof cell === 'string') {
3757					// set by checkbox id
3758					cell = $(datatable.tableBody).
3759						find('.m-checkbox--single > [type="checkbox"][value="' + cell + '"]');
3760				}
3761
3762				$(cell).prop('checked', true);
3763
3764				// normal table
3765				var row = $(cell).
3766					closest('.m-datatable__row').
3767					addClass('m-datatable__row--active');
3768
3769				var index = $(row).index() + 1;
3770				// lock table
3771				$(row).
3772					closest('.m-datatable__lock').
3773					parent().
3774					find('.m-datatable__row:nth-child(' + index + ')').
3775					addClass('m-datatable__row--active');
3776
3777				var ids = [];
3778				$(row).each(function(i, td) {
3779					var id = $(td).find('.m-checkbox--single:not(.m-checkbox--all) > [type="checkbox"]').val();
3780					if (typeof id !== 'undefined') {
3781						ids.push(id);
3782					}
3783				});
3784
3785				$(datatable).trigger('m-datatable--on-check', [ids]);
3786			},
3787
3788			/**
3789			 * Set checkbox inactive
3790			 * @param cell JQuery selector or checkbox ID
3791			 */
3792			setInactive: function(cell) {
3793				if (typeof cell === 'string') {
3794					// set by checkbox id
3795					cell = $(datatable.tableBody).
3796						find('.m-checkbox--single > [type="checkbox"][value="' + cell + '"]');
3797				}
3798
3799				$(cell).prop('checked', false);
3800
3801				// normal table
3802				var row = $(cell).
3803					closest('.m-datatable__row').
3804					removeClass('m-datatable__row--active');
3805				var index = $(row).index() + 1;
3806
3807				// lock table
3808				$(row).
3809					closest('.m-datatable__lock').
3810					parent().
3811					find('.m-datatable__row:nth-child(' + index + ')').
3812					removeClass('m-datatable__row--active');
3813
3814				var ids = [];
3815				$(row).each(function(i, td) {
3816					var id = $(td).find('.m-checkbox--single:not(.m-checkbox--all) > [type="checkbox"]').val();
3817					if (typeof id !== 'undefined') {
3818						ids.push(id);
3819					}
3820				});
3821
3822				$(datatable).trigger('m-datatable--on-uncheck', [ids]);
3823			},
3824
3825			/**
3826			 * Set all checkboxes active or inactive
3827			 * @param active
3828			 */
3829			setActiveAll: function(active) {
3830				// todo; check if child table also will set active?
3831				var checkboxes = $(datatable.table).find('.m-datatable__body .m-datatable__row').
3832					find('.m-datatable__cell .m-checkbox [type="checkbox"]');
3833				if (active) {
3834					Plugin.setActive(checkboxes);
3835				} else {
3836					Plugin.setInactive(checkboxes);
3837				}
3838			},
3839
3840			/**
3841			 * @deprecated in v5.0.6
3842			 * Get selected rows which are active
3843			 * @returns {jQuery}
3844			 */
3845			setSelectedRecords: function() {
3846				datatable.API.record = $(datatable.tableBody).find('.m-datatable__row--active');
3847				return datatable;
3848			},
3849
3850			/**
3851			 * Get selected records
3852			 * @returns {null}
3853			 */
3854			getSelectedRecords: function() {
3855				// support old method
3856				Plugin.setSelectedRecords();
3857				datatable.API.record = datatable.rows('.m-datatable__row--active').nodes();
3858				return datatable.API.record;
3859			},
3860
3861			/**
3862			 * Get options by dots notation path
3863			 * @param path String Dot notation path in string
3864			 * @returns {*}
3865			 */
3866			getOption: function(path) {
3867				return Plugin.getObject(path, options);
3868			},
3869
3870			/**
3871			 * Set global options nodes by dots notation path
3872			 * @param path
3873			 * @param object
3874			 */
3875			setOption: function(path, object) {
3876				options = Plugin.extendObj(options, path, object);
3877			},
3878
3879			/**
3880			 * Search filter for local & remote
3881			 * @param value
3882			 * @param columns. Optional list of columns to be filtered.
3883			 */
3884			search: function(value, columns) {
3885				if (typeof columns !== 'undefined') columns = $.makeArray(columns);
3886				var delay = (function() {
3887					return function(callback, ms) {
3888						clearTimeout(Plugin.timer);
3889						Plugin.timer = setTimeout(callback, ms);
3890					};
3891				})();
3892
3893				delay(function() {
3894					// get query parameters
3895					var query = Plugin.getDataSourceQuery();
3896
3897					// search not by columns
3898					if (typeof columns === 'undefined' && typeof value !== 'undefined') {
3899						var key = Plugin.getGeneralSearchKey();
3900						query[key] = value;
3901					}
3902
3903					// search by columns, support multiple columns
3904					if (typeof columns === 'object') {
3905						$.each(columns, function(k, column) {
3906							query[column] = value;
3907						});
3908						// remove empty element from arrays
3909						$.each(query, function(k, v) {
3910							if (v === '') {
3911								delete query[k];
3912							}
3913						});
3914					}
3915
3916					Plugin.setDataSourceQuery(query);
3917
3918					// local filter only. remote pagination will skip this block
3919					if (!options.data.serverFiltering) {
3920						Plugin.localDataUpdate();
3921					}
3922					Plugin.dataRender();
3923				}, Plugin.getOption('search.delay'));
3924			},
3925
3926			/**
3927			 * Set datasource params
3928			 * @param param
3929			 * @param value
3930			 */
3931			setDataSourceParam: function(param, value) {
3932				datatable.API.params = $.extend({}, {
3933					pagination: {page: 1, perpage: Plugin.getOption('data.pageSize')},
3934					sort: Plugin.getDefaultSortColumn(),
3935					query: {},
3936				}, datatable.API.params, Plugin.stateGet(Plugin.stateId));
3937
3938				datatable.API.params = Plugin.extendObj(datatable.API.params, param, value);
3939
3940				Plugin.stateKeep(Plugin.stateId, datatable.API.params);
3941			},
3942
3943			/**
3944			 * Get datasource params
3945			 * @param param
3946			 */
3947			getDataSourceParam: function(param) {
3948				datatable.API.params = $.extend({}, {
3949					pagination: {page: 1, perpage: Plugin.getOption('data.pageSize')},
3950					sort: Plugin.getDefaultSortColumn(),
3951					query: {},
3952				}, datatable.API.params, Plugin.stateGet(Plugin.stateId));
3953
3954				if (typeof param === 'string') {
3955					return Plugin.getObject(param, datatable.API.params);
3956				}
3957
3958				return datatable.API.params;
3959			},
3960
3961			/**
3962			 * Shortcode to datatable.getDataSourceParam('query');
3963			 * @returns {*}
3964			 */
3965			getDataSourceQuery: function() {
3966				return Plugin.getDataSourceParam('query') || {};
3967			},
3968
3969			/**
3970			 * Shortcode to datatable.setDataSourceParam('query', query);
3971			 * @param query
3972			 */
3973			setDataSourceQuery: function(query) {
3974				Plugin.setDataSourceParam('query', query);
3975			},
3976
3977			/**
3978			 * Get current page number
3979			 * @returns {number}
3980			 */
3981			getCurrentPage: function() {
3982				return $(datatable.table).
3983					siblings('.m-datatable__pager').
3984					last().
3985					find('.m-datatable__pager-nav').
3986					find('.m-datatable__pager-link.m-datatable__pager-link--active').
3987					data('page') || 1;
3988			},
3989
3990			/**
3991			 * Get selected dropdown page size
3992			 * @returns {*|number}
3993			 */
3994			getPageSize: function() {
3995				return $(datatable.table).
3996					siblings('.m-datatable__pager').
3997					last().
3998					find('.m-datatable__pager-size').
3999					val() || 10;
4000			},
4001
4002			/**
4003			 * Get total rows
4004			 */
4005			getTotalRows: function() {
4006				return datatable.API.params.pagination.total;
4007			},
4008
4009			/**
4010			 * Get full dataset in grid
4011			 * @returns {*|null|Array}
4012			 */
4013			getDataSet: function() {
4014				return datatable.originalDataSet;
4015			},
4016
4017			/**
4018			 * @deprecated in v5.0.6
4019			 * Hide column by column's field name
4020			 * @param fieldName
4021			 */
4022			hideColumn: function(fieldName) {
4023				// add hide option for this column
4024				$.map(options.columns, function(column) {
4025					if (fieldName === column.field) {
4026						column.responsive = {hidden: 'xl'};
4027					}
4028					return column;
4029				});
4030				// hide current displayed column
4031				var tds = $.grep($(datatable.table).find('.m-datatable__cell'), function(n, i) {
4032					return fieldName === $(n).data('field');
4033				});
4034				$(tds).hide();
4035			},
4036
4037			/**
4038			 * @deprecated in v5.0.6
4039			 * Show column by column's field name
4040			 * @param fieldName
4041			 */
4042			showColumn: function(fieldName) {
4043				// add hide option for this column
4044				$.map(options.columns, function(column) {
4045					if (fieldName === column.field) {
4046						delete column.responsive;
4047					}
4048					return column;
4049				});
4050				// hide current displayed column
4051				var tds = $.grep($(datatable.table).find('.m-datatable__cell'), function(n, i) {
4052					return fieldName === $(n).data('field');
4053				});
4054				$(tds).show();
4055			},
4056
4057			destroyScroller: function(element) {
4058				if (typeof element === 'undefined') element = datatable.tableBody;
4059				$(element).each(function() {
4060					if ($(this).hasClass('mCustomScrollbar')) {
4061						try {
4062							mApp.destroyScroller($(this));
4063						} catch (e) {
4064							console.log(e);
4065						}
4066					}
4067				});
4068			},
4069
4070			/**
4071			 * NEW API
4072			 */
4073
4074			nodeTr: [],
4075			nodeTd: [],
4076			nodeCols: [],
4077			recentNode: [],
4078
4079			table: function() {
4080				return datatable.table;
4081			},
4082
4083			/**
4084			 * Select a single row from the table
4085			 * @param selector
4086			 * @returns {jQuery}
4087			 */
4088			row: function(selector) {
4089				Plugin.rows(selector);
4090				Plugin.nodeTr = Plugin.recentNode = $(Plugin.nodeTr).first();
4091				return datatable;
4092			},
4093
4094			/**
4095			 * Select multiple rows from the table
4096			 * @param selector
4097			 * @returns {jQuery}
4098			 */
4099			rows: function(selector) {
4100				Plugin.nodeTr = Plugin.recentNode = $(datatable.tableBody).find(selector).filter('.m-datatable__row');
4101				return datatable;
4102			},
4103
4104			/**
4105			 * Select a single column from the table
4106			 * @param index zero-based index
4107			 * @returns {jQuery}
4108			 */
4109			column: function(index) {
4110				Plugin.nodeCols = Plugin.recentNode = $(datatable.tableBody).find('.m-datatable__cell:nth-child(' + (index + 1) + ')');
4111				return datatable;
4112			},
4113
4114			/**
4115			 * Select multiple columns from the table
4116			 * @param selector
4117			 * @returns {jQuery}
4118			 */
4119			columns: function(selector) {
4120				var context = datatable.table;
4121				if (Plugin.nodeTr === Plugin.recentNode) {
4122					context = Plugin.nodeTr;
4123				}
4124				var columns = $(context).find('.m-datatable__cell[data-field="' + selector + '"]');
4125				if (columns.length > 0) {
4126					Plugin.nodeCols = Plugin.recentNode = columns;
4127				} else {
4128					Plugin.nodeCols = Plugin.recentNode = $(context).find(selector).filter('.m-datatable__cell');
4129				}
4130				return datatable;
4131			},
4132
4133			cell: function(selector) {
4134				Plugin.cells(selector);
4135				Plugin.nodeTd = Plugin.recentNode = $(Plugin.nodeTd).first();
4136				return datatable;
4137			},
4138
4139			cells: function(selector) {
4140				var cells = $(datatable.tableBody).find('.m-datatable__cell');
4141				if (typeof selector !== 'undefined') {
4142					cells = $(cells).filter(selector);
4143				}
4144				Plugin.nodeTd = Plugin.recentNode = cells;
4145				return datatable;
4146			},
4147
4148			/**
4149			 * Delete the selected row from the table
4150			 * @returns {jQuery}
4151			 */
4152			remove: function() {
4153				if ($(Plugin.nodeTr.length) && Plugin.nodeTr === Plugin.recentNode) {
4154					$(Plugin.nodeTr).remove();
4155				}
4156				return datatable;
4157			},
4158
4159			/**
4160			 * Show or hide the columns or rows
4161			 */
4162			visible: function(bool) {
4163				if ($(Plugin.recentNode.length)) {
4164					if (bool) {
4165						if (Plugin.recentNode === Plugin.nodeCols) {
4166							Plugin.setOption('columns.' + Plugin.recentNode.index() + '.responsive', {});
4167						}
4168						$(Plugin.recentNode).show();
4169						Plugin.redraw();
4170					} else {
4171						if (Plugin.recentNode === Plugin.nodeCols) {
4172							Plugin.setOption('columns.' + Plugin.recentNode.index() + '.responsive', {hidden: 'xl'});
4173						}
4174						$(Plugin.recentNode).hide();
4175						Plugin.redraw();
4176					}
4177				}
4178			},
4179
4180			/**
4181			 * Get the the DOM element for the selected rows or columns
4182			 * @returns {Array}
4183			 */
4184			nodes: function() {
4185				return Plugin.recentNode;
4186			},
4187
4188			/**
4189			 * will be implemented soon
4190			 * @returns {jQuery}
4191			 */
4192			dataset: function() {
4193				return datatable;
4194			},
4195
4196		};
4197
4198		/**
4199		 * Public API methods can be used directly by datatable
4200		 */
4201		$.each(Plugin, function(funcName, func) {
4202			datatable[funcName] = func;
4203		});
4204
4205		// initialize main datatable plugin
4206		if (typeof options !== 'undefined') {
4207			if (typeof options === 'string') {
4208				var method = options;
4209				datatable = $(this).data('mDatatable');
4210				if (typeof datatable !== 'undefined') {
4211					options = datatable.options;
4212					Plugin[method].apply(this, Array.prototype.slice.call(arguments, 1));
4213				}
4214			} else {
4215				if (!datatable.data('mDatatable') && !$(this).hasClass('m-datatable--loaded')) {
4216					datatable.dataSet = null;
4217					datatable.textAlign = {
4218						left: 'm-datatable__cell--left',
4219						center: 'm-datatable__cell--center',
4220						right: 'm-datatable__cell--right',
4221					};
4222
4223					// merge default and user defined options
4224					options = $.extend(true, {}, $.fn.mDatatable.defaults, options);
4225
4226					datatable.options = options;
4227
4228					// init plugin process
4229					Plugin.init.apply(this, [options]);
4230
4231					$(datatable.wrap).data('mDatatable', datatable);
4232				}
4233			}
4234		} else {
4235			// get existing instance datatable
4236			datatable = $(this).data('mDatatable');
4237			if (typeof datatable === 'undefined') {
4238				$.error('mDatatable not initialized');
4239			}
4240			options = datatable.options;
4241		}
4242
4243		return datatable;
4244	};
4245
4246	// default options
4247	$.fn.mDatatable.defaults = {
4248		// datasource definition
4249		data: {
4250			type: 'local',
4251			source: null,
4252			pageSize: 10, // display records per page
4253			saveState: {
4254				// save datatable state(pagination, filtering, sorting, etc) in cookie or browser webstorage
4255				cookie: false,
4256				webstorage: true,
4257			},
4258
4259			serverPaging: false,
4260			serverFiltering: false,
4261			serverSorting: false,
4262
4263			autoColumns: false,
4264		},
4265
4266		// layout definition
4267		layout: {
4268			theme: 'default', // datatable will support multiple themes and designs
4269			class: 'm-datatable--brand', // custom wrapper class
4270			scroll: false, // enable/disable datatable scroll both horizontal and vertical when needed.
4271			height: null, // datatable's body's fixed height
4272			minHeight: 300,
4273			footer: false, // display/hide footer
4274			header: true, // display/hide header
4275
4276			// datatable custom scroll params
4277			smoothScroll: {
4278				scrollbarShown: true,
4279			},
4280
4281			// datatable spinner
4282			spinner: {
4283				overlayColor: '#FFFFFF',
4284				opacity: .8,
4285				type: 'loader',
4286				state: 'brand',
4287				message: true,
4288			},
4289
4290			// datatable UI icons
4291			icons: {
4292				sort: {asc: 'la la-arrow-up', desc: 'la la-arrow-down'},
4293				pagination: {
4294					next: 'la la-angle-right',
4295					prev: 'la la-angle-left',
4296					first: 'la la-angle-double-left',
4297					last: 'la la-angle-double-right',
4298					more: 'la la-ellipsis-h',
4299				},
4300				rowDetail: {expand: 'fa fa-caret-down', collapse: 'fa fa-caret-right'},
4301			},
4302		},
4303
4304		// column sorting
4305		sortable: true,
4306
4307		// resize column size with mouse drag coming soon)
4308		resizable: false,
4309
4310		// column based filtering (coming soon)
4311		filterable: false,
4312
4313		pagination: true,
4314
4315		// inline and bactch editing (cooming soon)
4316		editable: false,
4317
4318		// columns definition
4319		columns: [],
4320
4321		search: {
4322			// enable trigger search by keyup enter
4323			onEnter: false,
4324			// input text for search
4325			input: null,
4326			// search delay in milliseconds
4327			delay: 800,
4328		},
4329
4330		rows: {
4331			// deprecated
4332			callback: function() {},
4333			// call before row template
4334			beforeTemplate: function() {},
4335			// call after row template
4336			afterTemplate: function() {},
4337			// auto hide columns, if rows overflow. work on non locked columns
4338			autoHide: false,
4339		},
4340
4341		// toolbar
4342		toolbar: {
4343			// place pagination and displayInfo blocks according to the array order
4344			layout: ['pagination', 'info'],
4345
4346			// toolbar placement can be at top or bottom or both top and bottom repeated
4347			placement: ['bottom', 'top'],  //'top', 'bottom'
4348
4349			// toolbar items
4350			items: {
4351				// pagination
4352				pagination: {
4353					// pagination type(default or scroll)
4354					type: 'default',
4355
4356					// number of pages to display by breakpoints
4357					pages: {
4358						desktop: {
4359							layout: 'default',
4360							pagesNumber: 6,
4361						},
4362						tablet: {
4363							layout: 'default',
4364							pagesNumber: 3,
4365						},
4366						mobile: {
4367							layout: 'compact',
4368						},
4369					},
4370
4371					// navigation buttons
4372					navigation: {
4373						prev: true, // display prev link
4374						next: true, // display next link
4375						first: true, // display first link
4376						last: true // display last link
4377					},
4378
4379					// page size select
4380					pageSizeSelect: [] // display dropdown to select pagination size. -1 is used for "ALl" option
4381				},
4382
4383				// records info
4384				info: true,
4385			},
4386		},
4387
4388		// here we will keep all strings and message used by datatable UI so developer can easiliy translate to any language.
4389		// By default the stirngs will be in the plugin source and here can override it
4390		translate: {
4391			records: {
4392				processing: 'Please wait...',
4393				noRecords: 'No records found',
4394			},
4395			toolbar: {
4396				pagination: {
4397					items: {
4398						default: {
4399							first: 'First',
4400							prev: 'Previous',
4401							next: 'Next',
4402							last: 'Last',
4403							more: 'More pages',
4404							input: 'Page number',
4405							select: 'Select page size',
4406						},
4407						info: 'Displaying {{start}} - {{end}} of {{total}} records',
4408					},
4409				},
4410			},
4411		},
4412
4413		extensions: {},
4414	};
4415
4416}(jQuery));
4417// jquery extension to add animation class into element
4418jQuery.fn.extend({
4419    animateClass: function(animationName, callback) {
4420        var animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend';
4421        jQuery(this).addClass('animated ' + animationName).one(animationEnd, function() {
4422            jQuery(this).removeClass('animated ' + animationName);
4423        });
4424
4425        if (callback) {
4426            jQuery(this).one(animationEnd, callback);
4427        }
4428    },
4429    animateDelay: function(value) {
4430        var vendors = ['webkit-', 'moz-', 'ms-', 'o-', ''];
4431        for (var i = 0; i < vendors.length; i++) {
4432            jQuery(this).css(vendors[i] + 'animation-delay', value);
4433        }
4434    },
4435    animateDuration: function(value) {
4436        var vendors = ['webkit-', 'moz-', 'ms-', 'o-', ''];
4437        for (var i = 0; i < vendors.length; i++) {
4438            jQuery(this).css(vendors[i] + 'animation-duration', value);
4439        }
4440    }
4441});
4442(function ($) {
4443    // Plugin function
4444    $.fn.mDropdown = function (options) {
4445        // Plugin scope variable
4446        var dropdown = {};
4447        var element = $(this);
4448
4449        // Plugin class
4450        var Plugin = {
4451            /**
4452             * Run
4453             */
4454            run: function (options) {
4455                if (!element.data('dropdown')) {                      
4456                    // create instance
4457                    Plugin.init(options);
4458                    Plugin.build();
4459                    Plugin.setup();
4460                    
4461                    // assign instance to the element                    
4462                    element.data('dropdown', dropdown);
4463                } else {
4464                    // get instance from the element
4465                    dropdown = element.data('dropdown');
4466                }               
4467
4468                return dropdown;
4469            },
4470
4471            /**
4472             * Initialize
4473             */
4474            init: function(options) {
4475                dropdown.events = [];
4476                dropdown.eventOne = false;
4477                dropdown.close = element.find('.m-dropdown__close');
4478                dropdown.toggle = element.find('.m-dropdown__toggle');
4479                dropdown.arrow = element.find('.m-dropdown__arrow');
4480                dropdown.wrapper = element.find('.m-dropdown__wrapper');
4481                dropdown.scrollable = element.find('.m-dropdown__scrollable');
4482                dropdown.defaultDropPos = element.hasClass('m-dropdown--up') ? 'up' : 'down';
4483                dropdown.currentDropPos = dropdown.defaultDropPos;
4484
4485                dropdown.options = $.extend(true, {}, $.fn.mDropdown.defaults, options);
4486                if (element.data('drop-auto') === true) {
4487                    dropdown.options.dropAuto = true;
4488                } else if (element.data('drop-auto') === false) {
4489                    dropdown.options.dropAuto = false;
4490                }               
4491
4492                if (dropdown.scrollable.length > 0) {
4493                    if (dropdown.scrollable.data('min-height')) {
4494                        dropdown.options.minHeight = dropdown.scrollable.data('min-height');
4495                    }
4496
4497                    if (dropdown.scrollable.data('max-height')) {
4498                        dropdown.options.maxHeight = dropdown.scrollable.data('max-height');
4499                    }
4500                }                
4501            },
4502
4503            /**
4504             * Build DOM and init event handlers
4505             */
4506            build: function () {
4507                if (mUtil.isMobileDevice()) {
4508                    if (element.data('dropdown-toggle') == 'hover' || element.data('dropdown-toggle') == 'click') { 
4509                        dropdown.options.toggle = 'click';
4510                    } else {
4511                        dropdown.options.toggle = 'click'; 
4512                        dropdown.toggle.click(Plugin.toggle); 
4513                    }
4514                } else {
4515                    if (element.data('dropdown-toggle') == 'hover') {     
4516                        dropdown.options.toggle = 'hover';              
4517                        element.mouseleave(Plugin.hide);
4518                    } else if(element.data('dropdown-toggle') == 'click') {
4519                        dropdown.options.toggle = 'click';                  
4520                    } else {
4521                        if (dropdown.options.toggle == 'hover') {
4522                            element.mouseenter(Plugin.show);
4523                            element.mouseleave(Plugin.hide);
4524                        } else {
4525                            dropdown.toggle.click(Plugin.toggle);      
4526                        }
4527                    }
4528                }                
4529
4530                // handle dropdown close icon
4531                if (dropdown.close.length) {
4532                    dropdown.close.on('click', Plugin.hide);
4533                }
4534
4535                // disable dropdown close
4536                Plugin.disableClose();
4537            }, 
4538
4539            /**
4540             * Setup dropdown
4541             */
4542            setup: function () {
4543                if (dropdown.options.placement) {
4544                    element.addClass('m-dropdown--' + dropdown.options.placement);
4545                }
4546
4547                if (dropdown.options.align) {
4548                    element.addClass('m-dropdown--align-' + dropdown.options.align);
4549                } 
4550
4551                if (dropdown.options.width) {
4552                    dropdown.wrapper.css('width', dropdown.options.width);
4553                }
4554
4555                if (element.data('dropdown-persistent')) {
4556                    dropdown.options.persistent = true;
4557                }
4558        
4559                // handle height
4560                if (dropdown.options.minHeight) {
4561                    dropdown.scrollable.css('min-height', dropdown.options.minHeight);                    
4562                } 
4563
4564                if (dropdown.options.maxHeight) {
4565                    dropdown.scrollable.css('max-height', dropdown.options.maxHeight);     
4566                    dropdown.scrollable.css('overflow-y', 'auto'); 
4567
4568                    if (mUtil.isDesktopDevice()) {
4569                        mApp.initScroller(dropdown.scrollable, {});                
4570                    }   
4571                }      
4572
4573                // set zindex
4574                Plugin.setZindex();
4575            },
4576
4577            /**
4578             * sync 
4579             */
4580            sync: function () {
4581                $(element).data('dropdown', dropdown);
4582            }, 
4583
4584            /**
4585             * Sync dropdown object with jQuery element
4586             */
4587            disableClose: function () {
4588                element.on('click', '.m-dropdown--disable-close, .mCSB_1_scrollbar', function (e) {
4589                    e.preventDefault();
4590                    e.stopPropagation();
4591                });
4592            },
4593
4594            /**
4595             * Toggle dropdown
4596             */
4597            toggle: function () {
4598                if (dropdown.open) {
4599                    return Plugin.hide();
4600                } else {
4601                    return Plugin.show();
4602                }
4603            },
4604
4605            /**
4606             * Set content
4607             */
4608            setContent: function (content) {
4609                element.find('.m-dropdown__content').html(content);
4610                
4611                return dropdown;
4612            },
4613
4614            /**
4615             * Show dropdown
4616             */
4617            show: function() {
4618                if (dropdown.options.toggle == 'hover' && element.data('hover')) {
4619                    Plugin.clearHovered(); 
4620                    return dropdown;
4621                }
4622
4623                if (dropdown.open) {
4624                    return dropdown;
4625                }
4626
4627                if (dropdown.arrow.length > 0) {
4628                    Plugin.adjustArrowPos();
4629                }
4630
4631                Plugin.eventTrigger('beforeShow'); 
4632
4633                Plugin.hideOpened();
4634
4635                element.addClass('m-dropdown--open');
4636
4637                if (mUtil.isMobileDevice() && dropdown.options.mobileOverlay) {
4638                    var zIndex = dropdown.wrapper.css('zIndex') - 1;
4639                    var dropdownoff = $('<div class="m-dropdown__dropoff"></div>');
4640
4641                    dropdownoff.css('zIndex', zIndex);
4642                    dropdownoff.data('dropdown', element);
4643                    element.data('dropoff', dropdownoff);
4644                    element.after(dropdownoff);
4645                    dropdownoff.click(function(e) {
4646                        Plugin.hide();
4647                        $(this).remove();                    
4648                        e.preventDefault();
4649                    });
4650                } 
4651
4652                element.focus();
4653                element.attr('aria-expanded', 'true');
4654                dropdown.open = true;
4655
4656                Plugin.handleDropPosition();          
4657
4658                Plugin.eventTrigger('afterShow');
4659
4660                return dropdown;
4661            },
4662
4663            /**
4664             * Clear dropdown hover
4665             */
4666            clearHovered: function () {
4667                element.removeData('hover');
4668                var timeout = element.data('timeout');
4669                element.removeData('timeout');
4670                clearTimeout(timeout);
4671            },
4672
4673            /**
4674             * Hide hovered dropdown
4675             */
4676            hideHovered: function(force) {
4677                if (force) {
4678                    if (Plugin.eventTrigger('beforeHide') === false) {
4679                        // cancel hide
4680                        return;
4681                    }  
4682
4683                    Plugin.clearHovered();        
4684                    element.removeClass('m-dropdown--open');
4685                    dropdown.open = false;
4686                    Plugin.eventTrigger('afterHide');
4687                } else {
4688                    if (Plugin.eventTrigger('beforeHide') === false) {
4689                        // cancel hide
4690                        return;
4691                    }
4692                    var timeout = setTimeout(function() {
4693                        if (element.data('hover')) {
4694                            Plugin.clearHovered();        
4695                            element.removeClass('m-dropdown--open');
4696                            dropdown.open = false;
4697                            Plugin.eventTrigger('afterHide');
4698                        }
4699                    }, dropdown.options.hoverTimeout);
4700
4701                    element.data('hover', true);
4702                    element.data('timeout', timeout); 
4703                }     
4704            },
4705
4706            /**
4707             * Hide clicked dropdown
4708             */
4709            hideClicked: function() {    
4710                if (Plugin.eventTrigger('beforeHide') === false) {
4711                    // cancel hide
4712                    return;
4713                }             
4714                element.removeClass('m-dropdown--open');
4715                if (element.data('dropoff')) {
4716                    element.data('dropoff').remove();
4717                }
4718                dropdown.open = false;
4719                Plugin.eventTrigger('afterHide');
4720            },
4721
4722            /**
4723             * Hide dropdown
4724             */
4725            hide: function(force) {
4726                if (dropdown.open === false) {
4727                    return dropdown;
4728                }
4729
4730                if (dropdown.options.toggle == 'hover') {
4731                    Plugin.hideHovered(force);
4732                } else {
4733                    Plugin.hideClicked();
4734                }
4735
4736                if (dropdown.defaultDropPos == 'down' && dropdown.currentDropPos == 'up') {
4737                    element.removeClass('m-dropdown--up');
4738                    dropdown.arrow.prependTo(dropdown.wrapper);
4739                    dropdown.currentDropPos = 'down';
4740                }
4741
4742                return dropdown;                
4743            },
4744
4745            /**
4746             * Hide opened dropdowns
4747             */
4748            hideOpened: function() {
4749                $('.m-dropdown.m-dropdown--open').each(function() {
4750                    $(this).mDropdown().hide(true);
4751                });
4752            },
4753
4754            /**
4755             * Adjust dropdown arrow positions
4756             */
4757            adjustArrowPos: function() {
4758                var width = element.outerWidth();
4759                var alignment = dropdown.arrow.hasClass('m-dropdown__arrow--right') ? 'right' : 'left';
4760                var pos = 0;
4761
4762                if (dropdown.arrow.length > 0) {
4763                    if (mUtil.isInResponsiveRange('mobile') && element.hasClass('m-dropdown--mobile-full-width')) {
4764                        pos = element.offset().left + (width / 2) - Math.abs(dropdown.arrow.width() / 2) - parseInt(dropdown.wrapper.css('left'));
4765                        dropdown.arrow.css('right', 'auto');    
4766                        dropdown.arrow.css('left', pos);    
4767                        dropdown.arrow.css('margin-left', 'auto');
4768                        dropdown.arrow.css('margin-right', 'auto');
4769                    } else if (dropdown.arrow.hasClass('m-dropdown__arrow--adjust')) {
4770                        pos = width / 2 - Math.abs(dropdown.arrow.width() / 2);
4771                        if (element.hasClass('m-dropdown--align-push')) {
4772                            pos = pos + 20;
4773                        }
4774                        if (alignment == 'right') { 
4775                            dropdown.arrow.css('left', 'auto');  
4776                            dropdown.arrow.css('right', pos);
4777                        } else {                            
4778                            dropdown.arrow.css('right', 'auto');  
4779                            dropdown.arrow.css('left', pos);
4780                        }  
4781                    }                    
4782                }
4783            },
4784
4785            /**
4786             * Change dropdown drop position
4787             */
4788            handleDropPosition: function() {
4789                return;
4790                
4791                if (dropdown.options.dropAuto == true) {
4792                    if (Plugin.isInVerticalViewport() === false) {
4793                        if (dropdown.currentDropPos == 'up') {
4794                            element.removeClass('m-dropdown--up');
4795                            dropdown.arrow.prependTo(dropdown.wrapper);
4796                            dropdown.currentDropPos = 'down';
4797                        } else if (dropdown.currentDropPos == 'down') {
4798                            element.addClass('m-dropdown--up');
4799                            dropdown.arrow.appendTo(dropdown.wrapper);
4800                            dropdown.currentDropPos = 'up'; 
4801                        }
4802                    }
4803                }
4804            },
4805
4806            /**
4807             * Get zindex
4808             */
4809            setZindex: function() {
4810                var oldZindex = dropdown.wrapper.css('z-index');
4811                var newZindex = mUtil.getHighestZindex(element);
4812                if (newZindex > oldZindex) {
4813                    dropdown.wrapper.css('z-index', zindex);
4814                }
4815            },
4816
4817            /**
4818             * Check persistent
4819             */
4820            isPersistent: function () {
4821                return dropdown.options.persistent;
4822            },
4823
4824            /**
4825             * Check persistent
4826             */
4827            isShown: function () {
4828                return dropdown.open;
4829            },
4830
4831            /**
4832             * Check if dropdown is in viewport
4833             */
4834            isInVerticalViewport: function() {
4835                var el = dropdown.wrapper;
4836                var offset = el.offset();
4837                var height = el.outerHeight();
4838                var width = el.width();
4839                var scrollable = el.find('[data-scrollable]');
4840
4841                if (scrollable.length) {
4842                    if (scrollable.data('max-height')) {
4843                        height += parseInt(scrollable.data('max-height'));
4844                    } else if(scrollable.data('height')) {
4845                        height += parseInt(scrollable.data('height'));
4846                    }
4847                }
4848
4849                return (offset.top + height < $(window).scrollTop() + $(window).height());
4850            },
4851
4852            /**
4853             * Trigger events
4854             */
4855            eventTrigger: function(name) {
4856                for (i = 0; i < dropdown.events.length; i++) {
4857                    var event = dropdown.events[i];
4858                    if (event.name == name) {
4859                        if (event.one == true) {
4860                            if (event.fired == false) {
4861                                dropdown.events[i].fired = true;
4862                                return event.handler.call(this, dropdown);
4863                            }
4864                        } else {
4865                            return  event.handler.call(this, dropdown);
4866                        }
4867                    }
4868                }
4869            },
4870
4871            addEvent: function(name, handler, one) {
4872                dropdown.events.push({
4873                    name: name,
4874                    handler: handler,
4875                    one: one,
4876                    fired: false
4877                });
4878
4879                Plugin.sync();
4880
4881                return dropdown;
4882            }
4883        };
4884
4885        // Run plugin
4886        Plugin.run.apply(this, [options]);
4887
4888        //////////////////////
4889        // ** Public API ** //
4890        //////////////////////
4891       
4892        /**
4893         * Show dropdown
4894         * @returns {mDropdown}
4895         */
4896        dropdown.show = function () {
4897            return Plugin.show();
4898        };
4899
4900        /**
4901         * Hide dropdown
4902         * @returns {mDropdown}
4903         */
4904        dropdown.hide = function () {
4905            return Plugin.hide();
4906        };
4907
4908        /**
4909         * Toggle dropdown
4910         * @returns {mDropdown}
4911         */
4912        dropdown.toggle = function () {
4913            return Plugin.toggle();
4914        };
4915
4916        /**
4917         * Toggle dropdown
4918         * @returns {mDropdown}
4919         */
4920        dropdown.isPersistent = function () {
4921            return Plugin.isPersistent();
4922        };
4923
4924        /**
4925         * Check shown state
4926         * @returns {mDropdown}
4927         */
4928        dropdown.isShown = function () {
4929            return Plugin.isShown();
4930        };
4931
4932        /**
4933         * Check shown state
4934         * @returns {mDropdown}
4935         */
4936        dropdown.fixDropPosition = function () {
4937            return Plugin.handleDropPosition();
4938        };
4939
4940        /**
4941         * Set dropdown content
4942         * @returns {mDropdown}
4943         */
4944        dropdown.setContent = function (content) {
4945            return Plugin.setContent(content);
4946        };
4947
4948        /**
4949         * Set dropdown content
4950         * @returns {mDropdown}
4951         */
4952        dropdown.on =  function (name, handler) {
4953            return Plugin.addEvent(name, handler);
4954        };
4955
4956        /**
4957         * Set dropdown content
4958         * @returns {mDropdown}
4959         */
4960        dropdown.one =  function (name, handler) {
4961            return Plugin.addEvent(name, handler, true);
4962        };        
4963
4964        return dropdown;
4965    };
4966
4967    // default options
4968    $.fn.mDropdown.defaults = {
4969        toggle: 'click',
4970        hoverTimeout: 300,
4971        skin: 'default',
4972        height: 'auto',
4973        dropAuto: true,
4974        maxHeight: false,
4975        minHeight: false,
4976        persistent: false,
4977        mobileOverlay: true
4978    };
4979
4980    // global init
4981    if (mUtil.isMobileDevice()) {
4982        $(document).on('click', '[data-dropdown-toggle="click"] .m-dropdown__toggle, [data-dropdown-toggle="hover"] .m-dropdown__toggle', function(e) { 
4983            e.preventDefault(); 
4984            $(this).parent('.m-dropdown').mDropdown().toggle(); 
4985        });
4986    } else {
4987        $(document).on('click', '[data-dropdown-toggle="click"] .m-dropdown__toggle', function(e) { 
4988            e.preventDefault();
4989            $(this).parent('.m-dropdown').mDropdown().toggle();   
4990        });
4991        $(document).on('mouseenter', '[data-dropdown-toggle="hover"]', function(e) { 
4992             e.preventDefault();
4993            $(this).mDropdown().toggle();
4994        });
4995    }
4996
4997    // handle global document click
4998    $(document).on('click', function(e) {
4999        $('.m-dropdown.m-dropdown--open').each(function() {
5000            if (!$(this).data('dropdown')) {
5001                return;
5002            }        
5003            
5004            var target = $(e.target);
5005            var dropdown = $(this).mDropdown();
5006            var toggle = $(this).find('.m-dropdown__toggle');
5007
5008            if (toggle.length > 0 && target.is(toggle) !== true && toggle.find(target).length === 0 && target.find(toggle).length === 0 && dropdown.isPersistent() == false) {
5009                dropdown.hide();     
5010            } else if ($(this).find(target).length === 0) {
5011                dropdown.hide();       
5012            }
5013        });
5014    });
5015}(jQuery));
5016(function ($) {
5017    // Plugin function
5018    $.fn.mExample = function (options) {
5019        // Plugin scope variable
5020        var example = {};
5021        var element = $(this);
5022
5023        // Plugin class
5024        var Plugin = {
5025            /**
5026             * Run
5027             */
5028            run: function (options) {
5029                if (!element.data('example')) {                      
5030                    // create instance
5031                    Plugin.init(options);
5032                    Plugin.build();
5033                    Plugin.setup();
5034                    
5035                    // assign instance to the element                    
5036                    element.data('example', example);
5037                } else {
5038                    // get instance from the element
5039                    example = element.data('example');
5040                }               
5041
5042                return example;
5043            },
5044
5045            /**
5046             * Initialize
5047             */
5048            init: function(options) {
5049                example.events = [];
5050                example.scrollable = element.find('.m-example__scrollable');
5051                example.options = $.extend(true, {}, $.fn.mExample.defaults, options);
5052                if (example.scrollable.length > 0) {
5053                    if (example.scrollable.data('data-min-height')) {
5054                        example.options.minHeight = example.scrollable.data('data-min-height');
5055                    }
5056
5057                    if (example.scrollable.data('data-max-height')) {
5058                        example.options.maxHeight = example.scrollable.data('data-max-height');
5059                    }
5060                }                
5061            },
5062
5063            /**
5064             * Build DOM and init event handlers
5065             */
5066            build: function () {
5067                if (mUtil.isMobileDevice()) {
5068                    
5069                } else {
5070                    
5071                }                
5072            }, 
5073
5074            /**
5075             * Setup example
5076             */
5077            setup: function () {
5078               
5079            },
5080
5081            /**
5082             * Trigger events
5083             */
5084            eventTrigger: function(name) {
5085                for (i = 0; i < example.events.length; i++) {
5086                    var event = example.events[i];
5087                    if (event.name == name) {
5088                        if (event.one == true) {
5089                            if (event.fired == false) {
5090                                example.events[i].fired = true;
5091                                return event.handler.call(this, example);
5092                            }
5093                        } else {
5094                            return  event.handler.call(this, example);
5095                        }
5096                    }
5097                }
5098            },
5099
5100            addEvent: function(name, handler, one) {
5101                example.events.push({
5102                    name: name,
5103                    handler: handler,
5104                    one: one,
5105                    fired: false
5106                });
5107
5108                Plugin.sync();
5109            }
5110        };
5111
5112        // Run plugin
5113        Plugin.run.apply(this, [options]);
5114
5115        //////////////////////
5116        // ** Public API ** //
5117        //////////////////////
5118       
5119
5120        /**
5121         * Set example content
5122         * @returns {mExample}
5123         */
5124        example.on =  function (name, handler) {
5125            return Plugin.addEvent(name, handler);
5126        };
5127
5128        /**
5129         * Set example content
5130         * @returns {mExample}
5131         */
5132        example.one =  function (name, handler) {
5133            return Plugin.addEvent(name, handler, true);
5134        };        
5135
5136        return example;
5137    };
5138
5139    // default options
5140    $.fn.mExample.defaults = {
5141       
5142    };
5143}(jQuery));
5144(function($) {
5145
5146    // Plugin function
5147    $.fn.mHeader = function(options) {
5148        // Plugin scope variable
5149        var header = this;
5150        var element = $(this);
5151
5152        // Plugin class
5153        var Plugin = {
5154            /**
5155             * Run plugin
5156             * @returns {mHeader}
5157             */
5158            run: function(options) { 
5159                if (element.data('header')) {
5160                    header = element.data('header');                
5161                } else {
5162                    // reset header
5163                    Plugin.init(options);
5164
5165                    // reset header
5166                    Plugin.reset();
5167
5168                    // build header
5169                    Plugin.build();
5170
5171                    element.data('header', header);
5172                } 
5173
5174                return header;
5175            },
5176
5177            /**
5178             * Handles subheader click toggle
5179             * @returns {mHeader}
5180             */
5181            init: function(options) {                
5182                header.options = $.extend(true, {}, $.fn.mHeader.defaults, options);
5183            },
5184
5185            /**
5186             * Reset header
5187             * @returns {mHeader}
5188             */
5189            build: function() {
5190                Plugin.toggle();                   
5191            },
5192
5193            toggle: function() {
5194                var lastScrollTop = 0;
5195
5196                if (header.options.minimize.mobile === false && header.options.minimize.desktop === false) {
5197                    return;
5198                }          
5199
5200                $(window).scroll(function() {
5201                    var offset = 0;
5202
5203                    if (mUtil.isInResponsiveRange('desktop')) {
5204                        offset = header.options.offset.desktop;
5205                        on = header.options.minimize.desktop.on;
5206                        off = header.options.minimize.desktop.off;
5207                    } else if (mUtil.isInResponsiveRange('tablet-and-mobile')) {
5208                        offset = header.options.offset.mobile;
5209                        on = header.options.minimize.mobile.on;
5210                        off = header.options.minimize.mobile.off;
5211                    }
5212
5213                    var st = $(this).scrollTop();
5214
5215                    if (
5216                        (mUtil.isInResponsiveRange('tablet-and-mobile') && header.options.classic && header.options.classic.mobile) ||
5217                        (mUtil.isInResponsiveRange('desktop') && header.options.classic && header.options.classic.desktop)
5218                        
5219                        ) {
5220                        if (st > offset){ // down scroll mode
5221                            $("body").addClass(on);
5222                            $("body").removeClass(off);
5223                        } else { // back scroll mode
5224                            $("body").addClass(off);
5225                            $("body").removeClass(on);
5226                        }
5227                    } else {
5228                        if (st > offset && lastScrollTop < st){ // down scroll mode
5229                            $("body").addClass(on);
5230                            $("body").removeClass(off);
5231                        } else { // back scroll mode
5232                            $("body").addClass(off);
5233                            $("body").removeClass(on);
5234                        }
5235                        
5236                        lastScrollTop = st;
5237                    }
5238                });
5239            },
5240
5241            /**
5242             * Reset menu
5243             * @returns {mMenu}
5244             */
5245            reset: function() {
5246            }
5247        };
5248
5249        // Run plugin
5250        Plugin.run.apply(header, [options]);
5251
5252        //////////////////////
5253        // ** Public API ** //
5254        //////////////////////
5255
5256        /**
5257         * Disable header for given time
5258         * @returns {jQuery}
5259         */
5260        header.publicMethod = function() {
5261        	//return Plugin.publicMethod();
5262        };
5263
5264        // Return plugin instance
5265        return header;
5266    };
5267
5268    // Plugin default options
5269    $.fn.mHeader.defaults = {
5270        classic: false,
5271        offset: {
5272            mobile: 150,
5273            desktop: 200        
5274        },
5275        minimize: {
5276            mobile: false,
5277            desktop: false
5278        }
5279    }; 
5280}(jQuery));
5281(function($) {
5282
5283    // Plugin function
5284    $.fn.mMenu = function(options) {
5285        // Plugin scope variable
5286        var menu = this;
5287        var element = $(this);
5288
5289        // Plugin class
5290        var Plugin = {
5291            /**
5292             * Run plugin
5293             * @returns {mMenu}
5294             */
5295            run: function(options, reinit) { 
5296                if (element.data('menu') && reinit !== true) {
5297                    menu = element.data('menu');                
5298                } else {
5299                    // reset menu
5300                    Plugin.init(options);
5301                    
5302                    // reset menu
5303                    Plugin.reset();
5304
5305                    // build menu
5306                    Plugin.build();
5307
5308                    element.data('menu', menu);
5309                } 
5310
5311                return menu;
5312            },
5313
5314            /**
5315             * Handles submenu click toggle
5316             * @returns {mMenu}
5317             */
5318            init: function(options) { 
5319                menu.events = [];
5320
5321                // merge default and user defined options
5322                menu.options = $.extend(true, {}, $.fn.mMenu.defaults, options);
5323
5324                // pause menu
5325                menu.pauseDropdownHoverTime = 0;
5326            },
5327
5328            /**
5329             * Reset menu
5330             * @returns {mMenu}
5331             */
5332            build: function() {
5333                element.on('click', '.m-menu__toggle', Plugin.handleSubmenuAccordion);                
5334
5335                // dropdown mode(hoverable)
5336                if (Plugin.getSubmenuMode() === 'dropdown' || Plugin.isConditionalSubmenuDropdown()) {   
5337                	// dropdown submenu - hover toggle
5338	                element.on({mouseenter: Plugin.handleSubmenuDrodownHoverEnter, mouseleave: Plugin.handleSubmenuDrodownHoverExit}, '[data-menu-submenu-toggle="hover"]');
5339
5340	                // dropdown submenu - click toggle
5341	                element.on('click', '[data-menu-submenu-toggle="click"] > .m-menu__toggle, [data-menu-submenu-toggle="click"] > .m-menu__link .m-menu__toggle', Plugin.handleSubmenuDropdownClick);
5342                    element.on('click', '[data-menu-submenu-toggle="tab"] > .m-menu__toggle, [data-menu-submenu-toggle="tab"] > .m-menu__link .m-menu__toggle', Plugin.handleSubmenuDropdownTabClick);
5343                }
5344
5345                element.find('.m-menu__item:not(.m-menu__item--submenu) > .m-menu__link:not(.m-menu__toggle):not(.m-menu__link--toggle-skip)').click(Plugin.handleLinkClick);             
5346            },
5347
5348            /**
5349             * Reset menu
5350             * @returns {mMenu}
5351             */
5352            reset: function() {
5353            	// remove accordion handler
5354            	element.off('click', '.m-menu__toggle', Plugin.handleSubmenuAccordion);
5355
5356            	// remove dropdown handlers
5357            	element.off({mouseenter: Plugin.handleSubmenuDrodownHoverEnter, mouseleave: Plugin.handleSubmenuDrodownHoverExit}, '[data-menu-submenu-toggle="hover"]');
5358
5359                // dropdown submenu - click toggle
5360                element.off('click', '[data-menu-submenu-toggle="click"] > .m-menu__toggle, [data-menu-submenu-toggle="click"] > .m-menu__link .m-menu__toggle', Plugin.handleSubmenuDropdownClick);
5361                element.off('click', '[data-menu-submenu-toggle="tab"] > .m-menu__toggle, [data-menu-submenu-toggle="tab"] > .m-menu__link .m-menu__toggle', Plugin.handleSubmenuDropdownTabClick);
5362
5363                // reset mobile menu attributes
5364                menu.find('.m-menu__submenu, .m-menu__inner').css('display', '');
5365                menu.find('.m-menu__item--hover:not(.m-menu__item--tabs)').removeClass('m-menu__item--hover');
5366                menu.find('.m-menu__item--open:not(.m-menu__item--expanded)').removeClass('m-menu__item--open');
5367            },
5368
5369            /**
5370            * Get submenu mode for current breakpoint and menu state
5371            * @returns {mMenu}
5372            */
5373            getSubmenuMode: function() {                
5374                if (mUtil.isInResponsiveRange('desktop')) {
5375                    if (mUtil.isset(menu.options.submenu, 'desktop.state.body')) {
5376                        if ($('body').hasClass(menu.options.submenu.desktop.state.body)) {
5377                            return menu.options.submenu.desktop.state.mode;
5378                        } else {
5379                            return menu.options.submenu.desktop.default;
5380                        }
5381                    } else if (mUtil.isset(menu.options.submenu, 'desktop') ){
5382                        return menu.options.submenu.desktop;
5383                    }
5384                } else if (mUtil.isInResponsiveRange('tablet') && mUtil.isset(menu.options.submenu, 'tablet')) {
5385                    return menu.options.submenu.tablet;
5386                } else if (mUtil.isInResponsiveRange('mobile') && mUtil.isset(menu.options.submenu, 'mobile')) {
5387                    return menu.options.submenu.mobile;
5388                } else {
5389                    return false;
5390                }
5391            },
5392
5393            /**
5394            * Get submenu mode for current breakpoint and menu state
5395            * @returns {mMenu}
5396            */
5397            isConditionalSubmenuDropdown: function() {
5398                if (mUtil.isInResponsiveRange('desktop') && mUtil.isset(menu.options.submenu, 'desktop.state.body')) {
5399                    return true;
5400                } else {
5401                    return false;    
5402                }                
5403            },
5404
5405            /**
5406             * Handles menu link click
5407             * @returns {mMenu}
5408             */
5409            handleLinkClick: function(e) {    
5410                if (Plugin.eventTrigger('linkClick', $(this)) === false) {
5411                    e.preventDefault();
5412                };
5413
5414                if (Plugin.getSubmenuMode() === 'dropdown' || Plugin.isConditionalSubmenuDropdown()) { 
5415                    Plugin.handleSubmenuDropdownClose(e, $(this));
5416                }
5417            },
5418
5419            /**
5420             * Handles submenu hover toggle
5421             * @returns {mMenu}
5422             */
5423            handleSubmenuDrodownHoverEnter: function(e) {
5424                if (Plugin.getSubmenuMode() === 'accordion') {
5425                    return;
5426                }
5427
5428                if (menu.resumeDropdownHover() === false) {
5429                    return;
5430                }               
5431
5432                var item = $(this);
5433
5434                Plugin.showSubmenuDropdown(item);
5435
5436                if (item.data('hover') == true) {
5437                    Plugin.hideSubmenuDropdown(item, false);
5438                }
5439            },
5440
5441            /**
5442             * Handles submenu hover toggle
5443             * @returns {mMenu}
5444             */
5445            handleSubmenuDrodownHoverExit: function(e) {
5446                if (menu.resumeDropdownHover() === false) {
5447                    return;
5448                }
5449
5450                if (Plugin.getSubmenuMode() === 'accordion') {
5451                    return;
5452                }
5453
5454                var item = $(this);
5455                var time = menu.options.dropdown.timeout;
5456
5457                var timeout = setTimeout(function() {
5458                    if (item.data('hover') == true) {
5459                        Plugin.hideSubmenuDropdown(item, true);
5460                    }
5461                }, time);
5462
5463                item.data('hover', true);
5464                item.data('timeout', timeout);
5465            },
5466
5467            /**
5468             * Handles submenu click toggle
5469             * @returns {mMenu}
5470             */
5471            handleSubmenuDropdownClick: function(e) {
5472                if (Plugin.getSubmenuMode() === 'accordion') {
5473                    return;
5474                }
5475
5476                var item = $(this).closest('.m-menu__item');
5477
5478                if (item.data('menu-submenu-mode') == 'accordion') {
5479                    return;   
5480                }
5481
5482                if (item.hasClass('m-menu__item--hover') == false) {
5483                    item.addClass('m-menu__item--open-dropdown');
5484                    Plugin.showSubmenuDropdown(item);
5485                } else {
5486                    item.removeClass('m-menu__item--open-dropdown');
5487                    Plugin.hideSubmenuDropdown(item, true);
5488                }
5489
5490                e.preventDefault();
5491            },
5492
5493            /**
5494             * Handles tab click toggle
5495             * @returns {mMenu}
5496             */
5497            handleSubmenuDropdownTabClick: function(e) {
5498                if (Plugin.getSubmenuMode() === 'accordion') {
5499                    return;
5500                }
5501
5502                var item = $(this).closest('.m-menu__item');
5503
5504                if (item.data('menu-submenu-mode') == 'accordion') {
5505                    return;   
5506                }
5507
5508                if (item.hasClass('m-menu__item--hover') == false) {
5509                    item.addClass('m-menu__item--open-dropdown');
5510                    Plugin.showSubmenuDropdown(item);
5511                } 
5512
5513                e.preventDefault();
5514            },
5515
5516            /**
5517             * Handles submenu dropdown close on link click
5518             * @returns {mMenu}
5519             */
5520            handleSubmenuDropdownClose: function(e, el) {
5521                // exit if its not submenu dropdown mode
5522                if (Plugin.getSubmenuMode() === 'accordion') {
5523                    return;
5524                }
5525
5526                var shown = element.find('.m-menu__item.m-menu__item--submenu.m-menu__item--hover:not(.m-menu__item--tabs)');
5527
5528                // check if currently clicked link's parent item ha
5529                if (shown.length > 0 && el.hasClass('m-menu__toggle') === false && el.find('.m-menu__toggle').length === 0) {
5530                    // close opened dropdown menus
5531                    shown.each(function() {
5532                        Plugin.hideSubmenuDropdown($(this), true);    
5533                    });                     
5534                }
5535            },
5536
5537            /**
5538             * helper functions
5539             * @returns {mMenu}
5540             */
5541            handleSubmenuAccordion: function(e, el) {
5542                var item = el ? $(el) : $(this);
5543
5544                if (Plugin.getSubmenuMode() === 'dropdown' && item.closest('.m-menu__item').data('menu-submenu-mode') != 'accordion') {
5545                    e.preventDefault();
5546                    return;
5547                }
5548
5549                var li = item.closest('li');
5550                var submenu = li.children('.m-menu__submenu, .m-menu__inner');
5551
5552                if (item.closest('.m-menu__item').hasClass('m-menu__item--open-always')) {
5553                    return;
5554                }
5555
5556                if (submenu.length > 0) {
5557                    e.preventDefault();
5558                    var speed = menu.options.accordion.slideSpeed;
5559                    var hasClosables = false;
5560                    
5561                    if (li.hasClass('m-menu__item--open') === false) {
5562                        // hide other accordions
5563                        if (menu.options.accordion.expandAll === false) {
5564                            var closables = item.closest('.m-menu__nav, .m-menu__subnav').find('> .m-menu__item.m-menu__item--open.m-menu__item--submenu:not(.m-menu__item--expanded):not(.m-menu__item--open-always)');
5565                            closables.each(function() {
5566                                $(this).children('.m-menu__submenu').slideUp(speed, function() {
5567                                    Plugin.scrollToItem(item);
5568                                });                                
5569                                $(this).removeClass('m-menu__item--open');
5570                            });
5571
5572                            if (closables.length > 0) {
5573                                hasClosables = true;
5574                            }
5575                        }                         
5576
5577                        if (hasClosables) {
5578                            submenu.slideDown(speed, function() {
5579                                Plugin.scrollToItem(item);
5580                            }); 
5581                            li.addClass('m-menu__item--open');
5582                        } else {
5583                            submenu.slideDown(speed, function() {
5584                                Plugin.scrollToItem(item);
5585                            });
5586                            li.addClass('m-menu__item--open');
5587                        }                        
5588                    } else {  
5589                        submenu.slideUp(speed, function() {
5590                             Plugin.scrollToItem(item);
5591                        });                        
5592                        li.removeClass('m-menu__item--open');                  
5593                    }
5594                }
5595            },     
5596
5597            /**
5598             * scroll to item function
5599             * @returns {mMenu}
5600             */
5601            scrollToItem: function(item) {
5602                // handle auto scroll for accordion submenus
5603                if (mUtil.isInResponsiveRange('desktop') && menu.options.accordion.autoScroll && !element.data('menu-scrollable')) {                        
5604                    mApp.scrollToViewport(item);
5605                }
5606            },
5607
5608            /**
5609             * helper functions
5610             * @returns {mMenu}
5611             */
5612            hideSubmenuDropdown: function(item, classAlso) {
5613                // remove submenu activation class
5614                if (classAlso) {
5615                    item.removeClass('m-menu__item--hover');
5616                    item.removeClass('m-menu__item--active-tab');
5617                }
5618                // clear timeout
5619                item.removeData('hover');
5620                if (item.data('menu-dropdown-toggle-class')) {
5621                    $('body').removeClass(item.data('menu-dropdown-toggle-class'));
5622                }
5623                var timeout = item.data('timeout');
5624                item.removeData('timeout');
5625                clearTimeout(timeout);
5626            },
5627
5628            /**
5629             * helper functions
5630             * @returns {mMenu}
5631             */
5632            showSubmenuDropdown: function(item) {
5633                // close active submenus
5634                element.find('.m-menu__item--submenu.m-menu__item--hover, .m-menu__item--submenu.m-menu__item--active-tab').each(function() {
5635                    var el = $(this);
5636                    if (item.is(el) || el.find(item).length > 0 || item.find(el).length > 0) {
5637                        return;
5638                    } else {
5639                        Plugin.hideSubmenuDropdown(el, true); 
5640                    }
5641                });
5642
5643                // adjust submenu position
5644                Plugin.adjustSubmenuDropdownArrowPos(item);
5645                
5646                // add submenu activation class
5647                item.addClass('m-menu__item--hover');
5648
5649                if (item.data('menu-dropdown-toggle-class')) {
5650                    $('body').addClass(item.data('menu-dropdown-toggle-class'));
5651                } 
5652
5653                // handle auto scroll for accordion submenus
5654                if (Plugin.getSubmenuMode() === 'accordion' && menu.options.accordion.autoScroll) {
5655                    mApp.scrollTo(item.children('.m-menu__item--submenu'));
5656                }              
5657            },                
5658
5659            /**
5660             * Handles submenu click toggle
5661             * @returns {mMenu}
5662             */
5663            resize: function(e) {
5664                if (Plugin.getSubmenuMode() !== 'dropdown') {
5665                    return;
5666                }
5667
5668                var resize = element.find('> .m-menu__nav > .m-menu__item--resize');
5669                var submenu = resize.find('> .m-menu__submenu');
5670                var breakpoint;
5671                var currentWidth = mUtil.getViewPort().width;
5672                var itemsNumber = element.find('> .m-menu__nav > .m-menu__item').length - 1;
5673                var check;
5674
5675                if (
5676                    Plugin.getSubmenuMode() == 'dropdown' && 
5677                    (
5678                        (mUtil.isInResponsiveRange('desktop') && mUtil.isset(menu.options, 'resize.desktop') && (check = menu.options.resize.desktop) && currentWidth <= (breakpoint = resize.data('menu-resize-desktop-breakpoint'))) ||
5679                        (mUtil.isInResponsiveRange('tablet') && mUtil.isset(menu.options, 'resize.tablet') && (check = menu.options.resize.tablet) && currentWidth <= (breakpoint = resize.data('menu-resize-tablet-breakpoint'))) ||
5680                        (mUtil.isInResponsiveRange('mobile') && mUtil.isset(menu.options, 'resize.mobile') && (check = menu.options.resize.mobile) && currentWidth <= (breakpoint = resize.data('menu-resize-mobile-breakpoint')))
5681                    )
5682                    ) {
5683                 
5684                    var moved = submenu.find('> .m-menu__subnav > .m-menu__item').length; // currently move
5685                    var left = element.find('> .m-menu__nav > .m-menu__item:not(.m-menu__item--resize)').length; // currently left
5686                    var total = moved + left;
5687
5688                    if (check.apply() === true) {
5689                        // return
5690                        if (moved > 0) {
5691                            submenu.find('> .m-menu__subnav > .m-menu__item').each(function() {
5692                                var item = $(this);
5693
5694                                var elementsNumber = submenu.find('> .m-menu__nav > .m-menu__item:not(.m-menu__item--resize)').length;
5695                                element.find('> .m-menu__nav > .m-menu__item:not(.m-menu__item--resize)').eq(elementsNumber - 1).after(item);
5696
5697                                if (check.apply() === false) {
5698                                    item.appendTo(submenu.find('> .m-menu__subnav'));
5699                                    return false;
5700                                }         
5701
5702                                moved--;
5703                                left++;                        
5704                            });
5705                        }
5706                    } else {
5707                        // move
5708                        if (left > 0) {
5709                            var items = element.find('> .m-menu__nav > .m-menu__item:not(.m-menu__item--resize)');
5710                            var index = items.length - 1;
5711                                
5712                            for(var i = 0; i < items.length; i++) {
5713                                var item = $(items.get(index)); 
5714                                index--;
5715
5716                                if (check.apply() === true) {
5717                                    break;
5718                                }
5719
5720                                item.appendTo(submenu.find('> .m-menu__subnav'));
5721
5722                                moved++;
5723                                left--; 
5724                            } 
5725                        }
5726                    }
5727
5728                    if (moved > 0) {
5729                        resize.show();  
5730                    } else {
5731                        resize.hide();
5732                    }                   
5733                } else {    
5734                    submenu.find('> .m-menu__subnav > .m-menu__item').each(function() {
5735                        var elementsNumber = submenu.find('> .m-menu__subnav > .m-menu__item').length;
5736                        element.find('> .m-menu__nav > .m-menu__item').get(elementsNumber).after($(this));
5737                    });
5738
5739                    resize.hide();
5740                }
5741            },
5742
5743            /**
5744             * Handles submenu slide toggle
5745             * @returns {mMenu}
5746             */
5747            createSubmenuDropdownClickDropoff: function(el) {
5748                var zIndex = el.find('> .m-menu__submenu').css('zIndex') - 1;
5749                var dropoff = $('<div class="m-menu__dropoff" style="background: transparent; position: fixed; top: 0; bottom: 0; left: 0; right: 0; z-index: ' + zIndex + '"></div>');
5750                $('body').after(dropoff);
5751                dropoff.on('click', function(e) {
5752                    e.stopPropagation();
5753                    e.preventDefault();
5754                    $(this).remove();
5755                    Plugin.hideSubmenuDropdown(el, true);                    
5756                });
5757            },
5758
5759            /**
5760             * Handles submenu click toggle
5761             * @returns {mMenu}
5762             */
5763            adjustSubmenuDropdownArrowPos: function(item) {                
5764                var arrow = item.find('> .m-menu__submenu > .m-menu__arrow.m-menu__arrow--adjust');
5765                var submenu = item.find('> .m-menu__submenu');
5766                var subnav = item.find('> .m-menu__submenu > .m-menu__subnav');
5767                
5768                if (arrow.length > 0) {
5769                    var pos;
5770                    var link = item.children('.m-menu__link');
5771
5772                    if (submenu.hasClass('m-menu__submenu--classic') || submenu.hasClass('m-menu__submenu--fixed')) { 
5773                        if (submenu.hasClass('m-menu__submenu--right')) {
5774                            pos = item.outerWidth() / 2;
5775                            if (submenu.hasClass('m-menu__submenu--pull')) {
5776                                pos = pos + Math.abs(parseInt(submenu.css('margin-right')));    
5777                            }  
5778                            pos = submenu.width() - pos;
5779                        } else if (submenu.hasClass('m-menu__submenu--left')) {
5780                            pos = item.outerWidth() / 2;
5781                            if (submenu.hasClass('m-menu__submenu--pull')) {
5782                                pos = pos + Math.abs(parseInt(submenu.css('margin-left')));    
5783                            } 
5784                        }
5785                    } else  {
5786                        if (submenu.hasClass('m-menu__submenu--center') || submenu.hasClass('m-menu__submenu--full')) {
5787                            pos = item.offset().left - ((mUtil.getViewPort().width - submenu.outerWidth()) / 2);
5788                            pos = pos + (item.outerWidth() / 2);
5789                        } else if (submenu.hasClass('m-menu__submenu--left')) {
5790                            // to do
5791                        } else if (submenu.hasClass('m-menu__submenu--right')) {
5792                            // to do
5793                        }
5794                    } 
5795
5796                    arrow.css('left', pos);
5797                }
5798            },
5799
5800            /**
5801             * Handles submenu hover toggle
5802             * @returns {mMenu}
5803             */
5804            pauseDropdownHover: function(time) {
5805            	var date = new Date();
5806
5807            	menu.pauseDropdownHoverTime = date.getTime() + time;
5808            },
5809
5810            /**
5811             * Handles submenu hover toggle
5812             * @returns {mMenu}
5813             */
5814            resumeDropdownHover: function() {
5815            	var date = new Date();
5816
5817            	return (date.getTime() > menu.pauseDropdownHoverTime ? true : false);
5818            },
5819
5820            /**
5821             * Reset menu's current active item
5822             * @returns {mMenu}
5823             */
5824            resetActiveItem: function(item) {
5825                element.find('.m-menu__item--active').each(function() {
5826                    $(this).removeClass('m-menu__item--active');
5827                    $(this).children('.m-menu__submenu').css('display', '');
5828
5829                    $(this).parents('.m-menu__item--submenu').each(function() {
5830                        $(this).removeClass('m-menu__item--open');
5831                        $(this).children('.m-menu__submenu').css('display', '');
5832                    });
5833                });             
5834
5835                // close open submenus
5836                if (menu.options.accordion.expandAll === false) {
5837                    element.find('.m-menu__item--open').each(function() {
5838                        $(this).removeClass('m-menu__item--open');
5839                    });
5840                }
5841            },
5842
5843            /**
5844             * Sets menu's active item
5845             * @returns {mMenu}
5846             */
5847            setActiveItem: function(item) {
5848                // reset current active item
5849                Plugin.resetActiveItem();
5850
5851                var item = $(item);
5852                item.addClass('m-menu__item--active');
5853                item.parents('.m-menu__item--submenu').each(function() {
5854                    $(this).addClass('m-menu__item--open');
5855                });
5856            },
5857
5858            /**
5859             * Returns page breadcrumbs for the menu's active item
5860             * @returns {mMenu}
5861             */
5862            getBreadcrumbs: function(item) {
5863                var breadcrumbs = [];
5864                var item = $(item);
5865                var link = item.children('.m-menu__link');
5866
5867                breadcrumbs.push({
5868                    text: link.find('.m-menu__link-text').html(), 
5869                    title: link.attr('title'),
5870                    href: link.attr('href')
5871                });
5872
5873                item.parents('.m-menu__item--submenu').each(function() {
5874                    var submenuLink = $(this).children('.m-menu__link');
5875                    breadcrumbs.push({
5876                        text: submenuLink.find('.m-menu__link-text').html(), 
5877                        title: submenuLink.attr('title'),
5878                        href: submenuLink.attr('href')
5879                    });
5880                });
5881
5882                breadcrumbs.reverse();
5883
5884                return breadcrumbs;
5885            },
5886
5887            /**
5888             * Returns page title for the menu's active item
5889             * @returns {mMenu}
5890             */
5891            getPageTitle: function(item) {
5892                item = $(item);       
5893
5894                return item.children('.m-menu__link').find('.m-menu__link-text').html();
5895            },
5896
5897            /**
5898             * Sync 
5899             */
5900            sync: function () {
5901                $(element).data('menu', menu);
5902            }, 
5903
5904            /**
5905             * Trigger events
5906             */
5907            eventTrigger: function(name, args) {
5908                for (i = 0; i < menu.events.length; i++) {
5909                    var event = menu.events[i];
5910                    if (event.name == name) {
5911                        if (event.one == true) {
5912                            if (event.fired == false) {
5913                                menu.events[i].fired = true;
5914                                return event.handler.call(this, menu, args);
5915                            }
5916                        } else {
5917                            return  event.handler.call(this, menu, args);
5918                        }
5919                    }
5920                }
5921            },
5922
5923            addEvent: function(name, handler, one) {
5924                menu.events.push({
5925                    name: name,
5926                    handler: handler,
5927                    one: one,
5928                    fired: false
5929                });
5930
5931                Plugin.sync();
5932            }
5933        };
5934
5935        // Run plugin
5936        Plugin.run.apply(menu, [options]);
5937
5938        // Handle plugin on window resize
5939        if (typeof(options)  !== "undefined") {
5940            $(window).resize(function() {
5941                Plugin.run.apply(menu, [options, true]);
5942            });  
5943        }        
5944
5945        //////////////////////
5946        // ** Public API ** //
5947        //////////////////////
5948
5949        /**
5950         * Set active menu item
5951         */
5952        menu.setActiveItem = function(item) {
5953            return Plugin.setActiveItem(item);
5954        };
5955
5956        /**
5957         * Set breadcrumb for menu item
5958         */
5959        menu.getBreadcrumbs = function(item) {
5960            return Plugin.getBreadcrumbs(item);
5961        };
5962
5963        /**
5964         * Set page title for menu item
5965         */
5966        menu.getPageTitle = function(item) {
5967            return Plugin.getPageTitle(item);
5968        };
5969
5970        /**
5971         * Get submenu mode
5972         */
5973        menu.getSubmenuMode = function() {
5974            return Plugin.getSubmenuMode();
5975        };
5976
5977        /**
5978         * Hide dropdown submenu
5979         * @returns {jQuery}
5980         */
5981        menu.hideDropdown = function(item) {
5982            Plugin.hideSubmenuDropdown(item, true);
5983        };
5984
5985        /**
5986         * Disable menu for given time
5987         * @returns {jQuery}
5988         */
5989        menu.pauseDropdownHover = function(time) {
5990        	Plugin.pauseDropdownHover(time);
5991        };
5992
5993        /**
5994         * Disable menu for given time
5995         * @returns {jQuery}
5996         */
5997        menu.resumeDropdownHover = function() {
5998        	return Plugin.resumeDropdownHover();
5999        };
6000
6001        /**
6002         * Register event
6003         */
6004        menu.on =  function (name, handler) {
6005            return Plugin.addEvent(name, handler);
6006        };
6007
6008        // Return plugin instance
6009        return menu;
6010    };
6011
6012    // Plugin default options
6013    $.fn.mMenu.defaults = {
6014        // accordion submenu mode
6015        accordion: {   
6016            slideSpeed: 200,  // accordion toggle slide speed in milliseconds
6017            autoScroll: true, // enable auto scrolling(focus) to the clicked menu item
6018            expandAll: true   // allow having multiple expanded accordions in the menu
6019        },
6020        
6021        // dropdown submenu mode
6022        dropdown: {
6023            timeout: 500  // timeout in milliseconds to show and hide the hoverable submenu dropdown
6024        }
6025    }; 
6026
6027    // Plugin global lazy initialization
6028    $(document).on('click', function(e) {
6029        $('.m-menu__nav .m-menu__item.m-menu__item--submenu.m-menu__item--hover:not(.m-menu__item--tabs)[data-menu-submenu-toggle="click"]').each(function() {
6030            var  element = $(this).closest('.m-menu__nav').parent();
6031            menu = element.mMenu(); 
6032
6033            if (menu.getSubmenuMode() !== 'dropdown') { 
6034                return;
6035            }            
6036
6037            if ($(e.target).is(element) == false && element.find($(e.target)).length == 0) {
6038                var items = element.find('.m-menu__item--submenu.m-menu__item--hover:not(.m-menu__item--tabs)[data-menu-submenu-toggle="click"]');
6039                items.each(function() {
6040                    menu.hideDropdown($(this));
6041                });
6042            }          
6043        });
6044    });
6045}(jQuery));
6046(function ($) {
6047    // Plugin function
6048    $.fn.mMessenger = function (options) {
6049        // Plugin scope variable
6050        var messenger = {};
6051        var element = $(this);
6052
6053        // Plugin class
6054        var Plugin = {
6055            /**
6056             * Run
6057             */
6058            run: function (options) {
6059                if (!element.data('messenger')) {                      
6060                    // create instance
6061                    Plugin.init(options);
6062                    Plugin.build();
6063                    Plugin.setup();
6064                    
6065                    // assign instance to the element                    
6066                    element.data('messenger', messenger);
6067                } else {
6068                    // get instance from the element
6069                    messenger = element.data('messenger');
6070                }               
6071
6072                return messenger;
6073            },
6074
6075            /**
6076             * Initialize
6077             */
6078            init: function(options) {
6079                messenger.events = [];
6080                messenger.scrollable = element.find('.m-messenger__scrollable');
6081                messenger.options = $.extend(true, {}, $.fn.mMessenger.defaults, options);
6082                if (messenger.scrollable.length > 0) {
6083                    if (messenger.scrollable.data('data-min-height')) {
6084                        messenger.options.minHeight = messenger.scrollable.data('data-min-height');
6085                    }
6086
6087                    if (messenger.scrollable.data('data-max-height')) {
6088                        messenger.options.maxHeight = messenger.scrollable.data('data-max-height');
6089                    }
6090                }                
6091            },
6092
6093            /**
6094             * Build DOM and init event handlers
6095             */
6096            build: function () {
6097                if (mUtil.isMobileDevice()) {
6098                    
6099                } else {
6100                    
6101                }                
6102            }, 
6103
6104            /**
6105             * Setup messenger
6106             */
6107            setup: function () {
6108               
6109            },
6110
6111            /**
6112             * Trigger events
6113             */
6114            eventTrigger: function(name) {
6115                for (i = 0; i < messenger.events.length; i++) {
6116                    var event = messenger.events[i];
6117                    if (event.name == name) {
6118                        if (event.one == true) {
6119                            if (event.fired == false) {
6120                                messenger.events[i].fired = true;
6121                                return event.handler.call(this, messenger);
6122                            }
6123                        } else {
6124                            return  event.handler.call(this, messenger);
6125                        }
6126                    }
6127                }
6128            },
6129
6130            addEvent: function(name, handler, one) {
6131                messenger.events.push({
6132                    name: name,
6133                    handler: handler,
6134                    one: one,
6135                    fired: false
6136                });
6137
6138                Plugin.sync();
6139            }
6140        };
6141
6142        // Run plugin
6143        Plugin.run.apply(this, [options]);
6144
6145        //////////////////////
6146        // ** Public API ** //
6147        //////////////////////
6148       
6149
6150        /**
6151         * Set messenger content
6152         * @returns {mMessenger}
6153         */
6154        messenger.on =  function (name, handler) {
6155            return Plugin.addEvent(name, handler);
6156        };
6157
6158        /**
6159         * Set messenger content
6160         * @returns {mMessenger}
6161         */
6162        messenger.one =  function (name, handler) {
6163            return Plugin.addEvent(name, handler, true);
6164        };        
6165
6166        return messenger;
6167    };
6168
6169    // default options
6170    $.fn.mMessenger.defaults = {
6171       
6172    };
6173}(jQuery));
6174(function($) {
6175    // plugin setup
6176    $.fn.mOffcanvas = function(options) {
6177        // main object
6178        var offcanvas = this;
6179        var element = $(this);
6180
6181        /********************
6182         ** PRIVATE METHODS
6183         ********************/
6184        var Plugin = {
6185            /**
6186             * Run
6187             */
6188            run: function (options) {
6189                if (!element.data('offcanvas')) {                      
6190                    // create instance
6191                    Plugin.init(options);
6192                    Plugin.build();
6193                    
6194                    // assign instance to the element                    
6195                    element.data('offcanvas', offcanvas);
6196                } else {
6197                    // get instance from the element
6198                    offcanvas = element.data('offcanvas');
6199                }               
6200
6201                return offcanvas;
6202            },
6203
6204            /**
6205             * Handles suboffcanvas click toggle
6206             */
6207            init: function(options) {
6208                offcanvas.events = [];
6209
6210                // merge default and user defined options
6211                offcanvas.options = $.extend(true, {}, $.fn.mOffcanvas.defaults, options);
6212
6213                offcanvas.overlay;
6214                
6215                offcanvas.classBase = offcanvas.options.class;
6216                offcanvas.classShown = offcanvas.classBase + '--on';
6217                offcanvas.classOverlay = offcanvas.classBase + '-overlay';
6218                
6219                offcanvas.state = element.hasClass(offcanvas.classShown) ? 'shown' : 'hidden';
6220                offcanvas.close = offcanvas.options.close;
6221
6222                if (offcanvas.options.toggle && offcanvas.options.toggle.target) {
6223                    offcanvas.toggleTarget = offcanvas.options.toggle.target;
6224                    offcanvas.toggleState = offcanvas.options.toggle.state;
6225                } else {
6226                    offcanvas.toggleTarget = offcanvas.options.toggle; 
6227                    offcanvas.toggleState = '';
6228                }
6229            },
6230
6231            /**
6232             * Setup offcanvas
6233             */
6234            build: function() {
6235                // offcanvas toggle
6236                $(offcanvas.toggleTarget).on('click', Plugin.toggle);
6237
6238                if (offcanvas.close) {
6239                    $(offcanvas.close).on('click', Plugin.hide);
6240                }
6241            },
6242
6243            /**
6244             * sync 
6245             */
6246            sync: function () {
6247                $(element).data('offcanvas', offcanvas);
6248            }, 
6249
6250            /**
6251             * Handles offcanvas click toggle
6252             */
6253            toggle: function() {
6254                var el = $(this);
6255
6256                if (offcanvas.state == 'shown') {
6257                    Plugin.hide(el);
6258                } else {
6259                    Plugin.show(el);
6260                }
6261            },
6262
6263            /**
6264             * Handles offcanvas click toggle
6265             */
6266            show: function(el) {
6267                if (offcanvas.state == 'shown') {
6268                    return;
6269                }
6270
6271                var target = el ? $(el) : $(offcanvas.toggleTarget);
6272
6273                Plugin.eventTrigger('beforeShow');
6274
6275                if (offcanvas.toggleState != '') {
6276                    target.addClass(offcanvas.toggleState);
6277                }
6278                
6279                $('body').addClass(offcanvas.classShown);
6280                element.addClass(offcanvas.classShown);
6281
6282                offcanvas.state = 'shown';
6283
6284                if (offcanvas.options.overlay) {
6285                    var overlay = $('<div class="' + offcanvas.classOverlay + '"></div>');                
6286                    element.after(overlay);
6287                    offcanvas.overlay = overlay;
6288                    offcanvas.overlay.on('click', function(e) {
6289                        e.stopPropagation();
6290                        e.preventDefault();
6291                        Plugin.hide();
6292                    });
6293                } 
6294
6295                Plugin.eventTrigger('afterShow');
6296
6297                return offcanvas;
6298            },
6299
6300            /**
6301             * Handles offcanvas click toggle
6302             */
6303            hide: function(el) {
6304                if (offcanvas.state == 'hidden') {
6305                    return;
6306                }
6307
6308                var target = el ? $(el) : $(offcanvas.toggleTarget);
6309                                
6310                Plugin.eventTrigger('beforeHide');
6311
6312                if (offcanvas.toggleState != '') {
6313                    target.removeClass(offcanvas.toggleState);
6314                }
6315
6316                $('body').removeClass(offcanvas.classShown)
6317                element.removeClass(offcanvas.classShown);
6318
6319                offcanvas.state = 'hidden';
6320
6321                if (offcanvas.options.overlay) {
6322                    offcanvas.overlay.remove();
6323                } 
6324
6325                Plugin.eventTrigger('afterHide');
6326
6327                return offcanvas;
6328            },
6329
6330            /**
6331             * Trigger events
6332             */
6333            eventTrigger: function(name) {
6334                for (i = 0; i < offcanvas.events.length; i++) {
6335                    var event = offcanvas.events[i];
6336                    if (event.name == name) {
6337                        if (event.one == true) {
6338                            if (event.fired == false) {
6339                                offcanvas.events[i].fired = true;
6340                                return event.handler.call(this, offcanvas);
6341                            }
6342                        } else {
6343                            return  event.handler.call(this, offcanvas);
6344                        }
6345                    }
6346                }
6347            },
6348
6349            addEvent: function(name, handler, one) {
6350                offcanvas.events.push({
6351                    name: name,
6352                    handler: handler,
6353                    one: one,
6354                    fired: false
6355                });
6356
6357                Plugin.sync();
6358            }
6359        };
6360
6361        // main variables
6362        var the = this;
6363        
6364        // init plugin
6365        Plugin.run.apply(this, [options]);
6366
6367        /********************
6368         ** PUBLIC API METHODS
6369         ********************/
6370
6371        /**
6372         * Hide 
6373         */
6374        offcanvas.hide =  function () {
6375            return Plugin.hide();
6376        };
6377
6378        /**
6379         * Show 
6380         */
6381        offcanvas.show =  function () {
6382            return Plugin.show();
6383        };
6384
6385        /**
6386         * Get suboffcanvas mode
6387         */
6388        offcanvas.on =  function (name, handler) {
6389            return Plugin.addEvent(name, handler);
6390        };
6391
6392        /**
6393         * Set offcanvas content
6394         * @returns {mOffcanvas}
6395         */
6396        offcanvas.one =  function (name, handler) {
6397            return Plugin.addEvent(name, handler, true);
6398        };   
6399
6400        return offcanvas;
6401    };
6402
6403    // default options
6404    $.fn.mOffcanvas.defaults = {
6405        
6406    }; 
6407}(jQuery));
6408(function ($) {
6409    // Plugin function
6410    $.fn.mPortlet = function (options) {
6411        // Plugin scope variable
6412        var portlet = {};
6413        var element = $(this);
6414
6415        // Plugin class
6416        var Plugin = {
6417            /**
6418             * Run
6419             */
6420            run: function (options) {
6421                if (element.data('portlet-object')) {            
6422                    // get instance from the element
6423                    portlet = element.data('portlet-object');
6424                } else {                              
6425                    // create instance                   
6426                    Plugin.init(options);
6427                    Plugin.build();
6428                    
6429                    // assign instance to the element                    
6430                    element.data('portlet-object', portlet);
6431                }               
6432
6433                return portlet;
6434            },
6435
6436            /**
6437             * Initialize
6438             */
6439            init: function(options) {
6440                portlet.options = $.extend(true, {}, $.fn.mPortlet.defaults, options);
6441                portlet.events = [];
6442                portlet.eventOne = false;       
6443
6444                if ( element.find('> .m-portlet__body').length !== 0 ) {
6445                    portlet.body = element.find('> .m-portlet__body');
6446                } else if ( element.find('> .m-form').length !== 0 ) {
6447                    portlet.body = element.find('> .m-form');
6448                }
6449            },
6450
6451            /**
6452             * Build DOM and init event handlers
6453             */
6454            build: function () {
6455                // remove
6456                var remove = element.find('>
6456 .m-portlet__head [data-portlet-tool=remove]');
6457                if (remove.length === 1) {
6458                    remove.click(function(e) {
6459                        e.preventDefault();
6460                        Plugin.remove();
6461                    });
6462                }                 
6463
6464                // reload
6465                var reload = element.find('> .m-portlet__head [data-portlet-tool=reload]')
6466                if (reload.length === 1) {
6467                    reload.click(function(e) {
6468                        e.preventDefault();
6469                        Plugin.reload();
6470                    });
6471                }
6472
6473                // toggle
6474                var toggle = element.find('> .m-portlet__head [data-portlet-tool=toggle]');
6475                if (toggle.length === 1) {
6476                    toggle.click(function(e) {
6477                        e.preventDefault();
6478                        Plugin.toggle();
6479                    });
6480                }
6481
6482                // fullscreen
6483                var fullscreen = element.find('> .m-portlet__head [data-portlet-tool=fullscreen]');
6484                if (fullscreen.length === 1) {
6485                    fullscreen.click(function(e) {
6486                        e.preventDefault();
6487                        Plugin.fullscreen();
6488                    });
6489                }                    
6490
6491                Plugin.setupTooltips();
6492            }, 
6493
6494            /**
6495             * Remove portlet
6496             */
6497            remove: function () {
6498                if (Plugin.eventTrigger('beforeRemove') === false) {
6499                    return;
6500                }
6501
6502                if ( $('body').hasClass('m-portlet--fullscreen') && element.hasClass('m-portlet--fullscreen') ) {
6503                    Plugin.fullscreen('off');
6504                }
6505
6506                Plugin.removeTooltips();
6507
6508                element.remove();
6509                
6510                Plugin.eventTrigger('afterRemove');
6511            }, 
6512
6513            /**
6514             * Set content
6515             */
6516            setContent: function (html) {
6517                if (html) {
6518                    portlet.body.html(html);
6519                }               
6520            },
6521
6522            /**
6523             * Get body
6524             */
6525            getBody: function () {
6526                return portlet.body;
6527            },
6528
6529            /**
6530             * Get self
6531             */
6532            getSelf: function () {
6533                return element;
6534            },
6535
6536            /**
6537             * Setup tooltips
6538             */
6539            setupTooltips: function () {
6540                if (portlet.options.tooltips) {
6541                    var collapsed = element.hasClass('m-portlet--collapse') || element.hasClass('m-portlet--collapsed');
6542                    var fullscreenOn = $('body').hasClass('m-portlet--fullscreen') && element.hasClass('m-portlet--fullscreen');
6543
6544                    var remove = element.find('> .m-portlet__head [data-portlet-tool=remove]');
6545                    if (remove.length === 1) {
6546                        remove.attr('title', portlet.options.tools.remove);
6547                        remove.data('placement', fullscreenOn ? 'bottom' : 'top');
6548                        remove.data('offset', fullscreenOn ? '0,10px,0,0' : '0,5px');
6549                        remove.tooltip('dispose');
6550                        mApp.initTooltip(remove);
6551                    }
6552
6553                    var reload = element.find('> .m-portlet__head [data-portlet-tool=reload]');
6554                    if (reload.length === 1) {
6555                        reload.attr('title', portlet.options.tools.reload);
6556                        reload.data('placement', fullscreenOn ? 'bottom' : 'top');
6557                        reload.data('offset', fullscreenOn ? '0,10px,0,0' : '0,5px');
6558                        reload.tooltip('dispose');
6559                        mApp.initTooltip(reload);
6560                    }
6561
6562                    var toggle = element.find('> .m-portlet__head [data-portlet-tool=toggle]');
6563                    if (toggle.length === 1) {
6564                        if (collapsed) {
6565                            toggle.attr('title', portlet.options.tools.toggle.expand);
6566                        } else {
6567                            toggle.attr('title', portlet.options.tools.toggle.collapse);
6568                        }
6569                        toggle.data('placement', fullscreenOn ? 'bottom' : 'top');
6570                        toggle.data('offset', fullscreenOn ? '0,10px,0,0' : '0,5px');
6571                        toggle.tooltip('dispose');
6572                        mApp.initTooltip(toggle);
6573                    }
6574
6575                    var fullscreen = element.find('>
6575 .m-portlet__head [data-portlet-tool=fullscreen]');
6576                    if (fullscreen.length === 1) {
6577                        if (fullscreenOn) {
6578                            fullscreen.attr('title', portlet.options.tools.fullscreen.off);
6579                        } else {
6580                            fullscreen.attr('title', portlet.options.tools.fullscreen.on);
6581                        }
6582                        fullscreen.data('placement', fullscreenOn ? 'bottom' : 'top');
6583                        fullscreen.data('offset', fullscreenOn ? '0,10px,0,0' : '0,5px');
6584                        fullscreen.tooltip('dispose');
6585                        mApp.initTooltip(fullscreen);
6586                    }                
6587                }                   
6588            },
6589
6590            /**
6591             * Setup tooltips
6592             */
6593            removeTooltips: function () {
6594                if (portlet.options.tooltips) {
6595                    var remove = element.find('> .m-portlet__head [data-portlet-tool=remove]');
6596                    if (remove.length === 1) {
6597                        remove.tooltip('dispose');
6598                    }
6599
6600                    var reload = element.find('> .m-portlet__head [data-portlet-tool=reload]');
6601                    if (reload.length === 1) {
6602                        reload.tooltip('dispose');
6603                    }
6604
6605                    var toggle = element.find('> .m-portlet__head [data-portlet-tool=toggle]');
6606                    if (toggle.length === 1) {
6607                        toggle.tooltip('dispose');
6608                    }
6609
6610                    var fullscreen = element.find('> .m-portlet__head [data-portlet-tool=fullscreen]');
6611                    if (fullscreen.length === 1) {
6612                        fullscreen.tooltip('dispose');
6613                    }                
6614                }                   
6615            },
6616
6617            /**
6618             * Reload
6619             */
6620            reload: function () {
6621                Plugin.eventTrigger('reload');                
6622            },
6623
6624            /**
6625             * Toggle
6626             */
6627            toggle: function () {
6628                if (element.hasClass('m-portlet--collapse') || element.hasClass('m-portlet--collapsed')) {
6629                    Plugin.expand();                    
6630                } else {
6631                    Plugin.collapse();            
6632                }                  
6633            },
6634
6635            /**
6636             * Collapse
6637             */
6638            collapse: function() {
6639                if (Plugin.eventTrigger('beforeCollapse') === false) {
6640                    return;
6641                } 
6642
6643                portlet.body.slideUp(portlet.options.bodyToggleSpeed, function() {                        
6644                    Plugin.eventTrigger('afterCollapse');    
6645                });
6646
6647                element.addClass('m-portlet--collapse');
6648
6649                Plugin.setupTooltips();  
6650            },
6651
6652            /**
6653             * Expand
6654             */
6655            expand: function() {
6656                if (Plugin.eventTrigger('beforeExpand') === false) {
6657                    return;
6658                } 
6659
6660                portlet.body.slideDown(portlet.options.bodyToggleSpeed, function(){                        
6661                    Plugin.eventTrigger('afterExpand');                         
6662                });
6663
6664                element.removeClass('m-portlet--collapse');
6665                element.removeClass('m-portlet--collapsed');
6666
6667                Plugin.setupTooltips();
6668            },
6669
6670            /**
6671             * Toggle
6672             */
6673            fullscreen: function (mode) {
6674                var d = {};
6675                var speed = 300;
6676
6677                if (mode === 'off' || ($('body').hasClass('m-portlet--fullscreen') && element.hasClass('m-portlet--fullscreen'))) {
6678                    Plugin.eventTrigger('beforeFullscreenOff');
6679
6680                    $('body').removeClass('m-portlet--fullscreen');
6681                    element.removeClass('m-portlet--fullscreen');
6682
6683                    Plugin.setupTooltips();
6684                    
6685                    Plugin.eventTrigger('afterFullscreenOff');
6686                } else {
6687                    Plugin.eventTrigger('beforeFullscreenOn');
6688
6689                    element.addClass('m-portlet--fullscreen');
6690                    $('body').addClass('m-portlet--fullscreen');
6691
6692                    Plugin.setupTooltips();
6693                    
6694                    Plugin.eventTrigger('afterFullscreenOn');
6695                }                  
6696            }, 
6697
6698            /**
6699             * sync 
6700             */
6701            sync: function () {
6702                $(element).data('portlet', portlet);
6703            },
6704
6705            /**
6706             * Trigger events
6707             */
6708            eventTrigger: function(name) {
6709                for (i = 0; i < portlet.events.length; i++) {
6710                    var event = portlet.events[i];
6711                    if (event.name == name) {
6712                        if (event.one == true) {
6713                            if (event.fired == false) {
6714                                portlet.events[i].fired = true;
6715                                return event.handler.call(this, portlet);
6716                            }
6717                        } else {
6718                            return  event.handler.call(this, portlet);
6719                        }
6720                    }
6721                }
6722            },
6723
6724            /**
6725             * Add event
6726             */
6727            addEvent: function(name, handler, one) {
6728                portlet.events.push({
6729                    name: name,
6730                    handler: handler,
6731                    one: one,
6732                    fired: false
6733                });
6734
6735                Plugin.sync();
6736
6737                return portlet;
6738            }
6739        };
6740
6741        // Run plugin
6742        Plugin.run.apply(this, [options]);
6743
6744        //////////////////////
6745        // ** Public API ** //
6746        //////////////////////
6747       
6748        /**
6749         * Remove portlet
6750         * @returns {mPortlet}
6751         */
6752        portlet.remove = function () {
6753            return Plugin.remove(html);
6754        };
6755
6756        /**
6757         * Reload portlet
6758         * @returns {mPortlet}
6759         */
6760        portlet.reload = function () {
6761            return Plugin.reload();
6762        };
6763
6764        /**
6765         * Set portlet content
6766         * @returns {mPortlet}
6767         */
6768        portlet.setContent = function (html) {
6769            return Plugin.setContent(html);
6770        };
6771
6772        /**
6773         * Toggle portlet
6774         * @returns {mPortlet}
6775         */
6776        portlet.toggle = function () {
6777            return Plugin.toggle();
6778        };
6779
6780        /**
6781         * Collapse portlet
6782         * @returns {mPortlet}
6783         */
6784        portlet.collapse = function () {
6785            return Plugin.collapse();
6786        };
6787
6788        /**
6789         * Expand portlet
6790         * @returns {mPortlet}
6791         */
6792        portlet.expand = function () {
6793            return Plugin.expand();
6794        };
6795
6796        /**
6797         * Fullscreen portlet
6798         * @returns {mPortlet}
6799         */
6800        portlet.fullscreen = function () {
6801            return Plugin.fullscreen('on');
6802        };
6803
6804        /**
6805         * Fullscreen portlet
6806         * @returns {mPortlet}
6807         */
6808        portlet.unFullscreen = function () {
6809            return Plugin.fullscreen('off');
6810        };
6811
6812        /**
6813         * Get portletbody 
6814         * @returns {jQuery}
6815         */
6816        portlet.getBody = function () {
6817            return Plugin.getBody();
6818        };
6819
6820         /**
6821         * Get portletbody 
6822         * @returns {jQuery}
6823         */
6824        portlet.getSelf = function () {
6825            return Plugin.getSelf();
6826        };
6827
6828        /**
6829         * Set portlet content
6830         * @returns {mPortlet}
6831         */
6832        portlet.on =  function (name, handler) {
6833            return Plugin.addEvent(name, handler);
6834        };
6835
6836        /**
6837         * Set portlet content
6838         * @returns {mPortlet}
6839         */
6840        portlet.one =  function (name, handler) {
6841            return Plugin.addEvent(name, handler, true);
6842        };        
6843
6844        return portlet;
6845    };
6846
6847    // default options
6848    $.fn.mPortlet.defaults = {
6849        bodyToggleSpeed: 400,
6850        tooltips: true,
6851        tools: {
6852            toggle: {
6853                collapse: 'Collapse', 
6854                expand: 'Expand'
6855            },
6856            reload: 'Reload',
6857            remove: 'Remove',
6858            fullscreen: {
6859                on: 'Fullscreen',
6860                off: 'Exit Fullscreen'
6861            }        
6862        }
6863    };
6864}(jQuery));
6865(function($) {
6866    // Plugin function
6867    $.fn.mQuicksearch = function(options) {
6868
6869        // Plugin scope variables
6870        var qs = this;
6871        var element = $(this);
6872        
6873        // Plugin class        
6874        var Plugin = {
6875            /**
6876             * Run plugin 
6877             */
6878            run: function(options) {
6879                if (!element.data('qs')) {
6880                    // init plugin
6881                    Plugin.init(options);
6882                    // build dom
6883                    Plugin.build();                   
6884                    // store the instance in the element's data
6885                    element.data('qs', qs);
6886                } else {
6887                    // retrieve the instance fro the element's data
6888                    qs = element.data('qs'); 
6889                }
6890
6891                return qs;
6892            },
6893
6894            /**
6895             * Init plugin
6896             */
6897            init: function(options) {
6898                // merge default and user defined options
6899                qs.options = $.extend(true, {}, $.fn.mQuicksearch.defaults, options);
6900
6901                // form
6902                qs.form = element.find('form');
6903
6904                // input element
6905                qs.input = $(qs.options.input);
6906
6907                 // close icon
6908                qs.iconClose = $(qs.options.iconClose);
6909
6910                if (qs.options.type == 'default') {
6911                    // search icon
6912                    qs.iconSearch = $(qs.options.iconSearch);
6913                        
6914                    // cancel icon
6915                    qs.iconCancel = $(qs.options.iconCancel);
6916                }               
6917
6918                // dropdown
6919                qs.dropdown = element.mDropdown({mobileOverlay: fal
6919se});
6920
6921                // cancel search timeout
6922                qs.cancelTimeout;
6923
6924                // ajax processing state
6925                qs.processing = false;
6926            }, 
6927
6928            /**
6929             * Build plugin
6930             */
6931            build: function() {
6932                // attach input keyup handler
6933                qs.input.keyup(Plugin.handleSearch);
6934                
6935                if (qs.options.type == 'default') {
6936                    qs.input.focus(Plugin.showDropdown);
6937                    
6938                    qs.iconCancel.click(Plugin.handleCancel);
6939
6940                    qs.iconSearch.click(function() {
6941                        if (mUtil.isInResponsiveRange('tablet-and-mobile')) {
6942                            $('body').addClass('m-header-search--mobile-expanded');
6943                            qs.input.focus();
6944                        }
6945                    });
6946
6947                    qs.iconClose.click(function() {
6948                        if (mUtil.isInResponsiveRange('tablet-and-mobile')) {
6949                            $('body').removeClass('m-header-search--mobile-expanded');
6950                            Plugin.closeDropdown();
6951                        }
6952                    });
6953
6954                } else if (qs.options.type == 'dropdown') {
6955                    qs.dropdown.on('afterShow', function() {
6956                        qs.input.focus();
6957                    });
6958                    qs.iconClose.click(Plugin.closeDropdown);
6959                }               
6960            },
6961
6962            /**
6963             * Search handler
6964             */ 
6965            handleSearch: function(e) { 
6966                var query = qs.input.val();
6967
6968                if (query.length === 0) {
6969//                    qs.dropdown.hide();
6970//                    Plugin.handleCancelIconVisibility('on');
6971//                    Plugin.closeDropdown();
6972                    element.removeClass(qs.options.hasResultClass);
6973                }
6974
6975                if (query.length < qs.options.minLength || qs.processing == true) {
6976                    return;
6977                }
6978
6979                qs.processing = true;
6980                qs.form.addClass(qs.options.spinner);
6981                Plugin.handleCancelIconVisibility('off');
6982                
6983                $.ajax({
6984                    url: qs.options.source,
6985                    data: {query: query},
6986                    dataType: 'html',
6987                    success: function(res) {
6988                        qs.processing = false;
6989                        qs.form.removeClass(qs.options.spinner);
6990                        Plugin.handleCancelIconVisibility('on');
6991                        qs.dropdown.setContent(res).show();
6992                        element.addClass(qs.options.hasResultClass);    
6993                    },
6994                    error: function(res) {
6995                        qs.processing = false;
6996                        qs.form.removeClass(qs.options.spinner);
6997                        Plugin.handleCancelIconVisibility('on');
6998                        qs.dropdown.setContent(qs.options.templates.error.apply(qs, res)).show();  
6999                        element.addClass(qs.options.hasResultClass);   
7000                    }
7001                });
7002            }, 
7003
7004            /**
7005             * Handle cancel icon visibility
7006             */ 
7007            handleCancelIconVisibility: function(status) {
7008                if (qs.options.type == 'dropdown') {
7009                    //return;
7010                }
7011
7012                if (status == 'on') {
7013                    if (qs.input.val().length === 0) {                       
7014                        if (qs.iconCancel) qs.iconCancel.css('visibility', 'hidden');
7015                        if (qs.iconClose) qs.iconClose.css('visibility', 'hidden');
7016                    } else {
7017                        clearTimeout(qs.cancelTimeout);
7018                        qs.cancelTimeout = setTimeout(function() {
7019                            if (qs.iconCancel) qs.iconCancel.css('visibility', 'visible');
7020                            if (qs.iconClose) qs.iconClose.css('visibility', 'visible');
7021                        }, 500);                        
7022                    }
7023                } else {
7024                    if (qs.iconCancel) qs.iconCancel.css('visibility', 'hidden');
7025                    if (qs.iconClose) qs.iconClose.css('visibility', 'hidden');
7026                }
7027            },
7028
7029            /**
7030             * Cancel handler
7031             */ 
7032            handleCancel: function(e) {
7033                qs.input.val('');
7034                qs.iconCancel.css('visibility', 'hidden');
7035                element.removeClass(qs.options.hasResultClass);   
7036                //qs.input.focus();
7037
7038                Plugin.closeDropdown();
7039            },
7040
7041            /**
7042             * Cancel handler
7043             */ 
7044            closeDropdown: function() {
7045                qs.dropdown.hide();
7046            },
7047
7048            /**
7049             * Show dropdown
7050             */ 
7051            showDropdown: function(e) { 
7052                if (qs.dropdown.isShown() == false && qs.input.val().length > qs.options.minLength && qs.processing == false) {
7053                    qs.dropdown.show();
7054                    e.preventDefault();
7055                    e.stopPropagation();
7056                }
7057            }
7058        };
7059
7060        // Run plugin
7061        Plugin.run.apply(qs, [options]);
7062
7063        //////////////////////
7064        // ** Public API ** //
7065        //////////////////////
7066
7067        /**
7068         * Public method
7069         * @returns {mQuicksearch}
7070         */
7071        qs.test = function(time) {
7072        	//Plugin.method(time);
7073        };
7074
7075        // Return plugin object
7076        return qs;
7077    };
7078
7079    // Plugin default options
7080    $.fn.mQuicksearch.defaults = {
7081    	minLength: 1,
7082        maxHeight: 300,
7083    };
7084
7085}(jQuery));
7086(function($) {
7087    // plugin setup
7088    $.fn.mScrollTop = function(options) {
7089        // main object
7090        var scrollTop = this;
7091        var element = $(this);
7092
7093        /********************
7094         ** PRIVATE METHODS
7095         ********************/
7096        var Plugin = {
7097            /**
7098             * Run
7099             */
7100            run: function (options) {
7101                if (!element.data('scrollTop')) {                      
7102                    // create instance
7103                    Plugin.init(options);
7104                    Plugin.build();
7105                    
7106                    // assign instance to the element                    
7107                    element.data('scrollTop', scrollTop);
7108                } else {
7109                    // get instance from the element
7110                    scrollTop = element.data('scrollTop');
7111                }               
7112
7113                return scrollTop;
7114            },
7115
7116            /**
7117             * Handles subscrollTop click scrollTop
7118             */
7119            init: function(options) {
7120                scrollTop.element = element;    
7121                scrollTop.events = [];
7122
7123                // merge default and user defined options
7124                scrollTop.options = $.extend(true, {}, $.fn.mScrollTop.defaults, options);
7125            },
7126
7127            /**
7128             * Setup scrollTop
7129             */
7130            build: function() {
7131                // handle window scroll
7132                if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
7133                    $(window).bind("touchend touchcancel touchleave", function() {
7134                        Plugin.handle();
7135                    });
7136                } else {
7137                    $(window).scroll(function() {
7138                        Plugin.handle();
7139                    });
7140                }
7141
7142                // handle button click 
7143                element.on('click', Plugin.scroll);
7144            },
7145
7146            /**
7147             * sync 
7148             */
7149            sync: function () {
7150                $(element).data('scrollTop', scrollTop);
7151            }, 
7152
7153            /**
7154             * Handles offcanvas click scrollTop
7155             */
7156            handle: function() {
7157                var pos = $(window).scrollTop(); // current vertical position
7158                if (pos > scrollTop.options.offset) {
7159                    $("body").addClass('m-scroll-top--shown');
7160                } else {
7161                    $("body").removeClass('m-scroll-top--shown');
7162                }
7163            },
7164
7165            /**
7166             * Handles offcanvas click scrollTop
7167             */
7168            scroll: function(e) {
7169                e.preventDefault();
7170
7171                $("html, body").animate({
7172                    scrollTop: 0
7173                }, scrollTop.options.speed);
7174            },
7175
7176            /**
7177             * Trigger events
7178             */
7179            eventTrigger: function(name) {
7180                for (i = 0; i < scrollTop.events.length; i++) {
7181                    var event = scrollTop.events[i];
7182                    if (event.name == name) {
7183                        if (event.one == true) {
7184                            if (event.fired == false) {
7185                                scrollTop.events[i].fired = true;
7186                                return event.handler.call(this, scrollTop);
7187                            }
7188                        } else {
7189                            return  event.handler.call(this, scrollTop);
7190                        }
7191                    }
7192                }
7193            },
7194
7195            addEvent: function(name, handler, one) {
7196                scrollTop.events.push({
7197                    name: name,
7198                    handler: handler,
7199                    one: one,
7200                    fired: false
7201                });
7202
7203                Plugin.sync();
7204            }
7205        };
7206
7207        // main variables
7208        var the = this;
7209        
7210        // init plugin
7211        Plugin.run.apply(this, [options]);
7212
7213        /********************
7214         ** PUBLIC API METHODS
7215         ********************/
7216
7217        /**
7218         * Get subscrollTop mode
7219         */
7220        scrollTop.on =  function (name, handler) {
7221            return Plugin.addEvent(name, handler);
7222        };
7223
7224        /**
7225         * Set scrollTop content
7226         * @returns {mScrollTop}
7227         */
7228        scrollTop.one =  function (name, handler) {
7229            return Plugin.addEvent(name, handler, true);
7230        };   
7231
7232        return scrollTop;
7233    };
7234
7235    // default options
7236    $.fn.mScrollTop.defaults = {
7237        offset: 300,
7238        speed: 600
7239    }; 
7240}(jQuery));
7241(function($) {
7242    // plugin setup
7243    $.fn.mToggle = function(options) {
7244        // main object
7245        var toggle = this;
7246        var element = $(this);
7247
7248        /********************
7249         ** PRIVATE METHODS
7250         ********************/
7251        var Plugin = {
7252            /**
7253             * Run
7254             */
7255            run: function (options) {
7256                if (!element.data('toggle')) {                      
7257                    // create instance
7258                    Plugin.init(options);
7259                    Plugin.build();
7260                    
7261                    // assign instance to the element                    
7262                    element.data('toggle', toggle);
7263                } else {
7264                    // get instance from the element
7265                    toggle = element.data('toggle');
7266                }               
7267
7268                return toggle;
7269            },
7270
7271            /**
7272             * Handles subtoggle click toggle
7273             */
7274            init: function(options) {
7275                toggle.element = element;    
7276                toggle.events = [];
7277
7278                // merge default and user defined options
7279                toggle.options = $.extend(true, {}, $.fn.mToggle.defaults, options);
7280
7281                toggle.target = $(toggle.options.target);
7282                toggle.targetState = toggle.options.targetState;
7283                toggle.togglerState = toggle.options.togglerState;
7284
7285                toggle.state = mUtil.hasClasses(toggle.target, toggle.targetState) ? 'on' : 'off';
7286            },
7287
7288            /**
7289             * Setup toggle
7290             */
7291            build: function() {
7292                element.on('click', Plugin.toggle);
7293            },
7294
7295            /**
7296             * sync 
7297             */
7298            sync: function () {
7299                $(element).data('toggle', toggle);
7300            }, 
7301
7302            /**
7303             * Handles offcanvas click toggle
7304             */
7305            toggle: function() {
7306                if (toggle.state == 'off') {
7307                    Plugin.toggleOn();
7308                } else {
7309                    Plugin.toggleOff();
7310                }
7311                Plugin.eventTrigger('toggle');
7312
7313                return toggle;
7314            },
7315
7316            /**
7317             * Handles toggle click toggle
7318             */
7319            toggleOn: function() {
7320                Plugin.eventTrigger('beforeOn');
7321                
7322                toggle.target.addClass(toggle.targetState);
7323
7324                if (toggle.togglerState) {
7325                    element.addClass(toggle.togglerState);
7326                }
7327
7328                toggle.state = 'on';
7329
7330                Plugin.eventTrigger('afterOn');
7331
7332                return toggle;
7333            },
7334
7335            /**
7336             * Handles toggle click toggle
7337             */
7338            toggleOff: function() {
7339                Plugin.eventTrigger('beforeOff');
7340
7341                toggle.target.removeClass(toggle.targetState);
7342
7343                if (toggle.togglerState) {
7344                    element.removeClass(toggle.togglerState);
7345                }
7346
7347                toggle.state = 'off';
7348
7349                Plugin.eventTrigger('afterOff');
7350
7351                return toggle;
7352            },
7353
7354            /**
7355             * Trigger events
7356             */
7357            eventTrigger: function(name) {
7358                toggle.trigger(name);
7359                for (i = 0; i < toggle.events.length; i++) {
7360                    var event = toggle.events[i];
7361                    if (event.name == name) {
7362                        if (event.one == true) {
7363                            if (event.fired == false) {
7364                                toggle.events[i].fired = true;
7365                                return event.handler.call(this, toggle);
7366                            }
7367                        } else {
7368                            return  event.handler.call(this, toggle);
7369                        }
7370                    }
7371                }
7372            },
7373
7374            addEvent: function(name, handler, one) {
7375                toggle.events.push({
7376                    name: name,
7377                    handler: handler,
7378                    one: one,
7379                    fired: false
7380                });
7381
7382                Plugin.sync();
7383
7384                return toggle;
7385            }
7386        };
7387
7388        // main variables
7389        var the = this;
7390        
7391        // init plugin
7392        Plugin.run.apply(this, [options]);
7393
7394        /********************
7395         ** PUBLIC API METHODS
7396         ********************/
7397
7398
7399        /**
7400         * Get toggle state 
7401         */
7402        toggle.getState =  function () {
7403            return toggle.state;
7404        };
7405
7406        /**
7407         * Toggle 
7408         */
7409        toggle.toggle =  function () {
7410            return Plugin.toggle();
7411        };
7412
7413        /**
7414         * Toggle on 
7415         */
7416        toggle.toggleOn =  function () {
7417            return Plugin.toggleOn();
7418        };
7419
7420        /**
7421         * Toggle off 
7422         */
7423        toggle.toggleOff =  function () {
7424            return Plugin.toggleOff();
7425        };
7426
7427        /**
7428         * Attach event
7429         * @returns {mToggle}
7430         */
7431        toggle.on =  function (name, handler) {
7432            return Plugin.addEvent(name, handler);
7433        };
7434
7435        /**
7436         * Attach event that will be fired once
7437         * @returns {mToggle}
7438         */
7439        toggle.one =  function (name, handler) {
7440            return Plugin.addEvent(name, handler, true);
7441        };     
7442
7443        return toggle;
7444    };
7445
7446    // default options
7447    $.fn.mToggle.defaults = {
7448        togglerState: '',
7449        targetState: ''
7450    }; 
7451}(jQuery));
7452(function($) {
7453    // plugin setup
7454    $.fn.mWizard = function(options) {
7455        //== Main object
7456        var wizard = this;
7457        var element = $(this);
7458
7459        /********************
7460         ** PRIVATE METHODS
7461         ********************/
7462        var Plugin = {
7463            /**
7464             * Run
7465             */
7466            run: function (options) {
7467                if (!element.data('wizard')) {                      
7468                    //== Create instance
7469                    Plugin.init(options);
7470                    Plugin.build();
7471                    
7472                    //== Assign instance to the element                    
7473                    element.data('wizard', wizard);
7474                } else {
7475                    // get instance from the element
7476                    wizard = element.data('wizard');
7477                }               
7478
7479                return wizard;
7480            },
7481
7482            /**
7483             * Initialize Form Wizard
7484             */
7485            init: function(options) {
7486                //== Elements
7487                wizard.steps = wizard.find('.m-wizard__step');
7488                wizard.progress = wizard.find('.m-wizard__progress .progress-bar'); 
7489                wizard.btnSubmit = wizard.find('[data-wizard-action="submit"]'); 
7490                wizard.btnNext = wizard.find('[data-wizard-action="next"]'); 
7491                wizard.btnPrev = wizard.find('[data-wizard-action="prev"]'); 
7492                wizard.btnLast = wizard.find('[data-wizard-action="last"]'); 
7493                wizard.btnFirst = wizard.find('[data-wizard-action="first"]');  
7494
7495                //== Merge default and user defined options
7496                wizard.options = $.extend(true, {}, $.fn.mWizard.defaults, options);
7497
7498                //== Variables
7499                wizard.events = [];
7500                wizard.currentStep = 1;
7501                wizard.totalSteps = wizard.steps.length;  
7502
7503                //== Init current step
7504                if (wizard.options.startStep > 1) {
7505                    Plugin.goTo(wizard.options.startStep);
7506                }       
7507
7508                //== Init UI
7509                Plugin.updateUI();
7510            },
7511
7512            /**
7513             * Build Form Wizard
7514             */
7515            build: function() {
7516                //== Next button event handler
7517                wizard.btnNext.on('click', function (e) {
7518                    e.preventDefault();
7519                    Plugin.goNext();
7520                });
7521
7522                //== Prev button event handler
7523                wizard.btnPrev.on('click', function (e) {
7524                    e.preventDefault();
7525                    Plugin.goPrev();
7526                });
7527
7528                //== First button event handler
7529                wizard.btnFirst.on('click', function (e) {
7530                    e.preventDefault();
7531                    Plugin.goFirst();
7532                });
7533
7534                //== Last button event handler
7535                wizard.btnLast.on('click', function (e) {
7536                    e.preventDefault();
7537                    Plugin.goLast();
7538                });
7539
7540                wizard.find('.m-wizard__step a.m-wizard__step-number').on('click', function() {
7541                    var step = $(this).parents('.m-wizard__step');
7542                    var num;
7543                    $(this).parents('.m-wizard__steps').find('.m-wizard__step').each(function(index) {
7544                        if (step.is( $(this) )) {
7545                            num = (index + 1);
7546                            return;
7547                        }
7548                    });
7549
7550                    if (num) {
7551                        Plugin.goTo(num);
7552                    }                    
7553                });
7554            },
7555
7556            /**
7557             * Sync object instance
7558             */
7559            sync: function () {
7560                $(element).data('wizard', wizard);
7561            }, 
7562
7563            /**
7564             * Handles wizard click toggle
7565             */
7566            goTo: function(number) {
7567                //== Skip if this step is already shown
7568                if (number === wizard.currentStep) {
7569                    return;
7570                }
7571
7572                //== Validate step number
7573                if (number) {
7574                    number = parseInt(number); 
7575                } else {
7576                    number = Plugin.getNextStep();
7577                }
7578
7579                //== Before next and prev events
7580                var callback;
7581
7582                if (number > wizard.currentStep) {
7583                    callback = Plugin.eventTrigger('beforeNext');
7584                } else {
7585                    callback = Plugin.eventTrigger('beforePrev');
7586                }
7587
7588                //== Continue if no exit
7589                if (callback !== false) {
7590                    //== Set current step
7591                    wizard.currentStep = number;
7592
7593                    //== Update UI
7594                    Plugin.updateUI();             
7595
7596                    //== Trigger change event
7597                    Plugin.eventTrigger('change')       
7598                }
7599                
7600                //== After next and prev events
7601                if (number > wizard.startStep) {
7602                    Plugin.eventTrigger('afterNext');
7603                } else {
7604                    Plugin.eventTrigger('afterPrev');
7605                }
7606
7607                return wizard;
7608         
7608   },
7609
7610            updateUI: function(argument) {
7611                //== Update progress bar
7612                Plugin.updateProgress();
7613
7614                //== Show current target content
7615                Plugin.handleTarget();
7616
7617                //== Set classes
7618                Plugin.setStepClass();
7619
7620                //== Apply nav step classes
7621                wizard.find('.m-wizard__step').removeClass('m-wizard__step--current').removeClass('m-wizard__step--done');
7622                for (var i = 1; i < wizard.currentStep; i++) {
7623                    wizard.find('.m-wizard__step').eq(i - 1).addClass('m-wizard__step--done');
7624                }
7625                wizard.find('.m-wizard__step').eq(wizard.currentStep - 1).addClass('m-wizard__step--current');
7626            },
7627
7628            /**
7629             * Check last step
7630             */
7631            isLastStep: function() {
7632                return wizard.currentStep === wizard.totalSteps;
7633            },
7634
7635            /**
7636             * Check first step
7637             */
7638            isFirstStep: function() {
7639                return wizard.currentStep === 1;
7640            },
7641
7642            /**
7643             * Check between step
7644             */
7645            isBetweenStep: function() {
7646                return Plugin.isLastStep() === false && Plugin.isFirstStep() === false;
7647            },
7648
7649            /**
7650             * Set step class
7651             */
7652            setStepClass: function() {
7653                if (Plugin.isLastStep()) {
7654                    element.addClass('m-wizard--step-last');
7655                } else {
7656                    element.removeClass('m-wizard--step-last');
7657                }
7658
7659                if (Plugin.isFirstStep()) {
7660                    element.addClass('m-wizard--step-first');
7661                } else {
7662                    element.removeClass('m-wizard--step-first');
7663                }
7664
7665                if (Plugin.isBetweenStep()) {
7666                    element.addClass('m-wizard--step-between');
7667                } else {
7668                    element.removeClass('m-wizard--step-between');
7669                }
7670            },
7671
7672            /**
7673             * Go to the next step
7674             */
7675            goNext: function() {
7676                return Plugin.goTo( Plugin.getNextStep() );
7677            },
7678
7679            /**
7680             * Go to the prev step
7681             */
7682            goPrev: function() {
7683                return Plugin.goTo( Plugin.getPrevStep() );
7684            },
7685
7686            /**
7687             * Go to the last step
7688             */
7689            goLast: function() {
7690                return Plugin.goTo( wizard.totalSteps );
7691            },
7692
7693            /**
7694             * Go to the first step
7695             */
7696            goFirst: function() {
7697                return Plugin.goTo( 1 );
7698            },
7699
7700            /**
7701             * Set progress
7702             */
7703            updateProgress: function() {
7704                //== Calculate progress position
7705
7706                if (!wizard.progress) {
7707                    return;
7708                } 
7709
7710                //== Update progress
7711                if (element.hasClass('m-wizard--1')) {
7712                    var width = 100 * ((wizard.currentStep) / (wizard.totalSteps));
7713                    var offset = element.find('.m-wizard__step-number').width();
7714                    wizard.progress.css('width', 'calc(' + width + '% + ' + (offset / 2)  + 'px)');
7715                } else if (element.hasClass('m-wizard--2')) {
7716                    if (wizard.currentStep === 1) {
7717                        return;
7718                    }
7719
7720                    var step = element.find('.m-wizard__step').eq(0);
7721                    var progress = (wizard.currentStep - 1) * (100 * (1 / (wizard.totalSteps - 1)));
7722
7723                    if (mUtil.isInResponsiveRange('minimal-desktop-and-below')) {  
7724                        wizard.progress.css('height', progress + '%');
7725                    } else {
7726                        wizard.progress.css('width', progress + '%');
7727                    }
7728                } else {
7729                    var width = 100 * ((wizard.currentStep) / (wizard.totalSteps));
7730                    wizard.progress.css('width', width + '%'); 
7731                }             
7732            },
7733
7734            /**
7735             * Show/hide target content
7736             */
7737            handleTarget: function() {
7738                var step = wizard.steps.eq(wizard.currentStep - 1);
7739                var target = element.find( step.data('wizard-target') );
7740
7741                element.find('.m-wizard__form-step--current').removeClass('m-wizard__form-step--current');
7742                target.addClass('m-wizard__form-step--current');
7743            },
7744
7745            /**
7746             * Get next step
7747             */
7748            getNextStep: function() {
7749                if (wizard.totalSteps >= (wizard.currentStep + 1)) {
7750                    return wizard.currentStep + 1;
7751                } else {
7752                    return wizard.totalSteps;
7753                } 
7754            },
7755
7756            /**
7757             * Get prev step
7758             */
7759            getPrevStep: function() {
7760                if ((wizard.currentStep - 1) >= 1) {
7761                    return wizard.currentStep - 1;
7762                } else {
7763                    return 1;
7764                } 
7765            },
7766
7767            /**
7768             * Trigger event
7769             */
7770            eventTrigger: function(name) {
7771                for (i = 0; i < wizard.events.length; i++) {
7772                    var event = wizard.events[i];
7773                    if (event.name == name) {
7774                        if (event.one == true) {
7775                            if (event.fired == false) {
7776                                wizard.events[i].fired = true;
7777                                return event.handler.call(this, wizard);
7778                            }
7779                        } else {
7780                            return  event.handler.call(this, wizard);
7781                        }
7782                    }
7783                }
7784            },
7785
7786            /**
7787             * Register event
7788             */
7789            addEvent: function(name, handler, one) {
7790                wizard.events.push({
7791                    name: name,
7792                    handler: handler,
7793                    one: one,
7794                    fired: false
7795                });
7796
7797                Plugin.sync();
7798            }
7799        };
7800
7801        //== Main variables
7802        var the = this;
7803        
7804        //== Init plugin
7805        Plugin.run.apply(this, [options]);
7806
7807        /********************
7808         ** PUBLIC API METHODS
7809         ********************/
7810
7811        /**
7812         * Go to the next step 
7813         */
7814        wizard.goNext =  function () {
7815            return Plugin.goNext();
7816        };
7817
7818        /**
7819         * Go to the prev step 
7820         */
7821        wizard.goPrev =  function () {
7822            return Plugin.goPrev();
7823        };
7824
7825        /**
7826         * Go to the last step 
7827         */
7828        wizard.goLast =  function () {
7829            return Plugin.goLast();
7830        };
7831
7832        /**
7833         * Go to the first step 
7834         */
7835        wizard.goFirst =  function () {
7836            return Plugin.goFirst();
7837        };
7838
7839         /**
7840         * Go to a step
7841         */
7842        wizard.goTo =  function ( number ) {
7843            return Plugin.goTo( number );
7844        };
7845
7846        /**
7847         * Get current step number 
7848         */
7849        wizard.getStep =  function () {
7850            return wizard.currentStep;
7851        };
7852
7853        /**
7854         * Check last step 
7855         */
7856        wizard.isLastStep =  function () {
7857            return Plugin.isLastStep();
7858        };
7859
7860        /**
7861         * Check first step 
7862         */
7863        wizard.isFirstStep =  function () {
7864            return Plugin.isFirstStep();
7865        };
7866
7867        /**
7868         * Attach event
7869         * @returns {mwizard}
7870         */
7871        wizard.on =  function (name, handler) {
7872            return Plugin.addEvent(name, handler);
7873        };
7874
7875        /**
7876         * Attach event that will be fired once
7877         * @returns {mwizard}
7878         */
7879        wizard.one =  function (name, handler) {
7880            return Plugin.addEvent(name, handler, true);
7881        };   
7882
7883        return wizard;
7884    };
7885
7886    //== Default options
7887    $.fn.mWizard.defaults = {
7888        startStep: 1
7889    }; 
7890}(jQuery));
7891(function($) {
7892
7893	$.fn.mDatatable = $.fn.mDatatable || {};
7894
7895	/**
7896	 * @param datatable Main datatable plugin instance
7897	 * @param options Extension options
7898	 * @returns {*}
7899	 */
7900	$.fn.mDatatable.checkbox = function(datatable, options) {
7901
7902		var Extension = {
7903			selectedAllRows: false,
7904			selectedRows: [],
7905			unselectedRows: [],
7906
7907			init: function() {
7908				if (Extension.selectorEnabled()) {
7909					// requestIds is not null
7910					if (options.vars.requestIds) {
7911						// request ids in response
7912						datatable.setDataSourceParam(options.vars.requestIds, true);
7913					}
7914
7915					// select all on extension init
7916					Extension.selectedAllRows = datatable.getDataSourceParam(options.vars.selectedAllRows);
7917
7918					$(datatable).on('m-datatable--on-layout-updated', function(e, args) {
7919						if (args.table != $(datatable.wrap).attr('id')) return;
7920						datatable.ready(function() {
7921							Extension.initVars();
7922							Extension.initEvent();
7923							Extension.initSelect();
7924						});
7925					});
7926				}
7927			},
7928
7929			/**
7930			 * Init checkbox clicks event
7931			 */
7932			initEvent: function() {
7933				// select all checkbox click
7934				$(datatable.tableHead).find('.m-checkbox--all > [type="checkbox"]').click(function(e) {
7935					// clear selected and unselected rows
7936					Extension.selectedRows = Extension.unselectedRows = [];
7937					datatable.stateRemove('checkbox');
7938
7939					// select all rows
7940					if ($(this).is(':checked')) {
7941						Extension.selectedAllRows = true;
7942					} else {
7943						Extension.selectedAllRows = false;
7944					}
7945
7946					// local select all current page rows
7947					if (!options.vars.requestIds) {
7948						if ($(this).is(':checked')) {
7949							Extension.selectedRows = $.makeArray($(datatable.tableBody).find('.m-checkbox--single > [type="checkbox"]').
7950								map(function(i, chk) {
7951									return $(chk).val();
7952								}));
7953						}
7954						var storage = {};
7955						storage['selectedRows'] = $.unique(Extension.selectedRows);
7956						datatable.stateKeep('checkbox', storage);
7957					}
7958
7959					// keep selectedAllRows in datasource params
7960					datatable.setDataSourceParam(options.vars.selectedAllRows, Extension.selectedAllRows);
7961
7962					$(datatable).trigger('m-datatable--on-click-checkbox', [$(this)]);
7963				});
7964
7965				// single row checkbox click
7966				$(datatable.tableBody).find('.m-checkbox--single > [type="checkbox"]').click(function(e) {
7967					var id = $(this).val();
7968					if ($(this).is(':checked')) {
7969						Extension.selectedRows.push(id);
7970						// remove from unselected rows
7971						Extension.unselectedRows = Extension.remove(Extension.unselectedRows, id);
7972					} else {
7973						Extension.unselectedRows.push(id);
7974						// remove from selected rows
7975						Extension.selectedRows = Extension.remove(Extension.selectedRows, id);
7976					}
7977
7978					// local checkbox header check
7979					if (!options.vars.requestIds && Extension.selectedRows.length < 1) {
7980						// remove select all checkbox, if there is no checked checkbox left
7981						$(datatable.tableHead).find('.m-checkbox--all > [type="checkbox"]').prop('checked', false);
7982					}
7983
7984					var storage = {};
7985					storage['selectedRows'] = $.unique(Extension.selectedRows);
7986					storage['unselectedRows'] = $.unique(Extension.unselectedRows);
7987					datatable.stateKeep('checkbox', storage);
7988
7989					$(datatable).trigger('m-datatable--on-click-checkbox', [$(this)]);
7990				});
7991			},
7992
7993			initSelect: function() {
7994				// selected all rows from server
7995				if (Extension.selectedAllRows && options.vars.requestIds) {
7996					if (!datatable.hasClass('m-datatable--error')) {
7997						// set header select all checkbox checked
7998						$(datatable.tableHead).find('.m-checkbox--all > [type="checkbox"]').prop('checked', true);
7999					}
8000
8001					// set all checkbox in table body
8002					datatable.setActiveAll(true);
8003
8004					// remove unselected rows
8005					Extension.unselectedRows.forEach(function(id) {
8006						datatable.setInactive(id);
8007					});
8008
8009				} else {
8010					// single check for server and local
8011					Extension.selectedRows.forEach(function(id) {
8012						datatable.setActive(id);
8013					});
8014
8015					// local checkbox; check if all checkboxes of currect page are checked
8016					if (!datatable.hasClass('m-datatable--error') && $(datatable.tableBody).find('.m-checkbox--single > [type="checkbox"]').not(':checked').length < 1) {
8017						// set header select all checkbox checked
8018						$(datatable.tableHead).find('.m-checkbox--all > [type="checkbox"]').prop('checked', true);
8019					}
8020				}
8021			},
8022
8023			/**
8024			 * Check if selector is enabled from options
8025			 */
8026			selectorEnabled: function() {
8027				return $.grep(datatable.options.columns, function(n, i) {
8028					return n.selector || false;
8029				})[0];
8030			},
8031
8032			initVars: function() {
8033				// get single select/unselect from localstorage
8034				var storage = datatable.stateGet('checkbox');
8035				if (typeof storage !== 'undefined') {
8036					Extension.selectedRows = storage['selectedRows'] || [];
8037					Extension.unselectedRows = storage['unselectedRows'] || [];
8038				}
8039			},
8040
8041			getSelectedId: function(path) {
8042				Extension.initVars();
8043
8044				// server selected all rows
8045				if (Extension.selectedAllRows && options.vars.requestIds) {
8046					if (typeof path === 'undefined') path = options.vars.rowIds;
8047
8048					// if selected all rows, return id from response meta
8049					var selectedAllRows = datatable.getObject(path, datatable.lastResponse) || [];
8050
8051					if (selectedAllRows.length > 0) {
8052						// remove single unselected rows from selectedAllRows ids from server response emta
8053						Extension.unselectedRows.forEach(function(id) {
8054							selectedAllRows = Extension.remove(selectedAllRows, parseInt(id));
8055						});
8056					}
8057					return selectedAllRows;
8058				}
8059
8060				// else return single checked selected rows
8061				return Extension.selectedRows;
8062			},
8063
8064			remove: function(array, element) {
8065				return array.filter(function(e) {
8066					return e !== element;
8067				});
8068			},
8069		};
8070
8071		// make the extension accessible from datatable init
8072		datatable.checkbox = function() {
8073			return Extension;
8074		};
8075
8076		if (typeof options === 'object') {
8077			options = $.extend(true, {}, $.fn.mDatatable.checkbox.default, options);
8078			Extension.init.apply(this, [options]);
8079		}
8080
8081		return datatable;
8082	};
8083
8084	$.fn.mDatatable.checkbox.default = {
8085		vars: {
8086			// select all rows flag to be sent to the server
8087			selectedAllRows: 'selectedAllRows',
8088			// request id parameter's name
8089			requestIds: 'requestIds',
8090			// response path to all rows id
8091			rowIds: 'meta.rowIds',
8092		},
8093	};
8094
8095}(jQuery));
8096//== Set defaults
8097
8098$.notifyDefaults({
8099	template: '' +
8100	'<div data-notify="container" class="alert alert-{0} m-alert" role="alert">
8100' +
8101	'<button type="button" aria-hidden="true" class="close" data-notify="dismiss"></button>' +
8102	'<span data-notify="icon"></span>' +
8103	'<span data-notify="title">{1}</span>' +
8104	'<span data-notify="message">{2}</span>' +
8105	'<div class="progress" data-notify="progressbar">' +
8106	'<div class="progress-bar progress-bar-animated bg-{0}" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"></div>' +
8107	'</div>' +
8108	'<a href="{3}" target="{4}" data-notify="url"></a>' +
8109	'</div>'
8110});
8111//== Set defaults
8112swal.setDefaults({
8113	width: 400,
8114	padding: '2.5rem',
8115	buttonsStyling: false,
8116	confirmButtonClass: 'btn btn-success m-btn m-btn--custom',
8117	confirmButtonColor: null,
8118	cancelButtonClass: 'btn btn-secondary m-btn m-btn--custom',
8119	cancelButtonColor: null
8120});
8121Chart.elements.Rectangle.prototype.draw = function() {    
8122    var ctx = this._chart.ctx;
8123    var vm = this._view;
8124    var left, right, top, bottom, signX, signY, borderSkipped, radius;
8125    var borderWidth = vm.borderWidth;
8126
8127    // Set Radius Here
8128    // If radius is large enough to cause drawing errors a max radius is imposed
8129    var cornerRadius = this._chart.options.barRadius ? this._chart.options.barRadius : 0;
8130
8131    if (!vm.horizontal) {
8132        // bar
8133        left = vm.x - vm.width / 2;
8134        right = vm.x + vm.width / 2;
8135
8136        if (vm.y > 2 * cornerRadius) {
8137        	top = vm.y - cornerRadius;        
8138        } else {
8139        	top = vm.y;        
8140        }
8141
8142        bottom = vm.base;
8143        signX = 1;
8144        signY = bottom > top? 1: -1;
8145        borderSkipped = vm.borderSkipped || 'bottom';
8146        //console.log(vm.base + '-' + vm.y);
8147    } else {
8148        // horizontal bar
8149        left = vm.base;
8150        right = vm.x;
8151        top = vm.y - vm.height / 2;
8152        bottom = vm.y + vm.height / 2;
8153        signX = right > left? 1: -1;
8154        signY = 1;
8155        borderSkipped = vm.borderSkipped || 'left';
8156    }
8157
8158    // Canvas doesn't allow us to stroke inside the width so we can
8159    // adjust the sizes to fit if we're setting a stroke on the line
8160    if (borderWidth) {
8161        // borderWidth shold be less than bar width and bar height.
8162        var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
8163        borderWidth = borderWidth > barSize? barSize: borderWidth;
8164        var halfStroke = borderWidth / 2;
8165        // Adjust borderWidth when bar top position is near vm.base(zero).
8166        var borderLeft = left + (borderSkipped !== 'left'? halfStroke * signX: 0);
8167        var borderRight = right + (borderSkipped !== 'right'? -halfStroke * signX: 0);
8168        var borderTop = top + (borderSkipped !== 'top'? halfStroke * signY: 0);
8169        var borderBottom = bottom + (borderSkipped !== 'bottom'? -halfStroke * signY: 0);
8170        // not become a vertical line?
8171        if (borderLeft !== borderRight) {
8172            top = borderTop;
8173            bottom = borderBottom;
8174        }
8175        // not become a horizontal line?
8176        if (borderTop !== borderBottom) {
8177            left = borderLeft;
8178            right = borderRight;
8179        }
8180    }
8181
8182    ctx.beginPath();
8183    ctx.fillStyle = vm.backgroundColor;
8184    ctx.strokeStyle = vm.borderColor;
8185    ctx.lineWidth = borderWidth;
8186
8187    // Corner points, from bottom-left to bottom-right clockwise
8188    // | 1 2 |
8189    // | 0 3 |
8190    var corners = [
8191        [left, bottom],
8192        [left, top],
8193        [right, top],
8194        [right, bottom]
8195    ];
8196
8197    // Find first (starting) corner with fallback to 'bottom'
8198    var borders = ['bottom', 'left', 'top', 'right'];
8199    var startCorner = borders.indexOf(borderSkipped, 0);
8200    if (startCorner === -1) {
8201        startCorner = 0;
8202    }
8203
8204    function cornerAt(index) {
8205        return corners[(startCorner + index) % 4];
8206    }
8207
8208    // Draw rectangle from 'startCorner'
8209    var corner = cornerAt(0);
8210    ctx.moveTo(corner[0], corner[1]);
8211
8212    for (var i = 1; i < 4; i++) {
8213        corner = cornerAt(i);
8214        nextCornerId = i+1;
8215        if(nextCornerId == 4){
8216            nextCornerId = 0
8217        }
8218
8219        nextCorner = cornerAt(nextCornerId);
8220
8221        width = corners[2][0] - corners[1][0];
8222        height = corners[0][1] - corners[1][1];
8223        x = corners[1][0];
8224        y = corners[1][1];
8225        
8226        var radius = cornerRadius;
8227        
8228        // Fix radius being too large
8229        if(radius > height/2){
8230            radius = height/2;
8231        }if(radius > width/2){
8232            radius = width/2;
8233        }
8234
8235        ctx.moveTo(x + radius, y);
8236        ctx.lineTo(x + width - radius, y);
8237        ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
8238        ctx.lineTo(x + width, y + height - radius);
8239        ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
8240        ctx.lineTo(x + radius, y + height);
8241        ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
8242        ctx.lineTo(x, y + radius);
8243        ctx.quadraticCurveTo(x, y, x + radius, y);
8244    }
8245
8246    ctx.fill();
8247    if (borderWidth) {
8248        ctx.stroke();
8249    }
8250}; 
8251
8252  $.fn.markdown.defaults.iconlibrary = 'fa';
8253//$.fn.bootstrapSwitch.defaults.size = 'large';
8254//$.fn.bootstrapSwitch.defaults.onColor = 'success';
8255$.fn.timepicker.defaults = $.extend(true, {}, $.fn.timepicker.defaults, {
8256    icons: {
8257        up: 'la la-angle-up',
8258        down: 'la la-angle-down'  
8259    }
8260});
8261jQuery.validator.setDefaults({
8262    errorElement: 'div', //default input error message container
8263    errorClass: 'form-control-feedback', // default input error message class
8264    focusInvalid: false, // do not focus the last invalid input
8265    ignore: "",  // validate all fields including form hidden input
8266
8267    errorPlacement: function(error, element) { // render error placement for each input type
8268        var group = $(element).closest('.m-form__group-sub').length > 0 ? $(element).closest('.m-form__group-sub') : $(element).closest('.m-form__group');
8269        var help = group.find('.m-form__help');
8270
8271        if (group.find('.form-control-feedback').length !== 0) {
8272            return;
8273        }
8274
8275        if (help.length > 0) {
8276            help.before(error);
8277        } else {
8278            if ($(element).closest('.input-group').length > 0) {
8279                $(element).closest('.input-group').after(error);
8280            } else {
8281                if ($(element).is(':checkbox')) {
8282                    $(element).closest('.m-checkbox').find('>span').after(error);
8283                } else {
8284                    $(element).after(error);
8285                }                
8286            }            
8287        }
8288    },
8289
8290    highlight: function(element) { // hightlight error inputs
8291        var group = $(element).closest('.m-form__group-sub').length > 0  ? $(element).closest('.m-form__group-sub') : $(element).closest('.m-form__group');
8292
8293        console.log('add' + group.attr('class'));
8294
8295        group.addClass('has-danger'); // set error class to the control groupx
8296    },
8297
8298    unhighlight: function(element) { // revert the change done by hightlight
8299        var group = $(element).closest('.m-form__group-sub').length > 0  ? $(element).closest('.m-form__group-sub') : $(element).closest('.m-form__group');
8300
8301        group.removeClass('has-danger'); // set error class to the control group
8302    },
8303
8304    success: function(label, element) {
8305        var group = $(label).closest('.m-form__group-sub').length > 0  ? $(label).closest('.m-form__group-sub') : $(label).closest('.m-form__group');
8306
8307        //group.addClass('has-success').removeClass('has-danger'); // set success class and hide error class
8308        group.removeClass('has-danger'); // hide error class
8309        group.find('.form-control-feedback').remove();
8310    }
8311});
8312
8313jQuery.validator.addMethod("email", function(value, element) {
8314    if (/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/.test(value)) {
8315        return true;
8316    } else {
8317        return false;
8318    }
8319}, "Please enter a valid Email.");
8320var mLayout = function() {
8321    var horMenu;
8322    var asideMenu;
8323    var asideMenuOffcanvas;
8324    var horMenuOffcanvas;
8325
8326    var initStickyHeader = function() {
8327        var header = $('.m-header');
8328        var options = {
8329            offset: {},
8330            minimize:{}       
8331        };
8332
8333        if (header.data('minimize-mobile') == 'hide') {
8334            options.minimize.mobile = {};
8335            options.minimize.mobile.on = 'm-header--hide';
8336            options.minimize.mobile.off = 'm-header--show';
8337        } else {
8338            options.minimize.mobile = false;
8339        }
8340
8341        if (header.data('minimize') == 'hide') {
8342            options.minimize.desktop = {};
8343            options.minimize.desktop.on = 'm-header--hide';
8344            options.minimize.desktop.off = 'm-header--show';
8345        } else {
8346            options.minimize.desktop = false;
8347        }
8348
8349        if (header.data('minimize-offset')) {
8350            options.offset.desktop = header.data('minimize-offset');
8351        }
8352
8353        if (header.data('minimize-mobile-offset')) {
8354            options.offset.mobile = header.data('minimize-mobile-offset');
8355        }        
8356
8357        header.mHeader(options);
8358    }
8359
8360    // handle horizontal menu
8361    var initHorMenu = function() { 
8362        // init aside left offcanvas
8363        horMenuOffcanvas = $('#m_header_menu').mOffcanvas({
8364            class: 'm-aside-header-menu-mobile',
8365            overlay: true,
8366            close: '#m_aside_header_menu_mobile_close_btn',
8367            toggle: {
8368                target: '#m_aside_header_menu_mobile_toggle',
8369                state: 'm-brand__toggler--active'
8370            }            
8371        });
8372        
8373        horMenu = $('#m_header_menu').mMenu({
8374            // submenu modes
8375            submenu: {
8376                desktop: 'dropdown',
8377                tablet: 'accordion',
8378                mobile: 'accordion'
8379            },
8380            // resize menu on window resize
8381            resize: {
8382                desktop: function() {
8383                    var headerNavWidth = $('#m_header_nav').width();
8384                    var headerMenuWidth = $('#m_header_menu_container').width();
8385                    var headerTopbarWidth = $('#m_header_topbar').width();
8386                    var spareWidth = 20;
8387
8388                    if ((headerMenuWidth + headerTopbarWidth + spareWidth) > headerNavWidth ) {
8389                        return false;
8390                    } else {
8391                        return true;
8392                    }
8393                }
8394            }    
8395        });
8396    }
8397
8398    // handle vertical menu
8399    var initLeftAsideMenu = function() {
8400        var menu = $('#m_ver_menu');
8401
8402        // init aside menu
8403        var menuOptions = {  
8404            // submenu setup
8405            submenu: {
8406                desktop: {
8407                    // by default the menu mode set to accordion in desktop mode
8408                    default: (menu.data('menu-dropdown') == true ? 'dropdown' : 'accordion'),
8409                    // whenever body has this class switch the menu mode to dropdown
8410                    state: {
8411                        body: 'm-aside-left--minimize',  
8412                        mode: 'dropdown'
8413                    }
8414                },
8415                tablet: 'accordion', // menu set to accordion in tablet mode
8416                mobile: 'accordion'  // menu set to accordion in mobile mode
8417            },
8418
8419            //accordion setup
8420            accordion: {
8421                autoScroll: true,
8422                expandAll: false
8423            }
8424        };
8425
8426        asideMenu = menu.mMenu(menuOptions);
8427
8428        // handle fixed aside menu
8429        if (menu.data('menu-scrollable')) {
8430            function initScrollableMenu(obj) {    
8431                if (mUtil.isInResponsiveRange('tablet-and-mobile')) {
8432                    // destroy if the instance was previously created
8433                    mApp.destroyScroller(obj);
8434                    return;
8435                }
8436
8437                var height = mUtil.getViewPort().height - $('.m-header').outerHeight()
8438                    - ($('.m-aside-left .m-aside__header').length != 0 ? $('.m-aside-left .m-aside__header').outerHeight() : 0)
8439                    - ($('.m-aside-left .m-aside__footer').length != 0 ? $('.m-aside-left .m-aside__footer').outerHeight() : 0);
8440                    //- $('.m-footer').outerHeight(); 
8441
8442                // create/re-create a new instance
8443                mApp.initScroller(obj, {height: height});
8444            }
8445
8446            initScrollableMenu(asideMenu);
8447            
8448            mUtil.addResizeHandler(function() {            
8449                initScrollableMenu(asideMenu);
8450            });   
8451        }      
8452    }
8453
8454    // handle vertical menu
8455    var initLeftAside = function() {
8456        // init aside left offcanvas
8457        var asideOffcanvasClass = ($('#m_aside_left').hasClass('m-aside-left--offcanvas-default') ? 'm-aside-left--offcanvas-default' : 'm-aside-left');
8458
8459        asideMenuOffcanvas = $('#m_aside_left').mOffcanvas({
8460            class: asideOffcanvasClass,
8461            overlay: true,
8462            close: '#m_aside_left_close_btn',
8463            toggle: {
8464                target: '#m_aside_left_offcanvas_toggle',
8465                state: 'm-brand__toggler--active'                
8466            }            
8467        });        
8468    }
8469
8470    // handle sidebar toggle
8471    var initLeftAsideToggle = function() {
8472        var asideLeftToggle = $('#m_aside_left_minimize_toggle').mToggle({
8473            target: 'body',
8474            targetState: 'm-brand--minimize m-aside-left--minimize',
8475            togglerState: 'm-brand__toggler--active'
8476        }).on('toggle', function(toggle) {
8477            horMenu.pauseDropdownHover(800);
8478            asideMenu.pauseDropdownHover(800);
8479
8480            //== Remember state in cookie
8481            Cookies.set('sidebar_toggle_state', toggle.getState());
8482        });
8483
8484        //== Example: minimize the left aside on page load
8485        //== asideLeftToggle.toggleOn();
8486
8487        $('#m_aside_left_hide_toggle').mToggle({
8488            target: 'body',
8489            targetState: 'm-aside-left--hide',
8490            togglerState: 'm-brand__toggler--active'
8491        }).on('toggle', function() {
8492            horMenu.pauseDropdownHover(800);
8493            asideMenu.pauseDropdownHover(800);
8494        })
8495    }
8496
8497    var initTopbar = function() {
8498        $('#m_aside_header_topbar_mobile_toggle').click(function() {
8499            $('body').toggleClass('m-topbar--on');
8500        });                                  
8501
8502        // Animated Notification Icon 
8503        setInterval(function() {
8504            $('#m_topbar_notification_icon .m-nav__link-icon').addClass('m-animate-shake');
8505            $('#m_topbar_notification_icon .m-nav__link-badge').addClass('m-animate-blink');
8506        }, 3000);
8507
8508        setInterval(function() {
8509            $('#m_topbar_notification_icon .m-nav__link-icon').removeClass('m-animate-shake');
8510            $('#m_topbar_notification_icon .m-nav__link-badge').removeClass('m-animate-blink');
8511        }, 6000);
8512    }
8513
8514    // handle quick search
8515    var initQuicksearch = function() {
8516        var qs = $('#m_quicksearch');
8517
8518        qs.mQuicksearch({
8519            type: qs.data('search-type'), // quick search type
8520            source: '/admin/customers/search-autocomplete-api/',            
8521            spinner: 'm-loader m-loader--skin-light m-loader--right',
8522
8523            input: '#m_quicksearch_input',
8524            iconClose: '#m_quicksearch_close',
8525            iconCancel: '#m_quicksearch_cancel',
8526            iconSearch: '#m_quicksearch_search',
8527
8528            hasResultClass: 'm-list-search--has-result',
8529            minLength: 4,            
8530            templates: {
8531                error: function(qs) {
8532                    return '<div class="m-search-results m-search-results--skin-light"><span class="m-search-result__message">Something went wrong</div></div>';
8533                }                            
8534            }
8535        });      
8536    }
8537
8538    var initScrollTop = function() {
8539        $('[data-toggle="m-scroll-top"]').mScrollTop({
8540            offset: 300,
8541            speed: 600
8542        });
8543    }
8544
8545    return {
8546        init: function() {  
8547            this.initHeader();
8548            this.initAside();
8549        },
8550
8551        initHeader: function() {
8552            initStickyHeader();
8553            initHorMenu();
8554            initTopbar();
8555            initQuicksearch();
8556            initScrollTop();
8557        },
8558
8559        initAside: function() {
8560            initLeftAside();
8561            initLeftAsideMenu();            
8562            initLeftAsideToggle();
8563
8564            this.onLeftSidebarToggle(function(e) {
8565              var datatables = $('.m-datatable');
8566              $(datatables).each(function() {
8567                $(this).mDatatable('redraw');
8568              });
8569            });
8570        },
8571
8572        getAsideMenu: function() {
8573            return asideMenu;
8574        },
8575
8576        onLeftSidebarToggle: function(func) {
8577            $('#m_aside_left_minimize_toggle').mToggle().on('toggle', func);
8578        },
8579
8580        closeMobileAsideMenuOffcanvas: function() {
8581            if (mUtil.isMobileDevice()) {
8582                asideMenuOffcanvas.hide();
8583            }
8584        },
8585
8586        closeMobileHorMenuOffcanvas: function() {
8587            if (mUtil.isMobileDevice()) {
8588                horMenuOffcanvas.hide();
8589            }
8590        }
8591    };
8592}();
8593
8594$(document).ready(function() {
8595    if (mUtil.isAngularVersion() === false) {
8596        mLayout.init();
8597    }
8598});
8599

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.