47

I have a vue component with separate events for click/dblclick. Single click (de)selects row, dblclick opens edit form.

<ul class="data_row"
  v-for="(row,index) in gridData"
  @dblclick="showEditForm(row,$event)"
  @click="rowSelect(row,$event)"
>

Doing it like this, i get 3 events fired on double click. Two click events and lastly one dblclick. Since the click event fires first , is there a way (short of deferring click event for a fixed amount of ms) for stopping propagation of click event on double click ?

Fiddle here

Penny Liu
  • 15,447
  • 5
  • 79
  • 98
Corwin
  • 645
  • 1
  • 9
  • 17

9 Answers9

40

As suggested in comments, You can simulate the dblclick event by setting up a timer for a certain period of time(say x).

  • If we do not get another click during that time span, go for the single_click_function().
  • If we do get one, call double_click_function().
  • Timer will be cleared once the second click is received.
  • It will also be cleared once x milliseconds are lapsed.

See below code and working fiddle.

new Vue({
    el: '#app',
    data: {
        result: [],
        delay: 700,
        clicks: 0,
        timer: null
    },    
     mounted: function() {
        console.log('mounted');
     },      
     methods: {
        oneClick(event) {
          this.clicks++;
          if (this.clicks === 1) {
            this.timer = setTimeout( () => {
              this.result.push(event.type);
              this.clicks = 0
            }, this.delay);
          } else {
             clearTimeout(this.timer);  
             this.result.push('dblclick');
             this.clicks = 0;
          }         
        }      
     }
});
yeya
  • 1,968
  • 1
  • 21
  • 31
Saurabh
  • 71,488
  • 40
  • 181
  • 244
  • 1
    This is a good solution but in some cases, handling the single click after the end of the delay can be a bit unintuitive.Isn't it better to consider the first click as a single click right away (i.e. move the `self.result.push(event.type);` before the call to setTimeout) ? Then if a second click occurs during the delay, then it's a double click. File explorers actually work like that : a single click on a file is a selection right away, but if a fast second click occurs, the file is opened. – Sbu Apr 27 '18 at 22:51
  • @Sbu It depends heavily on what the functionality is supposed to be. The OP asked about firing either the first or the second function, not either the first or both (implied by asking about stopping the propagation of a `click` event in case a `dblclick` event is fired shortly after), which means that the provided answer (where only one or the other function is fired) is the most correct one in the given context. – Vaclav Pelc Nov 08 '21 at 11:17
14
<div id="example-1">
 <button v-on:dblclick="counter += 1, funcao()">Add 1</button>
   <p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
 el: '#example-1',
 data: {
   counter: 0
 },
 methods: {
   funcao: function(){
     alert("Sou uma funcao");
   }
 }
})

check out this working fiddle https://codepen.io/robertourias/pen/LxVNZX

Paul Roub
  • 36,322
  • 27
  • 84
  • 93
Benjamin Gakami
  • 1,610
  • 2
  • 16
  • 22
12

