bootstrap-vue-editable-table

A Bootstrap Vue editable table for editing cells using built-in Bootstrap form elements

Usage no npm install needed!

<script type="module">
  import bootstrapVueEditableTable from 'https://cdn.skypack.dev/bootstrap-vue-editable-table';
</script>

README

BootstrapVue Editable Table

BootstrapVue Editable Table is a Vue table component that enables cell editing and inherits/supports all other features from BootstrapVue Table.

Editable Demo on CodeSandbox

Demo

This is still an early stage beta version so please help by creating issues with proper labels (bug, question, enhancement...). Thank you in advance 🙏

If you'd like to contribute, please read this introductory article to understand the basic code structure. Whenever you are ready, just create a pull request 👍

Table of Contents

Prerequisite
Setup
Usage
Data Binding
Form Elements
Column Width
Custom Styling
Keyboard Keys
Events
Edit Properties
Custom Cell
Add and Remove Rows
Load Data via REST API
Supported Version
Roadmap

Prerequisite

A basic understanding of BootstrapVue Table.

You're required to install Bootstrap and Bootstrap Vue in your project:

npm install bootstrap bootstrap-vue

Setup

npm i bootstrap-vue-editable-table

Since this is a BootstrapVue component, you need to set it up the same way. The easiest approach is to register BootstrapVue in your app entry point (typically app.js or main.js):

import Vue from 'vue'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'

// Import Bootstrap and BootstrapVue CSS files (order is important)
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

// Make BootstrapVue available throughout your project
Vue.use(BootstrapVue)
// Optionally install the BootstrapVue icon components plugin
Vue.use(IconsPlugin)

Please refer to BoostrapVue Docs for more details.

Usage

Run example on CodeSandbox

<template>
<div>
    <b-editable-table bordered class="editable-table" v-model="items" :fields="fields" @input-change="handleInput">
      <template #cell(isActive)="data">
        <span v-if="data.value">Yes</span>
        <span v-else>No</span>
      </template>
    </b-editable-table>
</div>
</template>

<script>
import BEditableTable from 'bootstrap-vue-editable-table';
export default {
  components: {
    BEditableTable
  },
  data() {
    return {
      fields: [
        { key: "name", label: "Name", type: "text", editable: true, placeholder: "Enter Name...", class: "name-col"},
        { key: "department", label: "Department", type: "select", editable: true, class: "department-col" , options: [
          { value: 1, text: 'HR' },
          { value: 2, text: 'Engineer' },
          { value: 3, text: 'VP' },
          { value: 4, text: 'CEO'}
        ]},
        { key: "age", label: "Age", type:"range", min:"0", max:"100", editable: true, placeholder: "Enter Age...", class: "age-col" },
        { key: "dateOfBirth", label: "Date Of Birth", type: "date", editable: true, class: "date-col", locale: "en",
          "date-format-options": {
            year: "numeric",
            month: "numeric",
            day: "numeric",
          }, },
        { key: "isActive", label: "Is Active", type: "checkbox", editable: true, class: "is-active-col" }
      ],
       items: [
          { age: 40, name: 'Dickerson', department: 1, dateOfBirth: '1984-05-20', isActive: true },
          { age: 21, name: 'Larsen', department: 2, dateOfBirth: '1972-07-25', isActive: false },
          { age: 89, name: 'Geneva', department: 3, dateOfBirth: '1981-02-02', isActive: false },
          { age: 38, name: 'Jami', department: 4, dateOfBirth: '1964-10-19', isActive: true },
        ]
    };
  },
  methods: {
      handleInput(value, data) {}
  }
};
</script>

<style>
table.editable-table {
  margin: auto;
}

table.editable-table td {
  vertical-align: middle;
}

.editable-table .data-cell {
  padding: 8px;
  vertical-align: middle;
}

.editable-table .custom-checkbox {
  width: 50px;
}

.name-col {
  width: 120px;
}

.department-col {
  width: 150px;
}

.age-col {
  width: 100px;
}

.date-col {
  width: 200px;
}

.is-active-col {
  width: 100px
}
</style>

items and fields are the same properties used in BootstrapVue Table except we are introducing a new type and editable property in the fields object to indicate what element is required in every column and whether or not it should be editable. Also, v-model is supported for two-way binding but you can still use :items instead for one-way binding. More on that in the Data Binding section

