UNPKG

@gravityforms/components

Version:

UI components for use in Gravity Forms development. Both React and vanilla js flavors.

411 lines (388 loc) 16 kB
import Loader from '../Loader'; import { consoleError, consoleInfo, deepMerge, getNode, objectToAttributes, spacerClasses, trigger, uniqueId, } from '@gravityforms/utils'; /** * @function buttonTemplate * @description The template function used to generate our button html. * * @since 1.0.5 * * @param {object} options The options object for the class. * @param {string} options.activeText If interactive, what is the active text when the button is active? * @param {string} options.activeType What is the active type? Supports loading currently. * @param {string} options.attributes Arbitrary attributes for the button. * @param {Array} options.customClasses Custom classes for the button. * @param {string} options.html Arbitrary html to append inside the button. * @param {string} options.icon Icon name if wishing to use an icon button. * @param {string} options.iconPosition Icon position if using one. leading or trailing. * @param {string} options.id Id for the button. * @param {boolean} options.interactive Is the button interactive? If true the button has two states it transitions, set by activeType. * @param {string} options.label The label for the button. If interactive, the text displayed when inactive. * @param {boolean} options.round Does the button have round corners? * @param {string} options.size Size of the button. * @param {string|number|Array|object} options.spacing The spacing for the component, string, number, object or array. * @param {string} options.type The button type. * * @return {string} The button html. * @example * import { buttonTemplate } from '@gravityforms/components/html/admin/elements/Button'; * * function Example() { * const buttonHTML = buttonTemplate( options ); * document.body.insertAdjacentHTML( 'beforeend', buttonHTML ); * } * */ export const buttonTemplate = ( { activeText = '', activeType = '', attributes = '', customClasses = [], html = '', icon = '', iconPosition = 'leading', id = uniqueId( 'button' ), interactive = false, label = '', round = false, size = 'size-r', spacing = '', type = 'primary', } ) => { const componentAttrs = objectToAttributes( { id, class: [ 'gform-button', `gform-button--${ size }`, `gform-button--${ type }`, round ? 'gform-button--round' : '', interactive ? `gform-button--interactive` : '', activeType ? `gform-button--active-type-${ activeType }` : '', icon && iconPosition === 'leading' ? 'gform-button--icon-leading' : '', icon && iconPosition === 'trailing' ? 'gform-button--icon-trailing' : '', ...Object.keys( spacerClasses( spacing ) ), ...customClasses, ], } ); const iconHtml = icon ? `<i class="gform-button__icon gform-button__icon--inactive gform-icon gform-icon--${ icon }" data-js="button-icon"></i>` : ''; return ` <button ${ componentAttrs } ${ attributes }> ${ icon && iconPosition === 'leading' ? iconHtml : '' } ${ label ? `<span class="gform-button__text gform-button__text--inactive" data-js="button-inactive-text">${ label }</span>` : '' } ${ interactive ? `<span class="gform-button__text gform-button__text--active" data-js="button-active-text">${ activeText }</span>` : '' } ${ icon && iconPosition === 'trailing' ? iconHtml : '' } ${ html } </button> `; }; /** * @class Button * @description A button component that returns an instance method and event api. * * @since 1.0.5 * * @borrows buttonTemplate as buttonTemplate * * @param {object} options The options object for the class. * @param {string} options.activeText If interactive, what is the active text when the button is active? * @param {string} options.activeType What is the active type? Supports loading currently. * @param {string} options.attributes Arbitrary attributes for the button. * @param {Array} options.customClasses Custom classes for the button. * @param {boolean} options.disableWhileActive If interactive, disable the button while active? * @param {string} options.html Arbitrary html to append inside the button. * @param {string} options.icon Icon name if wishing to use an icon button. * @param {string} options.iconPosition Icon position if using one. leading or trailing. * @param {string} options.id Id for the button. * @param {boolean} options.interactive Is the button interactive? If true the button has two states it transitions, set by activeType. * @param {boolean} options.interactiveOnClick If interactive, does clicking the button swap the active state and fire the callbacks? * @param {string} options.label The label for the button. If interactive, the text displayed when inactive. * @param {object} options.loaderOptions All valid options for the loader component if using an interactive loader button. * @param {string} options.loaderOptions.additionalClasses Additional classes for the loader element. * @param {string} options.loaderOptions.background Background color for the loader. * @param {string} options.loaderOptions.foreground The color of the loader. * @param {boolean} options.loaderOptions.mask Should the loader mask an area? * @param {boolean} options.loaderOptions.showOnRender Visible on render? * @param {number} options.loaderOptions.size Size of the loader, decimal int values. * @param {boolean} options.lockSize If interactive, lock the width of the button when transitioning states? * @param {Function} options.onActive If interactive, a callback to fire when button goes into its active state. * @param {Function} options.onInactive If interactive, a callback to fire when button goes into its inactive state. * @param {boolean} options.rendered Is this button already rendered in the dom, eg by php? * @param {boolean} options.renderOnInit Render this button on init? * @param {boolean} options.round Does the button have round corners? * @param {string} options.size Size of the button. * @param {string|number|Array|object} options.spacing The spacing for the component, string, number, object or array. * @param {string} options.target The target to render to. Any valid css selector string. * @param {string} options.type The button type. * * @return {this} * @example * import Button from '@gravityforms/components/html/admin/elements/Button'; * * function Example() { * const buttonInstance = new Button( { * id: 'example-button', * renderOnInit: false, * target: '#example-target', * targetPosition: 'beforeend', * theme: 'cosmos', * } ); * * // Some time later we can render it. This is only done if we set renderOnInit to false. * // If true it will render on initialization. * buttonInstance.init(); * } * */ export default class Button { constructor( options = {} ) { this.options = deepMerge( { activeText: '', // if interactive, what is the active text when the button is active? activeType: '', // what is the active type? Supports loading currently. attributes: '', // arbitrary attributes for the button. customClasses: [], // custom classes for the button. disableWhileActive: true, // if interactive, disable the button while active? html: '', // arbitrary html to append inside the button. icon: '', // icon name if wishing to use an icon button. iconPosition: 'leading', // icon position if using one. leading or trailing. id: uniqueId( 'button' ), // id for the button. interactive: false, // is the button interactive? If true the button has two states it transitions, set by activeType. interactiveOnClick: true, // If interactive, does clicking the button swap the active state and fire the callbacks? label: '', // the label for the button. If interactive, teh text displayed when inactive. loaderOptions: { // all valid options for the loader component if using an interactive loader button. additionalClasses: 'gform-button__loader', // additional classes for the loader element. background: 'transparent', // background color for the loader. foreground: '#3e7da6', // the color of the loader. mask: false, // should the loader mask an area? showOnRender: false, // visible on render? size: 1, // size of the loader, decimal int values. }, lockSize: true, // if interactive, lock the width of the button when transitioning states? onActive: () => {}, // if interactive, a callback to fire when button goes into its active state. onInactive: () => {}, // if interactive, a callback to fire when button goes into its inactive state. rendered: false, // is this button already rendered in the dom, eg by php? renderOnInit: true, // render this button on init? round: false, // does the button have round corners? size: 'size-r', // size of the button. target: '', // the target to render to. Any valid css selector string. type: 'primary', // the button type. }, options, ); if ( ! this.options.target && ! this.options.rendered ) { consoleError( 'You must supply a target to the button component.' ); return; } /** * @event gform/button/pre_init * @type {object} * @description Fired before the component has started any internal init functions. A great chance to augment the options. * * @since 1.1.16 * * @property {object} instance The Component class instance. */ trigger( { event: 'gform/button/pre_init', native: false, data: { instance: this } } ); this.elements = {}; this.instances = {}; this.state = { active: false, }; if ( this.options.renderOnInit ) { this.init(); } } /** * @memberof Button * @description If interactive, handles activating this button and swapping the ui state, plus * executing any callbacks and firing useful events. * * @since 1.0.5 * * @fires gform/button/activated * * @return {void} */ activateButton() { const { activeType, disableWhileActive, lockSize, onActive } = this.options; const { button } = this.elements; /** * @event gform/button/activated * @type {object} * @description Fired when an interactive button is activated on document. * * @property {object} instance The Button class instance. */ trigger( { event: 'gform/button/activated', native: false, data: { instance: this } } ); if ( lockSize ) { const rect = button.getBoundingClientRect(); button.style.width = `${ rect.width }px`; } if ( disableWhileActive ) { button.disabled = true; } this.elements.button.classList.add( 'gform-button--activated' ); if ( activeType === 'loader' ) { this.instances.loader.showLoader(); } this.state.active = true; onActive( this ); } /** * @memberof Button * @description If interactive, handles deactivating this button and swapping the ui state, plus * executing any callbacks and firing useful events. * * @since 1.0.5 * * @fires gform/button/deactivated * * @return {void} */ deactivateButton() { const { activeType, disableWhileActive, lockSize, onInactive } = this.options; const { button } = this.elements; /** * @event gform/button/deactivated * @type {object} * @description Fired when an interactive button is deactivated on document. * * @property {object} instance The Button class instance. */ trigger( { event: 'gform/button/deactivated', native: false, data: { instance: this } } ); this.elements.button.classList.remove( 'gform-button--activated' ); if ( activeType === 'loader' ) { this.instances.loader.hideLoader(); } if ( disableWhileActive ) { button.disabled = false; } if ( lockSize ) { button.style.width = ''; } this.state.active = false; onInactive( this ); } /** * @memberof Button * @description If interactive and button is not active, activate it. * * @since 1.1.16 * * @return {void} */ handleButtonClick() { if ( this.state.active ) { return; } this.activateButton(); } /** * @memberof Button * @description Stores useful HTMLElements on the instance in the elements namespace after render * * @since 1.1.16 * * @return {void} */ storeElements() { const { button } = this.elements; const { activeText, icon, label } = this.options; if ( activeText ) { this.elements.activeText = getNode( 'button-active-text', button ); } if ( icon ) { this.elements.icon = getNode( 'button-icon', button ); } if ( label ) { this.elements.inactiveText = getNode( 'button-inactive-text', button ); } } /** * @memberof Button * @description If interactive render the elements that are present on active state. * * @since 1.1.16 * * @return {void} */ renderInteractive() { const { activeType, interactive, loaderOptions } = this.options; const { button } = this.elements; if ( ! interactive ) { return; } if ( activeType === 'loader' ) { loaderOptions.target = `#${ button.id }`; this.instances.loader = new Loader( loaderOptions ); } } /** * @memberof Button * @description Renders the component into the dom. * * @since 1.1.16 * * @return {void} */ render() { const { rendered, target } = this.options; if ( ! rendered ) { const renderTarget = getNode( target, document, true ); renderTarget.insertAdjacentHTML( 'beforeend', buttonTemplate( this.options ) ); } this.elements.button = getNode( `#${ this.options.id }`, document, true ); this.renderInteractive(); consoleInfo( `Gravity Forms Admin: Initialized button component on ${ target }.` ); } /** * @memberof Button * @description Bind event handles for the button instance. * * @since 1.1.16 * * @return {void} */ bindEvents() { const { interactive, interactiveOnClick } = this.options; if ( interactive && interactiveOnClick ) { this.elements.button.addEventListener( 'click', this.handleButtonClick.bind( this ) ); } } /** * @memberof Button * @description Initialize the component. * * @fires gform/button/post_render * * @since 1.1.16 * * @return {void} */ init() { this.render(); this.storeElements(); this.bindEvents(); /** * @event gform/button/post_render * @type {object} * @description Fired when the component has completed rendering and all class init functions have completed. * * @since 1.1.16 * * @property {object} instance The Component class instance. */ trigger( { event: 'gform/button/post_render', native: false, data: { instance: this } } ); } }