Skip to content
On this page

useTableSelection

Pattern - 1

Demo

This is a demo for a composable that helps in handling applications with multiple tables

NamePhoneAddressRoleAction
jimoh sodiq Adewale+23412345678Nigeria, Lagos 1123, addressAdmin
jimoh sodiq Adewale+23412345678Nigeria, Lagos 1123, addressAdmin
jimoh sodiq Adewale+23412345678Nigeria, Lagos 1123, addressAdmin
jimoh sodiq Adewale+23412345678Nigeria, Lagos 1123, addressAdmin
jimoh sodiq Adewale+23412345678Nigeria, Lagos 1123, addressAdmin

Selected Rows here:

 []

Code

ts
import { ref, computed, unref, watch } from "vue";
import type { Ref } from 'vue'

export function useTableSelection<T>(dataSource: Ref<T[]> | T[]) {
  
  // an array of selected rows
  const selectedRows: Ref<T[]> = ref([]);
  
  // the data source
  const source = computed<T[]>(() => {
    return unref(dataSource);
  });

  // boolean to check if all rows are selected
  const allRowsSelected = computed<boolean>(() => {
    return source.value.length === selectedRows.value.length;
  });

  // returns true is the data source is empty   
  const isEmptySource = computed(() => {
    return source.value.length == 0;
  });
  
  // returns true if at least one row is selected
  const isSelectedAvailable = computed(() => {
    return selectedRows.value.length > 0;
  });

  // sets the selected rows an empty array
  function clearSelectedRows() {
    selectedRows.value = [];
  }

  // updates the selected rows to only include data present in source in case of deletion
  watch(
    source.value,
    () => {
      selectedRows.value = selectedRows.value.filter((row) =>
        source.value.includes(row)
      );
    },
    { immediate: true }
  );

  // selects all the table rows
  function selectAllRows() {
    selectedRows.value = source.value;
  }
  
  // select or deselect all rows
  function toggleRowSelection() {
    if (selectedRows.value.length > 0) {
      selectedRows.value = [];
      return;
    }
    for (const item of source.value) {
      if (!selectedRows.value.includes(item)) {
        selectedRows.value.push(item);
      }
    }
  }

  return {
    source,
    selectedRows,
    allRowsSelected,
    toggleRowSelection,
    isEmptySource,
    isSelectedAvailable,
    clearSelectedRows,
    selectAllRows,
  };
}
vue
<template>
  <div>
    <main>
      <p>
        This is a demo for a composable that helps in handling applications with multiple tables
      </p>

      <!-- demo here -->
      <section class="w-full overflow-x-hidden">
        <div class="w-full overflow-x-auto">
          <table>
            <thead class="">
              <th>
                <input
                  :value="allRowsSelected"
                  :checked="isSelectedAvailable || allRowsSelected"
                  @input.prevent="toggleRowSelection"
                  type="checkbox"
                  class="w-4 h-4"
                />
              </th>
              <th>Name</th>
              <th>Phone</th>
              <th>Address</th>
              <th>Role</th>
              <th>Action</th>
            </thead>
            <tbody>
              <tr
                v-for="user in userDataList"
                :key="user.id"
                class="h-full cursor-pointer hover:bg-slate-100 transition-colors duration-200"
              >
                <td>
                  <input
                    v-model="selectedRows"
                    :value="user"
                    type="checkbox"
                    class="w-4 h-4"
                  />
                </td>
                <td>{{ user.name }}</td>
                <td>{{ user.phone }}</td>
                <td>{{ user.address }}</td>
                <td>{{ user.role }}</td>
                <td>
                  <select>
                    <option>Edit</option>
                    <option>Delete</option>
                  </select>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
        <div class="flex gap-x-3">
          <button class="rounded bg-gray-400 p-1.5 text-sm text-white" @click="clearSelectedRows">Clear Selection</button>
          <button class="rounded bg-gray-400 p-1.5 text-sm text-white" @click="selectAllRows">Select All Rows</button>
          <button class="rounded bg-gray-400 p-1.5 text-sm text-white" @click="deleteLastRow">Delete Last Row</button>
        </div>
        <h2 class="my-4">Selected Rows here:</h2>
        <pre class="text-xs md:text-base"> {{ selectedRows }}</pre>
      </section>
    </main>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { useTableSelection } from "../composables/useTableSelection";
interface User {
  id: string;
  name: string;
  phone?: string;
  address?: string;
  role: "Admin" | "User";
}

const userDataList = ref<User[]>([
  {
    id: "1",
    name: "jimoh sodiq Adewale",
    phone: "+23412345678",
    address: "Nigeria, Lagos 1123, address",
    role: "Admin",
  },
  {
    id: "2",
    name: "jimoh sodiq Adewale",
    phone: "+23412345678",
    address: "Nigeria, Lagos 1123, address",
    role: "Admin",
  },
  {
    id: "3",
    name: "jimoh sodiq Adewale",
    phone: "+23412345678",
    address: "Nigeria, Lagos 1123, address",
    role: "Admin",
  },
  {
    id: "4",
    name: "jimoh sodiq Adewale",
    phone: "+23412345678",
    address: "Nigeria, Lagos 1123, address",
    role: "Admin",
  },
  {
    id: "5",
    name: "jimoh sodiq Adewale",
    phone: "+23412345678",
    address: "Nigeria, Lagos 1123, address",
    role: "Admin",
  },
]);

function deleteLastRow() {
  userDataList.value.pop();
}

const {
  selectedRows,
  toggleRowSelection,
  selectAllRows,
  isSelectedAvailable,
  allRowsSelected,
  clearSelectedRows,
} = useTableSelection(userDataList);
</script>

<style scoped>
th {
  @apply font-semibold text-sm py-6 px-3;
}
td {
  @apply text-xs px-3;
}
</style>

Released under the MIT License.