5

I'm attempting to build a basic JS plugin that can be called after a click event to disable a button (to prevent users firing multiple API calls) and to give feedback that something is loading/happening. Here is how it looks:

enter image description here

This works great on an individual basis, but I want to re-write it as a plugin so I can reuse it across the site.

Here is a cut down version of the JS from file loader.plugin.js.

let originalBtnText;


export function showBtnLoader(btn, loadingText) {
  const clickedBtn = btn;
  const spinner = document.createElement('div');

  spinner.classList.add('spin-loader');

  originalBtnText = clickedBtn.textContent;
  clickedBtn.textContent = loadingText;
  clickedBtn.appendChild(spinner);
  clickedBtn.setAttribute('disabled', true);
  clickedBtn.classList.add('loading');

  return this;
}


export function hideBtnLoader(btn) {
  const clickedBtn = btn.target;
  clickedBtn.textContent = originalBtnText;
  clickedBtn.removeAttribute('disabled');
  clickedBtn.classList.remove('loading');

  return this;
}


export function btnLoader() {
  showBtnLoader();
  hideBtnLoader();
}

And here is an example of how I would like to use it.

import btnLoader from 'loaderPlugin';

const signupBtn = document.getElementById('signup-btn');

signupBtn.addEventListener('click', function(e) {
  e.preventDefault();
  btnLoader.showBtnLoader(signupBtn, 'Validating');
  // Call API here
});

// Following API response
hideBtnLoader(signupBtn);

The issue I have is that I want to store the originalBtnText from the showBtnLoader function and then use that variable in the hideBtnLoader function. I could of course achieve this in a different way (such as adding the value as a data attribute and grabbing it later) but I wondered if there is a simple way.

Another issue I have is that I don't know the correct way of calling each individual function and whether I am importing it correctly. I have tried the following.

btnLoader.showBtnLoader(signupBtn, 'Validating');
btnLoader(showBtnLoader(signupBtn, 'Validating'));
showBtnLoader(signupBtn, 'Validating');

But I get the following error:

Uncaught ReferenceError: showBtnLoader is not defined
    at HTMLButtonElement.<anonymous>

I have read some good articles and SO answers such as http://2ality.com/2014/09/es6-modules-final.html and ES6 export default with multiple functions referring to each other but I'm slightly confused as to the 'correct' way of doing this to make it reusable.

Any pointers would be much appreciated.

Community
  • 1
  • 1
GuerillaRadio
  • 1,267
  • 5
  • 29
  • 59
  • Import them in curly brackets from the target page { item , item } from './page'; and in the target page export { item, item } – Ozan May 19 '17 at 16:03
  • Plugin? Into what? – Bergi May 23 '17 at 23:01
  • unless they're `export default` you must explicitly import them via `import { showBtnLoader } from 'loaderPlugin';` – A. L May 24 '17 at 03:43

2 Answers2

2

I would export a function that creates an object with both show and hide functions, like this:

export default function(btn, loadingText) {

  function show() {
    const clickedBtn = btn;
    const spinner = document.createElement('div');

    spinner.classList.add('spin-loader');

    originalBtnText = clickedBtn.textContent;
    clickedBtn.textContent = loadingText;
    clickedBtn.appendChild(spinner);
    clickedBtn.setAttribute('disabled', true);
    clickedBtn.classList.add('loading');
  }

  function hide() {
    const clickedBtn = btn.target;
    clickedBtn.textContent = originalBtnText;
    clickedBtn.removeAttribute('disabled');
    clickedBtn.classList.remove('loading');
  }

  return {
    show,
    hide,
  };
}

Then, to use it:

import btnLoader from 'btnloader';

const signupBtn = document.getElementById('signup-btn');
const signupLoader = btnLoader( signupBtn, 'Validating' );

signupBtn.addEventListener('click', function(e) {
  e.preventDefault();
  signupLoader.show();
  // Call API here
});

// Following API response
signupLoader.hide();

If you need to hide it from a different file from where you showed it, then you can export the instance:

export const signupLoader = btnLoader( signupBtn, 'Validating' );

And later import it.

import { signupLoader } from 'signupform';

function handleApi() {
    signupLoader.hide();
}
coderkevin
  • 359
  • 1
  • 5
1

Youre maybe overriding the Element.prototype, to make it accessible right from that element. However, i wouldnt set values onto that element, i would rather return an object with all the neccessary stuff:

export function implementBtnLoader(){
 Element.prototype.showBtnLoader=function( loadingText) {
     const clickedBtn = this;
     const spinner = document.createElement('div');

     spinner.classList.add('spin-loader');

     var originalBtnText = clickedBtn.textContent;
     clickedBtn.textContent = loadingText;
     clickedBtn.appendChild(spinner);
     clickedBtn.setAttribute('disabled', true);
     clickedBtn.classList.add('loading');

    return {
        text:originalBtnText,
        el:this,
        hideBtnLoader: function() {
          const clickedBtn = this.target;
          clickedBtn.textContent = this.text;
          clickedBtn.removeAttribute('disabled');
          clickedBtn.classList.remove('loading');
          return this;
       }
    };
  };
}


export function btnLoader() {
   implementBtnLoader();
}

When imported, and implementBtnLoader was called, one can do:

var loader=document.getElementById("test").showBtnLoader();
console.log(loader.text);
loader.hideBtnLoader();
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • I've just implemented your suggestions Jonas but I'm now getting 'Uncaught TypeError: clickedBtn.showBtnLoader is not a function at HTMLButtonElement'. I am importing the function using 'import btnLoader from 'loaderPlugin';' and trying to call the loader using 'clickedBtn.showBtnLoader('Loading');' (clickedBtn is the click target). – GuerillaRadio May 22 '17 at 08:34
  • @GuerllillaRadio did youve executed the imported function? btnLoader(); – Jonas Wilms May 22 '17 at 13:43