Help us shape the 2021 DevExtreme Roadmap. Take our 5 minute survey.
Your search did not match any results.
Data Grid

Collaborative Editing


Multiple users can edit the DataGrid's data in real-time. In this demo, changes made in one UI component are broadcasted to the other UI component via the SignalR service.

To implement this functionality:

  1. Generate a session ID used to identify UI components that should be edited simultaneously (groupId in this demo).
  2. Configure CustomStores. In this demo, we use the createStore method (part of the extension). The onBeforeSend function is used to send the session ID from step 1 to the server.
  3. Create store instances — one per UI component.
  4. Create UI components and bind them to the store instances.
  5. Update all the store instances when a push notification is received (see the updateStores function).

Changes made collaboratively are lost if you refresh the page because the SignalR service broadcasts changes without saving them.

Copy to CodePen
$(function() { var BASE_PATH = ""; var url = BASE_PATH + "api/DataGridCollaborativeEditing/"; var groupId = new; var createStore = function() { return{ key: "ID", loadUrl: url, insertUrl: url, updateUrl: url, deleteUrl: url, onBeforeSend: function(method, ajaxOptions) { = groupId; } }) }; var createDataGrid = function(gridId, store) { $("#" + gridId).dxDataGrid({ dataSource: store, height: 600, showBorders: true, repaintChangesOnly: true, highlightChanges: true, paging: { enabled: false }, editing: { mode: "cell", refreshMode: "reshape", allowUpdating: true, allowDeleting: true, allowAdding: true, useIcons: true }, columns: [ { dataField: "Prefix", caption: "Title", width: 50, validationRules: [{ type: "required" }] }, { dataField: "FirstName", validationRules: [{ type: "required" }] }, { dataField: "StateID", caption: "State", lookup: { dataSource:{ "key": "ID", "loadUrl": BASE_PATH + "api/DataGridStatesLookup" }), displayExpr: "Name", valueExpr: "ID" }, validationRules: [{ type: "required" }] }, { dataField: "BirthDate", dataType: "date", validationRules: [{ type: "range", max: new Date(3000, 0), message: "Date can not be greater than 01/01/3000" }] } ] }); }; var store1 = createStore(); var store2 = createStore(); var updateStores = function(events) { store1.push(events); store2.push(events); }; createDataGrid("grid1", store1); createDataGrid("grid2", store2); var hubUrl = BASE_PATH + 'dataGridCollaborativeEditingHub?GroupId=' + groupId; var connection = new signalR.HubConnectionBuilder() .withUrl(hubUrl, { skipNegotiation: true, transport: signalR.HttpTransportType.WebSockets }) .configureLogging(signalR.LogLevel.Information) .build(); connection.start() .then(function() { connection.on("update", function(key, data) { updateStores([{ type: "update", key: key, data: data }]); }); connection.on("insert", function(data) { updateStores([{ type: "insert", data: data }]); }); connection.on("remove", function(key) { updateStores([{ type: "remove", key: key }]); }); }); });
<!DOCTYPE html> <html xmlns=""> <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" /> <script src=""></script> <script>window.jQuery || document.write(decodeURIComponent('%3Cscript src="js/jquery.min.js"%3E%3C/script%3E'))</script> <link rel="stylesheet" type="text/css" href="" /> <link rel="stylesheet" type="text/css" href="" /> <script src=""></script> <script src="js/signalr.js"></script> <script src=""></script> <script src="index.js"></script> <link rel="stylesheet" type="text/css" href="styles.css" /> </head> <body class="dx-viewport"> <div class="demo-container"> <div class="tables"> <div class="column"> <div id="grid1"></div> </div> <div class="column"> <div id="grid2"></div> </div> </div> </div> </body> </html>
.tables { display: flex; } .column:first-child { width: 50%; padding-right: 15px; } .column:last-child { width: 50%; padding-left: 15px; }