0

So what I'm trying to do, and I've made a small example project here, is when a user is editing a cell in a table, the cell disables for everyone else on that page. All good. Here I set it up so that when the user enters a cell, it disables for everyone else, and on blur/exiting the cell, it clears. Now the problem is, if a person is in a cell, it disables on other people's screens. But if anyone refreshes or if a new user goes on the page, it is not disabled, even if the main user is still in that cell. Is there a way with SignalR to know that someone is using a particular cell while he's using it, and not just when he enters/exits the cell?

C# Code:

    public class ChatHub : Hub
    {
        public void Send(string name, string message, bool boolean)
        {
            // Call the broadcastMessage method to update clients.
            Clients.Others.broadcastMessage(name, message, boolean);
        }
    }

HTML Code:

<table id="table">
    <thead>
        <tr>
            <th>HeaderOne</th>
            <th>HeaderTwo</th>
            <th>HeaderThree</th>
        </tr>
    </thead>
    <tbody>
        @for(int i = 0; i < 3; i++)
        {
        <tr>
            <td><input class="tdInput" /></td>
            <td><input class="tdInput" /></td>
            <td><input class="tdInput" /></td>
        </tr>
        }
    </tbody>
</table>

Javascript Code:

        $(function () {
            var conn = $.connection.chatHub;

            conn.client.broadcastMessage = function (col, row, boolean) {
                var cell = $("#table tr:eq(" + row + ") td:eq(" + col + ")");
                cell.find("input").prop('disabled', boolean);
            };

            $.connection.hub.start().done(function () {
                $(".tdInput").on('focus', function () {
                    var col = $(this).parent().index();
                    var row = $(this).closest('tr').index() + 1;
                    conn.server.send(col, row, true);
                });
                $(".tdInput").on('blur', function () {
                    var col = $(this).parent().index();
                    var row = $(this).closest('tr').index() + 1;
                    conn.server.send(col, row, false);
                });
            });
        });

Here's a simple implementation of option #2 from Christoph's comment:

In connection.hub.start(), add:

conn.server.refresh();

In the JS:

conn.client.resendStatus = function () {
    if ($('input:focus').length > 0) {
        var focused = $(":focus");
        var col = focused.parent().index();
        var row = focused.closest('tr').index() + 1;
        conn.server.send(col, row, true);
    }
};

In the Hub:

public void Refresh()
{
    Clients.Others.resendStatus();
}
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
James Verdune
  • 91
  • 1
  • 6

2 Answers2

1

In my opinion, there are two possible solutions:

  1. Track all locked cells on the server and mark them as locked when a user loads a page.
  2. Upon loading the page, send out a broadcast to all clients, telling them to resend their current editing state.

The second option is easier to handle, since you don't need any state tracking, and especially no timeouts. You will, however, have a short period after loading the page, where the cells edited by others are temporarily unlocked.

Christoph Herold
  • 1,799
  • 13
  • 18
  • Thank you! This helped a lot. I have edited my main post with code I think implements number 2. – James Verdune Apr 24 '19 at 02:54
  • I had another question, the program works, if user 1 enters a cell, it is disabled on user 2's screen. And when user 1 exits, it is cleared in user 2's screen. But if user 1 enters a screen and then refreshes or closes the browser, it is permanently disabled on user 2's screen until they refresh. I'm not sure how to get around this. Should I implement something like Garth's idea or should I clear all cells when exiting a page? – James Verdune Apr 24 '19 at 22:51
  • I just did a manual blur on the current focused element before leaving the page with beforeunload. I don't know if this is a good way to do it! – James Verdune Apr 24 '19 at 23:09
  • This is actually not the only case, when this might occur. If the user loses internet connection, you will get a similar effect, but handling before unload will not help. Instead, I would suggest handling the disconnect event in the SignalR hub: https://stackoverflow.com/questions/9789335/signalr-client-disconnection. On a client's disconnect, you could inform all clients to free all cells and resubmit their focus, so all cells are back in the state they should be in. On reconnect, the same thing may make sense, but you should handle collisions, in case another client has focused the same cell – Christoph Herold Apr 25 '19 at 06:14
  • I feel bad I'm asking to many questions, but another thing came up I have no idea how to fix. If I am changing many things at once, on my main project, it sometimes takes a bit of time to load. So if user 1 changes 10 cells at once in quick succession, and while the ajax is running, user 2 refreshes the page, then user 2's data will be old until they refresh again because I think the controller action runs before the ajax is done updating the database. It also happens if user 1 refreshes himself during the ajax calls. – James Verdune Apr 26 '19 at 17:23
  • I also think there may be a window where while loading the page, the signalR stuff hasn't loaded yet so it doesn't get updated with the appropriate data from another page's updates. – James Verdune Apr 26 '19 at 17:24
  • Since the data is kept on the server, it may make sense to at least keep a timestamp of the latest change on the server. Then, the clients could periodically send their data's timestamp to the server. The server can then compare that timestamp to the latest change, and if it has newer data, it can send the missing updates to the client. – Christoph Herold May 01 '19 at 22:19
1

You can add setTimeout, this will send lock/status every second until the user is in the cell.

$(function () {
    var conn = $.connection.chatHub;

    conn.client.broadcastMessage = function (col, row, boolean) {
        var cell = $("#table tr:eq(" + row + ") td:eq(" + col + ")");
        cell.find("input").prop('disabled', boolean);
    };

    $.connection.hub.start().done(function () {
        var flag=false;
        $(".tdInput").on('focus', function () {
            flag=true;
            while(flag)
            {
                setTimeout(function(){
                    var col = $(this).parent().index();
                    var row = $(this).closest('tr').index() + 1;
                    conn.server.send(col, row, true);
                },1000);
            }
        });

        $(".tdInput").on('blur', function () {
            flag=false;
            var col = $(this).parent().index();
            var row = $(this).closest('tr').index() + 1;
            conn.server.send(col, row, false);
        });
    });
});
GKG4
  • 440
  • 1
  • 4
  • 16
  • But, won't this place heavier load on the server than necessary? It will continuously notify all other clients every second. As long as no one reloads the page, this seems like a lot of overhead. – Christoph Herold Apr 24 '19 at 05:50
  • Yes it will, the only other option is from you only, to store on server :) – GKG4 Apr 24 '19 at 09:10