For select element, options can be passed as another property (as shown in the example above). Since this is a Boostrap Form Select, it supports a list of strings or key/value objects:

[
  { value: 'a', text: 'First option' },
  { value: 'b', text: 'Second Option' },
  { value: 'b', text: 'Third Option' }
]

Data Binding:

|Data | Binding | |--|--| | :items="items" | One-way binding | v-model="items" | Two-way binding

When using v-model the data will be updated automatically:

<b-editable-table v-model="items" :fields="fields"></b-editable-table>

Otherwise, using :items prop to pass data will require updating the data manually on input change:

Run example on CodeSandbox

<template>
<div>
    <b-editable-table :items="items" :fields="fields" @input-change="handleInput">
      <template #cell(isActive)="data">
        <span v-if="data.value">Yes</span>
        <span v-else>No</span>
      </template>
    </b-editable-table>
</div>
</template>

<script>
import BEditableTable from 'bootstrap-vue-editable-table';
export default {
  components: {
    BEditableTable
  },
  data() {
    return {
      fields: [
        { key: "name", label: "Name", type: "text", editable: true, placeholder: "Enter Name..."},
        { key: "department", label: "Department", type: "select", editable: true, class: "department-col" , options: [
          { value: 1, text: 'HR' },
          { value: 2, text: 'Engineer' },
          { value: 3, text: 'VP' },
          { value: 4, text: 'CEO'}
        ]},
        { key: "age", label: "Age", type:"range", min:"0", max:"100", editable: true, placeholder: "Enter Age..." },
        { key: "dateOfBirth", label: "Date Of Birth", type: "date", editable: true, class: "date-col", locale: "en",
          "date-format-options": {
            year: "numeric",
            month: "numeric",
            day: "numeric",
          }, },
        { key: "isActive", label: "Is Active", type: "checkbox", editable: true },
      ],
       items: [
          { age: 40, name: 'Dickerson', department: 1, dateOfBirth: '1984-05-20', isActive: true },
          { age: 21, name: 'Larsen', department: 2, dateOfBirth: '1972-07-25', isActive: false },
          { age: 89, name: 'Geneva', department: 3, dateOfBirth: '1981-02-02', isActive: false },
          { age: 38, name: 'Jami', department: 4, dateOfBirth: '1964-10-19', isActive: true }
        ]
    };
  },
  methods: {
      handleInput(value, data) {
        const updatedRow = {...this.items[data.index], [data.field.key]: value};
        this.$set(this.items, data.index, updatedRow);
      }
  }
};
</script>

Form Elements:

Every column requires a type and editable property in order to make the cell editable:

[
  { key: "name", label: "Name", type: "text", editable: true},
  { key: "department", label: "Department", type: "select", options: ['Accounting', 'Marketing', 'Development', 'HR'], editable: true },
  { key: "age", label: "Age", type: "number", editable: true },
  { key: "dateOfBirth", label: "Date Of Birth", type: "date", editable: true },
  { key: "isActive", label: "Is Active", type: "checkbox", editable: true },
]

Elements' attributes and properties are supported by passing them directly through the field object. For example, you can add size and locale props to the date picker as follows:

{ key:  "dateOfBirth", label:  "Date Of Birth", size:"lg", locale:"fr", type: "date", editable: true }

Supported Bootstrap form elements: |Type | Description | |--|--| | text | Bootstrap Form Text Input | number | Bootstrap Form Number Input | select | Bootstrap Form Select | date | Bootstrap Form Datepicker | checkbox | Bootstrap Form Checkbox | rating | Bootstrap Form Rating

Keyboard Keys:

|Key |Behavior | |--|--| | Tab | Move to the next cell | | Esc | Exit edit mode |

Column Width

To set the width of any column, you can pass the class property in the fields object. This feature is part of Bootstrap Vue Table. You can learn more from Bootstrap Table Docs.

Below is an example of how you set the width by adding a CSS class to the column:

fields: [
    { key: "name", label: "Name", type: "text", class: "name-col", editable: true, placeholder: "Enter Name..."},
    { key: "age", label: "Age", type:"range", class: "age-col", min:"0", max:"100", editable: true, placeholder: "Enter Age..." }
]
<style>
.name-col {
  width: 120px;
}
.age-col {
  width: 100px;
}
</style>

