GucciBananaKing99
GucciBananaKing99

Reputation: 1506

Disable Button for one minute when it's clicked

I am trying to make a button that will be disabled for 1 minute when you click on it. Only when the button is enabled and you click on it, 25 points should be added to a div (div starts at 0). After each click, the button will be disabled and the timer will start running.

Here are some pictures to make the whole thing a bit more understandable:

Timer starts at 1:00 and is disabled

enter image description here

enter image description here

enter image description here

Upvotes: 1

Views: 4207

Answers (4)

Oskar Grosser
Oskar Grosser

Reputation: 3454

One could create a Custom Element.
This requires knowledge in the following topics:

Basic structure

Defining a custom element requires a JS class that extends HTMLElement (or one of its sub-classes).

This class is then registered as an HTML-element by calling customElements.define() with its HTML-element name (must include - (hyphen) in its name, e.g. "example-element") and the class it is associated with.

I decided to call the class CounterElement, and the HTML-element <counter-instance>:

class CounterElement extends HTMLElement {
  constructor() {
    super(); // Required since we are extending a class
  }
}
customElements.define('counter-instance', CounterElement);

Setting up the Shadow Root

Setting up the Shadow Root is done like this:

  1. Create the shadow root by calling Element.attachShadow()
  2. Create and append its HTML-elements
  3. Add styling to the shadow root

Since some comment mentioned that accessing the elements would still be easy, I decided to use {mode: 'closed'} to keep unknowing users from accessing them. It is, however, not inaccessible. That would require a MutationObserver, but that is not included in this answer to keep it (somewhat) short, heh.

When using {mode: 'closed'}, one needs to keep a reference to the shadow root manually. I decided to use a private member-variable #shadow.

Now, the shadow root's elements need to be created and appended. Again, I kept a private reference for the "important" elements: #samp and #button.

I also added a little bit of styling.

class CounterElement extends HTMLElement {
  #shadow;
  #samp;
  #button;
  
  constructor() {
    super();
    
    // Shadow Root
    this.#shadow = this.attachShadow({mode: 'closed'});
    
    let div = document.createElement('div');
    let style = document.createElement('style');
    style.innerHTML = 'div{padding:0.2rem;border:1px solid black}'
        + 'samp{display:block}button{font-family:monospace}';
    
    this.#samp = document.createElement('samp');
    this.#button = document.createElement('button');
    
    div.append(this.#samp, this.#button);
    this.#shadow.append(style, div);
  }
}
customElements.define('counter-instance', CounterElement);

Setting up the elements

Now that all the important elements exist, we need to set them up correctly. By that, I mean add the texts they should have.

Since we will keep track of a counter and seconds, declaring them as member-variables makes sense.

To format the seconds to a mm:ss-format, I wrote a utility function formatTime(). That seems quite handy, so it can be in the global scope.
Note that you should try not to clutter the global scope with too many variables. You can use IIFEs to keep them local.

Now, adding the text is as easy as using HTMLElement.innerText.

In a comment you requested to have the button say "Collect" when the seconds have reached 0. This can be done using an if-else statement, but for brevity I used the Ternary Operator.

function formatTime(seconds) {
  var min = new String(Math.floor(seconds / 60));
  var sec = new String(seconds % 60);

  while (min.length < 2) min = '0' + min;
  while (sec.length < 2) sec = '0' + sec;

  return min + ':' + sec;
};

class CounterElement extends HTMLElement {
  counter = 0;
  seconds = 0;
  #shadow;
  #samp;
  #button;
  
  constructor() {
    super();
    
    // Shadow Root
    // ...
    
    this.updateText();
  }
  
  updateText() {
    this.#samp.innerText = this.counter;
    this.#button.innerText = this.seconds ? formatTime(this.seconds) : 'Collect';
  }
}
customElements.define('counter-instance', CounterElement);

Adding (the timer) functionality

The last step now is to add the timer, and the onclick-listener.

Since we want to start a timer when clicking on #button, and stop it after seconds reaches 0, we can use setInterval() and clearInterval() respectively.

The former function returns the interval's ID, the latter clears it using its ID. To use these functions as intended, we need to keep a reference to the interval's ID.

When #button is clicked, it should:

  • Reset seconds and increase counter
  • Start our timer
  • Be disabled for the duration of our timer
  • Update texts respecting the now-resetted values

Here is our (shortened) code implementing it:

// function formatTime(seconds) () { ... }

class CounterElement extends HTMLElement {
  counter = 0;
  seconds = 0;
  #shadow;
  #samp;
  #button;

