Skip to content

[Feature Request] Selecting multiple items on VDataTable with Shift-key #9958

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ems1985 opened this issue Dec 12, 2019 · 2 comments
Closed
Assignees
Labels
Milestone

Comments

@ems1985
Copy link

ems1985 commented Dec 12, 2019

Problem to solve

When using "show-select"s in a VDataTable there is currently no native way of quickly selecting multiple, neighboring items.

e.g. If you want to select the first 15 items in a table, you have to individually click on each checkbox.

Proposed solution

Listen for "Shift"-clicks.

Once you have marked an item, as soon as you mark another item (while holding the shift key) all the items in between will get selected as well.

For mobile devices instead of listening to shift click, you could listen to double clicks on the second item.

Code example

Here is some code, it's not perfect, but should illustrate what would be nice to have by default in the data table component.

// select everything between last 2 selected items

shiftClick(item) {
  if (this.selectedItems.length >= 2) {
    const secondToLastItemId = this.selectedItems[this.selectedItems.length - 2].id;
    const secondToLastItemIndex = this.items
      .map(rowData => rowData.id)
      .indexOf(secondToLastItemId);
    const lastItemIndex = this.items
      .map(rowData => rowData.id)
      .indexOf(item.id);
    let startIndex = null;
    let endIndex = null;

    if (lastItemIndex > secondToLastItemIndex) {
      startIndex = secondToLastItemIndex + 1;
      endIndex = lastItemIndex - 1;
    } else {
      startIndex = lastItemIndex + 1;
      endIndex = secondToLastItemIndex - 1;
    }
    for (let i = startIndex; i <= endIndex; i += 1) {
      if (
        !this.selectedItems.some(
          element => element.id === this.items[i].id
        )
      ) {
        this.selectedItems.push(this.items[i]);
      }
    }
  }
}
doubleClick(item) {
  if (this.selectedItems.length !== 0) {
    this.selectedItems.push(item);
    this.shiftClick(item);
  }
}
@NicoAiko
Copy link

This feature needs more attention imo.

@RyanCwynar
Copy link

We actually needed a feature like this on a current project. It wasn't very tough to implement.

Making sure that text wasn't highlighted when holding shift was one part of it, adding the window listeners to see when shift was being held, and then the little method to do bulk selection kind of like what OP posted.

<template>
  <v-app :class="{noselect: shiftKeyOn}">
    <v-main>
      <v-container>
        <v-card>
          <v-card-title>Shift + Click Bulk Select Demo</v-card-title>
          <v-data-table
            v-model="selectedRows"
            @current-items="current = $event"
            @item-selected="bulkSelect"
            :headers="headers"
            :items="desserts"
            item-key="id"
            class="elevation-1"
            show-select
            unselectable
          ></v-data-table>
        </v-card>
      </v-container>
    </v-main>
  </v-app>
</template>

<script>
import { desserts } from "@/mocks/data.json";
export default {
  name: "App",
  created() {
    const self = this;
    self.keyDownHandler = function ({ key }) {
      if (key == "Shift") self.shiftKeyOn = true;
    };
    self.keyUpHandler = function ({ key }) {
      if (key == "Shift") self.shiftKeyOn = false;
    };
    window.addEventListener("keydown", this.keyDownHandler);
    window.addEventListener("keyup", this.keyUpHandler);
  },
  beforeDestroy() {
    window.removeEventListener("keydown", this.keyDownHandler);
    window.removeEventListener("keyup", this.keyUpHandler);
  },
  data() {
    return {
      shiftKeyOn: false,
      current: [],
      selectedRows: [],
      headers: [
        { text: "ID", value: "id" },
        {
          text: "Dessert",
          align: "left",
          sortable: false,
          value: "name",
        },
        { text: "Calories", value: "calories" },
        { text: "Fat (g)", value: "fat" },
      ],
      desserts,
    };
  },
  methods: {
    bulkSelect({ item: b, value }) {
      const { selectedRows, current, shiftKeyOn } = this;

      if (selectedRows.length == 1 && value == true && shiftKeyOn) {
        const [a] = selectedRows;
        let start = current.findIndex((item) => item == a);
        let end = current.findIndex((item) => item == b);
        if (start - end > 0) {
          let temp = start;
          start = end;
          end = temp;
        }
        for (let i = start; i <= end; i++) {
          selectedRows.push(current[i]);
        }
      }
    },
  },
};
</script>
<style>
.noselect {
  -webkit-touch-callout: none; /* iOS Safari */
  -webkit-user-select: none; /* Safari */
  -khtml-user-select: none; /* Konqueror HTML */
  -moz-user-select: none; /* Old versions of Firefox */
  -ms-user-select: none; /* Internet Explorer/Edge */
  user-select: none; /* Non-prefixed version, currently
                                  supported by Chrome, Edge, Opera and Firefox */
}
</style>

Would be cool if something like this was just built in as an attrib.

I made a toy project showing it in action.
Codepen
Demo
repo

@KaelWD KaelWD added this to the v2.5.0 milestone Dec 23, 2020
KaelWD pushed a commit that referenced this issue Apr 24, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
resolves #9958
@KaelWD KaelWD closed this as completed Apr 24, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants