summaryrefslogtreecommitdiff
path: root/js/ladda/ladda.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/ladda/ladda.js')
-rw-r--r--js/ladda/ladda.js412
1 files changed, 412 insertions, 0 deletions
diff --git a/js/ladda/ladda.js b/js/ladda/ladda.js
new file mode 100644
index 0000000..40f2960
--- /dev/null
+++ b/js/ladda/ladda.js
@@ -0,0 +1,412 @@
+/*!
+ * 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
+
+ };
+
+}));