/*! 
 | 
 * Ladda 
 | 
 * http://lab.hakim.se/ladda 
 | 
 * MIT licensed 
 | 
 * 
 | 
 * Copyright (C) 2014 Hakim El Hattab, http://hakim.se 
 | 
 */ 
 | 
/* jshint node:true, browser:true */ 
 | 
(function( root, factory ) { 
 | 
  
 | 
    // CommonJS 
 | 
    if( typeof exports === 'object' )  { 
 | 
        module.exports = factory(require('spin.js')); 
 | 
    } 
 | 
    // AMD module 
 | 
    else if( typeof define === 'function' && define.amd ) { 
 | 
        define( [ 'spin' ], factory ); 
 | 
    } 
 | 
    // Browser global 
 | 
    else { 
 | 
        root.Ladda = factory( root.Spinner ); 
 | 
    } 
 | 
  
 | 
} 
 | 
(this, function( Spinner ) { 
 | 
    'use strict'; 
 | 
  
 | 
    // All currently instantiated instances of Ladda 
 | 
    var ALL_INSTANCES = []; 
 | 
  
 | 
    /** 
 | 
     * Creates a new instance of Ladda which wraps the 
 | 
     * target button element. 
 | 
     * 
 | 
     * @return An API object that can be used to control 
 | 
     * the loading animation state. 
 | 
     */ 
 | 
    function create( button ) { 
 | 
  
 | 
        if( typeof button === 'undefined' ) { 
 | 
            console.warn( "Ladda button target must be defined." ); 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        // The text contents must be wrapped in a ladda-label 
 | 
        // element, create one if it doesn't already exist 
 | 
        if( !button.querySelector( '.ladda-label' ) ) { 
 | 
            button.innerHTML = '<span class="ladda-label">'+ button.innerHTML +'</span>'; 
 | 
        } 
 | 
  
 | 
        // The spinner component 
 | 
        var spinner; 
 | 
  
 | 
        // Wrapper element for the spinner 
 | 
        var spinnerWrapper = document.createElement( 'span' ); 
 | 
        spinnerWrapper.className = 'ladda-spinner'; 
 | 
        button.appendChild( spinnerWrapper ); 
 | 
  
 | 
        // Timer used to delay starting/stopping 
 | 
        var timer; 
 | 
  
 | 
        var instance = { 
 | 
  
 | 
            /** 
 | 
             * Enter the loading state. 
 | 
             */ 
 | 
            start: function() { 
 | 
  
 | 
                // Create the spinner if it doesn't already exist 
 | 
                if( !spinner ) spinner = createSpinner( button ); 
 | 
  
 | 
                button.setAttribute( 'disabled', '' ); 
 | 
                button.setAttribute( 'data-loading', '' ); 
 | 
  
 | 
                clearTimeout( timer ); 
 | 
                spinner.spin( spinnerWrapper ); 
 | 
  
 | 
                this.setProgress( 0 ); 
 | 
  
 | 
                return this; // chain 
 | 
  
 | 
            }, 
 | 
  
 | 
            /** 
 | 
             * Enter the loading state, after a delay. 
 | 
             */ 
 | 
            startAfter: function( delay ) { 
 | 
  
 | 
                clearTimeout( timer ); 
 | 
                timer = setTimeout( function() { instance.start(); }, delay ); 
 | 
  
 | 
                return this; // chain 
 | 
  
 | 
            }, 
 | 
  
 | 
            /** 
 | 
             * Exit the loading state. 
 | 
             */ 
 | 
            stop: function() { 
 | 
  
 | 
                button.removeAttribute( 'disabled' ); 
 | 
                button.removeAttribute( 'data-loading' ); 
 | 
  
 | 
                // Kill the animation after a delay to make sure it 
 | 
                // runs for the duration of the button transition 
 | 
                clearTimeout( timer ); 
 | 
  
 | 
                if( spinner ) { 
 | 
                    timer = setTimeout( function() { spinner.stop(); }, 1000 ); 
 | 
                } 
 | 
  
 | 
                return this; // chain 
 | 
  
 | 
            }, 
 | 
  
 | 
            /** 
 | 
             * Toggle the loading state on/off. 
 | 
             */ 
 | 
            toggle: function() { 
 | 
  
 | 
                if( this.isLoading() ) { 
 | 
                    this.stop(); 
 | 
                } 
 | 
                else { 
 | 
                    this.start(); 
 | 
                } 
 | 
  
 | 
                return this; // chain 
 | 
  
 | 
            }, 
 | 
  
 | 
            /** 
 | 
             * Sets the width of the visual progress bar inside of 
 | 
             * this Ladda button 
 | 
             * 
 | 
             * @param {Number} progress in the range of 0-1 
 | 
             */ 
 | 
            setProgress: function( progress ) { 
 | 
  
 | 
                // Cap it 
 | 
                progress = Math.max( Math.min( progress, 1 ), 0 ); 
 | 
  
 | 
                var progressElement = button.querySelector( '.ladda-progress' ); 
 | 
  
 | 
                // Remove the progress bar if we're at 0 progress 
 | 
                if( progress === 0 && progressElement && progressElement.parentNode ) { 
 | 
                    progressElement.parentNode.removeChild( progressElement ); 
 | 
                } 
 | 
                else { 
 | 
                    if( !progressElement ) { 
 | 
                        progressElement = document.createElement( 'div' ); 
 | 
                        progressElement.className = 'ladda-progress'; 
 | 
                        button.appendChild( progressElement ); 
 | 
                    } 
 | 
  
 | 
                    progressElement.style.width = ( ( progress || 0 ) * button.offsetWidth ) + 'px'; 
 | 
                } 
 | 
  
 | 
            }, 
 | 
  
 | 
            enable: function() { 
 | 
  
 | 
                this.stop(); 
 | 
  
 | 
                return this; // chain 
 | 
  
 | 
            }, 
 | 
  
 | 
            disable: function () { 
 | 
  
 | 
                this.stop(); 
 | 
                button.setAttribute( 'disabled', '' ); 
 | 
  
 | 
                return this; // chain 
 | 
  
 | 
            }, 
 | 
  
 | 
            isLoading: function() { 
 | 
  
 | 
                return button.hasAttribute( 'data-loading' ); 
 | 
  
 | 
            }, 
 | 
  
 | 
            remove: function() { 
 | 
  
 | 
                clearTimeout( timer ); 
 | 
  
 | 
                button.removeAttribute( 'disabled', '' ); 
 | 
                button.removeAttribute( 'data-loading', '' ); 
 | 
  
 | 
                if( spinner ) { 
 | 
                    spinner.stop(); 
 | 
                    spinner = null; 
 | 
                } 
 | 
  
 | 
                for( var i = 0, len = ALL_INSTANCES.length; i < len; i++ ) { 
 | 
                    if( instance === ALL_INSTANCES[i] ) { 
 | 
                        ALL_INSTANCES.splice( i, 1 ); 
 | 
                        break; 
 | 
                    } 
 | 
                } 
 | 
  
 | 
            } 
 | 
  
 | 
        }; 
 | 
  
 | 
        ALL_INSTANCES.push( instance ); 
 | 
  
 | 
        return instance; 
 | 
  
 | 
    } 
 | 
  
 | 
    /** 
 | 
    * Get the first ancestor node from an element, having a 
 | 
    * certain type. 
 | 
    * 
 | 
    * @param elem An HTML element 
 | 
    * @param type an HTML tag type (uppercased) 
 | 
    * 
 | 
    * @return An HTML element 
 | 
    */ 
 | 
    function getAncestorOfTagType( elem, type ) { 
 | 
  
 | 
        while ( elem.parentNode && elem.tagName !== type ) { 
 | 
            elem = elem.parentNode; 
 | 
        } 
 | 
  
 | 
        return ( type === elem.tagName ) ? elem : undefined; 
 | 
  
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns a list of all inputs in the given form that 
 | 
     * have their `required` attribute set. 
 | 
     * 
 | 
     * @param form The from HTML element to look in 
 | 
     * 
 | 
     * @return A list of elements 
 | 
     */ 
 | 
    function getRequiredFields( form ) { 
 | 
  
 | 
        var requirables = [ 'input', 'textarea' ]; 
 | 
        var inputs = []; 
 | 
  
 | 
        for( var i = 0; i < requirables.length; i++ ) { 
 | 
            var candidates = form.getElementsByTagName( requirables[i] ); 
 | 
            for( var j = 0; j < candidates.length; j++ ) { 
 | 
                if ( candidates[j].hasAttribute( 'required' ) ) { 
 | 
                    inputs.push( candidates[j] ); 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
  
 | 
        return inputs; 
 | 
  
 | 
    } 
 | 
  
 | 
  
 | 
    /** 
 | 
     * Binds the target buttons to automatically enter the 
 | 
     * loading state when clicked. 
 | 
     * 
 | 
     * @param target Either an HTML element or a CSS selector. 
 | 
     * @param options 
 | 
     *          - timeout Number of milliseconds to wait before 
 | 
     *            automatically cancelling the animation. 
 | 
     */ 
 | 
    function bind( target, options ) { 
 | 
  
 | 
        options = options || {}; 
 | 
  
 | 
        var targets = []; 
 | 
  
 | 
        if( typeof target === 'string' ) { 
 | 
            targets = toArray( document.querySelectorAll( target ) ); 
 | 
        } 
 | 
        else if( typeof target === 'object' && typeof target.nodeName === 'string' ) { 
 | 
            targets = [ target ]; 
 | 
        } 
 | 
  
 | 
        for( var i = 0, len = targets.length; i < len; i++ ) { 
 | 
  
 | 
            (function() { 
 | 
                var element = targets[i]; 
 | 
  
 | 
                // Make sure we're working with a DOM element 
 | 
                if( typeof element.addEventListener === 'function' ) { 
 | 
                    var instance = create( element ); 
 | 
                    var timeout = -1; 
 | 
  
 | 
                    element.addEventListener( 'click', function( event ) { 
 | 
  
 | 
                        // If the button belongs to a form, make sure all the 
 | 
                        // fields in that form are filled out 
 | 
                        var valid = true; 
 | 
                        var form = getAncestorOfTagType( element, 'FORM' ); 
 | 
  
 | 
                        if( typeof form !== 'undefined' ) { 
 | 
                            var requireds = getRequiredFields( form ); 
 | 
                            for( var i = 0; i < requireds.length; i++ ) { 
 | 
                                // Alternatively to this trim() check, 
 | 
                                // we could have use .checkValidity() or .validity.valid 
 | 
                                if( requireds[i].value.replace( /^\s+|\s+$/g, '' ) === '' ) { 
 | 
                                    valid = false; 
 | 
                                } 
 | 
                            } 
 | 
                        } 
 | 
  
 | 
                        if( valid ) { 
 | 
                            // This is asynchronous to avoid an issue where setting 
 | 
                            // the disabled attribute on the button prevents forms 
 | 
                            // from submitting 
 | 
                            instance.startAfter( 1 ); 
 | 
  
 | 
                            // Set a loading timeout if one is specified 
 | 
                            if( typeof options.timeout === 'number' ) { 
 | 
                                clearTimeout( timeout ); 
 | 
                                timeout = setTimeout( instance.stop, options.timeout ); 
 | 
                            } 
 | 
  
 | 
                            // Invoke callbacks 
 | 
                            if( typeof options.callback === 'function' ) { 
 | 
                                options.callback.apply( null, [ instance ] ); 
 | 
                            } 
 | 
                        } 
 | 
  
 | 
                    }, false ); 
 | 
                } 
 | 
            })(); 
 | 
  
 | 
        } 
 | 
  
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Stops ALL current loading animations. 
 | 
     */ 
 | 
    function stopAll() { 
 | 
  
 | 
        for( var i = 0, len = ALL_INSTANCES.length; i < len; i++ ) { 
 | 
            ALL_INSTANCES[i].stop(); 
 | 
        } 
 | 
  
 | 
    } 
 | 
  
 | 
    function createSpinner( button ) { 
 | 
  
 | 
        var height = button.offsetHeight, 
 | 
            spinnerColor; 
 | 
  
 | 
        if( height === 0 ) { 
 | 
            // We may have an element that is not visible so 
 | 
            // we attempt to get the height in a different way 
 | 
            height = parseFloat( window.getComputedStyle( button ).height ); 
 | 
        } 
 | 
  
 | 
        // If the button is tall we can afford some padding 
 | 
        if( height > 32 ) { 
 | 
            height *= 0.8; 
 | 
        } 
 | 
  
 | 
        // Prefer an explicit height if one is defined 
 | 
        if( button.hasAttribute( 'data-spinner-size' ) ) { 
 | 
            height = parseInt( button.getAttribute( 'data-spinner-size' ), 10 ); 
 | 
        } 
 | 
  
 | 
        // Allow buttons to specify the color of the spinner element 
 | 
        if( button.hasAttribute( 'data-spinner-color' ) ) { 
 | 
            spinnerColor = button.getAttribute( 'data-spinner-color' ); 
 | 
        } 
 | 
  
 | 
        var lines = 12, 
 | 
            radius = height * 0.2, 
 | 
            length = radius * 0.6, 
 | 
            width = radius < 7 ? 2 : 3; 
 | 
  
 | 
        return new Spinner( { 
 | 
            color: spinnerColor || '#fff', 
 | 
            lines: lines, 
 | 
            radius: radius, 
 | 
            length: length, 
 | 
            width: width, 
 | 
            zIndex: 'auto', 
 | 
            top: 'auto', 
 | 
            left: 'auto', 
 | 
            className: '' 
 | 
        } ); 
 | 
  
 | 
    } 
 | 
  
 | 
    function toArray( nodes ) { 
 | 
  
 | 
        var a = []; 
 | 
  
 | 
        for ( var i = 0; i < nodes.length; i++ ) { 
 | 
            a.push( nodes[ i ] ); 
 | 
        } 
 | 
  
 | 
        return a; 
 | 
  
 | 
    } 
 | 
  
 | 
    // Public API 
 | 
    return { 
 | 
  
 | 
        bind: bind, 
 | 
        create: create, 
 | 
        stopAll: stopAll 
 | 
  
 | 
    }; 
 | 
  
 | 
})); 
 |