Custom Styling

There are no custom themes available yet but since it extends Bootstrap Vue Table, you can apply all options available from Bootstrap Table Docs as well as defining your own CSS class.

Here is an example of how to style and customize a table:

<template>
<div>
    <b-editable-table bordered class="editable-table" v-model="items" :fields="fields" @input-change="handleInput">
      <template #cell(isActive)="data">
        <span v-if="data.value">Yes</span>
        <span v-else>No</span>
      </template>
    </b-editable-table>
</div>
</template>

<script>
import BEditableTable from 'bootstrap-vue-editable-table';
export default {
  components: {
    BEditableTable
  },
  data() {
    return {
      fields: [
        { key: "name", label: "Name", type: "text", editable: true, placeholder: "Enter Name...", class: "name-col"},
        { key: "department", label: "Department", type: "select", editable: true, class: "department-col" , options: [
          { value: 1, text: 'HR' },
          { value: 2, text: 'Engineer' },
          { value: 3, text: 'VP' },
          { value: 4, text: 'CEO'}
        ]},
        { key: "age", label: "Age", type:"range", min:"0", max:"100", editable: true, placeholder: "Enter Age...", class: "age-col" },
        { key: "dateOfBirth", label: "Date Of Birth", type: "date", editable: true, class: "date-col", locale: "en",
          "date-format-options": {
            year: "numeric",
            month: "numeric",
            day: "numeric",
          }, },
        { key: "isActive", label: "Is Active", type: "checkbox", editable: true, class: "is-active-col" }
      ],
       items: [
          { age: 40, name: 'Dickerson', department: 1, dateOfBirth: '1984-05-20', isActive: true },
          { age: 21, name: 'Larsen', department: 2, dateOfBirth: '1972-07-25', isActive: false },
          { age: 89, name: 'Geneva', department: 3, dateOfBirth: '1981-02-02', isActive: false },
          { age: 38, name: 'Jami', department: 4, dateOfBirth: '1964-10-19', isActive: true },
        ]
    };
  },
  methods: {
      handleInput(value, data) {}
  }
};
</script>

<style>
table.editable-table {
  margin: auto;
}

table.editable-table td {
  vertical-align: middle;
}

.editable-table .data-cell {
  padding: 8px;
  vertical-align: middle;
}

.editable-table .custom-checkbox {
  width: 50px;
}

.name-col {
  width: 120px;
}

.department-col {
  width: 150px;
}

.age-col {
  width: 100px;
}

.date-col {
  width: 200px;
}

.is-active-col {
  width: 100px
}
</style>

.data-cell is an internal class used for the div element within every non-editable cell. You can customize it however you like.

Events:

|Event |Arguments | Description | |--|--|--| | input-change                     |value - Current cell value
data - Row data (the same object returned by Bootstrap)| Emitted when any cell input changes

Edit Properties:

|Property |Options| Default | Description | |--|--|--|--| | editMode |cell - Edit one cell
row - Edit all the cells of a row at once| cell| Change edit mode | editTrigger|click - Edit on mouse click
dblclick - Edit on mouse double click| click| Change edit trigger

Custom Cell

To customize a none editable cell, you can use Bootstraps' scoped slots.

Example rendering a boolean field to Yes or No value:

<b-editable-table v-model="items" :fields="fields">
    <template #cell(isActive)="data">
        <span v-if="data.value">Yes</span>
        <span v-else>No</span>
     </template>
</b-editable-table>

For more details about custom slots, please read BootstrapVue Table documentation

Add and Remove Rows

Run example on CodeSandbox

<template>
<div class="table-container">
    <b-button variant="success" @click="handleAdd()">Add</b-button>
    <b-editable-table bordered class="editable-table" v-model="items" :fields="fields">
      <template #cell(isActive)="data">
        <span v-if="data.value">Yes</span>
        <span v-else>No</span>
      </template>
      <template #cell(delete)="data">
          <BIconX class="remove-icon" @click="handleDelete(data)"></BIconX>
      </template>
    </b-editable-table>
</div>
</template>

