From 50e8fd53b3f1f97455871021fe7a8476d2d68e76 Mon Sep 17 00:00:00 2001 From: VijayalakshmiGSF4561 Date: Fri, 7 Aug 2026 20:06:15 +0530 Subject: [PATCH 1/2] 1046505: Updated collaborative editing files in ES5 DOCX Editor --- .../collaborative-editing/overview.md | 22 +- .../using-redis-cache-asp-net-core.md | 619 +++++++++++------- 2 files changed, 392 insertions(+), 249 deletions(-) diff --git a/Document-Processing/Word/Word-Processor/javascript-es5/collaborative-editing/overview.md b/Document-Processing/Word/Word-Processor/javascript-es5/collaborative-editing/overview.md index acd38952bd..ad0a72b6f5 100644 --- a/Document-Processing/Word/Word-Processor/javascript-es5/collaborative-editing/overview.md +++ b/Document-Processing/Word/Word-Processor/javascript-es5/collaborative-editing/overview.md @@ -1,23 +1,23 @@ --- layout: post -title: Collaborative Editing in JavaScript (ES5) Document editor control | Syncfusion -description: Learn about collaborative editing in Syncfusion JavaScript (ES5) Document editor control of Syncfusion Essential JS 2 and more. +title: Collaborative Editing in JavaScript (ES5) DOCX Editor Component | Syncfusion +description: Learn about collaborative editing in Syncfusion JavaScript (ES5) Document Editor control and how to implement real-time document collaboration. platform: document-processing control: Collaborative Editing documentation: ug domainurl: ##DomainURL## --- -# Collaborative Editing in javascript-es5 +# Collaborative Editing in JavaScript (ES5) Document Editor Component -Allows multiple users to work on the same document simultaneously. This can be done in real-time, so that collaborators can see the changes as they are made. Collaborative editing can be a great way to improve efficiency, as it allows team members to work together on a document without having to wait for others to finish their changes. +Allows multiple users to work on the same document simultaneously. This can be in real time so that collaborators can see them as they are made. Collaborative editing can be a great way to improve efficiency, as it allows team members to work together on a document without having to wait for others to finish their changes. ## Prerequisites -- *Real-time Transport Protocol*: This protocol facilitates instant communication between clients and the server, ensuring immediate updates during collaborative editing. +- *Real-Time Transport Protocol*: This protocol facilitates instant communication between clients and the server, ensuring immediate updates during collaborative editing. - *Distributed Cache or Database*: Used to temporarily store the queue of editing operations. -### Real time transport protocol +### Real-time transport protocol - *Managing Connections*: Keeps active connections open for real-time collaboration, allowing seamless communication between users and the server. - *Broadcasting Changes*: Ensures that any edits made by one user are instantly sent to all collaborators, keeping everyone on the same page with the latest document version. @@ -26,17 +26,17 @@ Allows multiple users to work on the same document simultaneously. This can be d To support collaborative editing, it's crucial to have a backing system that temporarily stores the editing operations of all active users. There are two primary options: -- ***Distributed Cache***: Handles more HTTP requests per second than a database approach. For example, a server with 2 vCPUs and 8GB RAM can process up to 125 requests per second using a distributed cache. We highly recommend using a distributed cache as a backing system over a database. +- ***Distributed Cache***: Handles more HTTP requests per second than a database approach. For example, a server with 2 vCPUs and 8GB of RAM can process up to 125 requests per second using a distributed cache. We highly recommend using a distributed cache as a backing system over a database. - ***Database***: With the same server configuration, it can handle up to 50 requests per second. -Using the distributed cache or database all the editing operations are queued in order and conflict resolution is performed using `Operational Transformation` algorithm. +With the distributed cache or database, all the editing operations are queued in order, and conflict resolution is performed using the `Operational Transformation` algorithm. ->**Tips**: To calculate the average requests per second of your application Assume the editor in your live application is actively used by 1000 users and each user’s edit can trigger 2 to 5 requests per second. The total requests per second of your applications will be around 2000 to 5000. In this case, you can finalize a configuration to support around 5000 average requests per second. +N> 1. To calculate the average requests per second of your application, assume the Document Editor in your live application is actively used by 1000 users, and each user's edit can trigger 2 to 5 requests per second. The total requests per second of your application will be around 2000 to 5000. In this case, you can finalize a configuration to support around 5000 average requests per second. ->**Note**: The above metrics are based solely on the collaborative editing module. Actual throughput may decrease depending on other server-side interactions, such as document importing, pasting formatted content, editing restrictions, and spell checking. Therefore, it is advisable to monitor your app’s traffic and choose a configuration that best suits your needs. +N> 2. The above metrics are based solely on the collaborative editing module. Actual throughput may decrease depending on other server-side interactions, such as document importing, pasting formatted content, editing restrictions, and spell checking. Therefore, it is advisable to monitor your app's traffic and choose a configuration that best suits your needs. -#### See Also +## See Also - [Collaborative editing using Redis cache in ASP.NET Core](../collaborative-editing/using-redis-cache-asp-net-core) - [Collaborative editing using Java](../collaborative-editing/using-redis-cache-java) diff --git a/Document-Processing/Word/Word-Processor/javascript-es5/collaborative-editing/using-redis-cache-asp-net-core.md b/Document-Processing/Word/Word-Processor/javascript-es5/collaborative-editing/using-redis-cache-asp-net-core.md index 1c19c94822..7767ea6ab5 100644 --- a/Document-Processing/Word/Word-Processor/javascript-es5/collaborative-editing/using-redis-cache-asp-net-core.md +++ b/Document-Processing/Word/Word-Processor/javascript-es5/collaborative-editing/using-redis-cache-asp-net-core.md @@ -1,86 +1,135 @@ --- layout: post -title: Collaborative Editing in JavaScript (ES5) Document editor | Syncfusion -description: Learn how to enable collaborative editing using Redis in Syncfusion JavaScript (ES5) Document editor control of Syncfusion Essential JS 2 and more. +title: Collaborative Editing in JavaScript (ES5) with Redis | Syncfusion +description: Learn how to enable collaborative editing using Redis in the Syncfusion JavaScript (ES5) Document Editor control of Syncfusion Essential JS 2 and more. platform: document-processing control: Collaborative Editing documentation: ug domainurl: ##DomainURL## --- -# Collaborative Editing in javascript-es5 with Redis in ASP.NET Core +# Collaborative Editing in JavaScript (ES5) with Redis in ASP.NET Core -Allows multiple users to work on the same document simultaneously. This can be done in real-time, so that collaborators can see the changes as they are made. Collaborative editing can be a great way to improve efficiency, as it allows team members to work together on a document without having to wait for others to finish their changes. +[JavaScript DOCX Editor](https://www.syncfusion.com/docx-editor-sdk/javascript-docx-editor) (Document Editor) supports collaborative editing which allows multiple users to work on the same document simultaneously. This can be done in real-time, so that collaborators can see the changes as they are made. ## Prerequisites -The following are needed to enable collaborative editing in [JavaScript DOCX Editor](https://www.syncfusion.com/docx-editor-sdk/javascript-docx-editor) (Document Editor). +The following are needed to enable collaborative editing in Document Editor. -* `SignalR` -* `Redis` +- SignalR +- Redis ## SignalR -In collaborative editing, real-time communication is Essential® for users to see each other's changes instantly. We use a real-time transport protocol to efficiently send and receive data as edits occur. For this, we utilize SignalR, which supports real-time data exchange between the client and server. SignalR ensures that updates are transmitted immediately, allowing seamless collaboration by handling the complexities of connection management and offering reliable communication channels. - -To make SignalR work in a distributed environment (with more than one server instance), it needs to be configured with either Azure SignalR Service or a Redis backplane. +SignalR enables real-time communication by instantly sending and receiving document changes between clients and the server, ensuring seamless collaboration. In distributed environments, it can be scaled using Azure SignalR Service or a Redis backplane. ### Scale-out SignalR using Azure SignalR service -Azure SignalR Service is a scalable, managed service for real-time communication in web applications. It enables real-time messaging between web clients (browsers) and your server-side application(across multiple servers). +Azure SignalR Service is a scalable, managed service for real-time communication in web applications. It enables real-time messaging between web clients (browsers) and your server-side application (across multiple servers). + +The following code snippet demonstrates how to configure Azure SignalR in an ASP.NET Core application using the `AddAzureSignalR` method in the "Program.cs" file of the web service project. -Below is a code snippet to configure Azure SignalR in an ASP.NET Core application using the ```AddAzureSignalR``` method +{% tabs %} +{% highlight C# tabtitle="C#" %} -```csharp -builder.Services.AddSignalR() .AddAzureSignalR("", options => { +builder.Services.AddSignalR().AddAzureSignalR("", options => { // Specify the channel name options.Channels.Add("document-editor"); - }); -``` +}); + +{% endhighlight %} +{% endtabs %} ### Scale-out SignalR using Redis -Using a Redis backplane, you can achieve horizontal scaling of your SignalR application. The SignalR leverages Redis to efficiently broadcast messages across multiple servers. This allows your application to handle large user bases with minimal latency. +A Redis backplane enables horizontal scaling in a SignalR application. SignalR uses Redis to efficiently broadcast messages across multiple servers, allowing the application to support a large number of users with minimal latency. + +In the SignalR application, install the following NuGet package: -In the SignalR app, install the following NuGet package: -* ` Microsoft.AspNetCore.SignalR.StackExchangeRedis` +- Microsoft.AspNetCore.SignalR.StackExchangeRedis -Below is a code snippet to configure Redis backplane in an ASP.NET Core application using the ```AddStackExchangeRedis ``` method +The following code snippet demonstrates how to configure the Redis backplane in an ASP.NET Core application using the `AddStackExchangeRedis` method in the "Program.cs" file of the web service project. + +{% tabs %} +{% highlight C# tabtitle="C#" %} -```csharp builder.Services.AddSignalR().AddStackExchangeRedis(""); -``` -Configure options as needed: -The following example shows how to add a channel prefix in the ConfigurationOptions object. +{% endhighlight %} +{% endtabs %} + +Configure the options as required. + +The following example demonstrates how to add a channel prefix using the ConfigurationOptions object. + +{% tabs %} +{% highlight C# tabtitle="C#" %} -```csharp builder.Services.AddDistributedMemoryCache().AddSignalR().AddStackExchangeRedis(connectionString, options => - { - options.Configuration.ChannelPrefix = "document-editor"; - }); -``` +{ + options.Configuration.ChannelPrefix = "document-editor"; +}); + +{% endhighlight %} +{% endtabs %} ## Redis -In collaborative editing, Redis is used to store temporary data that helps queue editing operations and resolve conflicts using the `Operational Transformation` algorithm. +In collaborative editing, Redis is used to store temporary data that helps queue editing operations and resolve conflicts using the `Operational Transformation` algorithm. + +All editing operations are stored in the Redis cache. To prevent memory buildup, a `SaveThreshold` limit can be configured at the application level. For example, if the SaveThreshold is set to 100, up to twice that number of editing operations are retained in Redis per document. When this limit is exceeded, the first 100 operations (as defined by the save threshold) are removed from the cache and automatically saved to the source document. + +The configuration and storage size of the Redis cache can be adjusted based on the following considerations: + +- *Storage Requirements*: A minimum of 400 KB of cache memory is required to edit a single document, with the capacity to store up to 100 editing operations. Storage requirements may increase based on the following factors: + + - *Images*: Increases with the number of images added to the document. + + - *Pasted content*: Depends on the size of the SFDT content. + +- *Connection Limits*: Redis has a limit on concurrent connections. The Redis configuration should be selected based on the user base to ensure optimal performance. + +> For better performance, a minimum `SaveThreshold` value of 100 is recommended. + +## Collaborative editing architecture + +Collaborative editing is built using three main components: + +### Client (JavaScript ES5 Document Editor) + +- Captures user edits in the document + +- Converts edits into operations and sends them to the server + +- Receives updates from other users and applies them to stay in sync + +### Real-time communication (SignalR) + +- Acts as the communication layer between clients and server + +- Sends and receives changes instantly + +- Broadcasts updates to all connected users in real time + +### Distributed cache (Redis) + +- Temporarily stores all editing operations -All editing operations in collaborative editing are stored in the Redis cache. To prevent memory buildup, we can configure a `SaveThreshold` limit at the application level. If the `SaveThreshold` is 100, editing operations up to twice the save threshold limit are kept in Redis per document. Once exceeded, the first 100 operations (as defined by the save threshold) are removed from the cache and automatically saved to the source input document. +- Maintains the correct order of changes -The configuration and store size of the Redis cache can be adjusted based on the following considerations. +- Resolves conflicts between multiple users using the OT algorithm -- *Storage Requirements*: A minimum of 400KB of cache memory is needed for editing a single document, with the capacity to store up to 100 editing operations. Storage needs may increase based on following factor. - - *Images*: Increases with the number of images added to the document. - - *Pasted content*: Depends on the size of the SFDT content. -- *Connection Limits*: Redis has a limit on concurrent connections. Choose the Redis configuration based on your user base to ensure optimal performance. +## Integrate collaborative editing in client side -> For better performance, we recommend to have minimum `SaveThreshold` limit of 100. +### Step 1: Integrate Document Editor in JavaScript ES5 sample -## How to enable collaborative editing in client side +Refer to the following documentation to get started with the [JavaScript ES5 Document Editor](../getting-started). -### Step 1: Enable collaborative editing in Document Editor +### Step 2: Enable collaborative editing -To enable collaborative editing, inject `CollaborativeEditingHandler` and set the property `enableCollaborativeEditing` to true in the Document Editor, like in the code snippet below. +To enable collaborative editing, inject [CollaborativeEditingHandler](https://ej2.syncfusion.com/documentation/api/document-editor/collaborativeeditinghandler) and set the [enableCollaborativeEditing](https://ej2.syncfusion.com/documentation/api/document-editor/index-default#enablecollaborativeediting) property to true in the Document Editor. + +The following code snippet demonstrates how to enable collaborative editing in the Document Editor. {% tabs %} {% highlight js tabtitle="index.js" %} @@ -88,9 +137,11 @@ To enable collaborative editing, inject `CollaborativeEditingHandler` and set th {% endhighlight %} {% endtabs %} -### Step 2: Configure SignalR to send and receive changes +### Step 3: Configure SignalR to send and receive changes + +To broadcast changes and receive updates from remote users, configure SignalR in the Document Editor. -To broadcast the changes made and receive changes from remote users, configure SignalR like below. +The following code snippet demonstrates how to configure SignalR in the Document Editor. {% tabs %} {% highlight js tabtitle="index.js" %} @@ -98,9 +149,11 @@ To broadcast the changes made and receive changes from remote users, configure S {% endhighlight %} {% endtabs %} -### Step 3: Join SignalR room while opening the document +### Step 4: Join SignalR room while opening the document + +When opening a document, a unique ID must be generated for each document. These unique IDs are then used to create rooms using SignalR, which facilitates real-time communication and collaborative editing among multiple users. -When opening a document, we need to generate a unique ID for each document. These unique IDs are then used to create rooms using SignalR, which facilitates sending and receiving data from the server. +The following code snippet demonstrates how to generate a unique ID and open a document. {% tabs %} {% highlight js tabtitle="index.js" %} @@ -108,9 +161,11 @@ When opening a document, we need to generate a unique ID for each document. Thes {% endhighlight %} {% endtabs %} -### Step 4: Broadcast current editing changes to remote users +### Step 5: Broadcast current editing changes to remote users -Changes made on the client-side need to be sent to the server-side to broadcast them to other connected users. To send the changes made to the server, use the method shown below from the document editor using the `contentChange` event. +Changes made on the client side must be transmitted to the server to be broadcast to other connected users. + +The following code snippet demonstrates how to send changes to the server using the [contentChange](https://ej2.syncfusion.com/documentation/api/document-editor/index-default#contentchange) event in the Document Editor. {% tabs %} {% highlight js tabtitle="index.js" %} @@ -118,216 +173,292 @@ Changes made on the client-side need to be sent to the server-side to broadcast {% endhighlight %} {% endtabs %} -## How to enable collaborative editing in ASP.NET Core - -### Step 1: Configure SignalR in ASP.NET Core - -We are using Microsoft SignalR to broadcast the changes. Please add the following configuration to your application's `Program.cs` file. - -```csharp - using Microsoft.Azure.SignalR; - ..... - builder.Services.AddSignalR(); - ..... - ..... - ..... - app.MapHub("/documenteditorhub"); - ..... - ..... -``` - -### Step 2: Configure SignalR hub to create room for collaborative editing session - -To manage groups for each document, create a folder named "Hub" and add a file named `DocumentEditorHub.cs` inside it. Add the following code to the file to manage SignalR groups using room names. - -Join the group by using unique id of the document by using `JoinGroup` method. - -```csharp -static Dictionary userManager = new Dictionary(); - internal static Dictionary> groupManager = new Dictionary>(); - - // Join to the specified room name - public async Task JoinGroup(ActionInfo info) - { - if (!userManager.ContainsKey(Context.ConnectionId)) - { - userManager.Add(Context.ConnectionId, info); - } - info.ConnectionId = Context.ConnectionId; - //Add the current connected use to the specified group - await Groups.AddToGroupAsync(Context.ConnectionId, info.RoomName); - if (groupManager.ContainsKey(info.RoomName)) - { - await Clients.Caller.SendAsync("dataReceived", "addUser", groupManager[info.RoomName]); - } - lock (groupManager) - { - if (groupManager.ContainsKey(info.RoomName)) - { - groupManager[info.RoomName].Add(info); - } - else - { - List actions = new List - { - info - }; - groupManager.Add(info.RoomName, actions); - } - } - // Notify other users in the group about new user joined the collaborative editing session. - Clients.GroupExcept(info.RoomName, Context.ConnectionId).SendAsync("dataReceived", "addUser", info); - } +The complete version of the code discussed above is available at the following [GitHub repository](https://github.com/SyncfusionExamples/EJ2-Document-Editor-Collaborative-Editing/tree/master/Client%20side%20with%20dotnet/JavaScript). -``` - -Handle user disconnection using SignalR. - -```csharp -//Handle disconnection from group. - public override Task OnDisconnectedAsync(Exception? e) - { - string roomName = userManager[Context.ConnectionId].RoomName; - if (groupManager.ContainsKey(roomName)) - { - groupManager[roomName].Remove(userManager[Context.ConnectionId]); - - if (groupManager[roomName].Count == 0) - { - groupManager.Remove(roomName); - //If all user disconnected from current room. Auto save the change to source document. - CollaborativeEditingController.UpdateOperationsToSourceDocument(roomName, “<>”, false); - } - } - - if (userManager.ContainsKey(Context.ConnectionId)) - { - //Notify other user in the group about user exit the collaborative editing session - Clients.OthersInGroup(roomName).SendAsync("dataReceived", "removeUser", Context.ConnectionId); - Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName); - userManager.Remove(Context.ConnectionId); - } - return base.OnDisconnectedAsync(e); - } +## Integrate collaborative editing in server side + +### Step 1: Create the Document Editor web service project + +Create an ASP.NET Core web service to handle server-side operations. + +### Step 2: Install required NuGet packages + +In the web service app, install the following NuGet package: -``` +- Microsoft.Azure.SignalR -### Step 3: Configure Redis cache connection string in application level +- Microsoft.AspNetCore.SignalR.StackExchangeRedis + +- Syncfusion.EJ2.WordEditor.AspNet.Core + +### Step 3: Configure Redis connection Configure the Redis that stores temporary data for the collaborative editing session. Provide the Redis connection string in `appsettings.json` file. ```json -..... - "ConnectionStrings": { - "RedisConnectionString": "<>" + +// other code snippet +"ConnectionStrings": { + "RedisConnectionString": "<>" +} +// other code snippet + +``` + +### Step 4: Configure SignalR in ASP.NET Core + +Microsoft SignalR is used to broadcast changes. Add the following configuration to the application's "Program.cs" file. + +{% tabs %} +{% highlight C# tabtitle="C#" %} + +using Microsoft.Azure.SignalR; + +// other Services + +// Add signalR services to the container. + +builder.Services.AddSignalR().AddStackExchangeRedis("Your Redis Connection String"); + +// other Services + +{% endhighlight %} +{% endtabs %} + +### Step 5: Configure SignalR Hub to create room for collaborative editing session + +To manage groups for each document, create a folder named "Hub" and add a file named `DocumentEditorHub.cs` inside it. + +#### 1. Mapping Hub details + +Map DocumentEditorHub in the "Program.cs" file using the following code. + +{% tabs %} +{% highlight C# tabtitle="C#" %} + +app.MapHub("/documenteditorhub"); + +{% endhighlight %} +{% endtabs %} + +#### 2. Join room + +Join the group using the unique ID of the document with the `JoinGroup` method. + +Add the following code to the file to manage SignalR groups using room names. + +{% tabs %} +{% highlight C# tabtitle="C#" %} + +// Join group based on the room name and store the user details in Redis cache. +public async Task JoinGroup(ActionInfo info) +{ + // Set the connection ID to info + info.ConnectionId = Context.ConnectionId; + // Add the connection ID to the group + await Groups.AddToGroupAsync(Context.ConnectionId, info.RoomName); + + // To ensure whether the room exists in the Redis cache + bool roomExists = await _db.KeyExistsAsync(info.RoomName + CollaborativeEditingHelper.UserInfoSuffix); + if (roomExists) { + // Fetch all connected users from Redis + var allUsers = await _db.HashGetAllAsync(info.RoomName + CollaborativeEditingHelper.UserInfoSuffix); + var userList = allUsers.Select(u => JsonConvert.DeserializeObject(u.Value)).ToList(); + + // Send the existing user details to the newly joined user. + await Clients.Caller.SendAsync("dataReceived", "addUser", userList); } -..... -``` + // Add user to Redis + await _db.HashSetAsync(info.RoomName + CollaborativeEditingHelper.UserInfoSuffix, Context.ConnectionId, JsonConvert.SerializeObject(info)); -### Step 4: Configure Web API actions for collaborative editing + // Store the room name with the connection ID + await _db.HashSetAsync(CollaborativeEditingHelper.ConnectionIdRoomMappingKey, Context.ConnectionId, info.RoomName); -#### Import File + // Notify all the existing users in the group about the new user + await Clients.GroupExcept(info.RoomName, Context.ConnectionId).SendAsync("dataReceived", "addUser", info); +} -1. When opening a document, check the Redis cache for pending operations and retrieve them for the collaborative editing session. -2. If pending operations exist, apply them to the WordDocument instance using the `UpdateActions` method before converting it to the SFDT format. - -```csharp -public string ImportFile([FromBody] FileInfo param) - { - ..... - ..... - DocumentContent content = new DocumentContent(); - - ..... - //Get source document from database/file system/blob storage - WordDocument document = GetSourceDocument(param.fileName); - ..... - //Get the temporary operations - List actions = await GetPendingOperations(param.fileName, 0, -1); - if(actions!=null && actions.Count > 0) - { - //Apply temporary data to the document. - document.UpdateActions(actions); - } - string json = Newtonsoft.Json.JsonConvert.SerializeObject(document); - content.version = 0; - content.sfdt = json; - return Newtonsoft.Json.JsonConvert.SerializeObject(content); - } - -``` - -#### Update editing records to Redis cache. - -Each edit operation performed by the user is sent to the server and pushed into a Redis list data structure. Each operation is assigned a version number upon insertion into Redis. - -After inserting the record into the server, the position of the current editing operation must be transformed relative to any previous editing operations not yet synced with the client using the `TransformOperation` method to resolve any potential conflicts with the help of the `Operational Transformation` algorithm. - -Once the conflict is resolved, the current operation is broadcast to all connected users within the group. - -```csharp -public async Task UpdateAction([FromBody] ActionInfo param) +{% endhighlight %} +{% endtabs %} + +#### 3. Handle user disconnection + +The following code snippet demonstrates how to disconnect a connection using SignalR. + +{% tabs %} +{% highlight C# tabtitle="C#" %} + +public override async Task OnDisconnectedAsync(Exception ? e) { - try - { - ActionInfo modifiedAction = AddOperationsToCache(param); - //After transformation broadcast changes to all users in the gropu - await _hubContext.Clients.Group(param.RoomName).SendAsync("dataReceived", "action", modifiedAction); - return modifiedAction; + // Get the room name associated with the connection ID + string roomName = await _db.HashGetAsync(CollaborativeEditingHelper.ConnectionIdRoomMappingKey, Context.ConnectionId); + // Remove user from Redis + await _db.HashDeleteAsync(roomName + CollaborativeEditingHelper.UserInfoSuffix, Context.ConnectionId); + + // Fetch all connected users from Redis + var allUsers = await _db.HashGetAllAsync(roomName + CollaborativeEditingHelper.UserInfoSuffix); + var userList = allUsers.Select(u => JsonConvert.DeserializeObject(u.Value)).ToList(); + + // Remove connection to room name mapping + await _db.HashDeleteAsync(CollaborativeEditingHelper.ConnectionIdRoomMappingKey, Context.ConnectionId); + + if (userList.Count == 0) { + // Auto save the pending operations to source document + RedisValue[] pendingOps = await _db.ListRangeAsync(roomName, 0, -1); + if (pendingOps.Length > 0) { + List < ActionInfo > actions = new List(); + // Prepare the message for adding it in background service queue. + foreach(var element in pendingOps) + { + actions.Add(JsonConvert.DeserializeObject(element.ToString())); + } + var message = new SaveInfo + { + Action = actions, + PartialSave = false, + RoomName = roomName, + }; + // Queue the message for background processing and save the operations to source document in background task + _ = saveTaskQueue.QueueBackgroundWorkItemAsync(message); } - catch - { - return null; + } + else { + // Notify remaining clients about the user disconnection + await Clients.Group(roomName).SendAsync("dataReceived", "removeUser", Context.ConnectionId); + } + await base.OnDisconnectedAsync(e); +} + +{% endhighlight %} +{% endtabs %} + +### Step 6: Configure Web API actions for collaborative editing + +Create "CollaborativeEditingController.cs" in the "Controllers" folder. + +This file includes the code snippets that handle server-side interactions for collaborative editing. + +#### Import File + +Used to open DOCX documents, verify the Redis cache for pending operations, and retrieve them for the collaborative editing session. + +The following code snippet demonstrates how to open the document. + +{% tabs %} +{% highlight C# tabtitle="C#" %} + +public async Task < string > ImportFile([FromBody] FileInfo param) +{ + try { + // Create a new instance of DocumentContent to hold the document data + DocumentContent content = new DocumentContent(); + // Retrieve the source document to be edited + // In this case, the file from the wwwroot folder is opened. + // We can modify the code to retrieve the document from a different location or source. + Syncfusion.EJ2.DocumentEditor.WordDocument document = GetSourceDocument(); + // Get the list of pending operations for the document + List < ActionInfo > actions = await GetPendingOperations(param.fileName, 0, -1); + if (actions != null && actions.Count > 0) { + // If there are any pending actions, update the document with these actions + document.UpdateActions(actions); } + // Serialize the updated document to SFDT format + string sfdt = Newtonsoft.Json.JsonConvert.SerializeObject(document); + content.version = 0; + content.sfdt = sfdt; + // Dispose of the document to free resources + document.Dispose(); + // Return the serialized content as a JSON string + return Newtonsoft.Json.JsonConvert.SerializeObject(content); + } + catch { + return null; + } } -private ActionInfo AddOperationsToCache(ActionInfo action) - { - int clientVersion = action.Version; - string insertScript = "-------" - ………… - ………… - ………… - ………… - - IDatabase database = _redisConnection.GetDatabase(); - // Define the keys for Redis operations based on the action's room name - RedisKey[] keys = new RedisKey[] { action.RoomName + CollaborativeEditingHelper.VersionSuffix, action.RoomName, action.RoomName + CollaborativeEditingHelper.RevisionSuffix }; - // Serialize the action and prepare values for the Redis script - RedisValue[] values = new RedisValue[] { JsonConvert.SerializeObject(action), clientVersion.ToString(), CollaborativeEditingHelper.SaveThreshold.ToString() }; - // Execute the Lua script in Redis and store the results - RedisResult[] results = (RedisResult[])await database.ScriptEvaluateAsync(insertScript, keys, values); - - List previousOperations = ((RedisResult[])results[1]).Select(value => JsonConvert.DeserializeObject(value.ToString())).ToList(); - previousOperations.ForEach(op => op.Version = ++clientVersion); - if (previousOperations.Count > 1) - { - // Set the current action to the last operation in the list - action = previousOperations.Last(); - // Transform operations that have not been transformed yet - previousOperations.Where(op => !op.IsTransformed).ToList().ForEach(op => CollaborativeEditingHandler.TransformOperation(op, previousOperations)); - } - action = actions[actions.Count - 1]; - action.Version = updateVersion; - //Return the transformed operation to broadcast it to other clients. - return action; - } - -``` - -#### Add Web API to get previous operation as a backup to get lost operations - -On the client side, messages broadcast using SignalR might be received out of order or lost due to network issues. In such cases, we need a backup method to retrieve missing operations from Redis. By using the following method, we can retrieve all operations performed after the last successful client-synchronized version and return any missing operations to the requesting client. - -```csharp +{% endhighlight %} +{% endtabs %} + +#### Update editing records to Redis cache + +Each edit operation made by the user is sent to the server and pushed into a Redis list data structure. Each operation is assigned a version number upon insertion into Redis. + +The following code snippet demonstrates how the operations are cached and updated. + +{% tabs %} +{% highlight C# tabtitle="C#" %} + +public async Task < ActionInfo > UpdateAction([FromBody] ActionInfo param) +{ + try { + ActionInfo modifiedAction = await AddOperationsToCache(param); + //After transformation broadcast changes to all users in the group + await _hubContext.Clients.Group(param.RoomName).SendAsync("dataReceived", "action", modifiedAction); + return modifiedAction; + } + catch { + return null; + } +} + +private async Task < ActionInfo > AddOperationsToCache(ActionInfo action) +{ + int clientVersion = action.Version; + // Initialize the database connection + IDatabase database = _redisConnection.GetDatabase(); + // Define the keys for Redis operations based on the action's room name + RedisKey[] keys = new RedisKey[] { action.RoomName + CollaborativeEditingHelper.VersionInfoSuffix, action.RoomName, action.RoomName + CollaborativeEditingHelper.RevisionInfoSuffix, action.RoomName + CollaborativeEditingHelper.ActionsToRemoveSuffix }; + // Serialize the action and prepare values for the Redis script + RedisValue[] values = new RedisValue[] { JsonConvert.SerializeObject(action), clientVersion.ToString(), CollaborativeEditingHelper.SaveThreshold.ToString() }; + // Execute the Lua script in Redis and store the results + RedisResult[] results = (RedisResult[])await database.ScriptEvaluateAsync(CollaborativeEditingHelper.InsertScript, keys, values); + + // Parse the version number from the script results + int version = int.Parse(results[0].ToString()); + // Deserialize the list of previous operations from the script results + List previousOperations = ((RedisResult[])results[1]).Select(value => JsonConvert.DeserializeObject(value.ToString())).ToList(); + // Increment the version for each previous operation + previousOperations.ForEach(op => op.Version = ++clientVersion); + + // Check if there are multiple previous operations to determine if transformation is needed + if (previousOperations.Count > 1) { + // Set the current action to the last operation in the list + action = previousOperations.Last(); + // Transform operations that have not been transformed yet + previousOperations.Where(op => !op.IsTransformed).ToList().ForEach(op => CollaborativeEditingHandler.TransformOperation(op, previousOperations)); + } + // Update the action's version and mark it as transformed + action.Version = version; + action.IsTransformed = true; + + // Other code snippets + + // Return the updated action + return action; +} + +{% endhighlight %} +{% endtabs %} + +#### Web API to retrieve previous operations (Backup for lost operations) + +On the client side, messages broadcast using SignalR may be received out of order or lost due to network issues. In such cases, a backup mechanism is required to retrieve missing operations from Redis. + +Using the following method, all operations performed after the last successfully synchronized client version can be retrieved, ensuring that any missing operations are returned to the requesting client. + +The following code snippet demonstrates how to track and retrieve pending operations. + +{% tabs %} +{% highlight C# tabtitle="C#" %} + public async Task GetActionsFromServer(ActionInfo param) { try { // Initialize necessary variables from the parameters and helper class int saveThreshold = CollaborativeEditingHelper.SaveThreshold; - string tableName = param.RoomName; + string roomName = param.RoomName; int lastSyncedVersion = param.Version; int clientVersion = param.Version; @@ -335,7 +466,7 @@ On the client side, messages broadcast using SignalR might be received out of or IDatabase database = _redisConnection.GetDatabase(); // Fetch actions that are effective and pending based on the last synced version - List actions = await GetEffectivePendingVersion(tableName, lastSyncedVersion); + List actions = await GetEffectivePendingVersion(roomName, lastSyncedVersion, database); // Increment the version for each action sequentially actions.ForEach(action => action.Version = ++clientVersion); @@ -350,14 +481,26 @@ On the client side, messages broadcast using SignalR might be received out of or // Serialize the filtered and transformed actions to JSON and return return Newtonsoft.Json.JsonConvert.SerializeObject(actions); } - catch (Exception ex) + catch { // In case of an exception, return an empty JSON object return "{}"; } } -``` -Full version of the code discussed about can be found in below GitHub location. +{% endhighlight %} +{% endtabs %} + +### Step 7: Create helper models and constants + +This step defines Redis key naming conventions, constants, and helper models to ensure consistency and maintainability across the application. It also sets a save threshold of 100 operations, enabling automatic persistence of changes at optimal intervals without affecting performance. To ensure reliability, a Lua script is used to execute Redis operations atomically, preventing conflicts when multiple users edit the document simultaneously. + +For more details about code snippet, please refer this [link](https://github.com/SyncfusionExamples/EJ2-Document-Editor-Collaborative-Editing/blob/master/Server%20side%20with%20distributed%20cache/ASP.NET%20Core/Using%20Redis/Model/CollaborativeEditingHelper.cs) + +### Step 8: Implement background task queue + +This step implements a thread-safe, bounded queue to handle document save requests asynchronously without blocking the main application flow. It uses a channel-based approach with a fixed capacity to efficiently manage concurrent operations. The background service processes each save request by loading the document, applying changes, saving the updated file, and clearing the cache to maintain consistency. + +For more details about this code logic, please refer this [link](https://github.com/SyncfusionExamples/EJ2-Document-Editor-Collaborative-Editing/tree/master/Server%20side%20with%20distributed%20cache/ASP.NET%20Core/Using%20Redis/Services) -GitHub Example: [`Collaborative editing examples`](https://github.com/SyncfusionExamples/EJ2-Document-Editor-Collaborative-Editing) \ No newline at end of file +N> [View sample in GitHub](https://github.com/SyncfusionExamples/EJ2-Document-Editor-Collaborative-Editing/tree/master/Server%20side%20with%20distributed%20cache/ASP.NET%20Core/Using%20Redis). \ No newline at end of file From 51f7e56ac9adab0584a4aff42ecd48f709bfceca Mon Sep 17 00:00:00 2001 From: VijayalakshmiGSF4561 Date: Fri, 7 Aug 2026 20:28:26 +0530 Subject: [PATCH 2/2] 1046505: Updated dialog and content control files in ES5 DOCX Editor --- .../javascript-es5/content-control.md | 30 +++++++++---------- .../Word-Processor/javascript-es5/dialog.md | 30 ++++++++++--------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/Document-Processing/Word/Word-Processor/javascript-es5/content-control.md b/Document-Processing/Word/Word-Processor/javascript-es5/content-control.md index bd88dac611..a4bb796d99 100644 --- a/Document-Processing/Word/Word-Processor/javascript-es5/content-control.md +++ b/Document-Processing/Word/Word-Processor/javascript-es5/content-control.md @@ -1,34 +1,34 @@ --- layout: post -title: Content control in JavaScript (ES5) Document editor control | Syncfusion -description: Learn here all about Content control in Syncfusion JavaScript (ES5) Document editor control of Syncfusion Essential JS 2 and more. +title: Content Control in JavaScript (ES5) DOCX Editor Component | Syncfusion +description: Learn here all about Content control in Syncfusion JavaScript (ES5) Document Editor control of Syncfusion Essential JS 2 and more. platform: document-processing control: Content control documentation: ug domainurl: ##DomainURL## --- -# Content control in JavaScript (ES5) Document editor control +# Content control in JavaScript (ES5) Document Editor Component -[JavaScript DOCX Editor](https://www.syncfusion.com/docx-editor-sdk/javascript-docx-editor) (Document Editor) provides support for inserting, editing content controls +[JavaScript DOCX Editor](https://www.syncfusion.com/docx-editor-sdk/javascript-docx-editor) (Document Editor) provides support for inserting, editing content controls. -Content controls can be categorized based on its occurrence in a document as follows, +Content controls can be categorized based on their occurrence in a document as follows: -InlineContentControl: Among inline content inside, as a child of a paragraph. -BlockContentControl: Among paragraphs and tables, as a child of a Body, HeaderFooter. +**Inline Content Control:** Among inline content within a paragraph as a child element. +**Block Content Control:** Among paragraphs and tables as a child of a Body or Header/Footer. ## Types of Content Controls * Rich Text * Plain Text * Check Box -* Date picker +* Date Picker * Drop-Down List and Combo Box * Picture ## Insert content control -Content control can be inserted using [`insertContentControl`](https://ej2.syncfusion.com/javascript/documentation/api/document-editor/editor/#insertContentControl) method in editor module. +Content control can be inserted using [`insertContentControl`](https://ej2.syncfusion.com/javascript/documentation/api/document-editor/editor#insertContentControl) method in editor module. {% highlight js %} //Insert Rich Text Content Control @@ -44,7 +44,7 @@ container.documentEditor.editor.insertContentControl('Text', 'Hello World'); //Insert CheckBox Content Control container.documentEditor.editor.insertContentControl('CheckBox'); -//Insert CheckBox Content Control with mention checked state +//Insert CheckBox Content Control with a checked state specified container.documentEditor.editor.insertContentControl('CheckBox', true); //Insert ComboBox Content Control @@ -54,7 +54,7 @@ container.documentEditor.editor.insertContentControl('ComboBox', 'One', ['One', //Insert Date Content Control container.documentEditor.editor.insertContentControl('Date'); -//Insert Date Content Control +//Insert Date Content Control with default date container.documentEditor.editor.insertContentControl('Date', '01/01/2024'); //Insert DropDownList Content Control @@ -70,7 +70,7 @@ container.documentEditor.editor.insertContentControl('Picture', 'data:image/png; ## Import content control properties -Content control properties can be set using the [`ContentControlInfo`](https://ej2.syncfusion.com/javascript/documentation/api/document-editor/#importContentControlData) and import it using [`importContentControlData`](https://ej2.syncfusion.com/javascript/documentation/api/document-editor/#importContentControlData) +Content control properties can be set using the [`ContentControlInfo`](https://ej2.syncfusion.com/javascript/documentation/api/document-editor/contentControlInfo) and imported using the [`importContentControlData`](https://ej2.syncfusion.com/javascript/documentation/api/document-editor#importContentControlData) method. {% highlight js %} var data = []; @@ -81,7 +81,7 @@ container.documentEditor.importContentControlData(data); ## Export content control properties -Content control properties can be exported using the [`exportContentControlData`](https://ej2.syncfusion.com/javascript/documentation/api/document-editor/#exportContentControlData) +Content control properties can be exported using the [`exportContentControlData`](https://ej2.syncfusion.com/javascript/documentation/api/document-editor#exportContentControlData) method. {% highlight js %} var contentControlInfos = container.documentEditor.exportContentControlData(); @@ -89,7 +89,7 @@ var contentControlInfos = container.documentEditor.exportContentControlData(); ## Reset content control -Content control properties can be reset using the [`resetContentControlData`](https://ej2.syncfusion.com/javascript/documentation/api/document-editor/#resetContentControlData) +Content control properties can be reset using the [`resetContentControlData`](https://ej2.syncfusion.com/javascript/documentation/api/document-editor#resetContentControlData) method. {% highlight js %} var data = []; @@ -98,4 +98,4 @@ data.push(contentControlData); container.documentEditor.resetContentControlData(data); {% endhighlight %} ->Note: Content control with custom XML mapping of file type WordML is converted as normal Rich Text Content Control to provide lossless round-tripping upon saving. +N> Content controls with custom XML mapping of file type WordML are converted as normal Rich Text Content Control to provide lossless round-tripping upon saving. diff --git a/Document-Processing/Word/Word-Processor/javascript-es5/dialog.md b/Document-Processing/Word/Word-Processor/javascript-es5/dialog.md index d956d63903..78dd768b64 100644 --- a/Document-Processing/Word/Word-Processor/javascript-es5/dialog.md +++ b/Document-Processing/Word/Word-Processor/javascript-es5/dialog.md @@ -1,20 +1,22 @@ --- layout: post -title: Dialog in JavaScript (ES5) Document editor control | Syncfusion -description: Learn here all about Dialog in Syncfusion JavaScript (ES5) Document editor control of Syncfusion Essential JS 2 and more. +title: Dialog in JavaScript (ES5) DOCX Editor Component | Syncfusion +description: Learn here all about Dialog in Syncfusion JavaScript (ES5) Document Editor Component of Syncfusion Essential JS 2 and more. platform: document-processing control: Dialog documentation: ug domainurl: ##DomainURL## --- -# Dialog in JavaScript (ES5) Document editor control +# Dialog in JavaScript (ES5) Document Editor Component -[JavaScript DOCX Editor](https://www.syncfusion.com/docx-editor-sdk/javascript-docx-editor) (Document Editor) provides dialog support to major operations such as insert or edit hyperlink, formatting text, paragraph, style, list and table properties. +[JavaScript DOCX Editor](https://www.syncfusion.com/docx-editor-sdk/javascript-docx-editor) (Document Editor) provides dialog support for major operations such as inserting or editing hyperlinks, and formatting text, paragraph, style, list, and table properties. ## Font Dialog -Font dialog allows you to modify all text properties for selected contents at once such as bold, italic, underline, font size, font color, strikethrough, subscript and superscript. +The Font dialog allows you to modify all text properties for selected contents at once, such as bold, italic, underline, font size, font color, strikethrough, subscript, and superscript. + +>Document Editor features are segregated into individual feature-wise modules. To enable the Font dialog for a document editor instance, set 'enableFontDialog' to true along with the required modules ('enableSelection', 'enableEditor') in the DocumentEditor constructor. Refer to the following example. @@ -31,7 +33,7 @@ Refer to the following example. ## Paragraph dialog -This dialog allows modifying the paragraph formatting for selection at once such as text alignment, indentation, and spacing. +This dialog allows modifying the paragraph formatting for the selection at once, such as text alignment, indentation, and spacing. To open this dialog, refer to the following example. @@ -48,7 +50,7 @@ To open this dialog, refer to the following example. ## Table dialog -This dialog allows creating and inserting a table at cursor position by specifying the required number of rows and columns. +This dialog allows creating and inserting a table at the cursor position by specifying the required number of rows and columns. To open this dialog, refer to the following example. @@ -69,7 +71,7 @@ This dialog allows you to perform the following operations: * View all bookmarks. * Navigate to a bookmark. -* Create a bookmark at current selection. +* Create a bookmark at the current selection. * Delete an existing bookmark. To open this dialog, refer to the following example. @@ -86,7 +88,7 @@ To open this dialog, refer to the following example. ## Hyperlink dialog -This dialog allows editing or inserting a hyperlink at cursor position. +This dialog allows editing or inserting a hyperlink at the cursor position. To open this dialog, refer to the following example. @@ -103,7 +105,7 @@ To open this dialog, refer to the following example. ## Table of contents dialog -This dialog allows creating and inserting table of contents at cursor position. If the table of contents already exists at cursor position, you can customize its properties. +This dialog allows creating and inserting a table of contents at the cursor position. If the table of contents already exists at the cursor position, you can customize its properties. To open this dialog, refer to the following example. @@ -120,7 +122,7 @@ To open this dialog, refer to the following example. ## Styles Dialog -This dialog allows managing the styles in a document. It will display all the styles in the document with options to modify the properties of the existing style or create new style with the help of ‘Style dialog’. Refer to the following example. +This dialog allows managing the styles in a document. It will display all the styles in the document with options to modify the properties of the existing style or create a new style with the help of the 'Style dialog'. Refer to the following example. {% tabs %} {% highlight js tabtitle="index.js" %} @@ -135,7 +137,7 @@ This dialog allows managing the styles in a document. It will display all the st ## Style dialog -You can directly use this dialog for modifying any existing style or add new style by providing the style name. +You can directly use this dialog for modifying any existing style or adding a new style by providing the style name. To open this dialog, refer to the following example. @@ -186,7 +188,7 @@ To open this dialog, refer to the following example. ## Table options dialog -This dialog allows customizing the default cell margins and spacing between each cells of the selected table. +This dialog allows customizing the default cell margins and spacing between each cell of the selected table. To open this dialog, refer to the following example. @@ -220,7 +222,7 @@ To open this dialog, refer to the following example. ## Page setup dialog -This dialog allows customizing margins, size, and layout options for pages of the section. +This dialog allows customizing margins, size, and layout options for the pages of the section. To open this dialog, refer to the following example.