Your search did not match any results.
Data Grid

Collaborative Editing

Documentation

This demo shows how users can use a SignalR service that broadcasts push notifications to edit the DataGrid's data in real time. You can open this page in another browser window and start editing to see this functionality. Changes made in one window are repeated in the other.

You can share the session ID (under the DataGrid) with other users to edit the DataGrid collectively.

Note that all changes are lost if you refresh the page because the SignalR service only broadcasts changes without saving them.

Copy to CodeSandBox
Apply
Reset
<template> <div id="data-grid-demo"> <dx-data-grid id="gridContainer" :data-source="dataSource" :show-borders="true" > <dx-editing :allow-adding="true" :allow-deleting="true" :allow-updating="true" mode="cell" /> <dx-paging :enabled="false"/> <dx-column :width="55" data-field="Prefix" caption="Title" /> <dx-column data-field="FirstName" /> <dx-column data-field="LastName" /> <dx-column :width="170" data-field="Position" /> <dx-column :lookup="lookup" :width="125" data-field="StateID" caption="State" /> <dx-column data-field="BirthDate" data-type="date" /> </dx-data-grid> <div class="options"> <div class="caption">Options</div> <div class="option"> <span>Session ID:</span> <dx-text-box id="sessionId" :value="sessionId" @value-changed="textBoxValueChanged" /> </div> </div> </div> </template> <script> import { DxDataGrid, DxColumn, DxPaging, DxEditing } from 'devextreme-vue/data-grid'; import { DxTextBox } from 'devextreme-vue'; import { HubConnectionBuilder, HttpTransportType } from '@aspnet/signalr'; import * as AspNetData from 'devextreme-aspnet-data-nojquery'; const BASE_PATH = 'https://js.devexpress.com/Demos/NetCore/'; const store = AspNetData.createStore({ key: 'ID', loadUrl: `${BASE_PATH}api/DataGridCollaborativeEditing/`, insertUrl: `${BASE_PATH}api/DataGridCollaborativeEditing/`, updateUrl: `${BASE_PATH}api/DataGridCollaborativeEditing/`, deleteUrl: `${BASE_PATH}api/DataGridCollaborativeEditing/`, onBeforeSend: function(operation, ajaxSettings) { ajaxSettings.data.sessionId = getSessionId(); } }), statesStore = AspNetData.createStore({ key: 'ID', loadUrl: `${BASE_PATH}api/DataGridStatesLookup` }); export default { components: { DxDataGrid, DxColumn, DxPaging, DxEditing, DxTextBox }, data() { return { dataSource: store, lookup: { dataSource: statesStore, displayExpr: 'Name', valueExpr: 'ID' }, sessionId: getSessionId() }; }, methods: { textBoxValueChanged: function(e) { setSessionId(e.value); } } }; const connection = new HubConnectionBuilder() .withUrl(`${BASE_PATH}dataGridCollaborativeEditingHub`, { skipNegotiation: true, transport: HttpTransportType.WebSockets }) .build(); connection.start() .then(function() { connection.on('update', function(key, data, sessionId) { sessionId === getSessionId() && store.push([{ type: 'update', key: key, data: data }]); }); connection.on('insert', function(data, sessionId) { sessionId === getSessionId() && store.push([{ type: 'insert', data: data }]); }); connection.on('remove', function(key, sessionId) { sessionId === getSessionId() && store.push([{ type: 'remove', key: key }]); }); }); var SESSION_KEY = 'dx-demo-collaborative-editing-session-id'; var sessionId; function getSessionId() { if (sessionId) { return sessionId; } var value = localStorage.getItem(SESSION_KEY) || generateRandomNumber(); setSessionId(value); return value; } function setSessionId(value) { sessionId = value; localStorage.setItem(SESSION_KEY, value); } function generateRandomNumber() { var max = 1000000, min = 0; return Math.floor(Math.random() * (max - min + 1)) + min; } </script> <style scoped> #sessionId { width: 100px; } .options { padding: 20px; margin-top: 20px; background-color: rgba(191, 191, 191, 0.15); } .caption { font-weight: 500; font-size: 18px; } .option { margin-top: 10px; } .option > span { position: relative; top: 2px; margin-right: 10px; } .option > .dx-widget { width: 500px; display: inline-block; vertical-align: middle; } </style>
import Vue from 'vue'; import App from './App.vue'; new Vue({ el: '#app', components: { App }, template: '<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/19.1.3/css/dx.common.css" /> <link rel="dx-theme" data-theme="generic.light" href="https://cdn3.devexpress.com/jslib/19.1.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"> <span v-if="false">Loading...</span> </div> </div> </body> </html>
System.config({ transpiler: 'plugin-babel', paths: { 'npm:': 'https://unpkg.com/' }, map: { vue: 'npm:vue@2.5.16/dist/vue.esm.browser.js', 'vue-loader': 'npm:systemjs-vue-browser@latest/index.js', 'devextreme': 'npm:devextreme@19.1', 'devextreme-vue': 'npm:devextreme-vue@19.1', jszip: 'npm:jszip@3.1.3/dist/jszip.min.js', 'quill': 'npm:quill@1.3.6/dist/quill.js', 'devexpress-diagram': 'npm:devexpress-diagram', 'devextreme-aspnet-data-nojquery': 'npm:devextreme-aspnet-data-nojquery@2.2.1', 'plugin-babel': 'npm:systemjs-plugin-babel@0/plugin-babel.js', 'systemjs-babel-build': 'npm:systemjs-plugin-babel@0/systemjs-babel-browser.js', 'tslib': 'npm:tslib@1.6.1', '@aspnet/signalr': 'npm:@aspnet/signalr@1.0.0/dist/cjs/index.js' }, meta: { '*.vue': { loader: 'vue-loader' }, 'devextreme-aspnet-data-nojquery': { 'esModule': true } }, packages: { 'devextreme-vue': { main: 'index.js' }, 'devextreme': { defaultExtension: 'js' } }, babelOptions: { sourceMaps: false, stage0: true } });