<script>
import BEditableTable from 'bootstrap-vue-editable-table';
import {BIconX} from 'bootstrap-vue';
export default {
  components: {
    BEditableTable
  },
  data() {
    return {
      fields: [
        { key: "name", label: "Name", type: "text", editable: true, placeholder: "Enter Name...", class: "name-col"},
        { key: "department", label: "Department", type: "select", editable: true, class: "department-col" , options: [
          { value: 1, text: 'HR' },
          { value: 2, text: 'Engineer' },
          { value: 3, text: 'VP' },
          { value: 4, text: 'CEO'}
        ]},
        { key: "age", label: "Age", type:"range", min:"0", max:"100", editable: true, placeholder: "Enter Age...", class: "age-col" },
        { key: "dateOfBirth", label: "Date Of Birth", type: "date", editable: true, class: "date-col", locale: "en",
          "date-format-options": {
            year: "numeric",
            month: "numeric",
            day: "numeric",
          }, },
        { key: "isActive", label: "Is Active", type: "checkbox", editable: true, class: "is-active-col" },
        { key: "delete", label: "" }
      ],
       items: [
          { age: 40, name: 'Dickerson', department: 1, dateOfBirth: '1984-05-20', isActive: true },
          { age: 21, name: 'Larsen', department: 2, dateOfBirth: '1972-07-25', isActive: false },
          { age: 89, name: 'Geneva', department: 3, dateOfBirth: '1981-02-02', isActive: false },
          { age: 38, name: 'Jami', department: 4, dateOfBirth: '1964-10-19', isActive: true },
        ]
    };
  },
  methods: {
      handleAdd() {
        this.items.unshift({});
      },
      handleDelete(data) {
        this.items.splice(data.index, 1);
      }
  }
};
</script>

<style>
.table-container {
  margin: 10px;
}

table.editable-table {
  margin-top: 10px;
}

table.editable-table td {
  vertical-align: middle;
}

.editable-table .data-cell {
  padding: 8px;
  vertical-align: middle;
}

.editable-table .custom-checkbox {
  width: 50px;
}

.remove-icon {
    color: red;
    cursor: pointer;
    font-size: 20px;
}

.name-col {
  width: 120px;
}

.department-col {
  width: 150px;
}

.age-col {
  width: 100px;
}

.date-col {
  width: 200px;
}

.is-active-col {
  width: 100px
}
</style>

Load Data via REST API

Run example on CodeSandbox

<template>
<div>
    <b-editable-table :busy="loading" bordered class="editable-table" v-model="users" :fields="fields">
      <template #cell(isActive)="data">
        <span v-if="data.value">Yes</span>
        <span v-else>No</span>
      </template>
      <template #table-busy>
        <div class="text-center text-danger my-2">
          <b-spinner class="align-middle"></b-spinner>
          <strong>Loading...</strong>
        </div>
      </template>
    </b-editable-table>
</div>
</template>

<script>
import BEditableTable from 'bootstrap-vue-editable-table';
import {BSpinner} from 'bootstrap-vue';
export default {
  components: {
    BEditableTable,
    BSpinner
  },
  data() {
    return {
      fields: [
        { key: "name", label: "Name", type: "text", editable: true },
        { key: "email", label: "Email", type: "email", editable: true },
        { key: "phone", label: "Phone", type: "text", editable: true }
      ],
      users: [],
      loading: false
    };
  },
  async mounted() {
    this.loading = true;
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    const users = await response.json();
    this.users = users;
    this.loading = false;
  }
};
</script>

<style>
table.editable-table {
  margin: auto;
}

table.editable-table td {
  vertical-align: middle;
}

.editable-table .data-cell {
  padding: 8px;
  vertical-align: middle;
}

.editable-table .custom-checkbox {
  width: 50px;
}

.name-col {
  width: 120px;
}

.department-col {
  width: 150px;
}

.age-col {
  width: 100px;
}

.date-col {
  width: 200px;
}

.is-active-col {
  width: 100px
}
</style>

Supported Version

This has been tested on:

  • vue v2.6.12
  • bootstrap v4.3.1
  • bootstrap-vue v2.21.2

We are looking to support as many versions as possible so please create an issue if you encounter compatibility issues 🙏

Roadmap

  • Tabbing
  • Two-way binding
  • Support all bootstrap form elements
    • Text
    • Select
    • Number
    • Date
    • Checkbox
    • Rating
    • Tags
    • File upload
  • Enable row editing (allows to edit all the cells of a row at once)
  • Validation
  • Vue 3 support
  • Styling themes