Warning the user when the page is modified

In many applications, the last commit wins principle is how conflicts are being resolved: when two users edit the same resource at the same time, the last one to press the save button overwrites any previous changes.

There are ways around this issue, like entity versioning or extensive literature on the topic of distributed consensus. Nevertheless, let’s stick to a simple solution and see how we can notify the user when a change has been made so that at the very least (s)he can get a chance to deal with the situation. As soon as the content has been changed in the database, the user can decide what is the best course of action to take: overwrite or reload.

To start with, we need to add an alert alert-warning message div in the page. But we want it to show-up only when the pageModified scope variable is set to true.

  1. <div class="col-md-12">
  2. <div class="alert alert-warning ng-class:{'invisible': !pageModified};" role="alert">
  3. The page has been modified by another user.
  4. <a href="#" ng-click="load(pageId)">Reload</a>
  5. </div>
  6. </div>

Now, pageModified must be set to true when this page is saved. Let’s register an event bus handler for the page.saved address:

  1. var clientUuid = generateUUID(); (1)
  2. eb.onopen = function () {
  3. eb.registerHandler("page.saved", function (error, message) { (2)
  4. if (message.body (3)
  5. && $scope.pageId === message.body.id (4)
  6. && clientUuid !== message.body.client) { (5)
  7. $scope.$apply(function () { (6)
  8. $scope.pageModified = true; (7)
  9. });
  10. }
  11. });
  12. };
  1. We do not want to print the warning if we modified the content ourselves so we need a client identifier.

  2. The callback will be invoked when a message is received on the page.saved address.

  3. Check that the body is not empty.

  4. Make sure this event is related to the current wiki page.

  5. Check that we are not the origin of the changes.

  6. Since the event bus client is not managed by AngularJS, $scope.$apply wraps the callback to perform proper scope life cycle.

  7. Set pageModified to true.

Eventually we have to push messages when the content of a page is saved in the database:

  1. dbService.rxSavePage(id, page.getString("markdown"))
  2. .doOnComplete(() -> { (1)
  3. JsonObject event = new JsonObject()
  4. .put("id", id) (2)
  5. .put("client", page.getString("client")); (3)
  6. vertx.eventBus().publish("page.saved", event); (4)
  7. })
  8. .subscribe(() -> apiResponse(context, 200, null, null), t -> apiFailure(context, t));
  1. rxSavePage returns a Single<Void> object. On success (i.e. no database failure) we publish an event.

  2. The message contains the page identifier.

  3. The message contains the client identifier.

  4. The event is published on the page.saved address.

If we open the application in two tabs inside the same browser (or different browsers), select the same page on both, and update the content in one, the warning message is printed:

edited warning

We could easily leverage the SockJS bridge for other purposes, like showing how many users are currently on a given page, supporting live comments in a chat box, etc. The key point is that both the server and the client sides share the same programming model by message passing over the event-bus.