ladda.js原始碼

風靈使發表於2018-12-31

ES6語法

/*!
 * Ladda
 * http://lab.hakim.se/ladda
 * MIT licensed
 *
 * Copyright (C) 2018 Hakim El Hattab, http://hakim.se
 */

 import {Spinner} from 'spin.js';

// 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.
 */
export function create( button ) {

	if( typeof button === 'undefined' ) {
		console.warn( "Ladda button target must be defined." );
		return;
	}

	// The button must have the class "ladda-button"
	if ( !button.classList.contains('ladda-button') ) {
		button.classList.add( 'ladda-button' );
	}

	// Style is required, default to "expand-right"
	if( !button.hasAttribute( 'data-style' ) ) {
		button.setAttribute( 'data-style', 'expand-right' );
	}

	// The text contents must be wrapped in a ladda-label
	// element, create one if it doesn't already exist
	if( !button.querySelector( '.ladda-label' ) ) {
		var laddaLabel = document.createElement( 'span' );
		laddaLabel.className = 'ladda-label';
		wrapContent( button, laddaLabel );
	}

	// 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.disabled = true;
			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() {

			if (instance.isLoading()) {
				button.disabled = false;
				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() {
			return this.isLoading() ? this.stop() : this.start();
		},

		/**
		 * 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';
			}

		},

		isLoading: function() {

			return button.hasAttribute( 'data-loading' );

		},

		remove: function() {

			clearTimeout( timer );

			button.disabled = false;
			button.removeAttribute( 'data-loading' );

			if( spinner ) {
				spinner.stop();
				spinner = null;
			}

			ALL_INSTANCES.splice( ALL_INSTANCES.indexOf(instance), 1 );

		}

	};

	ALL_INSTANCES.push( instance );

	return instance;

}

/**
 * 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.
 *          - callback A function to be called with the Ladda
 *            instance when a target button is clicked.
 */
export function bind( target, options ) {

	var targets;

	if( typeof target === 'string' ) {
		targets = document.querySelectorAll( target );
	}
	else if( typeof target === 'object' ) {
		targets = [ target ];
	} else {
		throw new Error('target must be string or object');
	}

	options = options || {};

	for( var i = 0; i < targets.length; i++ ) {
		bindElement(targets[i], options);
	}

}

/**
 * Stops ALL current loading animations.
 */
export function stopAll() {

	for( var i = 0, len = ALL_INSTANCES.length; i < len; i++ ) {
		ALL_INSTANCES[i].stop();
	}

}

/**
* 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;

}

function createSpinner( button ) {

	var height = button.offsetHeight,
		spinnerColor,
		spinnerLines;

	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' );
	}

	// Allow buttons to specify the number of lines of the spinner
	if( button.hasAttribute( 'data-spinner-lines' ) ) {
		spinnerLines = parseInt( button.getAttribute( 'data-spinner-lines' ), 10 );
	}

	var radius = height * 0.2,
		length = radius * 0.6,
		width = radius < 7 ? 2 : 3;

	return new Spinner( {
		color: spinnerColor || '#fff',
		lines: spinnerLines || 12,
		radius: radius,
		length: length,
		width: width,
		animation: 'ladda-spinner-line-fade',
		zIndex: 'auto',
		top: 'auto',
		left: 'auto',
		className: ''
	} );

}

function wrapContent( node, wrapper ) {

	var r = document.createRange();
	r.selectNodeContents( node );
	r.surroundContents( wrapper );
	node.appendChild( wrapper );

}

function bindElement( element, options ) {
	if( typeof element.addEventListener !== 'function' ) {
		return;
	}

	var instance = create( element );
	var timeout = -1;

	element.addEventListener( 'click', function() {

		// 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' && !form.hasAttribute('novalidate') ) {
			// Modern form validation
			if( typeof form.checkValidity === 'function' ) {
				valid = form.checkValidity();
			}
		}

		if( valid ) {
			// This is asynchronous to avoid an issue where disabling
			// 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 );

}

ES2015語法

'use strict';

Object.defineProperty(exports, "__esModule", {
	value: true
});

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /*!
                                                                                                                                                                                                                                                                               * Ladda
                                                                                                                                                                                                                                                                               * http://lab.hakim.se/ladda
                                                                                                                                                                                                                                                                               * MIT licensed
                                                                                                                                                                                                                                                                               *
                                                                                                                                                                                                                                                                               * Copyright (C) 2018 Hakim El Hattab, http://hakim.se
                                                                                                                                                                                                                                                                               */

exports.create = create;
exports.bind = bind;
exports.stopAll = stopAll;

var _spin = require('spin.js');

// 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 button must have the class "ladda-button"
	if (!button.classList.contains('ladda-button')) {
		button.classList.add('ladda-button');
	}

	// Style is required, default to "expand-right"
	if (!button.hasAttribute('data-style')) {
		button.setAttribute('data-style', 'expand-right');
	}

	// The text contents must be wrapped in a ladda-label
	// element, create one if it doesn't already exist
	if (!button.querySelector('.ladda-label')) {
		var laddaLabel = document.createElement('span');
		laddaLabel.className = 'ladda-label';
		wrapContent(button, laddaLabel);
	}

	// 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 start() {

			// Create the spinner if it doesn't already exist
			if (!spinner) {
				spinner = createSpinner(button);
			}

			button.disabled = true;
			button.setAttribute('data-loading', '');

			clearTimeout(timer);
			spinner.spin(spinnerWrapper);

			this.setProgress(0);

			return this; // chain
		},

		/**
   * Enter the loading state, after a delay.
   */
		startAfter: function startAfter(delay) {

			clearTimeout(timer);
			timer = setTimeout(function () {
				instance.start();
			}, delay);

			return this; // chain
		},

		/**
   * Exit the loading state.
   */
		stop: function stop() {

			if (instance.isLoading()) {
				button.disabled = false;
				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 toggle() {
			return this.isLoading() ? this.stop() : this.start();
		},

		/**
   * Sets the width of the visual progress bar inside of
   * this Ladda button
   *
   * @param {Number} progress in the range of 0-1
   */
		setProgress: function setProgress(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';
			}
		},

		isLoading: function isLoading() {

			return button.hasAttribute('data-loading');
		},

		remove: function remove() {

			clearTimeout(timer);

			button.disabled = false;
			button.removeAttribute('data-loading');

			if (spinner) {
				spinner.stop();
				spinner = null;
			}

			ALL_INSTANCES.splice(ALL_INSTANCES.indexOf(instance), 1);
		}

	};

	ALL_INSTANCES.push(instance);

	return instance;
}

/**
 * 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.
 *          - callback A function to be called with the Ladda
 *            instance when a target button is clicked.
 */
function bind(target, options) {

	var targets;

	if (typeof target === 'string') {
		targets = document.querySelectorAll(target);
	} else if ((typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object') {
		targets = [target];
	} else {
		throw new Error('target must be string or object');
	}

	options = options || {};

	for (var i = 0; i < targets.length; i++) {
		bindElement(targets[i], options);
	}
}

/**
 * Stops ALL current loading animations.
 */
function stopAll() {

	for (var i = 0, len = ALL_INSTANCES.length; i < len; i++) {
		ALL_INSTANCES[i].stop();
	}
}

/**
* 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;
}

function createSpinner(button) {

	var height = button.offsetHeight,
	    spinnerColor,
	    spinnerLines;

	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');
	}

	// Allow buttons to specify the number of lines of the spinner
	if (button.hasAttribute('data-spinner-lines')) {
		spinnerLines = parseInt(button.getAttribute('data-spinner-lines'), 10);
	}

	var radius = height * 0.2,
	    length = radius * 0.6,
	    width = radius < 7 ? 2 : 3;

	return new _spin.Spinner({
		color: spinnerColor || '#fff',
		lines: spinnerLines || 12,
		radius: radius,
		length: length,
		width: width,
		animation: 'ladda-spinner-line-fade',
		zIndex: 'auto',
		top: 'auto',
		left: 'auto',
		className: ''
	});
}

function wrapContent(node, wrapper) {

	var r = document.createRange();
	r.selectNodeContents(node);
	r.surroundContents(wrapper);
	node.appendChild(wrapper);
}

function bindElement(element, options) {
	if (typeof element.addEventListener !== 'function') {
		return;
	}

	var instance = create(element);
	var timeout = -1;

	element.addEventListener('click', function () {

		// 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' && !form.hasAttribute('novalidate')) {
			// Modern form validation
			if (typeof form.checkValidity === 'function') {
				valid = form.checkValidity();
			}
		}

		if (valid) {
			// This is asynchronous to avoid an issue where disabling
			// 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);
}

ES6線上編譯ES5工具地址

相關文章