External Form

The data of the KendoVue Grid can be edited by using an external form.

<div id="vueapp" class="vue-app">
    <Grid ref="grid"
        :style="{height: '440px'}"
        @edit="edit"
        @remove="remove"
        :data-items="products"
        :columns="columns">
    <grid-toolbar>
        <button title="Add new"
                class="k-button k-primary"
                @click='insert' >
            Add new
        </button>
    </grid-toolbar>
    <grid-norecords>
        There is no data available custom
    </grid-norecords>
    </Grid>
    <dialog-container v-if="productInEdit" :data-item="productInEdit" @save="save" @cancel="cancel">
    </dialog-container>
</div>
import Vue from 'vue';
import { Grid, GridToolbar, GridNoRecords } from '@progress/kendo-vue-grid';
import { sampleProducts } from './app/data';
import { DialogContainer } from './app/dialog-container';

const CommandCell = Vue.component("template-component", {
    props: {
        field: String,
        dataItem: Object,
        format: String,
        className: String,
        columnIndex: Number,
        columnsCount: Number,
        rowType: String,
        level: Number,
        expanded: Boolean,
        editor: String
    },
    template: ` <td>
                    <button
                        class="k-primary k-button k-grid-edit-command"
                        @click="editHandler"
                    >
                        Edit
                    </button>
                    &nbsp;
                    <button
                        class="k-button k-grid-remove-command"
                        @click="removeHandler"
                    >
                        Remove
                    </button>
                </td>`,
    methods: {
        editHandler: function() {
            this.$emit('edit', this.dataItem);
        },
        removeHandler: function() {
            if(confirm('Confirm deleting: ' + this.dataItem.ProductName)){
               this.$emit('remove', this.dataItem);
            }
        }
    }
});

Vue.component('Grid', Grid);
Vue.component('grid-toolbar', GridToolbar);
Vue.component('grid-norecords', GridNoRecords);
Vue.component('dialog-container', DialogContainer);

new Vue({
    el: '#vueapp',
    data: function () {
        return {
            products: sampleProducts.slice(0, 7),
            productInEdit: undefined,
            columns: [
                { field: 'ProductID', editable: false, title: 'ID', width: '50px' },
                { field: 'ProductName', title: 'Name' },
                { field: 'UnitsInStock', title: 'Units', filter: 'numeric', width: '150px', editor: 'numeric' },
                { field: "Discontinued" },
                { cell: CommandCell, width: '180px' }
            ]
        };
    },
    methods: {
       edit(dataItem) {
          this.productInEdit = this.cloneProduct(dataItem);
       },
       remove(dataItem) {
          this.products = this.products.filter(p => p.ProductID !== dataItem.ProductID);
      },
       save() {
        const dataItem = this.productInEdit;
        const products = this.products.slice();
        const isNewProduct = dataItem.ProductID === undefined;

        if (isNewProduct) {
            products.unshift(this.newProduct(dataItem));
        } else {
            const index = products.findIndex(p => p.ProductID === dataItem.ProductID);
            products.splice(index, 1, dataItem);
        }

        this.products= products;
        this.productInEdit= undefined;
      },
      cancel () {
          this.productInEdit= undefined;
      },
      insert () {
          this.productInEdit = { };
      },
      dialogTitle() {
          return `${this.productInEdit.ProductID === undefined ? 'Add' : 'Edit'} product`;
      },
      cloneProduct(product) {
          return Object.assign({}, product);
      },
      newProduct(source) {
          const id = this.products.reduce((acc, current) => Math.max(acc, current.ProductID || 0), 0) + 1;
          const newProduct = {
              ProductID: id,
              ProductName: '',
              UnitsInStock: 0,
              Discontinued: false
          };

          return Object.assign(newProduct, source);
      }
    }
});

