Confirm before navigating away from unsaved edits, using javascript and Backbone

Here’s an approach to putting up a confirmation dialog whenever the user navigates away from a view containing unsaved edits, so that the user doesn’t unintentionally lose those edits.

A working example is available on github.

Use Case

My use case is as follows. Our application is a single page application written in JavaScript that makes use of Backbone routing to navigate between major tasks. Each of those major tasks is implemented by one or more view managers (basically Backbone Views which implement a view container and controller for multiple views that work on a defined set of data). We allow the user to make a set of edits and explicitly decide when to save them to the server, rather than push every edit to the server as it happens. We don’t want the user to accidentally lose those edits before they are saved. Some of the ways that could occur are:

  • User selects an in-application navigation that triggers Backbone routing
  • User refreshes URL or types a new URL
  • User closes the browser window or tab


The basic approach I took was inspired by Kevin Yao’s post, Page Closing Confirmation with jQuery Custom Event. I liked his idea of listening for the window ‘beforeunload’ event and triggering a custom event that any interested party (in our case, the view managers) could respond to. However, I wanted to use Backbone for the events rather than plain JQuery, and I needed to catch in-application navigation as well.

Catching the ‘beforeunload’ event

The following code is called as part of our application overall setup.

$(window).bind('beforeunload', function(){
  var status = {
    unsavedEdits: false

  Backbone.trigger('myapp:navigate', status);
  if (status.unsavedEdits) {
    return status.msg || "There are unsaved edits. Continuing will discard these edits.";

Line 1 listens for the ‘beforeunload’ event which happens when the user closes the tab, reloads or enters a new URL.

Lines 2-5 set up a ‘status’ object in the event will be used by the view managers to communicate if they have unsaved edits, and to provide a message to be displayed.

Line 6 triggers a custom event ‘myapp:navigate’, using the Backbone object as a global event bus.

Lines 7-9 checks whether any of the ‘myapp:navigate’ handlers have set the status.unsavedEdits flag. If so, it returns a non-empty string, which the browser may or may not choose to display (Firefox does not – see WindowEventHandlers.onbeforeunload).

Communicating if there are unsaved edits

The following code is in the view managers:

this.listenTo(Backbone, "myapp:navigate", function(status){
  if (this.unsavedEdits()){
    status.unsavedEdits = true;
    status.msg = "There are unsaved edits to XXX. Continuing will discard these edits.";

Line 1 listens for the custom ‘myapp:navigate’ event on the global event bus. When it occurs, lines 2-5 check whether there are unsaved edits, and if so, set the status.unsavedEdits flag, and optionally a message to be displayed.

Catching in-application navigation

To catch in-application navigation, I added code to the router to check for unsaved edits before routing to a new URL. Arguably, it may be preferable to intercept any navigation events before they reach the router. However, I opted to handle the logic in a single place, rather than adding logic wherever there is navigation.

The following code is in a routine that gets triggered for any route change resulting in a change of view managers (i.e. where there is a potential for unsaved edits to be lost).

var status = { 
  unsavedEdits: false
Backbone.trigger('myapp:navigate', status);

if(status.unsavedEdits) {
  var confirmed = window.confirm(status.msg || "There are unsaved edits. Continuing will discard these edits.");
  if (!confirmed){
    var history = this.history;
    if (history.length > 1){
      this.navigate(history[history.length-2], {replace: true});							

Lines 1-6 are the same as before… set up a status object, trigger a custom ‘myapp:navigate” event on the global event bus, and check whether the unsavedEdits flag has been set.

If the flag is set, line 7 uses a windows confirmation dialog to warn the user and get their response.

If the user does not want to proceed, we now have the issue that the URL has been added to the history and needs to be removed (or else subsequent navigation to that URL may be disabled). Lines 9-12 achieve this by replacing the newly added URL with its predecessor, using navigate with the replace option.