  constructor() {
    super();

    // Shadow Root
    // ...
    
    let intervalId;
    let intervalStep = () => { // Will be called every second
      this.updateText();
      if (--this.seconds <= 0) {
        clearInterval(this.#intervalId);
        this.#button.disabled = false;
      }
    };
    
    this.#button.addEventListener('click', () => {
      this.seconds = 60;
      this.counter += 25;
      this.#intervalId = setInterval(intervalStep, 1000);
      this.#button.disabled = true;
      this.updateText();
    });
    
    this.updateText();
  }
  
  // Member-functions ...
}
customElements.define('counter-instance', CounterElement);

Conclusion

As mentioned before, accessing the elements of the shadow root is still possible using the Developer Tools. Fully disabling the ability of changing the button's disabled-property can be achieved using a MutationObserver, keeping the property from being changed, depending on what number seconds currently has.

Here is the full code as a Stack-Snippet, so you can try it out yourselves:

function formatTime(seconds) {
  var min = new String(Math.floor(seconds / 60));
  var sec = new String(seconds % 60);

  while (min.length < 2) min = '0' + min;
  while (sec.length < 2) sec = '0' + sec;

  return min + ':' + sec;
};

class CounterElement extends HTMLElement {
  counter = 0;
  seconds = 0;
  #shadow;
  #samp;
  #button;
  
  constructor() {
    super();
    
    // Shadow Root
    this.#shadow = this.attachShadow({mode: 'closed'});
    
    let div = document.createElement('div');
    let style = document.createElement('style');
    style.innerHTML = 'div{padding:0.2rem;border:1px solid black}'
        + 'samp{display:block}button{font-family:monospace}';
    
    this.#samp = document.createElement('samp');
    this.#button = document.createElement('button');
    
    div.append(this.#samp, this.#button);
    this.#shadow.append(style, div);
    
    let intervalId;
    let intervalStep = () => { // Will be called every second
      if (--this.seconds <= 0) {
        clearInterval(intervalId);
        this.#button.disabled = false;
      }
      this.updateText();
    };
    
    this.#button.addEventListener('click', () => {
      this.seconds = 60;
      this.counter += 25;
      intervalId = setInterval(intervalStep, 1000);
      this.#button.disabled = true;
      this.updateText();
    });

    this.updateText();
  }
  
  updateText() {
    this.#samp.innerText = this.counter;
    this.#button.innerText = this.seconds ? formatTime(this.seconds) : 'Collect';
  }
}
customElements.define('counter-instance', CounterElement);
/* Ignore; only for styling purposes */
body {
  display: flex;
  gap: 0.6rem;
}
<!-- Works with multiple elements -->
<counter-instance></counter-instance>
<counter-instance></counter-instance>

Upvotes: 1

Andrei Ionita
Andrei Ionita

Reputation: 54

Something like:

const button = document.getElementById("myBtn")

function freeze () {
  button.disabled = true
  setTimeout(function() { 
    button.disabled = false
  }, 60000);
}

button.onclick = function () {
 if(button.disabled) { // Avoid user removing disable in the html
  // Add 25 to whatever
    freeze()
  }
}

Upvotes: 1

Sumit Singh
Sumit Singh

Reputation: 38

Here is your code

$('#btn').prop('disabled',true);
startCountDown();

function getCounter(){
  return  parseInt($('#counter').html());
}
function setCounter(count) {
  $('#counter').html(count);
}

$("#btn").click(function() {
  setCounter(getCounter()+25);
  $('#btn').prop('disabled',true);
  startCountDown();
});

function startCountDown() {
  var minutes = 0,
    seconds = 59;
  $("#countdown").html(minutes + ":" + seconds);
  var count = setInterval(function() {
    if (parseInt(minutes) < 0 || parseInt(seconds) <=0 ) {
      $("#countdown").html(minutes + ":" + seconds);
      clearInterval(count);
      $('#btn').prop('disabled',false);
    } else {
      $("#countdown").html(minutes + ":" + seconds);
      seconds--;
      if (seconds < 10) seconds = "0" + seconds;
    }
  }, 1000);
}
#counter{
  font-size: 25px;
  color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="counter">0</div>
<button id="btn">
<span id="countdown">0:00</span>
</button>

Upvotes: 1

Davide
Davide

Reputation: 1

Another possibility would be as follow:

Here your button:

<button id="MyBtn" onclick="disable_button_3_secs(); return false;">Disabled me for 3 seconds</button>

Here your script:

    var timeout_lenght = 3000;
    var myBtn = document.getElementById("MyBtn");

    function disable_button_3_secs() {
        myBtn.disabled = true;
        Enable_at_timer_out();
    }

    function Enable_at_timer_out() {
        setTimeout(function () {myBtn.disabled = false; }, timeout_lenght);
    }

I personally like to have more control over my code so I always use variables whenever I can. I have set the timer to 3 seconds but you can change it as you wish.

Davide

Upvotes: 0

Related Questions