21

I have this code for Google analytics on a button. I need it to be executed only once, so that the user can't change statistics by pressing the button many times. I tried solutions from similar topics, but they don't work. Please help. This is my code.

<script>
    function klikaj(i) {
        gtag('event', 'first-4', {
            'event_category' : 'cat-4',
            'event_label' : 'site'
        });
    }

    document.body.addEventListener("click", klikaj(i), {once:true})
</script>


<div id="thumb0" class="thumbs" onclick="klikaj('rad1')">My button</div>
Salman A
  • 262,204
  • 82
  • 430
  • 521
Alex
  • 229
  • 1
  • 2
  • 6
  • https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener – Lee Taylor Aug 24 '19 at 21:11
  • 2
    Your posted code should only run the listener once. If it's not working, post the non-working coe. What are the other solutions you've tried? In what way didn't they work? Note that you're attaching two listeners: one to the body and one to the button. – RobG Aug 24 '19 at 21:14
  • Please don't edit your question to show the solution instead of the problem; once your question is solved, we prefer that you either mark the solution with a checkmark if it's one of the answers (as you did with [med morad's answer](https://stackoverflow.com/a/57641619/5386374)), or answer your own question if it's not (yes, this is allowed). This makes it more helpful for others in the future, if they encounter a problem similar to your own. Thanks. ^_^ – Justin Time - Reinstate Monica Aug 25 '19 at 20:10
  • just set the button disabled inside `klikaj` – RozzA Aug 25 '19 at 21:48
  • in `document.body.addEventListener("click", klikaj(i), {once:true})`, you need to pass the function reference, not call it. – njzk2 Aug 26 '19 at 01:32

7 Answers7

13

Remove onclick attribute on your button and register listener via JavaScript, as you tried to do:

<div id="thumb0" class="thumbs"
  style="border: 1px solid; cursor: pointer; float: left">
    My button
</div>
<script>
    function klikaj(i) {
        console.log(i);
    }
    document.getElementById('thumb0')
        .addEventListener("click", function(event) {
            klikaj('rad1');
        }, {once: true});
</script>

If your browser doesn't support { once: true } option, you can remove event listener manually:

<div id="thumb0" class="thumbs"
  style="border: 1px solid;cursor: pointer;float:left">
    My button
</div>

<script>
    function klikaj(i) {
        console.log(i);
    }
    function onClick(event) {
      klikaj('rad1');
      document
        .getElementById('thumb0')
        .removeEventListener("click", onClick);
    }
    
    document
      .getElementById('thumb0')
      .addEventListener("click", onClick);
</script>
Nenad
  • 24,809
  • 11
  • 75
  • 93
  • Is the Event Listener removed from the element after it's fired? – CloudBranch Aug 24 '19 at 21:21
  • 1
    Unfortunately, `once: true` isn’t supported by IE and Edge. – sunknudsen Aug 24 '19 at 21:23
  • Yes, it is removed because of the options parameter `{ once: true }`. You can read it on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) – Nenad Aug 24 '19 at 21:23
  • @sunknudsen Then why did you use `{ once: true }` in your code example, if you have to support IE? – Nenad Aug 24 '19 at 21:24
  • Thanks for answer, but unfortunately this code (both options) does not send event to Google analitics for some reason, like my did – Alex Aug 25 '19 at 08:02
  • First example was working (except in IE/Edge). Second example I fixed. Converted both in code snippets, so you can actually try them here. – Nenad Aug 25 '19 at 08:18
10

you could use removeAttribute() like this: document.getElementById('thumb0').removeAttribute("onclick");

or you could let the function return false like this: document.getElementById('thumb0').onclick = ()=> false

med morad
  • 497
  • 4
  • 12
  • Thanks for answer, but unfortunately this code not stopping execution again. Did I understand you correctly? – Alex Aug 25 '19 at 08:10
  • this is a working example ```
    click me
    ```
    – med morad Aug 25 '19 at 08:33
7

I would recommend setting a variable and checking its value.

<script>
    var clicked = false;
    function klikaj(i) {
        if (clicked === false) {
            gtag('event', 'first-4', {
                'event_category' : 'cat-4',
                'event_label' : 'site'
            });
        }
        clicked = true;
    }
    ...
</script>

Or removing the onclick event as suggested by others,

<script>
    function klikaj(i) {
        gtag('event', 'first-4', {
            'event_category' : 'cat-4',
            'event_label' : 'site'
        });
        document.getElementById('thumb0).onclick = undefined;
    }
    ...
</script>

Note that once: true is unfortunately not supported in IE and Edge. See https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

sunknudsen
  • 6,356
  • 3
  • 39
  • 76
  • 3
    Better to remove the listener. – Lee Taylor Aug 24 '19 at 21:12
  • Thanks for answer, but unfortunately this code too does not send event to Google analitics for some reason, like my did – Alex Aug 25 '19 at 08:05
  • Hi @Alex, what is the value and type of the `gtag` variable. You can get them by adding console.log(gtag, typeof gtag); just before you call the `gtag` function. – sunknudsen Aug 25 '19 at 11:28
  • Do you have a production (live) example of your code that we can look at that where Google Analytics is configured? – sunknudsen Aug 25 '19 at 11:29
3

Event Handlers & Listeners

There are three ways* to register an event to an element. The following examples show how to register the click event to a link with the class .once** which calls the function test() when triggered.

  1. Event Listener (recommended)
    • document.querySelector('.once').addEventListener('click', test);`
  2. On-event Attribute (not recommended)
    • <a href='#/' class='once'onclick='test()'>Click</a>
  3. On-event Property
    • document.querySelector('.once').onclick = test;`

*See DOM on-event handlers for details
** .once class is not relevant for #2


Issues

The OP (Original Post) has an event listener (see #1 above) registering a click event to the <body> tag and an on-event attribute (see #2 above) registering the click event to a <div>. Each one calls a function (aka callback function) named klikaj() which is redundant. Clicking the body (which is normally everywhere) isn't useful when you intend to have the user click a div. Should the user click anywhere but the div, klikaj() will be called. Should the user click the div, klikaj() will be called twice. I suggest that you remove both event handlers and replace them with this:

A.

document.getElementById('thumb0').addEventListener("click", klikaj);

Note that klikaj has no parenthesis () because the browser interprets () as to run the function now instead of when the user triggers the registered event (see #1 and #3 above). Should an event handler have additional statements and/or callback functions then an anonymous function should be wrapped around it and normal syntax applies:

B.

document.getElementById('thumb0').addEventListener("click", function(event) { 
  klikaj();
  console.log('clicked');
});

A cleaner alternative is to add extra lines in the definition of the callback function instead and registering events like #A.


Solution

Simply add the following statement as the last line of klikaj():

this.style.pointerEvents = "none";

That will render the clicked tag unclickable. Applied to OP code it should be like this:

<div id="thumb0" class="thumbs">Thumb 0</div>
<script>
  function klikaj(event) {
    gtag('event', 'first-4', {
      'event_category' : 'cat-4',
      'event_label' : 'site'
    });
    this.style.pointerEvents = "none";   
  }

  document.getElementById('thumb0').addEventListener("click", klikaj);
</script>


Demo

The following demo has two links:

  1. .default - a normal link registered to the click event which when triggered calls test()

  2. .once - a link registered to the click event which when triggered calls test() and renders the link unclickable.

function test() {
  console.log('test');
}

document.querySelector('.default').onclick = function(e) {
  test();
}

document.querySelector('.once').onclick = function(e) {
  test();
  this.style.pointerEvents = 'none';
}
<a href='#/' class='default'>Default</a><br>
<a href='#/' class='once'>Once</a>
zer00ne
  • 41,936
  • 6
  • 41
  • 68
1

There is a problem with the way you are trying to attach your handler function. The function klikaj(i) returns undefined so you are attaching undefined to the button. If you want to execute klikaj(i) when the button is clicked, put it inside a closure like this:

const button = document.querySelector('button')
const i = 10
function klikaj(i) {console.log('clicked once')}

button.addEventListener('click', () => { klikaj(i) }, {once: true})
<button>Hello world</button>

If the browser does not support the {once: true} you can simulate it using:

const button = document.querySelector('button')
const i = 10
function klikaj(i) {console.log("clicked once")}

function clickOnceHandler(event) {
  klikaj(i)
  event.currentTarget.removeEventListener('click', clickOnceHandler)
}

button.addEventListener('click', clickOnceHandler)
<button>Hello world</button>
Damiaan Dufaux
  • 4,427
  • 1
  • 22
  • 33
1

Just use a flag variable and set it upon the first execution:

var handlerExecuted = false;
function clickHandler() {
  if (!handlerExecuted) {
    console.log("call gtag() here");
    handlerExecuted = true;
  } else {
    console.log("not calling gtag() function");
  }
}
document
  .getElementById("thumb0")
  .addEventListener("click", clickHandler);
<div id="thumb0" class="thumbs">My button</div>

An advance variation that uses closures and could be used on multiple buttons:

function clickHandlerFactory() {
  var handlerExecuted = false;
  return function() {
    if (!handlerExecuted) {
      console.log("call gtag() here");
      handlerExecuted = true;
    } else {
      console.log("not calling gtag() function");
    }
  }
}
[...document.querySelectorAll(".thumbs")].forEach(function(el) {
  el.addEventListener("click", clickHandlerFactory());
});
<div id="thumb0" class="thumbs">Button 1</div>
<div id="thumb1" class="thumbs">Button 2</div>
Salman A
  • 262,204
  • 82
  • 430
  • 521
0

If you want the function to be called only when user clicks the button, you will have remove the click event listener from the body.

To fire your gtag function only once you can change the function definition of klikaj inside the function body itself. After the first call gtag will never be called.

The below code works fine.

<script>
    function klikaj(i) {
        gtag('event', 'first-4', {
            'event_category' : 'cat-4',
            'event_label' : 'site'
        });
        klikaj = function() {};
    }
</script>


<div id="thumb0" class="thumbs" onclick="klikaj('rad1')">
    My button
</div>