Knockout binding for onbeforeunload

While continuing on my knockoutjs adventure, we recently had a need to prompt the user when they were leaving the page. A common use case for this is when the user is editing something and navigates away from the page before saving any changes. The navigation can be done by using the back button, a link on the page, or closing the browser or tab. Javascript provides a helpful event, the onbeforeunload event.

The typical pattern for using this event is as follows:

1
2
3
4
5
window.onbeforeunload = confirmExit;
function confirmExit()
{

return "You have not saved your work."
}

You set onbeforeunload to a function that returns a string. The string will show up in a dialog box like this when navigating away:

This has been around for a while. But I wanted to be able to do this in a way that used the MVVM pattern in Knockout. I wanted to have this controlled by data binding between my markup and viewmodel.

So I created the beforeUnloadText custom binding.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Author: Tim Larson @codethug
ko.bindingHandlers.beforeUnloadText = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
if (window.onbeforeunload == null) {
window.onbeforeunload = function(){
var value = valueAccessor();
var promptText = ko.utils.unwrapObservable(value);
if (typeof promptText == "undefined" || promptText == null) {
// Return nothing. This will cause the prompt not to appear
} else {
if (promptText != null && typeof promptText != "string") {
var err = "Error: beforeUnloadText binding must be " +
"against a string or string observable. " +
"Binding was done against a " + typeof promptText;
console.log(err);
console.log(promptText);
// By returning the error string, it will display in the
// onbeforeunload dialog box to the user. We could throw an
// exception here, but the page would unload and the
// exception would be lost.
return err;
}
return promptText;
}
};

} else {
var err = "onbeforeupload has already been set";
throw new Error(err);
}
}
};

And here is how you use it (jsFiddle demo)

1
2
3
4
5
6
<body data-bind="beforeUnloadText: beforeUnloadPrompt">
<input type="text" value="Hello World!"></input>
<button data-bind="click:Save">Save</button>

[Google](http://www.google.com)
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var ViewModel = function() {

var self = this;

self.Save = function() {
// Setting the prompt to null will cause the
// prompt to not appear when the user is
// navigation away from the page
self.beforeUnloadPrompt(null);
}

self.beforeUnloadPrompt = ko.observable("You forgot to save your work");
};

ko.applyBindings(new ViewModel());

The idea is that the user is editing something, and we want to prompt them to save their work before they leave the page. However, once they have saved their work, there is no further need for a prompt, so the prompt is disabled by setting it to null.