Your search did not match any results.
Data Grid

Edit State Management (CTP)

Our DataGrid widget manages its edit state automatically. If your use case requires full control over the editing process, you can use the API members below to manage state manually. In this demo, we manage state with a help of the Vuex library.

Widget Options

  • editing.editRowKey
    The key for the row being edited.

  • editing.editColumnName
    The name or data field of the column being edited.

  • editing.changes
    Pending row changes.

Use these options to access and change edit state. Two-way bind them to component properties so that you can get and set the options at runtime. In this demo, we bind the editRowKey and changes options to computed properties and display their values under the DataGrid.

Utility Method

Event Handlers

  • onSaving / onSaved
    Functions that are called before / after pending row changes are saved via the UI or programmatically.

  • onEditCanceling / onEditCanceled
    Functions that are called before / after editing is canceled and pending row changes are discarded.

Use these functions to perform custom actions. In this demo, the onSaving function sends pending changes to a server. The function's parameter e contains fields for this capability. To implement the same in your application, follow these steps:

  1. Disable built-in edit state management
    Set the e.cancel field to true.

  2. Send a request to the server
    Pending changes are stored in the e.changes array. This array has only a single element in all edit modes, except for batch. Check if this element is not empty and send it to the server (see the saveChange action in store.js).

  3. Apply the same changes to the widget's data source
    If the server successfully saves changes, call the applyChanges method to save the same changes in the widget's data source (see the updateOrders mutation in store.js).

  4. Reset edit state
    Assign null to the editRowKey and an empty array to the changes option (see the updateEditRowKey and updateChanges mutations in store.js).

NOTE

This functionality is available as a community technology preview (CTP). Should you have any questions or suggestions prior to its official release, please email your comments to support@devexpress.com.

