Hack #33. Confirm Before Closing Modified Pages

Don't lose your changes in web forms when you accidentally close your browser window.

It's becoming more and more common for complex tasks to be performed on the Web. Of course, there is web-based email, and weblogging and wikis are also popular. Message boards are a great way to form a community, and there are many more online applications that are used every day by many people. One of the drawbacks, though, in not using a normal desktop program is losing that prompt, "Are you sure you wish to exit? You have unsaved work."

With Greasemonkey, we can restore this functionality and save the hassle caused by closing a window and losing your unsubmitted form data.

The Code

This script uses the power of the onbeforeunload event to catch the browser just before it moves off the page. When the page loads, the script finds all <textarea> elements and records the initial value of each one. Then, we register an onbeforeunload event handler to call our function that checks the current value of each <textarea>. If the current value differs from the previously recorded value, we display a dialog box to give the user the chance to save his work.

To make sure we don't interfere when the user actually submits the form, the script attaches an onsubmit event handler to all forms. This handler sets an internal flag to record that the user submitted the form and that we should not bother checking for unsubmitted data, since the user just submitted it!

Save the following user script as protect-textarea.user.js:

	// ==UserScript==
	// @name		  Protect Textarea
	// @namespace	  http://www.arantius.com/
	// @description	  Confirm before closing a web page with modified textareas
	// @include		  *
	// @exclude		  http*://*mail.google.com/*
	// ==/UserScript==

	// based on code by Anthony Lieuallen
	// and included here with his gracious permission
	// http://www.arantius.com/article/arantius/protect+textarea/

	//indicator to skip handler because the unload is caused by form submission
	var _pt_skip=false;
	var real_submit = null;

	//find all textarea elements and record their original value
	var els=document.evaluate('//textarea',
		document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	for (var el=null, i=0; el=els.snapshotItem(i); i++) {
		var real_el = el.wrappedJSObject || el;
		real_el._pt_orig_value=el.value;
	}

	//if i>0 we found textareas, so do the rest
	if (i == 0) { return; }
	
	//this function handles the case where we are submitting the form,
	//in this case, we do not want to bother the user about losing data
	var handleSubmit = function() {
		_pt_skip=true;
		return real_submit();
	}
		
	//this function will handle the event when the page is unloaded and
	//check to see if any textareas have been modified
	var handleUnload = function() {
		if (_pt_skip) { return; }
		var els=document.getElementsByTagName('textarea');
		for (var el=null, i=0; el=els[i]; i++) {
			var real_el = el.wrappedJSObject || el;
			if (real_el._pt_orig_value!=el.value) {
				return 'You have modified a textarea, and have not ' +
				'submitted the form.';
				}
			}
		}
		
		// trap form submit to set flag
		real_submit = HTMLFormElement.prototype.submit;
		HTMLFormElement.prototype.submit = handleSubmit;
		window.addEventListener('submit', handleSubmit, true);

		// trap unload to check for unmodified textareas
		unsafeWindow.onbeforeunload = handleUnload;

Running the Hack

After installing the user script (Tools Install This User Script), go to http://www.iupui.edu/~webtrain/tutorials/forms_sample.html . At the bottom of the form is a large box for entering additional comments. Enter some text, and then try to close the browser window. You will see a confirmation dialog, as shown in Figure 4-5.

Unsaved changes dialog

Figure 4-5. Unsaved changes dialog

If you press Cancel, you'll stay right where you are and can submit the form. If you press OK, the browser window will close.

Hacking the Hack

This hack can easily be extended to monitor all form fields, not just <textarea> elements. Instead of using document.getElementsByTagName to find only <textarea> elements, we can use an XPath expression to look for <input> elements, too.

	var els=document.evaluate('//textarea|//input',
	document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	for (var el=null, i=0; el=els.snapshotItem(i); i++) {
		…
	}

This will cause the script to protect all form fields containing text boxes, checkboxes, and radio buttons. It will not handle drop-down select boxes, though, because they function differently. It's more complicated than just adding //select to the XPath expression and examining the selectedIndex attribute of the <select> element, because some <select> boxes have multiple selections.

Anthony Lieuallen

Get Greasemonkey Hacks now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.