export const sampleProducts = [
    {
        "ProductID": 1,
        "ProductName": "Chai",
        "SupplierID": 1,
        "CategoryID": 1,
        "QuantityPerUnit": "10 boxes x 20 bags",
        "UnitPrice": 18,
        "UnitsInStock": 39,
        "UnitsOnOrder": 0,
        "ReorderLevel": 10,
        "Discontinued": false,
        "Category": {
            "CategoryID": 1,
            "CategoryName": "Beverages",
            "Description": "Soft drinks, coffees, teas, beers, and ales"
        },
        "FirstOrderedOn": new Date(1996, 8, 20)
    },
    {
        "ProductID": 2,
        "ProductName": "Chang",
        "SupplierID": 1,
        "CategoryID": 1,
        "QuantityPerUnit": "24 - 12 oz bottles",
        "UnitPrice": 19,
        "UnitsInStock": 17,
        "UnitsOnOrder": 40,
        "ReorderLevel": 25,
        "Discontinued": false,
        "Category": {
            "CategoryID": 1,
            "CategoryName": "Beverages",
            "Description": "Soft drinks, coffees, teas, beers, and ales"
        },
        "FirstOrderedOn": new Date(1996, 7, 12)
    },
    {
        "ProductID": 3,
        "ProductName": "Aniseed Syrup",
        "SupplierID": 1,
        "CategoryID": 2,
        "QuantityPerUnit": "12 - 550 ml bottles",
        "UnitPrice": 10,
        "UnitsInStock": 13,
        "UnitsOnOrder": 70,
        "ReorderLevel": 25,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        },
        "FirstOrderedOn": new Date(1996, 8, 26)
    },
    {
        "ProductID": 4,
        "ProductName": "Chef Anton's Cajun Seasoning",
        "SupplierID": 2,
        "CategoryID": 2,
        "QuantityPerUnit": "48 - 6 oz jars",
        "UnitPrice": 22,
        "UnitsInStock": 53,
        "UnitsOnOrder": 0,
        "ReorderLevel": 0,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        },
        "FirstOrderedOn": new Date(1996, 9, 19)
    },
    {
        "ProductID": 5,
        "ProductName": "Chef Anton's Gumbo Mix",
        "SupplierID": 2,
        "CategoryID": 2,
        "QuantityPerUnit": "36 boxes",
        "UnitPrice": 21.35,
        "UnitsInStock": 0,
        "UnitsOnOrder": 0,
        "ReorderLevel": 0,
        "Discontinued": true,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        },
        "FirstOrderedOn": new Date(1996, 7, 17)
    },
    {
        "ProductID": 6,
        "ProductName": "Grandma's Boysenberry Spread",
        "SupplierID": 3,
        "CategoryID": 2,
        "QuantityPerUnit": "12 - 8 oz jars",
        "UnitPrice": 25,
        "UnitsInStock": 120,
        "UnitsOnOrder": 0,
        "ReorderLevel": 25,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        },
        "FirstOrderedOn": new Date(1996, 9, 19)
    },
    {
        "ProductID": 7,
        "ProductName": "Uncle Bob's Organic Dried Pears",
        "SupplierID": 3,
        "CategoryID": 7,
        "QuantityPerUnit": "12 - 1 lb pkgs.",
        "UnitPrice": 30,
        "UnitsInStock": 15,
        "UnitsOnOrder": 0,
        "ReorderLevel": 10,
        "Discontinued": false,
        "Category": {
            "CategoryID": 7,
            "CategoryName": "Produce",
            "Description": "Dried fruit and bean curd"
        },
        "FirstOrderedOn": new Date(1996, 7, 22)
    },
    {
        "ProductID": 8,
        "ProductName": "Northwoods Cranberry Sauce",
        "SupplierID": 3,
        "CategoryID": 2,
        "QuantityPerUnit": "12 - 12 oz jars",
        "UnitPrice": 40,
        "UnitsInStock": 6,
        "UnitsOnOrder": 0,
        "ReorderLevel": 0,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        },
        "FirstOrderedOn": new Date(1996, 11, 1)
    },
    {
        "ProductID": 9,
        "ProductName": "Mishi Kobe Niku",
        "SupplierID": 4,
        "CategoryID": 6,
        "QuantityPerUnit": "18 - 500 g pkgs.",
        "UnitPrice": 97,
        "UnitsInStock": 29,
        "UnitsOnOrder": 0,
        "ReorderLevel": 0,
        "Discontinued": true,
        "Category": {
            "CategoryID": 6,
            "CategoryName": "Meat/Poultry",
            "Description": "Prepared meats"
        },
        "FirstOrderedOn": new Date(1997, 1, 21)
    },
    {
        "ProductID": 10,
        "ProductName": "Ikura",
        "SupplierID": 4,
        "CategoryID": 8,
        "QuantityPerUnit": "12 - 200 ml jars",
        "UnitPrice": 31,
        "UnitsInStock": 31,
        "UnitsOnOrder": 0,
        "ReorderLevel": 0,
        "Discontinued": false,
        "Category": {
            "CategoryID": 8,
            "CategoryName": "Seafood",
            "Description": "Seaweed and fish"
        },
        "FirstOrderedOn": new Date(1996, 8, 5)
    }
];

