123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- /*!
- * Ladda
- * http://lab.hakim.se/ladda
- * MIT licensed
- *
- * Copyright (C) 2015 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,
- spinnerWrapper = button.querySelector( '.ladda-spinner' );
- // Wrapper element for the spinner
- if( !spinnerWrapper ) {
- 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', 'select' ];
- 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;
- }
- // Radiobuttons and Checkboxes need to be checked for the "checked" attribute
- if( (requireds[i].type === 'checkbox' || requireds[i].type === 'radio' ) && !requireds[i].checked ) {
- 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
- };
- }));
|