i have a simpler solution i think (i'm using vue-class but same principle apply):

private timeoutId = null;
onClick() {
        if(!this.timeoutId)
        {
            this.timeoutId = setTimeout(() => {
                // simple click
            }, 50);//tolerance in ms
        }else{
            clearTimeout(this.timeoutId);
            // double click
        }
    }

it does not need to count the number of clicks.

Lk77
  • 2,203
  • 1
  • 10
  • 15
5

The time must be short between click and click.

In order to get the click and double click, only one counter is required to carry the number of clicks(for example 0.2s) and it is enough to trap the user's intention when he clicks slowly or when he performs several that would be the case of the double click or default case.

I leave here with code how I implement these features.

new Vue({
   el: '#app',
   data: {numClicks:0, msg:''},
   methods: {
      // detect click event
      detectClick: function() {
        this.numClicks++;
        if (this.numClicks === 1) {          // the first click in .2s
            var self = this;
            setTimeout(function() {
                switch(self.numClicks) {     // check the event type
                      case 1:
                        self.msg = 'One click';
                        break;
                      default:
                        self.msg = 'Double click';
                }
                self.numClicks = 0;               // reset the first click
            }, 200);                              // wait 0.2s
        } // if
      }  // detectClick function
   }
});
span { color: red }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.0/vue.js"></script>

<div id='app'>
  <button @click='detectClick'>
    Test Click Event, num clicks
    <span>{{ numClicks }}</span>
   </button>
  <h2>Last Event: <span>{{ msg }}</span></h2>
</div>
fitorec
  • 4,257
  • 2
  • 24
  • 18
5

I use this approach for the same problem. I use a promise that is resolved either by the timeout of 200ms being triggered, or by a second click being detected. It works quite well in my recent web apps.

<div id="app">
  <div 
    @click="clicked().then((text) => {clickType = text})">
      {{clickType}}
  </div>
</div>

<script>
new Vue({
  el: "#app",
  data: {
    click: undefined,
    clickType: 'Click or Doubleclick ME'
  },
  methods: {
    clicked () {
      return new Promise ((resolve, reject) => {
        if (this.click) {
          clearTimeout(this.click)
          resolve('Detected DoubleClick')
        }
        this.click = setTimeout(() => {
         this.click = undefined
         resolve('Detected SingleClick')
        }, 200)
      })
    }
  }
})
</script>

Working fiddle: https://jsfiddle.net/MapletoneMartin/9m62Lrwf/

MartinSRimsbo
  • 196
  • 1
  • 4
4

vue Component

// html 
 <div class="grid-content">
    <el-button 
   @click.native="singleClick" 
   @dblclick.native="doubleClick" 
   class="inline-cell">
    click&dbclickOnSameElement</el-button>
 </div>
// script
<script>
let time = null;  // define time be null 
export default {
  name: 'testComponent',
  data() {
    return {
       test:''
    };
  },
  methods: {

   singleClick() {
     // first clear  time
      clearTimeout(time);
      time = setTimeout(() => {
        console.log('single click ing')
      }, 300); 
    },
  
   doubleClick() {
      clearTimeout(time);  
      console.log('double click ing');
    }
  }
}
</script>
George Wayne
  • 160
  • 1
  • 5
0
selectedFolder = ''; // string of currently selected item
folderSelected = false; // preview selected item

selectFolder(folder) {
    if (this.selectedFolder == folder) {
        // double click
      this.folderSelected = false;
      this.$store.dispatch('get_data_for_this_folder', folder);
    } else {
      // single click
      this.selectedFolder = folder;
      this.folderSelected = true;
    }
},
Dazzle
  • 2,880
  • 3
  • 25
  • 52
0

@click.stop handles a single click and @dblclick.stop handles double click

<v-btn :ripple="false"
            class="ma-0"
            @click.stop="$emit('editCompleteGrvEvent', props.item)"
            @dblclick.stop="$emit('sendCompleteGrvEvent',props.item)">
    <v-icon>send</v-icon>
    </v-btn>
fallin.1
  • 31
  • 5
0

Unless you need to do expensive operations on single select, you can rework rowSelect into a toggle. Setting a simple array is going to be a lot faster, reliable, and more straightforward compared to setting up and canceling timers. It won't matter much if the click event fires twice, but you can easily handle that in the edit function.

<template>
  <ul>
    <li :key="index" v-for="(item, index) in items">
      <a
        :class="{ 'active-class': selected.indexOf(item) !== -1 }"
        @click="toggleSelect(item)"
        @dblclick="editItem(item)"
      >
        {{ item.title }}
      </a>
      <!-- Or use a checkbox with v-model
      <label @dblclick="editItem(item)">
        <input type="checkbox" :value="item.id" v-model.lazy="selected" />
        {{ item.title }}
      </label>
      -->
    </li>
  </ul>
</template>

<script>
export default {
  data: function () {
    return {
      items: [
        {
          id: 1,
          title: "Item 1",
        },
        {
          id: 2,
          title: "Item 2",
        },
        {
          id: 3,
          title: "Item 3",
        },
      ],
      selected: [],
    };
  },

  methods: {
    editItem(item) {
      /*
       * Optionally put the item in selected
       * A few examples, pick one that works for you:
       */

      // this.toggleSelect(item); // If the item was selected before dblclick, it will still be selected. If it was unselected, it will still be unselected.

      // this.selected = []; // Unselect everything.

      // Make sure this item is selected:
      // let index = this.selected.indexOf(item.id);
      // if (index === -1) {
      //   this.selected.push(item.id);
      // }

      // Make sure this item is unselected:
      // let index = this.selected.indexOf(item.id);
      // if (index !== -1) {
      //   this.selected.splice(index, 1);
      // }

      this.doTheThingThatOpensTheEditorHere(item);
    },

    toggleSelect(item) {
      let index = this.selected.indexOf(item.id);
      index === -1
        ? this.selected.push(item.id)
        : this.selected.splice(index, 1);
    },

    // For fun, get an array of items that are selected:
    getSelected() {
      return this.items.filter((item) => this.selected.indexOf(item.id) !== -1);
    },
  },
};
</script>