import Vue from 'vue';
import { Dialog, DialogActionsBar } from '@progress/kendo-vue-dialogs';
import { NumericTextBox, Input } from '@progress/kendo-vue-inputs';
Vue.component('k-input', Input);
Vue.component('numerictextbox', NumericTextBox);
Vue.component('k-dialog', Dialog);
Vue.component('dialog-actions-bar', DialogActionsBar);

export const DialogContainer = {
  props: {
      dataItem: Object
  },
  data: function(){
    return {
        productInEdit: {
          ProductName: '',
          UnitsInStock: 0,
          Discontinued: false
        }
    };
  },
  created: function(){
    if(this.$props.dataItem){
      this.productInEdit = this.$props.dataItem;
    }
  },
  methods: {
      cancel(){
        this.$emit('cancel');
      },
      save(){
        this.$emit('save');
      }
  },
  template: `<k-dialog @close="cancel">
                <div :style="{ marginBottom: '1rem' }">
                    <label>
                    Product Name<br />
                    <k-input
                        type="text"
                        :name="'ProductName'"
                        v-model= "productInEdit.ProductName"
                    ></k-input>
                    </label>
                </div>
                <div :style="{ marginBottom: '1rem' }">
                    <label>
                    Units In Stock<br />
                    <numerictextbox
                        :name="'UnitsInStock'"
                        v-model="productInEdit.UnitsInStock"
                    ></numerictextbox>
                    </label>
                </div>
                <div>
                    <label>
                        <input
                            type="checkbox"
                            :name="'Discontinued'"
                            v-model="productInEdit.Discontinued"
                        />
                        Discontinued product
                    </label>
                </div>
            <dialog-actions-bar>
                <button
                    class="k-button"
                    @click="cancel"
                >
                    Cancel
                </button>
                <button
                    class="k-button k-primary"
                    @click="save"
                >
                    Save
                </button>
            </dialog-actions-bar>
        </k-dialog>`,
};

Setup

The following example utilizes the Kendo UI for Vue Dialog as a modal form for editing the data of the Grid.
1. When a record is in edit mode, show the container and pass the edited item to the DialogContainer component.

<dialog-container v-if="productInEdit" :data-item="productInEdit" @save="save" @cancel="cancel">
    </dialog-container>
  1. Inside DialogContainer, bind the editors to the value of the row data with v-model.
<k-dialog @close="cancel">
                <div :style="{ marginBottom: '1rem' }">
                    <label>
                    Product Name<br />
                    <k-input
                        type="text"
                        :name="'ProductName'"
                        v-model= "productInEdit.ProductName"
                    ></k-input>
                    </label>
                </div>
                <div :style="{ marginBottom: '1rem' }">
                    <label>
                    Units In Stock<br />
                    <numerictextbox
                        :name="'UnitsInStock'"
                        v-model="productInEdit.UnitsInStock"
                    ></numerictextbox>
                    </label>
                </div>
                <div>
                    <label>
                        <input
                            type="checkbox"
                            :name="'Discontinued'"
                            v-model="productInEdit.Discontinued"
                        />
                        Discontinued product
                    </label>
                </div>
            <dialog-actions-bar>
                <button
                    class="k-button"
                    @click="cancel"
                >
                    Cancel
                </button>
                <button
                    class="k-button k-primary"
                    @click="save"
                >
                    Save
                </button>
            </dialog-actions-bar>
        </k-dialog>
  1. To improve its performance, the Grid refreshes its data on each Update button click. To update the Grid on type, directly update the data state of the Grid on change.

In this article