javascriptcheckboxvuejs3v-model

Nested Table with checkboxes in Vue


enter image description here

So I have a nested table and i want the checkboxes in the header of the table to automatically check all the checkbox in its child

This is my vue code:

// TEMPLATE
<table class="table">
  <thead>
    <tr>
      <th><input type="checkbox" value="row-all" v-model="selectAllCheckbox"></th> --> CHECKBOX IN HEADER OF PARENT TABLE
      ...
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><input type="checkbox" :value="`row-${row.id}`" v-model="checkedRows"></td> --> CHECKBOX IN BODY OF PARENT TABLE
      ...
    </tr>
    <tr>
      <table>
        <thead>
          <tr>
            <th><input type="checkbox" :value="`child-${row.id}`" v-model="selectAllCheckbox"></th> --> CHECKBOX IN HEADER OF CHILD TABLE
            ...
          </tr>
        </thead>
        <tbody>
          <tr>
            <td><input type="checkbox" :value="`child-${row.id}-row-${index}`" v-model="checkedRows"></td> --> CHECKBOX IN BODY OF CHILD TABLE
          </tr>
        </tbody>
      </table>
    </tr>
  </tbody>
</table>
// SCRIPT
const selectAllCheckbox = ref(new Set())
const checkedRows = ref(new Set())

watch(() => selectAllCheckbox.value, () => {
  if (selectAllCheckbox.value.has('row-all')) {
    for (const key in shownData.value) {
      checkedRows.value.add(`row-${shownData.value[key].id}`)
    }
  } else {
    for (const key in shownData.value) {
      checkedRows.value.delete(`row-${shownData.value[key].id}`)
    }
  }
  for (const key in shownData.value) {
    if (selectAllCheckbox.value.has(`child-${shownData.value[key].id}`)) {
      for (let index = 0; index < shownData.value[key].product_entries.length; index++) {
        checkedRows.value.add(`child-${shownData.value[key].id}-row-${index}`)
      }
    } else {
      for (let index = 0; index < shownData.value[key].product_entries.length; index++) {
        checkedRows.value.delete(`child-${shownData.value[key].id}-row-${index}`)
      }
    }
  }
})
watch(() => checkedRows.value, () => {
  let condition = true
  for (const key in shownData.value) {
    if (!checkedRows.value.has(`row-${shownData.value[key].id}`)) {
      condition = false
    }
  }
  if (condition) {
    selectAllCheckbox.value.add('row-all')
  } else {
    selectAllCheckbox.value.delete('row-all')
  }

  for (const key in shownData.value) {
    condition = true
    for (let index = 0; index < shownData.value[key].product_entries.length; index++) {
      if (!checkedRows.value.has(`child-${shownData.value[key].id}-row-${index}`)) {
        condition = false
      }
    }
    if (condition && shownData.value[key].product_entries.length > 0) {
      selectAllCheckbox.value.add(`child-${shownData.value[key].id}`)
    } else {
      selectAllCheckbox.value.delete(`child-${shownData.value[key].id}`)
    }
  }
})

My main question is about the checkboxes. Now the code above works fine, my question is:

  1. As you can see, I put probably more than a hundred loop inside the watch function, is this ok? i mean, will the app be slow?
  2. If the answer above is yes, how do I approach this problem then? Is there any example to solve this problem?

Solution

  • Basically my recommendation is always decompose everything to smaller components.

    You can use a combination of :checked and @input and computed (for the all selected checkboxes):

    VUE SFC PLAYGROUND

    <script setup>
    import { reactive, computed } from 'vue'
    const props = defineProps({items:Array});
    const selectedRows = reactive(new Set);
    
    
    const allSelected = computed({
      get(){ return selectedRows.size === props.items.length; },
      set(val){
        val ? props.items.forEach(item => selectedRows.add(item.id)) : selectedRows.clear();
      }
    });
    
    
    
    </script>
    
    <template>
      <table>
        <thead>
          <tr><td><input type="checkbox" v-model="allSelected"></td></tr>
        </thead>
        <tbody v-for="item in items">
          <tr><td><input type="checkbox" :checked="selectedRows.has(item.id)" 
          @input="selectedRows[selectedRows.has(item.id) ? 'delete' : 'add'](item.id)"/></td><td>{{ item.title }}</td></tr>
          <tr><td colspan="2" style="padding-left:32px"><table-select v-if=item.children :items="item.children"/></td></tr>
        </tbody>
      </table>
    </template>