0

New to vue and so far I'm loving it. Basically trying use BV tables to create a excel like table layout. When I click on the cell text, I want it to be replaced with an input box and revert back to text on blur. Sorting doesn't work on input fields, from what I understand, so hiding seemed like the best option.

This code technically works (duno how to get to actually run in SO's code editor), but I'm trying not to have a isHidden field for each field. Currently I would need isHiddenStartTime and isHiddenEndTime plus 1 for every other field. Considering only 1 should need the flag at a time, it seems messy.

new Vue({
  el: "#app",
  data() {
    return {
      fields: [
        {
          key: 'startTime',
          sortable: true
          },
        {
          key: 'endTime',
          sortable: true
        }
      ],
      port: [
        {
          "portNumber": 1,
          "startTime": "00:00:00.00",
          "endTime": "21:59:59.01",
          "isHidden": false,
          "hiddenType": ""
        }, 
        {
          "portNumber": 7,
          "startTime": "00:00:00.00",
          "endTime": "22:59:59.00",
          "isHidden": false,
          "hiddenType": ""
        }
      ]
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue@2.5.0/dist/bootstrap-vue.js"></script>

<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css"/>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />

<div id="app">
  <b-table  :items="port" :fields="fields" striped fixed responsive="sm">
    <template v-slot:cell(endTime)="{ item }">
      <div v-if="!item.isHidden" @click="item.isHidden = true">
        {{ item.endTime }}
      </div>
      <div v-else>
        <b-form-input 
          v-model="item.endTime" 
          @blur="item.isHidden = false" 
          placeholder="Enter end time" 
          autofocus
        ></b-form-input>
      </div>
    </template>
  </b-table>
</div>

I tried adding a hiddenType with the name of the field added on click, but the v-if runs before the click. If I use the same flag (port.isHidden), the entire row changes. If I put the flag in the fields, the entire column changes. Considering it's a list, I don't see a correct way to use ref.

Again, new to view, all the issues make sense, I just can't find a clean way to make it work.

Hiws
  • 8,230
  • 1
  • 19
  • 36
VirtualLife
  • 402
  • 5
  • 14

1 Answers1

2

Instead of having a isHidden property for each field, you can create a property that contains the current field that you're editing.

In the below example i create a isEditingField property and set that to the key of the column, so either startTime or endTime. Then on blur i simply remove the property from the object again with $delete.

It's important that you use $set to create the new property if it doesn't already exist on the object, otherwise it wont be reactive. You can read more about this [here].(https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats)

You might also notice that I'm using v-slot:cell() without specifying a field. This is because it's a fallback slot, which slot all fields that aren't specifically slotted. So in this case it will count for all the slots without having to create a specific one for each.

new Vue({
  el: "#app",
  data() {
    return {
      fields: [
        {
          key: 'startTime',
          sortable: true
          },
        {
          key: 'endTime',
          sortable: true
        }
      ],
      port: [
        {
          "portNumber": 1,
          "startTime": "00:00:00.00",
          "endTime": "21:59:59.01",
          "hiddenType": ""
        }, 
        {
          "portNumber": 7,
          "startTime": "00:00:00.00",
          "endTime": "22:59:59.00",
          "hiddenType": ""
        }
      ]
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue@2.5.0/dist/bootstrap-vue.js"></script>

<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css"/>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />

<div id="app">
  <b-table  :items="port" :fields="fields" striped fixed responsive="sm">
    <template v-slot:cell()="{ item, field: { key } }">
      <div v-if="item.isEditingField == key">
        <b-form-input 
          v-model="item[key]" 
          @blur="$delete(item, 'isEditingField')" 
          placeholder="Enter end time" 
          autofocus
        ></b-form-input>
      </div>
      <div v-else @click="$set(item, 'isEditingField', key)">
        {{ item[key] }}
      </div>
    </template>
  </b-table>
</div>
tony19
  • 125,647
  • 18
  • 229
  • 307
Hiws
  • 8,230
  • 1
  • 19
  • 36
  • Thanks, perfect. Trying to understand v-slot:cell()="{ item, field: { key } }". Think I understand cell() being empty, but the item, field: {key} is throwing me off. Any link to docs explaining the details by chance? Can't seem to find anything similar. – VirtualLife Feb 23 '20 at 18:18
  • 1
    @VirtualLife It's explained [here](https://vuejs.org/v2/guide/components-slots.html#Destructuring-Slot-Props). It allows your markup to be a lot cleaner, so instead of having to do `v-slot:cell()="row"` and writing `row.field.key` inside the template, you can do `v-slot:cell="{ field: { key } }"` and now you can simply write `key` inside the template, making the markup shorter. Especially if you reuse a property a lot. – Hiws Feb 23 '20 at 18:44