/**
 * (c) jSuites Javascript Web Components (v3.9.3)
 *
 * Website: https://jsuites.net
 * Description: Create amazing web based applications.
 *
 * MIT License
 *
 */
;(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    global.jSuites = factory();
}(this, (function () {

    'use strict';

jSuites.crop = (function(el, options) {
    var obj = {};
    obj.options = {};

    // Default configuration
    var defaults = {
        area: [ 800, 600 ],
        crop: [ 200, 150 ],
        value: null,
        onload: null,
        onchange: null,
        remoteParser: null,
        allowResize: true,
        text: {
            extensionNotAllowed:'The extension is not allowed',
            imageTooSmall:'The resolution is too low, try a image with a better resolution. width > 800px',
        }
    };

    // Loop through our object
    for (var property in defaults) {
        if (options && options.hasOwnProperty(property)) {
            obj.options[property] = options[property];
        } else {
            obj.options[property] = defaults[property];
        }
    }

    // Default for mobile
    if (jSuites.getWindowWidth() < 800) {
        if (! obj.options.area[0]) {
            obj.options.area[0] = window.clientWidth * 2;
        }
        if (! obj.options.area[1]) {
            obj.options.area[1] = window.clientHeight * 2;
        }
    }

    el.classList.add('jcrop');
    // Upload icon
    el.classList.add('jupload');

    // Area
    el.style.width = obj.options.area[0] + 'px';
    el.style.height = obj.options.area[1] + 'px';

    // Area do crop
    var crop = document.createElement('div');
    crop.classList.add('jcrop-area');
    el.appendChild(crop);

    // Canvas editor
    var canvas = document.createElement('canvas');
    var context = canvas.getContext('2d');
    el.appendChild(canvas);

    // Image
    var drawImage = function() {
        if (el.clientHeight > image.height) {
            var pointY = (el.clientHeight - image.height) / 2;
        } else {
            var pointY = 0;
        }

        if (el.clientWidth > image.width) {
            var pointX = (el.clientWidth - image.width) / 2;
        } else {
            var pointX = 0;
        }

        image.left = pointX;
        image.top = pointY;

        context.translate(pointX, pointY);
        context.drawImage(image, 0, 0, image.width, image.height);
    }

    var image = new Image();
    image.onload = function onload() {
        obj.resetCanvas();

        var w = obj.options.area[0] / this.naturalWidth;
        var h = obj.options.area[1] / this.naturalHeight;

        // Proportion
        var p = this.naturalHeight > this.naturalWidth ? h : w;

        // Image size
        this.width = this.naturalWidth * p;
        this.height = this.naturalHeight * p;

        // Do adjustment
        canvas.width = obj.options.area[0];
        canvas.height = obj.options.area[1];

        if (this.width > canvas.width) {
            canvas.width = this.width;
        }

        if (this.height > canvas.height) {
            canvas.height = this.height;
        }

        drawImage();

        // Edition model
        el.classList.add('jcrop_edition');

        // Reset selection on desktop only
        if (jSuites.getWindowWidth() > 800) {
            obj.resetCropSelection();
        } else {
            crop.classList.add('mobile');
        }

        // Onchange
        if (typeof(obj.options.onchange) == 'function') {
            obj.options.onchange(el, image);
        }
    };

    // Properties
    var properties = {
        zoom: {
            origin: { x: null, y: null, },
            scale: 1,
        },
        contrast: 0,
        brightness: 0,
        rotate: 0,
        saturation: 0,
    };

    var secondCanvas = document.createElement('canvas');
    var secondContext = secondCanvas.getContext('2d');
    var secondImage = document.createElement('img');

    // Reload filters
    var refreshFilters = function() {
        secondCanvas.width = image.width;
        secondCanvas.height = image.height;

        secondContext.clearRect(0, 0, secondContext.width, secondContext.height);

        secondImage.width = secondCanvas.width;
        secondImage.height = secondCanvas.height;

        if (image) {
            //drawImage();
            secondContext.drawImage(image, 0, 0, image.width, image.height);
        }

        // Performs the contrast, if its value is different from the initial
        if (properties.contrast) {
            runContrast();
        }

        // Performs the brightness, if its value is different from the initial
        if (properties.brightness) {
            runBrightness();
        }

        if (properties.greyScale) {
            runGreyScale();
        }

        if (properties.saturation) {
            runSaturation();
        }

        secondImage.src = secondCanvas.toDataURL(obj.getImageType);
        secondImage.onload = function() {
            context.drawImage(secondImage, 0, 0, secondImage.width, secondImage.height);
        }
    }

    /**
     * Reset crop to the initial conditions
     */
    obj.resetCropSelection = function() {
        crop.style.left = (obj.options.area[0]/2 - obj.options.crop[0]/2) + 'px';
        crop.style.top = (obj.options.area[1]/2 - obj.options.crop[1]/2) + 'px';
        crop.style.width = obj.options.crop[0] + 'px';
        crop.style.height = obj.options.crop[1] + 'px';
        crop.style.zIndex = 1;
    }

    obj.getCropSelection = function() {
        obj.getSelectionCoordinates();
    }

    // Reset canvas
    obj.resetCanvas = function() {
        context.setTransform(1, 0, 0, 1, 0, 0);
        context.clearRect(0,0,canvas.width,canvas.height);
    }

    // Reset all the properties
    obj.reset = function() {
        // Reset canvas
        obj.resetCanvas();
        // Reset state
        properties = {
            zoom: {
                origin: { x: null, y: null },
                scale: 1,
            },
            contrast: 0,
            brightness: 0,
            rotate: 0,
            greyScale: 0,
            saturation: 0,
        }
        // Stop edition
        el.classList.remove('jcrop_edition')
    }

    // Apply the contrast on the image data
    var runContrast = function() {
        var contrast = properties.contrast;
        var imageData = secondContext.getImageData(0, 0, secondCanvas.width, secondCanvas.height);
        var data = imageData.data;  // Note: original dataset modified directly!
        contrast *= 255;
        var factor = (contrast + 255) / (255.01 - contrast);  //add .1 to avoid /0 error.

        for (var i = 0; i < data.length; i+=4) {
            data[i] = factor * (data[i] - 128) + 128;
            data[i+1] = factor * (data[i+1] - 128) + 128;
            data[i+2] = factor * (data[i+2] - 128) + 128;
        }

        secondContext.putImageData(imageData, 0, 0);

        return imageData;  //optional (e.g. for filter function chaining)
    }

    // Change the contrast and apply that to the image
    obj.contrast = function(val) { // contrast input as percent; range [-1..1]
        if (! Number.isNaN(parseFloat(val))) {
            properties.contrast = val;
        }

        refreshFilters();
    }

    // Apply the brightness on the image data
    var runBrightness = function () {  // brightness input as percent; range [-1..1]
        var brightness = properties.brightness;
        var imageData = secondContext.getImageData(0, 0, secondCanvas.width, secondCanvas.height);

        var data = imageData.data;  // Note: original dataset modified directly!
        brightness *= 255;

        for (var i = 0; i < data.length; i+=4) {
            data[i] += brightness;
            data[i+1] += brightness;
            data[i+2] += brightness;
        }

        secondContext.putImageData(imageData, 0, 0);

        return imageData;  //optional (e.g. for filter function chaining)
    }

    // Change the brightness and apply that to the image
    obj.brightness = function(val) {
        if (! Number.isNaN(parseFloat(val))) {
            properties.brightness = val;
        }

        refreshFilters();
    }

    /**
     * Returns the current image type
     */
    obj.getImageType = function() {
        var dataType = image.src.substr(0,20);
        if(dataType.includes('data')){
            return dataType.split('/')[1].split(';')[0];
        }
        return null;
    }
     /**
     * Returns cropped area coordinates
     */
    obj.getSelectionCoordinates = function() {
        return {
            left: crop.offsetLeft,
            top: crop.offsetTop,
            right: crop.offsetLeft + crop.clientWidth,
            bottom: crop.offsetTop + crop.clientHeight
        }
    }

    /**
     * Returns cropped image on canvas
     */
    obj.getCroppedImage = function() {
        return jSuites.image.create({
            extension: obj.getImageType(),
            file: obj.getCroppedContent(),
            name: '',
            size: '',
        });
    }

    /**
     * Get cropped base64 content
     */
    obj.getCroppedContent = function() {
        var coordinates = obj.getSelectionCoordinates();
        var canvasCropped = document.createElement('canvas');
        var contextCropped = canvasCropped.getContext('2d');
        canvasCropped.width = crop.clientWidth;
        canvasCropped.height = crop.clientHeight;

        // Get cropped selection
        var imageData = context.getImageData(coordinates.left, coordinates.top, crop.clientWidth, crop.clientHeight);
        contextCropped.putImageData(imageData,0,0);
        return canvasCropped.toDataURL(obj.getImageType());
    }

    /**
     * Get cropped as blob
     */
    obj.getCroppedAsBlob = function(callback) {
        if (! typeof(callback) == 'function') {
            console.error('Callback not defined')
            return false;
        }

        var coordinates = obj.getSelectionCoordinates();
        var canvasCropped = document.createElement('canvas');
        var contextCropped = canvasCropped.getContext('2d');
        canvasCropped.width = crop.clientWidth;
        canvasCropped.height = crop.clientHeight;

        // Get cropped selection
        var imageData = context.getImageData(coordinates.left, coordinates.top, crop.clientWidth, crop.clientHeight);
        contextCropped.putImageData(imageData,0,0);

        // Callback
        canvasCropped.toBlob(callback);
    }

    /**
     * Returns the current image on canvas
     */
    obj.getImage = function() {
        return image;
    }

    /**
     *  Returns the attachment input
     */
    obj.getFileInput = function() { 
        return attachmentInput;
    }

    /**
     * Returns the current canvas
     */
    obj.getCanvas = function() {
        return canvas;
    }

    /**
     * Load a image from the computer
     */
    obj.addFromFile = function(file) {
        var type = file.type.split('/');
        if (type[0] == 'image') {
            var imageFile = new FileReader();
            imageFile.addEventListener("load", function (v) {
                image.src = v.srcElement.result;
            });
            imageFile.readAsDataURL(file);
        } else {
            alert(text.extentionNotAllowed);
        }
    }

    /**
     * Load a image from a remote URL
     */
    obj.addFromUrl = function(src) {
        if (src.substr(0,4) != 'data' && ! obj.options.remoteParser) {
            console.error('remoteParser not defined in your initialization');
        } else {
            src = obj.options.remoteParser + src;
            image.src = src;
        }
    }

    // Runs image movements
    var runMove = function() {
        context.translate(image.left, image.top);
    }

    // Reload resizers and filters
    var refreshResizers = function() {
        // Clear canvas
        context.setTransform(1, 0, 0, 1, 0, 0);
        context.clearRect(0, 0, canvas.width, canvas.height);

        runMove();

        // Performs the zoom, if its value is different from the initial
        if (properties.zoom.scale !== 1) {
            runZoom();
        }

        // Performs the rotation, if its value is different from the initial
        if (properties.rotate) {
            runRotate();
        }

        // Calls the filter reloader
        if (properties.brightness || properties.contrast) {
            context.drawImage(secondImage, 0, 0, secondImage.width, secondImage.height);
        } else {
            context.drawImage(image, 0, 0, image.width, image.height);
        }
    }

    // Apply the zoom on the image
    var runZoom = function() {
        context.translate((properties.zoom.origin.x - image.left), (properties.zoom.origin.y - image.top));
        context.scale(properties.zoom.scale, properties.zoom.scale);
        context.translate(-(properties.zoom.origin.x - image.left), -(properties.zoom.origin.y - image.top));
    }

    /**
     * Change the zoom and apply that to the image
     * @param mixed value / null to refresh or int for a new percentage of zoom
     */
    obj.zoom = function(value) {
        if (value) {
            properties.zoom.scale = value;
        }
        refreshResizers();
    }

    // Apply the rotations on the image
    var runRotate = function() {
        var value = properties.rotate;
        value *= 180;

        context.translate(image.width / 2, image.height / 2);
        context.rotate(value * Math.PI / 180);
        context.translate(- image.width / 2, - image.height / 2);
    }

    // Change the rotation and apply that to the image
    obj.rotate = function(val) {  // rotate input as percent; range [-1..1]
        if (! Number.isNaN(parseFloat(val))) {
            properties.rotate = val;
        }

        refreshResizers();
    }

    // HTML element to load a image from the computer
    var attachmentInput = document.createElement('input');
    attachmentInput.type = 'file';
    attachmentInput.setAttribute('accept', 'image/*');
    attachmentInput.onchange = function() {
        for (var i = 0; i < this.files.length; i++) {
            obj.addFromFile(this.files[i]);
        }
    }

    /** Events start here **/
    var editorAction = null;

    var editorMouseUp = function(e) {
        editorAction = false;
    }

    var editorMouseDown = function(e) {
        if (e.target.classList.contains('jcrop-area')) {
            var rect = e.target.getBoundingClientRect();

            var offsetX = (e.target.style.left !== '') ? e.target.style.left : '0px';
            var offsetY = (e.target.style.top !== '') ? e.target.style.top : '0px';

            if (e.target.style.cursor) {
                editorAction = {
                    e: e.target,
                    x: e.clientX,
                    y: e.clientY,
                    w: rect.width,
                    h: rect.height,
                    d: e.target.style.cursor,
                    xOffset: e.clientX - parseInt(offsetX.slice(0, offsetX.length - 2)),
                    yOffset: e.clientY - parseInt(offsetY.slice(0, offsetY.length - 2)),
                }

                if (! e.target.style.width) {
                    e.target.style.width = rect.width + 'px';
                }

                if (! e.target.style.height) {
                    e.target.style.height = rect.height + 'px';
                }

                var s = window.getSelection();
                if (s.rangeCount) {
                    for (var i = 0; i < s.rangeCount; i++) {
                        s.removeRange(s.getRangeAt(i));
                    }
                }
            } else {
                e.target.style.cursor = 'move';

                // Tracking for moving
                editorAction = {
                    e: e.target,
                    x: e.clientX,
                    y: e.clientY,
                    w: rect.width,
                    h: rect.height,
                    d: e.target.style.cursor,
                    xOffset: e.clientX - parseInt(offsetX.slice(0, offsetX.length - 2)),
                    yOffset: e.clientY - parseInt(offsetY.slice(0, offsetY.length - 2)),
                }
            }
        } else { 
            editorAction = true;
        }
    }

    var editorMouseMove = function(e) {
        if (e.target.classList.contains('jcrop-area') && obj.options.allowResize == true) {
            var rect = e.target.getBoundingClientRect();
            if (e.clientY - rect.top < 5) {
                if (rect.width - (e.clientX - rect.left) < 5) {
                    e.target.style.cursor = 'ne-resize';
                } else if (e.clientX - rect.left < 5) {
                    e.target.style.cursor = 'nw-resize';
                } else {
                    e.target.style.cursor = 'n-resize';
                }
            } else if (rect.height - (e.clientY - rect.top) < 5) {
                if (rect.width - (e.clientX - rect.left) < 5) {
                    e.target.style.cursor = 'se-resize';
                } else if (e.clientX - rect.left < 5) {
                    e.target.style.cursor = 'sw-resize';
                } else {
                    e.target.style.cursor = 's-resize';
                }
            } else if (rect.width - (e.clientX - rect.left) < 5) {
                e.target.style.cursor = 'e-resize';
            } else if (e.clientX - rect.left < 5) {
                e.target.style.cursor = 'w-resize';
            } else {
                if (! e.which) {
                    e.target.style.cursor = '';
                }
            }
        }

        // Change position or size
        if (e.which == 1 && editorAction && editorAction.d) {
            if (editorAction.d == 'move') {
                // Change the position of the cropper
                var cropOffsetX = e.clientX - editorAction.xOffset;
                var cropOffsetY = e.clientY - editorAction.yOffset;

                if (cropOffsetX < 0) {
                    editorAction.e.style.left = '0px';
                } else if (cropOffsetX > el.offsetWidth - crop.offsetWidth - 2) {
                    editorAction.e.style.left = el.offsetWidth - crop.offsetWidth - 2 + 'px';
                } else {
                    editorAction.e.style.left = cropOffsetX + 'px';
                }
                
                if (cropOffsetY < 0) {
                    editorAction.e.style.top = '0px';
                } else if (cropOffsetY > el.offsetHeight - crop.offsetHeight - 2){
                    editorAction.e.style.top = el.offsetHeight - crop.offsetHeight - 2 + 'px';
                } else {
                    editorAction.e.style.top = cropOffsetY + 'px';
                }
            } else {
                // Change the size of the cropper
                if (editorAction.d == 'e-resize' || editorAction.d == 'ne-resize' ||  editorAction.d == 'se-resize') {
                    // Handles size changes involving the right side of the cropper
                    var newWidth = editorAction.w + e.clientX - editorAction.x;
                    var offset = editorAction.e.style.left.slice(0, editorAction.e.style.left.length - 2);

                    if (newWidth < obj.options.crop[0]) {
                        editorAction.e.style.width = obj.options.crop[0] + 'px';
                    } else if (newWidth + parseInt(offset) > el.offsetWidth - 2) {
                        editorAction.e.style.width = el.offsetWidth - offset - 2 + 'px';
                    } else {
                        editorAction.e.style.width = editorAction.w + e.clientX - editorAction.x + 'px';
                    }
                } else if (editorAction.d == 'w-resize' || editorAction.d == 'nw-resize' ||  editorAction.d == 'sw-resize') {
                    // Handles size changes involving the left side of the cropper
                    var newOffset = e.clientX - editorAction.xOffset;
                    var newWidth = editorAction.x + editorAction.w - e.clientX;

                    if (newWidth < obj.options.crop[0]) {
                        editorAction.e.style.left = editorAction.x + editorAction.w - obj.options.crop[0] - editorAction.xOffset + 'px';
                        editorAction.e.style.width = obj.options.crop[0] + 'px';
                    } else if (newOffset < 0) {
                        editorAction.e.style.left = '0px';
                        editorAction.e.style.width = editorAction.x + editorAction.w - editorAction.xOffset + 'px';
                    } else {
                        editorAction.e.style.left = newOffset + 'px';
                        editorAction.e.style.width = newWidth + 'px';
                    }
                }

                if (editorAction.d == 's-resize' || editorAction.d == 'se-resize' ||  editorAction.d == 'sw-resize') {
                    // Handles size changes involving the top side of the cropper
                    var newHeight = editorAction.h + e.clientY - editorAction.y;
                    var offset = editorAction.e.style.top.slice(0, editorAction.e.style.top.length - 2);

                    if (newHeight < obj.options.crop[1]) {
                        editorAction.e.style.height = obj.options.crop[1] + 'px';
                    } else if (newHeight + parseInt(offset) > el.offsetHeight - 2) {
                        editorAction.e.style.height = el.offsetHeight - parseInt(offset) - 2 + 'px';
                    } else {
                        editorAction.e.style.height = newHeight + 'px';
                    }
                } else if (editorAction.d == 'n-resize' || editorAction.d == 'ne-resize' ||  editorAction.d == 'nw-resize') {
                    // Handles size changes involving the bottom side of the cropper
                    var newOffset = e.clientY - editorAction.yOffset;
                    var newHeight = editorAction.h + editorAction.y - e.clientY;

                    if (newHeight < obj.options.crop[1]) {
                        editorAction.e.style.top = editorAction.y + editorAction.h - obj.options.crop[1] - editorAction.yOffset + 'px';
                        editorAction.e.style.height = obj.options.crop[1] + 'px';
                    } else if (newOffset < 0) {
                        editorAction.e.style.top = '0px';
                        editorAction.e.style.height = editorAction.y + editorAction.h - editorAction.yOffset + 'px';
                    } else {
                        editorAction.e.style.top = newOffset + 'px';
                        editorAction.e.style.height = newHeight + 'px';
                    }
                }
            }
        }
    }

    // image state to change its current position in el container (mobile only)
    var imageState = {
        mousedown: false,
        mouseX: 0,
        mouseY: 0
    }

    var touchstartListener = function(e) {
        if (! e.target.classList.contains('jcrop-area')) {
            imageState.mousedown = true;
        }

        if (e.changedTouches && e.changedTouches[0]) {
            imageState.mouseX = e.changedTouches[0].clientX;
            imageState.mouseY = e.changedTouches[0].clientY;
        } else {
            imageState.mouseX = e.clientX;
            imageState.mouseY = e.clientY;
        }
    }

    var imageMoveListener = function(e) {
        if (el.classList.contains('jcrop_edition')) {
            // Mark position
            if (e.changedTouches && e.changedTouches[0]) {
                var x = e.changedTouches[0].clientX;
                var y = e.changedTouches[0].clientY;
            } else {
                var x = e.clientX;
                var y = e.clientY;
            }

            var currentX = x;
            var newX = currentX - imageState.mouseX;
            imageState.mouseX = currentX;

            var currentY = y;
            var newY = currentY - imageState.mouseY;
            imageState.mouseY = currentY;

            if (imageState.mousedown) {
                image.left += newX/properties.zoom.scale;
                image.top += newY/properties.zoom.scale;
                refreshResizers();
            }

            e.preventDefault();
        }
    }

    el.addEventListener('mouseup', editorMouseUp);
    el.addEventListener('mousedown', editorMouseDown);
    el.addEventListener('mousemove', editorMouseMove);
    el.addEventListener('touchstart',touchstartListener);
    el.addEventListener('touchmove', imageMoveListener);
    el.addEventListener('mousedown',touchstartListener);
    document.addEventListener('mouseup', function(e) {
        imageState.mousedown = false;
    });
    el.addEventListener('mousemove', imageMoveListener);

    el.addEventListener("dblclick", function(e) {
        jSuites.click(attachmentInput);
    });

    el.addEventListener('dragenter', function(e) {
        el.style.border = '1px dashed #000';
    });

    el.addEventListener('dragleave', function(e) {
        el.style.border = '1px solid #eee';
    });

    el.addEventListener('dragstop', function(e) {
        el.style.border = '1px solid #eee';
    });

    el.addEventListener('dragover', function(e) {
        e.preventDefault();
    });

    el.addEventListener('drop', function(e) {
        e.preventDefault();  
        e.stopPropagation();


        var html = (e.originalEvent || e).dataTransfer.getData('text/html');
        var file = (e.originalEvent || e).dataTransfer.files;

        if (file.length) {
            for (var i = 0; i < e.dataTransfer.files.length; i++) {
                obj.addFromFile(e.dataTransfer.files[i]);
            }
        } else if (html) {
            // Create temp element
            var div = document.createElement('div');
            div.innerHTML = html;

            // Extract images
            var img = div.querySelector('img');

            if (img) {
                obj.addFromUrl(img.src);
            }
        }

        el.style.border = '1px solid #eee';

        return false;
    });

    el.addEventListener("wheel", function(e) {
        if (el.classList.contains('jcrop_edition')) {
            // Update scale
            if (e.deltaY > 0) {
                if (properties.zoom.scale > 0.1) {
                    properties.zoom.scale *= 0.9;
                }
            } else {
                if (properties.zoom.scale < 5) {
                    properties.zoom.scale *= 1.1;
                }
            }
            properties.zoom.scale = parseFloat(properties.zoom.scale.toFixed(2));

            // Update coordinates
            var rect = el.getBoundingClientRect();
            var x = e.clientX - rect.left;
            var y = e.clientY - rect.top;

            // Zoom on the image
            var c = context.getImageData(x, y, 1, 1).data;
            if (c[3] != 0) {
                properties.zoom.origin.x = x;
                properties.zoom.origin.y = y;
            } else {
            }

            // Apply zoom
            obj.zoom();

            e.preventDefault();
        }
    });

    // mobile events
    el.addEventListener("click", function(e) {
        if (! el.classList.contains('jcrop_edition')) {
            jSuites.click(attachmentInput);
        }
    });

    // Onchange
    if (typeof(obj.options.onload) == 'function') {
        obj.options.onload(el, obj);
    }

    // Initial image
    if (obj.options.value) {
        image.src = obj.options.value;
    }

    el.crop = obj;

    return obj;
});

jSuites.login = (function(el, options) {
    var obj = {};
    obj.options = {};

    // Default configuration
    var defaults = {
        url: window.location.href,
        prepareRequest: null,
        accessToken: null,
        deviceToken: null,
        facebookUrl: null,
        facebookAuthentication: null,
        maxHeight: null,
        onload: null,
        onsuccess: null,
        onerror: null,
        message: null,
        logo: null,
        newProfile: false,
        newProfileUrl: false,
        newProfileLogin: false,
        fullscreen: false,
        newPasswordValidation: null,
    };

    // Loop through our object
    for (var property in defaults) {
        if (options && options.hasOwnProperty(property)) {
            obj.options[property] = options[property];
        } else {
            obj.options[property] = defaults[property];
        }
    }

    // Message console container
    if (! obj.options.message) {
        var messageElement = document.querySelector('.message');
        if (messageElement) {
            obj.options.message = messageElement;
        }
    }

    // Action
    var action = null;

    // Container
    var container = document.createElement('form');
    el.appendChild(container);

    // Logo
    var divLogo = document.createElement('div');
    divLogo.className = 'jlogin-logo'
    container.appendChild(divLogo);

    if (obj.options.logo) {
        var logo = document.createElement('img');
        logo.src = obj.options.logo;
        divLogo.appendChild(logo);
    }

    // Code
    var labelCode = document.createElement('label');
    labelCode.innerHTML = 'Please enter here the code received';
    var inputCode = document.createElement('input');
    inputCode.type = 'number';
    inputCode.id = 'code';
    inputCode.setAttribute('maxlength', 6);
    var divCode = document.createElement('div');
    divCode.appendChild(labelCode);
    divCode.appendChild(inputCode);

    // Hash
    var inputHash = document.createElement('input');
    inputHash.type = 'hidden';
    inputHash.name = 'h';
    var divHash = document.createElement('div');
    divHash.appendChild(inputHash);

    // Recovery
    var inputRecovery = document.createElement('input');
    inputRecovery.type = 'hidden';
    inputRecovery.name = 'recovery';
    inputRecovery.value = '1';
    var divRecovery = document.createElement('div');
    divRecovery.appendChild(inputRecovery);

    // Login
    var labelLogin = document.createElement('label');
    labelLogin.innerHTML = 'Login';
    var inputLogin = document.createElement('input');
    inputLogin.type = 'text';
    inputLogin.name = 'login';
    inputLogin.setAttribute('autocomplete', 'off');
    inputLogin.onkeyup = function() {
        this.value = this.value.toLowerCase().replace(/[^a-zA-Z0-9_+]+/gi, '');
    } 
    var divLogin = document.createElement('div');
    divLogin.appendChild(labelLogin);
    divLogin.appendChild(inputLogin);

    // Name
    var labelName = document.createElement('label');
    labelName.innerHTML = 'Name';
    var inputName = document.createElement('input');
    inputName.type = 'text';
    inputName.name = 'name';
    var divName = document.createElement('div');
    divName.appendChild(labelName);
    divName.appendChild(inputName);

    // Email
    var labelUsername = document.createElement('label');
    labelUsername.innerHTML = 'E-mail';
    var inputUsername = document.createElement('input');
    inputUsername.type = 'text';
    inputUsername.name = 'username';
    inputUsername.setAttribute('autocomplete', 'new-username');
    var divUsername = document.createElement('div');
    divUsername.appendChild(labelUsername);
    divUsername.appendChild(inputUsername);

    // Password
    var labelPassword = document.createElement('label');
    labelPassword.innerHTML = 'Password';
    var inputPassword = document.createElement('input');
    inputPassword.type = 'password';
    inputPassword.name = 'password';
    inputPassword.setAttribute('autocomplete', 'new-password');
    var divPassword = document.createElement('div');
    divPassword.appendChild(labelPassword);
    divPassword.appendChild(inputPassword);
    divPassword.onkeydown = function(e) {
        if (e.keyCode == 13) {
            obj.execute();
        }
    }

    // Repeat password
    var labelRepeatPassword = document.createElement('label');
    labelRepeatPassword.innerHTML = 'Repeat the new password';
    var inputRepeatPassword = document.createElement('input');
    inputRepeatPassword.type = 'password';
    inputRepeatPassword.name = 'password';
    var divRepeatPassword = document.createElement('div');
    divRepeatPassword.appendChild(labelRepeatPassword);
    divRepeatPassword.appendChild(inputRepeatPassword);

    // Remember checkbox
    var labelRemember = document.createElement('label');
    labelRemember.innerHTML = 'Remember me on this device';
    var inputRemember = document.createElement('input');
    inputRemember.type = 'checkbox';
    inputRemember.name = 'remember';
    inputRemember.value = '1';
    labelRemember.appendChild(inputRemember);
    var divRememberButton = document.createElement('div');
    divRememberButton.className = 'rememberButton';
    divRememberButton.appendChild(labelRemember);

    // Login button
    var actionButton = document.createElement('input');
    actionButton.type = 'button';
    actionButton.value = 'Log In';
    actionButton.onclick = function() {
        obj.execute();
    }
    var divActionButton = document.createElement('div');
    divActionButton.appendChild(actionButton);

    // Cancel button
    var cancelButton = document.createElement('div');
    cancelButton.innerHTML = 'Cancel';
    cancelButton.className = 'cancelButton';
    cancelButton.onclick = function() {
        obj.requestAccess();
    }
    var divCancelButton = document.createElement('div');
    divCancelButton.appendChild(cancelButton);

    // Captcha
    var labelCaptcha = document.createElement('label');
    labelCaptcha.innerHTML = 'Please type here the code below';
    var inputCaptcha = document.createElement('input');
    inputCaptcha.type = 'text';
    inputCaptcha.name = 'captcha';
    var imageCaptcha = document.createElement('img');
    var divCaptcha = document.createElement('div');
    divCaptcha.className = 'jlogin-captcha';
    divCaptcha.appendChild(labelCaptcha);
    divCaptcha.appendChild(inputCaptcha);
    divCaptcha.appendChild(imageCaptcha);

    // Facebook
    var facebookButton = document.createElement('div');
    facebookButton.innerHTML = 'Login with Facebook';
    facebookButton.className = 'facebookButton';
    var divFacebookButton = document.createElement('div');
    divFacebookButton.appendChild(facebookButton);
    divFacebookButton.onclick = function() {
        obj.requestLoginViaFacebook();
    }
    // Forgot password
    var inputRequest = document.createElement('span');
    inputRequest.innerHTML = 'Request a new password';
    var divRequestButton = document.createElement('div');
    divRequestButton.className = 'requestButton';
    divRequestButton.appendChild(inputRequest);
    divRequestButton.onclick = function() {
        obj.requestNewPassword();
    }
    // Create a new Profile
    var inputNewProfile = document.createElement('span');
    inputNewProfile.innerHTML = 'Create a new profile';
    var divNewProfileButton = document.createElement('div');
    divNewProfileButton.className = 'newProfileButton';
    divNewProfileButton.appendChild(inputNewProfile);
    divNewProfileButton.onclick = function() {
        obj.newProfile();
    }

    el.className = 'jlogin';

    if (obj.options.fullscreen == true) {
        el.classList.add('jlogin-fullscreen');
    }

    /** 
     * Show message
     */
    obj.showMessage = function(data) {
        var message = (typeof(data) == 'object') ? data.message : data;

        if (typeof(obj.options.showMessage) == 'function') {
            obj.options.showMessage(data);
        } else {
            jSuites.alert(data);
        }
    }

    /**
     * New profile
     */
    obj.newProfile = function() {
        container.innerHTML = '';
        container.appendChild(divLogo);
        if (obj.options.newProfileLogin) {
            container.appendChild(divLogin);
        }
        container.appendChild(divName);
        container.appendChild(divUsername);
        container.appendChild(divActionButton);
        if (obj.options.facebookAuthentication == true) {
            container.appendChild(divFacebookButton);
        }
        container.appendChild(divCancelButton);

        // Reset inputs
        inputLogin.value = '';
        inputUsername.value = '';
        inputPassword.value = '';

        // Button
        actionButton.value = 'Create new profile';

        // Action
        action = 'newProfile';
    }

    /**
     * Request the email with the recovery instructions
     */
    obj.requestNewPassword = function() {
        if (Array.prototype.indexOf.call(container.children, divCaptcha) >= 0) {
            var captcha = true;
        }

        container.innerHTML = '';
        container.appendChild(divLogo);
        container.appendChild(divRecovery);
        container.appendChild(divUsername);
        if (captcha) {
            container.appendChild(divCaptcha);
        }
        container.appendChild(divActionButton);
        container.appendChild(divCancelButton);
        actionButton.value = 'Request a new password';
        inputRecovery.value = 1;

        // Action
        action = 'requestNewPassword';
    }

    /**
     * Confirm recovery code
     */
    obj.codeConfirmation = function() {
        container.innerHTML = '';
        container.appendChild(divLogo);
        container.appendChild(divHash);
        container.appendChild(divCode);
        container.appendChild(divActionButton);
        container.appendChild(divCancelButton);
        actionButton.value = 'Confirm code';
        inputRecovery.value = 2;

        // Action
        action = 'codeConfirmation';
    }

    /**
     * Update my password
     */
    obj.changeMyPassword = function(hash) {
        container.innerHTML = '';
        container.appendChild(divLogo);
        container.appendChild(divHash);
        container.appendChild(divPassword);
        container.appendChild(divRepeatPassword);
        container.appendChild(divActionButton);
        container.appendChild(divCancelButton);
        actionButton.value = 'Change my password';
        inputHash.value = hash;

        // Action
        action = 'changeMyPassword';
    }

    /**
     * Request access default method
     */
    obj.requestAccess = function() {
        container.innerHTML = '';
        container.appendChild(divLogo);
        container.appendChild(divUsername);
        container.appendChild(divPassword);
        container.appendChild(divActionButton);
        if (obj.options.facebookAuthentication == true) {
            container.appendChild(divFacebookButton);
        }
        container.appendChild(divRequestButton);
        container.appendChild(divRememberButton);
        container.appendChild(divRequestButton);
        if (obj.options.newProfile == true) {
            container.appendChild(divNewProfileButton);
        }

        // Button
        actionButton.value = 'Login';

        // Password
        inputPassword.value = '';

        // Email persistence
        if (window.localStorage.getItem('username')) {
            inputUsername.value = window.localStorage.getItem('username');
            inputPassword.focus();
        } else {
            inputUsername.focus();
        }

        // Action
        action = 'requestAccess';
    }

    /**
     * Request login via facebook
     */
    obj.requestLoginViaFacebook = function() {
        if (typeof(deviceNotificationToken) == 'undefined') {
            FB.getLoginStatus(function(response) {
                if (! response.status || response.status != 'connected') {
                    FB.login(function(response) {
                        if (response.authResponse) {
                            obj.execute({ f:response.authResponse.accessToken });
                        } else {
                            obj.showMessage('Not authorized by facebook');
                        }
                    }, {scope: 'public_profile,email'});
                } else {
                    obj.execute({ f:response.authResponse.accessToken });
                }
            }, true);
        } else {
            jDestroy = function() {
                fbLogin.removeEventListener('loadstart', jStart);
                fbLogin.removeEventListener('loaderror', jError);
                fbLogin.removeEventListener('exit', jExit);
                fbLogin.close();
                fbLogin = null;
            }

            jStart = function(event) {
                var url = event.url;
                if (url.indexOf("access_token") >= 0) {
                    setTimeout(function(){
                        var u = url.match(/=(.*?)&/);
                        if (u[1].length > 32) {
                            obj.execute({ f:u[1] });
                        }
                        jDestroy();
                   },500);
                }

                if (url.indexOf("error=access_denied") >= 0) {
                   setTimeout(jDestroy ,500);
                   // Not authorized by facebook
                   obj.showMessage('Not authorized by facebook');
                }
            }

            jError = function(event) {
                jDestroy();
            }
        
            jExit = function(event) {
                jDestroy();
            }

            fbLogin = window.open(obj.options.facebookUrl, "_blank", "location=no,closebuttoncaption=Exit,disallowoverscroll=yes,toolbar=no");
            fbLogin.addEventListener('loadstart', jStart);
            fbLogin.addEventListener('loaderror', jError);
            fbLogin.addEventListener('exit', jExit);
        }

        // Action
        action = 'requestLoginViaFacebook';
    }

    // Perform request
    obj.execute = function(data) {
        // New profile
        if (action == 'newProfile') {
            var pattern = new RegExp(/^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/);
            if (! inputUsername.value || ! pattern.test(inputUsername.value)) {
                var message = 'Invalid e-mail address'; 
            }

            var pattern = new RegExp(/^[a-zA-Z0-9\_\-\.\s+]+$/);
            if (! inputLogin.value || ! pattern.test(inputLogin.value)) {
                var message = 'Invalid username, please use only characters and numbers';
            }

            if (message) {
                obj.showMessage(message);
                return false;
            }
        } else if (action == 'changeMyPassword') {
            if (inputPassword.value.length < 3) {
                var message = 'Password is too short';
            } else  if (inputPassword.value != inputRepeatPassword.value) {
                var message = 'Password should match';
            } else {
                if (typeof(obj.options.newPasswordValidation) == 'function') {
                    var val = obj.options.newPasswordValidation(obj, inputPassword.value, inputPassword.value);
                    if (val != undefined) {
                        message = val;
                    }
                }
            }

            if (message) {
                obj.showMessage(message);
                return false;
            }
        }

        // Keep email
        if (inputUsername.value != '') {
            window.localStorage.setItem('username', inputUsername.value);
        }

        // Captcha
        if (Array.prototype.indexOf.call(container.children, divCaptcha) >= 0) {
            if (inputCaptcha.value == '') {
                obj.showMessage('Please enter the captch code below');
                return false;
            }
        }

        // Url
        var url = obj.options.url;

        // Device token
        if (obj.options.deviceToken) {
            url += '?token=' + obj.options.deviceToken;
        }

        // Callback
        var onsuccess = function(result) {
            if (result) {
                // Successfully response
                if (result.success == 1) {
                    // Recovery process
                    if (action == 'requestNewPassword') {
                        obj.codeConfirmation();
                    } else if (action == 'codeConfirmation') {
                        obj.requestAccess();
                    } else if (action == 'newProfile') {
                        obj.requestAccess();
                        // New profile
                        result.newProfile = true;
                    }

                    // Token
                    if (result.token) {
                        // Set token
                        obj.options.accessToken = result.token;
                        // Save token
                        window.localStorage.setItem('Access-Token', result.token);
                    }
                }

                // Show message
                if (result.message) {
                    // Show message
                    obj.showMessage(result.message)
                }

                // Request captcha code
                if (! result.data) {
                    if (Array.prototype.indexOf.call(container.children, divCaptcha) >= 0) {
                        divCaptcha.remove();
                    }
                } else {
                    container.insertBefore(divCaptcha, divActionButton);
                    imageCaptcha.setAttribute('src', 'data:image/png;base64,' + result.data);
                }

                // Give time to user see the message
                if (result.hash) {
                    // Change password
                    obj.changeMyPassword(result.hash);
                } else if (result.url) {
                    // App initialization
                    if (result.success == 1) {
                        if (typeof(obj.options.onsuccess) == 'function') {
                            obj.options.onsuccess(result);
                        } else {
                            if (result.message) {
                                setTimeout(function() { window.location.href = result.url; }, 2000);
                            } else {
                                window.location.href = result.url;
                            }
                        }
                    } else {
                        if (typeof(obj.options.onerror) == 'function') {
                            obj.options.onerror(result);
                        }
                    }
                }
            }
        }

        // Password
        if (! data) {
            var data = jSuites.form.getElements(el, true);
            // Encode passworfd
            if (data.password) {
                data.password = jSuites.sha512(data.password);
            }
            // Recovery code
            if (Array.prototype.indexOf.call(container.children, divCode) >= 0 && inputCode.value) {
                data.h = jSuites.sha512(inputCode.value);
            }
        }

        // Loading
        el.classList.add('jlogin-loading');

        // Url
        var url = (action == 'newProfile' && obj.options.newProfileUrl) ? obj.options.newProfileUrl : obj.options.url;

        // Remote call
        jSuites.ajax({
            url: url,
            method: 'POST',
            dataType: 'json',
            data: data,
            success: function(result) {
                // Remove loading
                el.classList.remove('jlogin-loading');
                // Callback
                onsuccess(result);
            },
            error: function(result) {
                // Error
                el.classList.remove('jlogin-loading');

                if (typeof(obj.options.onerror) == 'function') {
                    obj.options.onerror(result);
                }
            }
        });
    }

    var queryString = window.location.href.split('?');
    if (queryString[1] && queryString[1].length == 130 && queryString[1].substr(0,2) == 'h=') {
        obj.changeMyPassword(queryString[1].substr(2));
    } else {
        obj.requestAccess();
    }

    return obj;
});

jSuites.login.sha512 = jSuites.sha512;

jSuites.menu = (function(el, options) {
    var obj = {};

    obj.show = function() {
        el.style.display = 'block';
        jSuites.animation.slideLeft(el, 1);
    }

    obj.hide = function() {
        jSuites.animation.slideLeft(el, 0, function() {
            el.style.display = '';
        });
    }

    obj.load = function() {
        if (localStorage) {
            var menu = el.querySelectorAll('nav');
            for (var i = 0; i < menu.length; i++) {
                menu[i].classList.remove('selected');
                if (menu[i].getAttribute('data-id')) {
                    var state = localStorage.getItem('jmenu-' + menu[i].getAttribute('data-id'));
                    if (state === null || state == 1) {
                        menu[i].classList.add('selected');
                    }
                }
            }
            var href = localStorage.getItem('jmenu-href');
            if (href) {
                var menu = document.querySelector('.jmenu a[href="'+ href +'"]');
                if (menu) {
                    menu.classList.add('selected');
                }
            }
        }
    }

    obj.select = function(o) {
        var menu = el.querySelectorAll('nav a');
        for (var i = 0; i < menu.length; i++) {
            menu[i].classList.remove('selected');
        }
        o.classList.add('selected');
    }

    var actionDown = function(e) {
        if (e.target.tagName == 'H2') {
            if (e.target.parentNode.classList.contains('selected')) {
                e.target.parentNode.classList.remove('selected');
                localStorage.setItem('jmenu-' + e.target.parentNode.getAttribute('data-id'), 0);
            } else {
                e.target.parentNode.classList.add('selected');
                localStorage.setItem('jmenu-' + e.target.parentNode.getAttribute('data-id'), 1);
            }
        } else if (e.target.tagName == 'A') {
            // Mark link as selected
            obj.select(e.target);
            // Keep the refernce in case load the page again
            localStorage.setItem('jmenu-href', e.target.getAttribute('href'));
            // Close menu if is oped
            obj.hide();
        }
    }

    if ('ontouchstart' in document.documentElement === true) {
        el.addEventListener('touchstart', actionDown);
    } else {
        el.addEventListener('mousedown', actionDown);
    }

    // Add close action
    var i = document.createElement('i');
    i.className = 'material-icons small-screen-only close';
    i.innerText = 'close';
    i.onclick = function() {
        obj.hide();
    }
    el.appendChild(i);

    // Add menu class
    el.classList.add('jmenu');

    /*var h1 = window.innerHeight;
    var h2 = el.offsetTop;

    // Add the scroll
    jSuites.scroll(el, {
        //height: (h1 - h2) + 'px'
        height: '1000px',
    });*/

    // Load state
    obj.load();

    // Keep reference
    el.menu = obj;

    return obj;
});


jSuites.signature = (function(el, options) {
    var obj = {};
    obj.options = {};

    // Default configuration
    var defaults = {
        width: '100%',
        height: '120px',
        lineWidth: 3,
        onchange: null,
        value: null,
        readonly: false,
    }

    // Loop through our object
    for (var property in defaults) {
        if (options && options.hasOwnProperty(property)) {
            obj.options[property] = options[property];
        } else {
            obj.options[property] = defaults[property];
        }
    }

    el.style.width = obj.options.width;
    el.style.height = obj.options.height;
    el.classList.add('jsignature');

    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');
    el.appendChild(canvas);

   // Position
    var x = null;
    var y = null;

    // Coordinates
    var coordinates = [];

    obj.setValue = function(c) {
        obj.reset();

        ctx.beginPath();
        ctx.lineWidth = obj.options.lineWidth;
        ctx.lineCap = 'round';
        ctx.strokeStyle = '#000';
        ctx.moveTo(c[0][0], c[0][1]);

        for (var i = 1; i < c.length; i++) {
            ctx.lineTo(c[i][0], c[i][1]);
            ctx.stroke();
        }
    }

    obj.getValue = function() {
        return coordinates;
    }

    obj.reset = function() {
        coordinates = [];
        ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, canvas.width, canvas.height);
    }

    var setPosition = function(e) {
        // Mark position
        if (e.changedTouches && e.changedTouches[0]) {
            var rect = e.target.getBoundingClientRect();
            x = e.changedTouches[0].clientX - rect.x;
            y = e.changedTouches[0].clientY - rect.y;
        } else {
            x = e.offsetX;
            y = e.offsetY;
        }
    }

    var resize = function() {
        ctx.canvas.width = el.offsetWidth;
        ctx.canvas.height = el.offsetHeight;
    }

    var draw = function(e) {
        if (x == null || obj.options.readonly == true) {
            return false;
        } else {
            e = e || window.event;
            if (e.buttons) {
                var mouseButton = e.buttons;
            } else if (e.button) {
                var mouseButton = e.button;
            } else {
                var mouseButton = e.which;
            }

            coordinates.push([ x, y ]);

            ctx.beginPath();
            ctx.lineWidth = obj.options.lineWidth;
            ctx.lineCap = 'round';
            ctx.strokeStyle = '#000';
            ctx.moveTo(x, y);
            setPosition(e);
            ctx.lineTo(x, y);
            ctx.stroke();

            e.preventDefault();
            e.stopPropagation();
        }
    }

    var finalize = function() {
        x = null;
        y = null;
    }

    window.addEventListener('resize', resize);

    if ('ontouchmove' in document.documentElement === true) {
        el.addEventListener('touchstart', setPosition);
        el.addEventListener('touchmove', draw);
        el.addEventListener('touchend', finalize);
    } else {
        el.addEventListener('mousedown', setPosition);
        el.addEventListener('mousemove', draw);
        el.addEventListener('mouseup', finalize);
    }

    resize();

    if (obj.options.value) {
        obj.setValue(obj.options.value);
    }

    el.signature = obj;

    return obj;
});

jSuites.template = (function(el, options) {
    var obj = {};
    obj.options = {};

    // Default configuration
    var defaults = {
        url: null,
        data: null,
        filter: null,
        pageNumber: 0,
        numberOfPages: 0,
        template: null,
        render: null,
        noRecordsFound: 'No records found',
        containerClass: null,
        // Searchable
        search: null,
        searchInput: true,
        searchPlaceHolder: '',
        searchValue: '',
        // Remote search
        remoteData: null,
        // Pagination page number of items
        pagination: null,
        onload: null,
        onchange: null,
        onsearch: null,
        onclick: null,
    }

    // Loop through our object
    for (var property in defaults) {
        if (options && options.hasOwnProperty(property)) {
            obj.options[property] = options[property];
        } else {
            obj.options[property] = defaults[property];
        }
    }

    // Reset content
    el.innerHTML = '';

    // Input search
    if (obj.options.search && obj.options.searchInput == true) {
        // Timer
        var searchTimer = null;

        // Search container
        var searchContainer = document.createElement('div');
        searchContainer.className = 'jtemplate-results';
        obj.searchInput = document.createElement('input');
        obj.searchInput.value = obj.options.searchValue;
        obj.searchInput.onkeyup = function(e) {
            // Clear current trigger
            if (searchTimer) {
                clearTimeout(searchTimer);
            }
            // Prepare search
            searchTimer = setTimeout(function() {
                obj.search(obj.searchInput.value.toLowerCase());
                searchTimer = null;
            }, 300)
        }
        searchContainer.appendChild(obj.searchInput);
        el.appendChild(searchContainer);

        if (obj.options.searchPlaceHolder) {
            obj.searchInput.setAttribute('placeholder', obj.options.searchPlaceHolder);
        }
    }

    // Pagination container
    if (obj.options.pagination) {
        var pagination = document.createElement('div');
        pagination.className = 'jtemplate-pagination';
        el.appendChild(pagination);
    }

    // Content
    var container = document.createElement('div');
    if (obj.options.containerClass) {
        container.className = obj.options.containerClass;
    }
    container.classList.add ('jtemplate-content');
    el.appendChild(container);

    // Data container
    var searchResults = null;

    obj.updatePagination = function() {
        // Reset pagination container
        if (pagination) {
            pagination.innerHTML = '';
        }

        // Create pagination
        if (obj.options.pagination > 0 && obj.options.numberOfPages > 1) {
            // Number of pages
            var numberOfPages = obj.options.numberOfPages;

            // Controllers
            if (obj.options.pageNumber < 6) {
                var startNumber = 1;
                var finalNumber = numberOfPages < 10 ? numberOfPages : 10;
            } else if (numberOfPages - obj.options.pageNumber < 5) {
                var startNumber = numberOfPages - 9;
                var finalNumber = numberOfPages;
                if (startNumber < 1) {
                    startNumber = 1;
                }
            } else {
                var startNumber = obj.options.pageNumber - 4;
                var finalNumber = obj.options.pageNumber + 5;
            }

            // First
            if (startNumber > 1) {
                var paginationItem = document.createElement('div');
                paginationItem.innerHTML = '<';
                paginationItem.title = 1;
                pagination.appendChild(paginationItem);
            }

            // Get page links
            for (var i = startNumber; i <= finalNumber; i++) {
                var paginationItem = document.createElement('div');
                paginationItem.innerHTML = i;
                pagination.appendChild(paginationItem);

                if (obj.options.pageNumber == i - 1) {
                    paginationItem.style.fontWeight = 'bold';
                }
            }

            // Last
            if (finalNumber < numberOfPages) {
                var paginationItem = document.createElement('div');
                paginationItem.innerHTML = '>';
                paginationItem.title = numberOfPages - 1;
                pagination.appendChild(paginationItem);
            }
        }
    }

    /**
     * Append data to the template and add to the DOMContainer
     * @param data
     * @param contentDOMContainer
     */
    obj.setContent = function(a, b) {
        var c = obj.options.template[Object.keys(obj.options.template)[0]](a);
        if ((c instanceof Element || c instanceof HTMLDocument)) {
            b.appendChild(c);
        } else {
            b.innerHTML = c;
        }
    }

    obj.addItem = function(data, beginOfDataSet) {
        // Append itens
        var content = document.createElement('div');
        // Append data
        if (beginOfDataSet) {
            obj.options.data.unshift(data);
        } else {
            obj.options.data.push(data);
        }
        // If is empty remove indication
        if (container.classList.contains('jtemplate-empty')) {
            container.classList.remove('jtemplate-empty');
            container.innerHTML = '';
        }
        // Get content
        obj.setContent(data, content);
        // Add animation
        jSuites.animation.fadeIn(content.children[0]);
        // Add and do the animation
        if (beginOfDataSet) {
            container.prepend(content.children[0]);
        } else {
            container.append(content.children[0]);
        }
        // Onchange method
        if (typeof(obj.options.onchange) == 'function') {
            obj.options.onchange(el, obj.options.data);
        }
    }

    obj.removeItem = function(element) {
        if (Array.prototype.indexOf.call(container.children, element) > -1) {
            // Remove data from array
            var index = obj.options.data.indexOf(element.dataReference);
            if (index > -1) {
                obj.options.data.splice(index, 1);
            }
            // Remove element from DOM
            jSuites.animation.fadeOut(element, function() {
                element.parentNode.removeChild(element);

                if (! container.innerHTML) {
                    container.classList.add('jtemplate-empty');
                    container.innerHTML = obj.options.noRecordsFound;
                }
            });
        } else {
            console.error('Element not found');
        }
    }

    obj.setData = function(data) {
        if (data) {
            obj.options.pageNumber = 1;
            obj.options.searchValue = '';
            // Set data
            obj.options.data = data;
            // Reset any search results
            searchResults = null;

            // Render new data
            obj.render();

            // Onchange method
            if (typeof(obj.options.onchange) == 'function') {
                obj.options.onchange(el, obj.options.data);
            }
        }
    }

    obj.appendData = function(data, pageNumber) {
        if (pageNumber) {
            obj.options.pageNumber = pageNumber;
        }

        var execute = function(data) {
            // Concat data
            obj.options.data.concat(data);
            // Number of pages
            if (obj.options.pagination > 0) {
                obj.options.numberOfPages = Math.ceil(obj.options.data.length / obj.options.pagination);
            }
            var startNumber = 0;
            var finalNumber = data.length;
            // Append itens
            var content = document.createElement('div');
            for (var i = startNumber; i < finalNumber; i++) {
                obj.setContent(data[i], content)
                content.children[0].dataReference = data[i]; // TODO: data[i] or i?
                container.appendChild(content.children[0]);
            }
        }

        if (obj.options.url && obj.options.remoteData == true) {
            // URL
            var url = obj.options.url;
            // Data
            var ajaxData = {};
            // Options for backend search
            if (obj.options.remoteData) {
                // Search value
                if (obj.options.searchValue) {
                    ajaxData.q = obj.options.searchValue;
                }
                // Page number
                if (obj.options.pageNumber) {
                    ajaxData.p = obj.options.pageNumber;
                }
                // Number items per page
                if (obj.options.pagination) {
                    ajaxData.t = obj.options.pagination;
                }
            }
            // Remote loading
            jSuites.ajax({
                url: url,
                method: 'GET',
                data: ajaxData,
                dataType: 'json',
                success: function(data) {
                    execute(data);
                }
            });
        } else {
            if (! obj.options.data) {
                console.log('TEMPLATE: no data or external url defined');
            } else {
                execute(data);
            }
        }
    }

    obj.renderTemplate = function() {
        // Data container
        var data = searchResults ? searchResults : obj.options.data;

        // Data filtering
        if (typeof(obj.options.filter) == 'function') {
            data = obj.options.filter(data);
        }

        // Reset pagination
        obj.updatePagination();

        if (! data.length) {
            container.innerHTML = obj.options.noRecordsFound;
            container.classList.add('jtemplate-empty');
        } else {
            // Reset content
            container.classList.remove('jtemplate-empty');

            // Create pagination
            if (obj.options.pagination && data.length > obj.options.pagination) {
                var startNumber = (obj.options.pagination * obj.options.pageNumber);
                var finalNumber = (obj.options.pagination * obj.options.pageNumber) + obj.options.pagination;

                if (data.length < finalNumber) {
                    var finalNumber = data.length;
                }
            } else {
                var startNumber = 0;
                var finalNumber = data.length;
            }

            // Append itens
            var content = document.createElement('div');
            for (var i = startNumber; i < finalNumber; i++) {
                // Get content
                obj.setContent(data[i], content);
                content.children[0].dataReference = data[i]; 
                container.appendChild(content.children[0]);
            }
        }
    }

    obj.render = function(pageNumber, forceLoad) {
        // Update page number
        if (pageNumber != undefined) {
            obj.options.pageNumber = pageNumber;
        } else {
            if (! obj.options.pageNumber && obj.options.pagination > 0) {
                obj.options.pageNumber = 0;
            }
        }

        // Render data into template
        var execute = function() {
            // Render new content
            if (typeof(obj.options.render) == 'function') {
                container.innerHTML = obj.options.render(obj);
            } else {
                container.innerHTML = '';
            }

            // Load data
            obj.renderTemplate();

            // Onload
            if (typeof(obj.options.onload) == 'function') {
                obj.options.onload(el, obj);
            }
        }

        if (obj.options.url && (obj.options.remoteData == true || forceLoad)) {
            // URL
            var url = obj.options.url;
            // Data
            var ajaxData = {};
            // Options for backend search
            if (obj.options.remoteData) {
                // Search value
                if (obj.options.searchValue) {
                    ajaxData.q = obj.options.searchValue;
                }
                // Page number
                if (obj.options.pageNumber) {
                    ajaxData.p = obj.options.pageNumber;
                }
                // Number items per page
                if (obj.options.pagination) {
                    ajaxData.t = obj.options.pagination;
                }
            }
            // Remote loading
            jSuites.ajax({
                url: url,
                method: 'GET',
                dataType: 'json',
                data: ajaxData,
                success: function(data) {
                    // Search and keep data in the client side
                    if (data.hasOwnProperty("total")) {
                        obj.options.numberOfPages = Math.ceil(data.total / obj.options.pagination);
                        obj.options.data = data.data;
                    } else {
                        // Number of pages
                        if (obj.options.pagination > 0) {
                            obj.options.numberOfPages = Math.ceil(data.length / obj.options.pagination);
                        }
                        obj.options.data = data;
                    }

                    // Load data for the user
                    execute();
                }
            });
        } else {
            if (! obj.options.data) {
                console.log('TEMPLATE: no data or external url defined');
            } else {
                // Number of pages
                if (obj.options.pagination > 0) {
                    if (searchResults) {
                        obj.options.numberOfPages = Math.ceil(searchResults.length / obj.options.pagination);
                    } else {
                        obj.options.numberOfPages = Math.ceil(obj.options.data.length / obj.options.pagination);
                    }
                }
                // Load data for the user
                execute();
            }
        }
    }

    obj.search = function(query) {
        obj.options.pageNumber = 0;
        obj.options.searchValue = query ? query : '';

        // Filter data
        if (obj.options.remoteData == true || ! query) {
            searchResults = null;
        } else {
            var test = function(o, query) {
                for (var key in o) {
                    var value = o[key];

                    if ((''+value).toLowerCase().search(query) >= 0) {
                        return true;
                    }
                }
                return false;
            }

            searchResults = obj.options.data.filter(function(item) {
                return test(item, query);
            });
        }

        obj.render();

        if (typeof(obj.options.onsearch) == 'function') {
            obj.options.onsearch(el, obj, query);
        }
    }

    obj.refresh = function() {
        obj.render();
    }

    obj.reload = function() {
        obj.render(0, true);
    }

    el.addEventListener('mousedown', function(e) {
        if (e.target.parentNode.classList.contains('jtemplate-pagination')) {
            var index = e.target.innerText;
            if (index == '<') {
                obj.render(0);
            } else if (index == '>') {
                obj.render(e.target.getAttribute('title'));
            } else {
                obj.render(parseInt(index)-1);
            }
            e.preventDefault();
        }
    });

    el.addEventListener('click', function(e) {
        if (typeof(obj.options.onclick) == 'function') {
            obj.options.onclick(el, obj, e);
        }
    });

    el.template = obj;

    // Render data
    obj.render(0, true);

    return obj;
});

jSuites.timeline = (function(el, options) {
    var obj = {};
    obj.options = {};

    // Two digits
    var two = function(value) {
        value = '' + value;
        if (value.length == 1) {
            value = '0' + value;
        }
        return value;
    }

    // Default date format
    if (! options.date) {
        var date = new Date();
        var y = date.getFullYear();
        var m = two(date.getMonth() + 1);
        date = y + '-' + m;
    }

    // Default configurations
    var defaults = {
        url: null,  
        data: [],
        date: date,
        months: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ],
        monthsFull: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ],
        onaction: null,
        text: {
            noInformation: '<div class="jtimeline-message">No information for this period</div>',
        }
    };

    // Loop through our object
    for (var property in defaults) {
        if (options && options.hasOwnProperty(property)) {
            obj.options[property] = options[property];
        } else {
            obj.options[property] = defaults[property];
        }
    }

    // Add class
    el.classList.add('jtimeline');

    // Header
    var timelineHeader = document.createElement('div');
    timelineHeader.className = 'jtimeline-header';

    var timelineLabel = document.createElement('div');
    timelineLabel.className = 'jtimeline-label';

    var timelineNavigation = document.createElement('div');
    timelineNavigation.className = 'jtimeline-navigation';

    // Labels 
    var timelineMonth = document.createElement('div');
    timelineMonth.className = 'jtimeline-month';
    timelineMonth.innerHTML = '';
    timelineLabel.appendChild(timelineMonth);

    var timelineYear = document.createElement('div');
    timelineYear.className = 'jtimeline-year';
    timelineYear.innerHTML = '';
    timelineLabel.appendChild(timelineYear);

    // Navigation
    var timelinePrev = document.createElement('div');
    timelinePrev.className = 'jtimeline-prev';
    timelinePrev.innerHTML = '<i class="material-icons">keyboard_arrow_left</i>';
    timelineNavigation.appendChild(timelinePrev);

    var timelineNext = document.createElement('div');
    timelineNext.className = 'jtimeline-next';
    timelineNext.innerHTML = '<i class="material-icons">keyboard_arrow_right</i>';
    timelineNavigation.appendChild(timelineNext);

    timelineHeader.appendChild(timelineLabel);
    timelineHeader.appendChild(timelineNavigation);

    // Data container
    var timelineContainer = document.createElement('div');
    timelineContainer.className = 'jtimeline-container';

    // Append headers
    el.appendChild(timelineHeader);
    el.appendChild(timelineContainer);

    // Date
    if (obj.options.date.length > 7) {
        obj.options.date = obj.options.date.substr(0, 7)
    }

    // Action
    var action = function(o) {
        // Get item
        var item = o.parentNode.parentNode.parentNode.parentNode;
        // Get id
        var id = item.getAttribute('data-id');

    }

    obj.setData = function(rows) {
        var data = [];
        for (var i = 0; i < rows.length; i++) {
            var d = rows[i].date.substr(0,7);

            // Create the object if not exists
            if (! data[d]) {
                data[d] = [];
            }

            // Create array
            data[d].push(rows[i]);
        };
        obj.options.data = data;
        obj.render(obj.options.date);
    }

    obj.add = function(data) {
        var date = data.date.substr(0,7);

        // Create the object if not exists
        if (! obj.options.data[date]) {
            obj.options.data[date] = [];
        }

        // Format date
        data.date = data.date.substr(0,10);

        // Append data
        obj.options.data[date].push(data);

        // Reorder
        obj.options.data[date] = obj.options.data[date].order();

        // Render
        obj.render(date);
    }

    obj.remove = function(item) {
        var index = item.getAttribute('data-index');
        var date = item.getAttribute('data-date');

        jSuites.animation.fadeOut(item, function() {
            item.remove();
        });

        obj.options.data[date].splice(index, 1);
    }

    obj.reload = function() {
        var date = obj.options.date
        obj.render(date);
    }

    obj.render = function(date) {
        // Filter
        if (date.length > 7) {
            var date = date.substr(0,7);
        }

        // Update current date
        obj.options.date = date;

        // Reset data
        timelineContainer.innerHTML = '';

        // Days
        var timelineDays = [];

        // Itens
        if (! obj.options.data[date]) {
            timelineContainer.innerHTML = obj.options.text.noInformation;
        } else {
            for (var i = 0; i < obj.options.data[date].length; i++) {
                var v = obj.options.data[date][i];
                var d = v.date.split('-');

                // Item container
                var timelineItem = document.createElement('div');
                timelineItem.className = 'jtimeline-item';
                timelineItem.setAttribute('data-id', v.id);
                timelineItem.setAttribute('data-index', i);
                timelineItem.setAttribute('data-date', date);

                // Date
                var timelineDateContainer = document.createElement('div');
                timelineDateContainer.className = 'jtimeline-date-container';

                var timelineDate = document.createElement('div');
                if (! timelineDays[d[2]]) {
                    timelineDate.className = 'jtimeline-date jtimeline-date-bullet';
                    timelineDate.innerHTML = d[2];
                } else {
                    timelineDate.className = 'jtimeline-date';
                    timelineDate.innerHTML = '';
                }
                timelineDateContainer.appendChild(timelineDate);

                var timelineContent = document.createElement('div');
                timelineContent.className = 'jtimeline-content';

                // Title
                if (! v.title) {
                    v.title = v.subtitle ? v.subtitle : 'Information';
                }

                var timelineTitleContainer = document.createElement('div');
                timelineTitleContainer.className = 'jtimeline-title-container';
                timelineContent.appendChild(timelineTitleContainer);

                var timelineTitle = document.createElement('div');
                timelineTitle.className = 'jtimeline-title';
                timelineTitle.innerHTML = v.title;
                timelineTitleContainer.appendChild(timelineTitle);

                var timelineControls = document.createElement('div');
                timelineControls.className = 'jtimeline-controls';
                timelineTitleContainer.appendChild(timelineControls);

                var timelineEdit = document.createElement('i');
                timelineEdit.className = 'material-icons timeline-edit';
                timelineEdit.innerHTML = 'edit';
                timelineEdit.onclick = function() {
                    if (typeof(obj.options.onaction) == 'function') {
                        obj.options.onaction(obj, this);
                    }
                }
                if (v.author == 1) {
                    timelineControls.appendChild(timelineEdit);
                }

                var timelineSubtitle = document.createElement('div');
                timelineSubtitle.className = 'jtimeline-subtitle';
                timelineSubtitle.innerHTML = v.subtitle ? v.subtitle : '';
                timelineContent.appendChild(timelineSubtitle);

                // Text
                var timelineText = document.createElement('div');
                timelineText.className = 'jtimeline-text';
                timelineText.innerHTML = v.text;
                timelineContent.appendChild(timelineText);

                // Tag
                var timelineTags = document.createElement('div');
                timelineTags.className = 'jtimeline-tags';
                timelineContent.appendChild(timelineTags);

                if (v.tags) {
                    var createTag = function(name, color) {
                        var timelineTag = document.createElement('div');
                        timelineTag.className = 'jtimeline-tag';
                        timelineTag.innerHTML = name;
                        if (color) {
                            timelineTag.style.backgroundColor = color;
                        }
                        return timelineTag; 
                    }

                    if (typeof(v.tags) == 'string') {
                        var t = createTag(v.tags);
                        timelineTags.appendChild(t);
                    } else {
                        for (var j = 0; j < v.tags.length; j++) {
                            var t = createTag(v.tags[j].text, v.tags[j].color);
                            timelineTags.appendChild(t);
                        }
                    }
                }

                // Day
                timelineDays[d[2]] = true;

                // Append Item
                timelineItem.appendChild(timelineDateContainer);
                timelineItem.appendChild(timelineContent);
                timelineContainer.appendChild(timelineItem);
            };
        }

        // Update labels
        var d = date.split('-');
        timelineYear.innerHTML = d[0];
        timelineMonth.innerHTML = obj.options.monthsFull[parseInt(d[1]) - 1];
    }

    obj.next = function() {
        // Update current date
        var d = obj.options.date.split('-');
        // Next month
        d[1]++;
        // Next year
        if (d[1] > 12) {
            d[0]++;
            d[1] = 1;
        }
        date = d[0] + '-' + (d[1] < 10 ? '0' + d[1] : d[1]);

        // Animation
        jSuites.animation.slideLeft(timelineContainer, 0, function() {
            obj.render(date);
            jSuites.animation.slideRight(timelineContainer, 1);
        });
    }

    obj.prev = function() {
        // Update current date
        var d = obj.options.date.split('-');
        // Next month
        d[1]--;
        // Next year
        if (d[1] < 1) {
            d[0]--;
            d[1] = 12;
        }
        date = d[0] + '-' + (d[1] < 10 ? '0' + d[1] : d[1]);

        // Animation
        jSuites.animation.slideRight(timelineContainer, 0, function() {
            obj.render(date);
            jSuites.animation.slideLeft(timelineContainer, 1);
        });
    }

    obj.load = function() {
        // Init
        if (obj.options.url) {
            jSuites.ajax({
                url: obj.options.url,
                type: 'GET',
                dataType:'json',
                success: function(data) {
                    // Timeline data
                    obj.setData(data);
                }
            });
        } else {
            // Timeline data
            obj.setData(obj.options.data);
        }
    }

    obj.reload = function() {
        obj.load();
    }

    obj.load();

    var timelineMouseUpControls = function(e) {
        if (e.target.classList.contains('jtimeline-next') || e.target.parentNode.classList.contains('jtimeline-next')) {
            obj.next();
        } else if (e.target.classList.contains('jtimeline-prev') || e.target.parentNode.classList.contains('jtimeline-prev')) {
            obj.prev();
        }
    }

    if ('ontouchend' in document.documentElement === true) {
        el.addEventListener("touchend", timelineMouseUpControls);
    } else {
        el.addEventListener("mouseup", timelineMouseUpControls);
    }

    // Add global events
    el.addEventListener("swipeleft", function(e) {
        obj.next();
        e.preventDefault();
        e.stopPropagation();
    });

    el.addEventListener("swiperight", function(e) {
        obj.prev();
        e.preventDefault();
        e.stopPropagation();
    });

    // Orderby
    Array.prototype.order = function() {
        return this.slice(0).sort(function(a, b) {
            var valueA = a.date;
            var valueB = b.date;

            return (valueA > valueB) ? 1 : (valueA < valueB) ? -1 : 0;
        });
    }

    el.timeline = obj;

    return obj;
});



    return jSuites;

})));