Copy to CodeSandBox
Apply
Reset
<template> <div> <DxLoadPanel :position="loadPanelPosition" :visible="isLoading" /> <DxDataGrid id="gridContainer" key-expr="OrderID" :data-source="orders" :show-borders="true" :repaint-changes-only="true" @saving="onSaving" > <DxEditing mode="row" :allow-adding="true" :allow-deleting="true" :allow-updating="true" v-model:changes="changes" v-model:edit-row-key="editRowKey" /> <DxColumn data-field="OrderID" :allow-editing="false" /> <DxColumn data-field="ShipName"/> <DxColumn data-field="ShipCountry"/> <DxColumn data-field="ShipCity"/> <DxColumn data-field="ShipAddress"/> <DxColumn data-field="OrderDate" data-type="date" /> <DxColumn data-field="Freight"/> </DxDataGrid> <div class="options"> <div class="caption">Options</div> <div class="option"> <span>Edit Row Key:</span> <div id="editRowKey">{{ editRowKey === null ? "null" : editRowKey.toString() }}</div> </div> <div class="option"> <span>Changes:</span> <div id="changes">{{ changesText }}</div> </div> </div> </div> </template> <script> import { DxDataGrid, DxColumn, DxEditing } from 'devextreme-vue/data-grid'; import { DxLoadPanel } from 'devextreme-vue/load-panel'; import { mapGetters, mapActions } from 'vuex'; export default { components: { DxDataGrid, DxColumn, DxEditing, DxLoadPanel }, data() { return { loadPanelPosition: { of: '#gridContainer' } }; }, computed: { ...mapGetters(['orders', 'isLoading']), editRowKey: { get() { return this.$store.state.editRowKey; }, set(value) { this.setEditRowKey(value); } }, changes: { get() { return this.$store.state.changes; }, set(value) { this.setChanges(value); } }, changesText: { get() { return JSON.stringify(this.changes.map((change) => ({ type: change.type, key: change.type !== 'insert' ? change.key : undefined, data: change.data })), null, ' '); } } }, created() { this.loadOrders(); }, methods: { ...mapActions(['setEditRowKey', 'setChanges', 'loadOrders', 'insert', 'update', 'remove', 'saveChange']), onSaving(e) { e.cancel = true; e.promise = this.saveChange(e.changes[0]); } } }; </script> <style scoped> #gridContainer { height: 440px; } .options { padding: 20px; margin-top: 20px; background-color: rgba(191, 191, 191, 0.15); } .caption { margin-bottom: 10px; font-weight: 500; font-size: 18px; } .option { margin-bottom: 10px; } .option > span { position: relative; top: 2px; margin-right: 14px; } .option > div { display: inline-block; font-weight: bold; } </style>
import { createStore } from 'vuex'; import 'whatwg-fetch'; import applyChanges from 'devextreme/data/apply_changes'; import { sendRequest } from './utils.js'; const URL = 'https://js.devexpress.com/Demos/Mvc/api/DataGridWebApi'; export default createStore({ state: { orders: [], changes: [], editRowKey: null, isLoading: false }, getters: { orders(state) { return state.orders; }, isLoading(state) { return state.isLoading; } }, mutations: { updateIsLoading(state, isLoading) { state.isLoading = isLoading; }, updateEditRowKey(state, editRowKey) { state.editRowKey = editRowKey; }, updateChanges(state, changes) { state.changes = changes; }, updateOrders(state, { change, data }) { if (change) { change.data = data; state.orders = applyChanges(state.orders, [change], { keyExpr: 'OrderID' }); } else { state.orders = data; } } }, actions: { setEditRowKey(context, value) { context.commit('updateEditRowKey', value); }, setChanges(context, value) { context.commit('updateChanges', value); }, async loadOrders(context) { context.commit('updateIsLoading', true); try { const { data } = await sendRequest(`${URL}/Orders?skip=700`); context.commit('updateOrders', { data }); } finally { context.commit('updateIsLoading', false); } }, async insert(context, change) { const data = await sendRequest(`${URL}/InsertOrder`, 'POST', { values: JSON.stringify(change.data) }); context.commit('updateOrders', { change, data }); }, async update(context, change) { const data = await sendRequest(`${URL}/UpdateOrder`, 'PUT', { key: change.key, values: JSON.stringify(change.data) }); context.commit('updateOrders', { change, data }); }, async remove(context, change) { const data = await sendRequest(`${URL}/DeleteOrder`, 'DELETE', { key: change.key }); context.commit('updateOrders', { change, data }); }, async saveChange({ commit, dispatch }, change) { if (change && change.type) { commit('updateIsLoading', true); try { switch (change.type) { case 'insert': await dispatch('insert', change); break; case 'update': await dispatch('update', change); break; case 'remove': await dispatch('remove', change); break; } } finally { commit('updateIsLoading', false); } } commit('updateEditRowKey', null); commit('updateChanges', []); } } });
export async function sendRequest(url, method = 'GET', data) { data = data || {}; const params = Object.keys(data) .map((key) => { return `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`; }) .join('&'); const result = await fetch(url, { method: method, body: params || null, headers: method === 'GET' ? {} : { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' }, credentials: 'include' }); if (result.ok) { const text = await result.text(); return text && JSON.parse(text); } else { const json = await result.json(); throw json.Message; } }
import { createApp } from 'vue'; import App from './App.vue'; import store from './store.js'; createApp(App).use(store).mount('#app');
<!DOCTYPE html> <html> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/20.2.3/css/dx.common.css" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/20.2.3/css/dx.light.css" /> <script src="https://unpkg.com/core-js@2.4.1/client/shim.min.js"></script> <script src="https://unpkg.com/systemjs@0.21.3/dist/system.js"></script> <script type="text/javascript" src="config.js"></script> <script type="text/javascript"> System.import('./index.js'); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="app"/> </div> </body> </html>
export const customers = [{ 'ID': 1, 'CompanyName': 'Super Mart of the West', 'Address': '702 SW 8th Street', 'City': 'Bentonville', 'State': 'Arkansas', 'Zipcode': 72716, 'Phone': '(800) 555-2797', 'Fax': '(800) 555-2171', 'Website': 'http://www.nowebsitesupermart.com' }, { 'ID': 2, 'CompanyName': 'Electronics Depot', 'Address': '2455 Paces Ferry Road NW', 'City': 'Atlanta', 'State': 'Georgia', 'Zipcode': 30339, 'Phone': '(800) 595-3232', 'Fax': '(800) 595-3231', 'Website': 'http://www.nowebsitedepot.com' }, { 'ID': 3, 'CompanyName': 'K&S Music', 'Address': '1000 Nicllet Mall', 'City': 'Minneapolis', 'State': 'Minnesota', 'Zipcode': 55403, 'Phone': '(612) 304-6073', 'Fax': '(612) 304-6074', 'Website': 'http://www.nowebsitemusic.com' }, { 'ID': 4, 'CompanyName': "Tom's Club", 'Address': '999 Lake Drive', 'City': 'Issaquah', 'State': 'Washington', 'Zipcode': 98027, 'Phone': '(800) 955-2292', 'Fax': '(800) 955-2293', 'Website': 'http://www.nowebsitetomsclub.com' }, { 'ID': 5, 'CompanyName': 'E-Mart', 'Address': '3333 Beverly Rd', 'City': 'Hoffman Estates', 'State': 'Illinois', 'Zipcode': 60179, 'Phone': '(847) 286-2500', 'Fax': '(847) 286-2501', 'Website': 'http://www.nowebsiteemart.com' }, { 'ID': 6, 'CompanyName': 'Walters', 'Address': '200 Wilmot Rd', 'City': 'Deerfield', 'State': 'Illinois', 'Zipcode': 60015, 'Phone': '(847) 940-2500', 'Fax': '(847) 940-2501', 'Website': 'http://www.nowebsitewalters.com' }, { 'ID': 7, 'CompanyName': 'StereoShack', 'Address': '400 Commerce S', 'City': 'Fort Worth', 'State': 'Texas', 'Zipcode': 76102, 'Phone': '(817) 820-0741', 'Fax': '(817) 820-0742', 'Website': 'http://www.nowebsiteshack.com' }, { 'ID': 8, 'CompanyName': 'Circuit Town', 'Address': '2200 Kensington Court', 'City': 'Oak Brook', 'State': 'Illinois', 'Zipcode': 60523, 'Phone': '(800) 955-2929', 'Fax': '(800) 955-9392', 'Website': 'http://www.nowebsitecircuittown.com' }, { 'ID': 9, 'CompanyName': 'Premier Buy', 'Address': '7601 Penn Avenue South', 'City': 'Richfield', 'State': 'Minnesota', 'Zipcode': 55423, 'Phone': '(612) 291-1000', 'Fax': '(612) 291-2001', 'Website': 'http://www.nowebsitepremierbuy.com' }, { 'ID': 10, 'CompanyName': 'ElectrixMax', 'Address': '263 Shuman Blvd', 'City': 'Naperville', 'State': 'Illinois', 'Zipcode': 60563, 'Phone': '(630) 438-7800', 'Fax': '(630) 438-7801', 'Website': 'http://www.nowebsiteelectrixmax.com' }, { 'ID': 11, 'CompanyName': 'Video Emporium', 'Address': '1201 Elm Street', 'City': 'Dallas', 'State': 'Texas', 'Zipcode': 75270, 'Phone': '(214) 854-3000', 'Fax': '(214) 854-3001', 'Website': 'http://www.nowebsitevideoemporium.com' }, { 'ID': 12, 'CompanyName': 'Screen Shop', 'Address': '1000 Lowes Blvd', 'City': 'Mooresville', 'State': 'North Carolina', 'Zipcode': 28117, 'Phone': '(800) 445-6937', 'Fax': '(800) 445-6938', 'Website': 'http://www.nowebsitescreenshop.com' }];
System.config({ transpiler: 'plugin-babel', meta: { '*.vue': { loader: 'vue-loader' }, }, paths: { 'npm:': 'https://unpkg.com/' }, map: { 'vue': 'npm:vue@3.0.0/dist/vue.esm-browser.js', 'vue-loader': 'npm:dx-systemjs-vue-browser@1.0.15/index.js', 'whatwg-fetch': 'npm:whatwg-fetch@2.0.4/fetch.js', 'mitt': 'npm:mitt/dist/mitt.umd.js', 'vuex': 'npm:vuex@4.0.0-beta.4/dist/vuex.esm-browser.js', 'rrule': 'npm:rrule@2.6.6/dist/es5/rrule.js', 'luxon': 'npm:luxon@1.25.0/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign@1.1.0', 'devextreme': 'npm:devextreme@20.2.3', 'devextreme-vue': 'npm:devextreme-vue@20.2.3', 'jszip': 'npm:jszip@3.5.0/dist/jszip.min.js', 'devextreme-quill': 'npm:devextreme-quill@0.9.5/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.0.0/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@2.0.0/dist/dx-gantt.js', 'preact': 'npm:preact@10.5.5/dist/preact.js', 'preact/hooks': 'npm:preact@10.5.5/hooks/dist/hooks.js', 'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js', 'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js' }, packages: { 'devextreme-vue': { main: 'index.js' }, 'devextreme': { defaultExtension: 'js' }, 'devextreme/events/utils': { main: 'index' }, 'devextreme/events': { main: 'index' }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js' } }, babelOptions: { sourceMaps: false, stage0: true } });