Giter Club home page Giter Club logo

jquery.dirtyforms's Introduction

jquery-dirtyforms MyGet Build Status

Dirty Forms Logo

Dirty Forms is a jQuery plugin to help prevent users from losing data when editing forms.

Dirty Forms will alert a user when they attempt to leave a page without submitting a form they have entered data into. It alerts them in a modal popup box, and also falls back to the browser's default onBeforeUnload handler for events outside the scope of the document such as, but not limited to, page refreshes and browser navigation buttons.

Oh, and it's pretty easy to use.

$('form').dirtyForms();

Existing solutions were not flexible enough, so Dirty Forms was created to support a wider range of scenarios including support for dynamically added inputs and forms, TinyMCE and CKEditor rich text editor "dirtyness" support, custom widget "dirtyness" support, interoperability with any dialog framework (or the browser's built-in dialog), a "stash" that can be used in cases where dialogs don't have stacking support and you want to have a form inside of a dialog, and correct handling of events to refire when the user decides to proceed to an off-page destination.

Features

  • NO dependencies (except for jQuery of course).
  • Supports multiple forms.
  • Works on forms of any size.
  • Wide browser support.
  • Advanced state management - tracks the original values of fields so they are "clean" when reset to the original value.
  • Works with your existing dialog framework for the best user experience (optional).
  • Falls back to the browser's dialog (if the browser supports it).
  • Pluggable helper system that reads and updates the dirty state of custom widgets and common rich text editor frameworks (optional).
  • Event handler customization (for IFrame support).
  • Small size (about 2.5 KB gzipped).
  • Universal Module Definition (UMD) support for AMD, Node/CommonJS, Browserify, etc.
  • Hosted on jsDelivr CDN for easy combining of modules into a single HTTP request.

Supported Browsers

  • IE 8+
  • Google Chrome 1+
  • Firefox 4+
  • Safari 5+

Prerequisites

If you are using a Package Manager, these dependencies will be installed automatically, but depending on your environment you may still need to add references to them manually.

Download & Installation

Dirty Forms should be the last jQuery plugin included in the page, as it needs to be the last bound handler in the event stack (except of course for Dirty Forms helpers and dialog modules).

There are several different ways to get the code. Some examples below:

CDN

Dirty Forms is available over jsDelivr CDN and can directly be included on every page.

<script type="text/javascript" src="//cdn.jsdelivr.net/jquery.dirtyforms/2.0.0/jquery.dirtyforms.min.js"></script>

jsDelivr also supports on-the-fly concatenation of files, so you can reference only 1 URL to get jQuery and jquery.dirtyforms in one request.

<script type="text/javascript" src="//cdn.jsdelivr.net/g/[email protected],[email protected]"></script>

Self-Hosted

Download and save one of two available files to include Dirty Forms to your page, either the latest distribution or the latest minified version.

<script type="text/javascript" src="jquery.dirtyforms.min.js"></script>

You can also conveniently get all of the latest Dirty Forms files in one Zip Download.

Package Managers

Dirty Forms is even available through NPM, Bower, and NuGet. Just use one of the following commands below to install the helper, including all dependencies.

NPM version Bower version NuGet version

NPM

// NPM
$ npm install jquery.dirtyforms

// Bower
$ bower install jquery.dirtyforms

// NuGet
PM> Install-Package jquery.dirtyforms

SourceMaps

A SourceMap file is also available via CDN or your favorite package manager.

CDN

<script type="text/javascript" src="//cdn.jsdelivr.net/jquery.dirtyforms/2.0.0/jquery.dirtyforms.min.js.map"></script>

Package Managers

NPM, Bower, and NuGet will install the SourceMap file into the destination directory.

jquery.dirtyforms.min.js.map

Usage

$(function() {

	// Enable for all forms.
	$('form').dirtyForms();

	// Enable for just forms of class 'sodirty'.
	$('form.sodirty').dirtyForms();

	// Customize the title and message.
	// Note that title is not supported by browser dialogs, so you should 
	// only set it if you are using a custom dialog or dialog module.
	$('form').dirtyForms({ 
		dialog: { title: 'Wait!' }, 
		message: 'You forgot to save your details. If you leave now, they will be lost forever.' 
	});

	// Enable Debugging (non-minified file only).
	$('form').dirtyForms({ debug: true });

	// Check if anything inside a div with CSS class watch is dirty.
	if ($('div.watch').dirtyForms('isDirty')) {
		// There was something dirty inside of the div
	}

	// Select all forms that are dirty, and set them clean.
	// This will make them forget the current dirty state and any changes
	// after this call will make the form dirty again.
	$('form:dirty').dirtyForms('setClean');

	// Rescan to sync the dirty state with any dynamically added forms/fields
	// or changes to the ignore state. This comes in handy when styling fields
	// with CSS that are dirty.
	$('form').dirtyForms('rescan');

	// Select all forms that are listening for changes.
	$('form:dirtylistening');

	// Enable/disable the reset and submit buttons when the state transitions
	// between dirty and clean. You will need to first set the initial button
	// state to disabled (either in JavaScript or by setting the attributes in HTML).
	$('form').find('[type="reset"],[type="submit"]').attr('disabled', 'disabled');
	$('form').on('dirty.dirtyforms clean.dirtyforms', function (ev) {
        var $form = $(ev.target);
        var $submitResetButtons = $form.find('[type="reset"],[type="submit"]');
        if (ev.type === 'dirty') {
            $submitResetButtons.removeAttr('disabled');
        } else {
            $submitResetButtons.attr('disabled', 'disabled');
        }
    });

	// Add a form dynamically and begin tracking it.
	var $form = $('<form action="/" id="watched-form" method="post">' +
		'<input id="inputa" type="text" />' +
		'<button id="submita" type="submit" value="Submit">Submit</button>' +
		'</form>');
	$('body').append($form);
	$form.dirtyForms();

});

Setting a Form Dirty Manually

NOTE: This example shows how to set the state of a form dirty. However, it is generally recommended that each of the widgets (whether built by you or a 3rd party) have their own dirty/clean state. Instead of "pushing" the dirty state into Dirty Forms, you should aim to allow Dirty Forms to read the dirty state of each widget using one or more custom helpers. You can then opt to make each widget "undo" its dirty state when the user reverts their edits in the widget, which will provide a better user experience and make it similar to the rest of Dirty Forms state management behavior.

In version 1.x, there was a setDirty method that allowed you to set the form dirty manually. In version 2.x, this method has been removed since this functionality is effectively a duplication of what a custom helper does. It is now recommended that you create a custom helper to set a form dirty manually. You will need to attach a piece of data to the form to track whether it is dirty. In this example, we use a CSS class mydirty for this purpose. Note that this class should not be the same value as $.DirtyForms.dirtyClass because Dirty Forms automatically removes this value when the user undoes their edits (which is probably not the behavior you are after).

$('#watched-form').dirtyForms({
    helpers:
        [
            {
                isDirty: function ($node, index) {
                    if ($node.is('form')) {
                        return $node.hasClass('mydirty');
                    }
                }
            }
        ]
});

You can then use the jQuery addClass and removeClass methods to get the same behavior as version 1.x.

function setDirty() {
    $('#watched-form').addClass('mydirty');
}

function setClean() {
    $('#watched-form').removeClass('mydirty');
}

Or, you can improve the behavior of setClean by instead implementing the setClean helper method as needed by your application so it is automatically reset to clean when the dirtyForms('setClean') method is called.

$('#watched-form').dirtyForms({
    helpers:
        [
            {
                isDirty: function ($node, index) {
                    if ($node.is('form')) {
                        return $node.hasClass('mydirty');
                    }
                },
				setClean: function($node, index, excludeIgnored) {
					if ($node.is('form')) {
						$node.removeClass('mydirty');
					}
				}
            }
        ]
});

In that case, calling $('#watched-form').dirtyForms('setClean') will set both the fields/forms that are tracked by Dirty Forms and your custom mydirty class to a non-dirty state in one method call instead of having to call your custom setClean method separately.

NOTE: dirtyForms('setClean') is called automatically when a reset button is clicked by the user on a particular form.

Ignoring Things

Set the ignoreSelector option to ignore specific fields, anchors, or buttons.

$('form').dirtyForms({ ignoreSelector: 'a.ignore-me' });

Alternatively, add the value of $.DirtyForms.ignoreClass to any elements you wish to ignore, and Dirty Forms will ignore them.

$('#ignored-element').addClass($.DirtyForms.ignoreClass);

If you want to ignore more than one element at a time, you can add the value of $.DirtyForms.ignoreClass (with the default value dirtyignore) to a containing element.

<div class="dirtyignore">

    <!-- Everything here will be ignored - anchor, input, textarea, and select -->

</div>

And of course that means if you ignore the topmost element on the page, you will effectively disable Dirty Forms.

$('html').addClass($.DirtyForms.ignoreClass);

You can re-enable elements so Dirty Forms watches them again simply by removing the ignore class.

$('html').removeClass($.DirtyForms.ignoreClass);

Default Anchor Ignoring Behavior

The default behavior ignores anchor tags under the following scenarios. If you want an anchor to be ignored for any other purpose, you should use the ignoreClasss either on the anchor itself or an ancestor container. The default behavior can be changed by overriding the event handling (see Event Handler Customization).

target="_blank"

If the target is a blank browser window, we assume that the page is not going to reload because (at least in theory) a new browser or tab will open. Note that if the target attribute is changed dynamically, the anchor tag will automatically be un-ignored.

<a href="http://www.google.com" target="_blank">Go to Google</a>

onClick return false;

If the onClick event returns false, the click will be ignored by Dirty Forms.

<a href="http://www.google.com" onclick="alert('This click is ignored');return false;">Go to Google</a>

NOTE: Due to a bug in jQuery < 1.5, Dirty Forms will not ignore anchors in this case, so you will need to use the ignoreClass instead.

.preventDefault() is Called

If the click event handler calls .preventDefault() on its event, Dirty Forms will ignore the click.

<a id="google-link" href="http://www.google.com">Go to Google</a>
$('#google-link').click(function (event) {
    // Cancel the default browser action
	event.preventDefault();
});

NOTE: Anchor tags with no HREF were ignored in previous versions of Dirty Forms, but now these tags are included by default. We can't make any assumptions about what an anchor tag does whether or not it has an HREF tag. If you want to ignore them by default, add the ignoreSelector: 'a:not([href])'.

Options

The following options are available to set during declaration of .dirtyForms() or alternatively via $.DirtyForms.OPTIONNAME = OPTIONVALUE or get via OPTIONVALUE = $.DirtyForms.OPTIONNAME.

$('form').dirtyForms({ message: 'Doh! You forgot to save.' });

// OR

$.DirtyForms.message = 'Doh! You forgot to save.';
Name Type Default Description
message string You've made changes on this page which aren't saved. If you leave you will lose these changes. Sets the message of the dialog (whether JavaScript/CSS dialog or the browser's built in dialog - note that some browsers do not show this message).
dirtyClass string dirty The class applied to elements and forms when they're considered dirty. Note you can use this to style the elements to make them stand out if they are dirty (or for debugging).
listeningClass string dirtylisten The class applied to elements that are having their inputs monitored for change.
ignoreClass string dirtyignore The CSS class applied to elements that you wish to be ignored by Dirty Forms. This class can also be applied to container elements (such as <div> or <form>) to ignore every element within the container.
ignoreSelector string '' A jQuery selector that can be set to ignore specific elements.
fieldSelector string input:not([type='button'],[type='image'],[type='submit'],[type='reset'],[type='file'],[type='search']),select,textarea A jQuery selector indicating which input fields to include in the scan.
helpers Array [] An array for helper objects. See Helpers below.
dialog object false An object that will be used to fire the JavaScript/CSS dialog. A false setting indicates to always use the browser's dialog. See Dialogs below.
debug boolean false Set to true to log messages to the console (or firebug). If your browser doesn't support this, there will be alerts instead.

NOTE: debug is not available in the minified version. If you need to turn this on, be sure to switch the file reference to the uncompressed jquery.dirtyforms.js file.

Public Methods

.dirtyForms( options )

Initializes Dirty Forms, overrides any of the default options, and stores the original values of the fields of all of the forms that match or are descendants of the selector. The scan.dirtyforms event is triggered for each form that is found. To scan all forms, simply use the 'form' selector.

$('form').dirtyForms();
options (Optional)

An options object.

$('form').dirtyForms({ message: 'You better save first.', dirtyClass: 'sooooooo-dirty'});

For a list of available options, see Options.

.dirtyForms( 'isDirty', excludeHelpers )

Returns true if any non-ignored elements that match or are descendants of the selector are dirty.

excludeHelpers (Optional)

Set to true to exclude helpers from the operation. The default is false.

.dirtyForms( 'setClean', excludeIgnored, excludeHelpers )

Marks all fields that match the selector (or are descendants of the selector) clean. Also calls the setClean method of all nested helpers. In other words, removes the dirtyClass and resets the state so any changes from the current point will cause the form to be dirty. If the operation marks all elements within a form clean, it will also mark the form clean even if it is not included in the selector.

excludeIgnored (Optional)

Set to true to exclude ignored fields from the operation. The default is false.

excludeHelpers (Optional)

Set to true to exclude helpers from the operation. The default is false.

.dirtyForms( 'rescan', excludeIgnored, excludeHelpers )

Scans all fields that match the selector (or are descendants of the selector) and stores their original values of any dynamically added fields. Also calls the rescan method of all nested helpers. Ignores any original values that had been set previously during prior scans or the .dirtyForms('setClean') method. Also synchronizes the dirty state of fields with any changes to the ignore status, which can be helpful if you are styling elements differently if they have the dirty class.

excludeIgnored (Optional)

Set to true to exclude ignored fields from the operation. The default is false.

excludeHelpers (Optional)

Set to true to exclude helpers from the operation. The default is false.

Events

Form Events

Simply bind a function to any of these hooks to respond to the corresponding event. The form that triggered the event can be accessed through the event.target property.

$(document).bind('dirty.dirtyforms', function(event) { 
	// Access the form that triggered the event
    var $form = $(event.target);
});

// Or, bind to a specific form to listen for the event
$('form#my-form').bind('dirty.dirtyforms', function () {
	// Access the form that triggered the event
    var $form = $(this);
});
Name Parameters Description
dirty.dirtyforms event Raised when a form changes from clean state to dirty state.
clean.dirtyforms event Raised when a form changes from dirty state to clean state. This may happen when the last element within the form is marked clean using $('#element-id').dirtyForms('setClean') or when the user undoes all of their edits.
scan.dirtyforms event Raised after the form is scanned for new fields (whether during initialization or when subsequently calling .dirtyForms()).
rescan.dirtyforms event Raised after the form is rescanned for new fields (when calling .dirtyForms('rescan')).
setclean.dirtyforms event Raised after the .dirtyForms('setClean')) method is called or when the user clicks the reset button.

Document Events

Simply bind a function to any of these hooks to respond to the corresponding event.

$(document).bind('proceed.dirtyforms', function() { 
    // ...stuff to do before proceeding to the link/button that was clicked... 
});
Name Parameters Description
stay.dirtyforms event Raised when the choice.commit() method is called with choice.proceed set to false before running any stay actions. In other words, called immediately when the user makes a stay choice.
afterstay.dirtyforms event Raised when the choice.commit() method is called with choice.proceed set to false after running any stay actions.
proceed.dirtyforms event, refireEvent Raised when the choice.commit() method is called with choice.proceed set to true before running any proceed actions. In other words, called immediately when the user makes a proceed choice. Passes the event that will be re-fired as the second parameter. Useful if you need to do things like save data back to fields which is normally part of event propagation - ala TinyMCE.
defer.dirtyforms event Raised prior to showing the dialog to the user (whether a custom dialog, or the browser's dialog). Useful for accessing elements on the page prior to showing the dialog.
beforeunload.dirtyforms event Non-cancelable event, raised prior leaving the page which may happen either as result of user selection if forms were dirty or due to a normal page exit of no changes were made.
bind.dirtyforms event Raised before event binding (the first time that .dirtyForms() is called), allowing customization of event handlers. Useful to interoperate with IFrames. See Customizing Event Handlers for details.

Selectors

Name Description
:dirty Selects all non-ignored elements with the dirty class attached. For example, form:dirty will select all non-ignored forms that are currently dirty.
:dirtylistening Selects all elements that has the listening class attached. This will be all forms that are currently listening for changes (provided all of them had the class added by calling .dirtyForms()).
:dirtyignored Selects all elements that are currently ignored by Dirty Forms through the ignoreSelector, a helper's ignoreSelector or an element or a descendant of an element that has the $.DirtyForms.ignoreClass applied. Useful for checking whether an element is ignored ($('.my-element').is(':dirtyignored')).

Helpers

Dirty Forms was created because the similar plugins that existed were not flexible enough. To provide more flexibility a basic helper framework has been added. With this system you can add in new helper objects which will provide additional ability to check for whether a form is dirty or not.

This is useful for custom widgets or when you're using 3rd party frameworks such as with TinyMCE or CKEditor.

Available Helpers

Custom Helpers

Helpers can be created by implementing and then pushing the helper to the $.DirtyForms.helpers array.

$.DirtyForms.helpers.push(myHelper);
Members

isDirty( $node, index ) (Optional)

Should return the dirty status of the helper. You can use jQuery to select all of the helpers within the node and test their dirty status.

isDirty: function ($node) {
	var isDirty = false;
	
	// Search for all tinymce elements (either the current node or any nested elements).
	$node.filter(':tinymce').add($node.find(':tinymce')).each(function () {

		if ($(this).tinymce().isDirty()) {
			isDirty = true;

			// Return false to exit out of the each function
			return false;
		}
		
	});
	
	return isDirty;
}
$node

A jQuery object representing one of the elements of the .dirtyForms('isDirty') method selector.

index

The index number (integer) of the current $node within the .dirtyForms('isDirty') method selector.

setClean( $node, index, excludeIgnored ) (Optional)

Should reset the dirty status of the helper so isDirty(form) will return false the next time it is called.

setClean: function ($node) {

	// Search for all tinymce elements (either the current node or any nested elements).
	$node.filter(':tinymce').add($node.find(':tinymce')).each(function () {
		if ($(this).tinymce().isDirty()) {
			//Force not dirty state
			$(this).tinymce().isNotDirty = 1; 
		}
	});

}
$node

A jQuery object representing one of the elements of the .dirtyForms('isDirty') method selector.

index

The index number (integer) of the current $node within the .dirtyForms('isDirty') method selector.

excludeIgnored

A boolean value indicating whether to include or exclude ignored elements. Note that you can test whether it is ignored using the :dirtyignored selector.

rescan( $node, index, excludeIgnored ) (Optional)

If the helper requires extra logic in order to track the original state, this method can be used to track the values of any elements that were dynamically added since the last scan or rescan.

$node

A jQuery object representing one of the elements of the .dirtyForms('isDirty') method selector.

index

The index number (integer) of the current $node within the .dirtyForms('isDirty') method selector.

excludeIgnored

A boolean value indicating whether to include or exclude ignored elements. Note that you can test whether it is ignored using the :dirtyignored selector.

ignoreSelector (Optional Property)

A jQuery selector of any anchor, input, select, or textarea elements to exclude from interacting with Dirty Forms. This works similarly to putting the ignoreClass on a specific element, but can be included with a specific helper.

ignoreSelector: '.mceEditor a,.mceMenu a'

Helper Example

To respect the way jQuery selectors work, all children of the form as well as the form itself should have your custom isDirty() and setClean() logic applied.

// Example helper, the form is always considered dirty
(function($){

	// Create a new object, with an isDirty method
	var alwaysDirty = {
		// Ignored elements will not activate the dialog
		ignoreSelector : '.editor a, a.toolbar',
		isDirty : function($node){
			// Perform dirty check on a given node (usually a form element)
			return true;
		},
		setClean : function($node){
			// Perform logic to reset the node so the isDirty function will return true
			// the next time it is called for this node.

		}
		// To ensure full support with jQuery selectors,
		// make sure to run the action on all descendent
		// children of the node parameter. This is
		// accomplished easily by using the .find() jQuery
		// method.
		//
		// $node.find('.mySelector').each(function(){
		//     Run desired action against the child
		//     node here
		//     doSomething(this);
		// });
		// Run desired action against $(node) to handle the case
		// of a selector for a specific DOM element
		// if ($node.hasClass('.mySelector')) { doSomething(node); }

	}
	// Push the new object onto the helpers array
	$.DirtyForms.helpers.push(alwaysDirty);

})(jQuery);

See the TinyMCE Helper Source Code for another complete example.

Dialogs

The default browser dialog can be overridden by setting a new dialog object or integrating one of the pre-built dialog modules.

NOTE: This works when the user attempts to leave the page by clicking hyperlinks within the page only. If the user interacts with the browser directly, the browser's dialog will be called instead since browsers don't provide a way to override this behavior.

Available Dialog Modules

Custom Dialog Integration

You can create your own dialog integration by implementing the following members.

open( choice, message, ignoreClass ) (Required)

Opens the dialog.

choice

An object that can be used to interact with Dirty Forms. It contains the following members.

Name Type Default Description
staySelector string '' A jQuery selector. The matching elements will have their click event bound to the stay choice (when the user decides to stay on the current page).
proceedSelector string '' A jQuery selector. The matching elements will have their click event bound to the proceed choice (when the user decides to leave the current page). Generally, this should be a single element on the dialog to prevent the user from losing their form edits too easily.
bindEscKey bool true If true, the keydown event will be bound and if the escape key is pressed when the dialog is open, it will trigger the stay choice.
bindEnterKey bool false If true, the keydown event will be bound and if the enter key is pressed when the dialog is open, it will trigger the stay choice.
proceed bool false A flag that when set false will trigger a stay choice when the commit() method is called and will trigger a proceed choice when set true and the commit() method is called.
commit(event) function N/A An event handler that commits the choice that is stored in the proceed property.
message

The main message to show in the body of the dialog.

ignoreClass

A handy reference to the $.DirtyForms.ignoreClass that can be used to make Dirty Forms ignore elements (such as anchors) of the dialog.

close( proceeding, unstashing ) (Optional)

Closes the dialog. This method is called after the choice is committed.

proceeding

true if this is the proceed choice, false if this is the stay choice.

unstashing

true if the unstash() method will be called after this method. This can help to prevent issues from dialogs that respond to their corresponding close command too late and it closes the stash dialog instead of the Dirty Forms confirmation dialog.

stash() (Optional)

Stash returns the current contents of a dialog to be unstashed after a dialog choice of stay. All JavaScript datatypes are supported, including object and jQuery objects, and will be passed back as the stash parameter of the unstash() method. Use to store the current dialog content (from the application), when it's about to be replaced with the Dirty Forms confirmation dialog. This function can be omitted or return false if you don't wish to stash anything.

See the Modal Dialog Stashing section for more information.

unstash( stash, event ) (Optional)

Unstash handles restoring the content of the dialog. You can omit this method (or return false) if you don't need to use stashing.

stash

The value that was returned from the stash() method (unless it was false, then unstash() won't be called).

event

The event that triggered the unstash (typically a button or anchor click).

See the Modal Dialog Stashing section for more information.

stashSelector (Optional Property)

A jQuery selector used to select the element whose child form will be cloned and put into the stash. This should be a class or id of a modal dialog with a form in it, not the dialog that Dirty Forms will show its confirmation message in. The purpose of stashing the form separately is to re-attach it to the DOM and refire events on if the user decides to proceed. This property can be omitted if you are not using stashing, but is required if you are using stashing.

See the Modal Dialog Stashing section for more information.

title (Optional Property)

Although this property is not used by Dirty Forms, you can define it to make it possible for the end user of the dialog module to set the text of the dialog title.

If contributing a new dialog module, please include this property and set the default value to Are you sure you want to do that?.

proceedButtonText (Optional Property)

Although this property is not used by Dirty Forms, you can define it to make it possible for the end user of the dialog module to set the text of the proceed button.

If contributing a new dialog module, please include this property and set the default value to Leave This Page.

stayButtonText (Optional Property)

Although this property is not used by Dirty Forms, you can define it to make it possible for the end user of the dialog module to set the text of the stay button.

If contributing a new dialog module, please include this property and set the default value to Stay Here.

preMessageText (Optional Property)

Although this property is not used by Dirty Forms, you can define it to make it possible for the end user of the dialog module to prepend text or HTML to the dialog message.

If contributing a new dialog module, consider adding this property if suitable for the dialog framework.

postMessageText (Optional Property)

Although this property is not used by Dirty Forms, you can define it to make it possible for the end user of the dialog module to append text or HTML to the dialog message.

If contributing a new dialog module, consider adding this property if suitable for the dialog framework.

width (Optional Property)

Although this property is not used by Dirty Forms, you can define it to make it possible for the end user of the dialog module to set the width of the dialog (if the dialog framework supports setting the width through JavaScript).

If contributing a new dialog module, consider adding this property if suitable for the dialog framework.

class (Optional Property)

Although this property is not used by Dirty Forms, you can define it to make it possible for the end user of the dialog module to set the class of the outermost element of the dialog. This can make it easy for the end user to style the dialog with CSS. Omit this setting if the dialog framework is using themes, since it typically doesn't make sense to override styles of an existing theme.

If contributing a new dialog module, consider adding this property if suitable for the dialog framework.

Dialog Examples

BlockUI Dialog Example

Here is an example dialog setup using the BlockUI jQuery plugin.

$(function() {

	$('form').dirtyForms({
		dialog: {
			open: function (choice, message) {
				$.blockUI({
					message: '<span class="dirty-dialog">' +
							'<h3>Are you sure you want to do that?</h3>' +
							'<p>' + message + '</p>' +
							'<span>' +
								'<button type="button" class="dirty-proceed">Leave This Page</button> ' +
								'<button type="button" class="dirty-stay">Stay Here</button>' +
							'</span>' +
						'</span>',
					css: {
						width: '400px',
						padding: '10px',
						cursor: 'auto'
					},
					overlayCSS: {
						cursor: 'auto'
					}
				});

				// Bind Events

				// By default, BlockUI binds the Enter key to the first button, 
				// which would be the proceed button in our case. So, we need
				// to take control and bind it to the stay action instead to 
				// prevent it from being a dangerous key.
				choice.bindEnterKey = true;

				// Bind both buttons to the appropriate actions.
				// Also bind the overlay so if the user clicks outside the dialog, 
				// it closes and stays on the page (optional).
				choice.staySelector = '.dirty-dialog .dirty-stay,.blockOverlay';
				choice.proceedSelector = '.dirty-dialog .dirty-proceed';
			},
			close: function () {
				$.unblockUI();
			}
		}
	});
	
});

Note that you can also specify the dialog using the syntax $.DirtyForms.dialog = { /* the dialog members */ };. This way they can be packaged as separate reusable modules.

jQuery UI Dialog Example

Here is a more advanced example using jQuery UI Dialog. The jQuery UI close event is used along with a combination of the choice.proceed property and choice.commit() method to ensure every time the dialog is closed a choice is made. In addition, we are setting the dialog on the public $.DirtyForms.dialog property, making this into a separate dialog module that will automatically override the default browser dialog without specifying the dialog when calling .dirtyForms().

$(function() {

	// jQuery UI requires that the HTML be in the DOM
	// already before it is called. So we add it during
	// page load.
    $('body').append('<div id="dirty-dialog" style="display:none;" />');

	$.DirtyForms.dialog = {
		// Custom properties to allow overriding later using 
		// the syntax $.DirtyForms.dialog.title = 'custom title';
		
        title: 'Are you sure you want to do that?',
        proceedButtonText: 'Leave This Page',
        stayButtonText: 'Stay Here',
        preMessageText: '<span class="ui-icon ui-icon-alert" style="float:left; margin:2px 7px 25px 0;"></span>',
        postMessageText: '',
        width: 430,
		
        // Dirty Forms Methods
        open: function (choice, message) {
            $('#dirty-dialog').dialog({
                open: function () {
                    // Set the focus on close button. This takes care of the 
					// default action by the Enter key, ensuring a stay choice
					// is made by default.
                    $(this).parents('.ui-dialog')
						   .find('.ui-dialog-buttonpane button:eq(1)')
						   .focus();
                },
				
				// Whenever the dialog closes, we commit the choice
                close: choice.commit,
                title: this.title,
                width: this.width,
                modal: true,
                buttons: [
                    {
                        text: this.proceedButtonText,
                        click: function () {
							// Indicate the choice is the proceed action
                            choice.proceed = true;
                            $(this).dialog('close');
                        }
                    },
                    {
                        text: this.stayButtonText,
                        click: function () {
							// We don't need to take any action here because
							// this will fire the close event handler and
							// commit the choice (stay) for us automatically.
                            $(this).dialog('close');
                        }
                    }
                ]
            });
			
			// Inject the content of the dialog using jQuery .html() method.
            $('#dirty-dialog').html(this.preMessageText + message + this.postMessageText);
        },
        close: function () {
			// This is called by Dirty Forms when the 
			// Escape key is pressed, so we will close
			// the dialog manually. This overrides the default
			// Escape key behavior of jQuery UI, which would
			// ordinarily not fire the close: event handler 
			// declared above.
            $('#dirty-dialog').dialog('close');
        }
	};
	
});

Modal Dialog Stashing

Dialog stashing is meant for the following scenario.

  1. The dialog framework doesn't allow overlaying a modal dialog on top of another modal dialog.
  2. A form is hosted inside a modal dialog.
  3. The modal dialog with the form has an anchor tag that navigates off of the page.

You don't need to use stashing if any of the above items are false.

If you have a form and link which is in a modal dialog (a modal dialog created by some other part of your application) then when the Dirty Forms modal fires, the original modal is removed. So the stash saves the content from the original modal dialog while Dirty Forms shows its modal dialog, and then re-shows the original modal dialog with the edits if the user chooses to stay on the page.

TIP: If you stash a jQuery object, it will contain the state of the DOM including edits to fields.

Dialog Stashing Example

Here is an example of dialog stashing using Facebox.

$(function() {

    $.DirtyForms.dialog = {
        // Custom properties and methods to allow overriding (may differ per dialog)
        title: 'Are you sure you want to do that?',
        proceedButtonClass: 'button medium red',
        proceedButtonText: 'Leave This Page',
        stayButtonClass: 'button medium',
        stayButtonText: 'Stay Here',

        // Typical Dirty Forms Properties and Methods

        
        open: function (choice, message, ignoreClass) {
		
            var content =
                '<h1>' + this.title + '</h1>' +
                '<p>' + message + '</p>' +
                '<p>' +
                    '<a href="#" class="dirty-proceed ' + ignoreClass + ' ' + this.proceedButtonClass + '">' + this.proceedButtonText + '</a>' +
                    '<a href="#" class="dirty-stay ' + ignoreClass + ' ' + this.stayButtonClass + '">' + this.stayButtonText + '</a>' +
                '</p>';
            $.facebox(content);

            // Bind Events
            choice.bindEnterKey = true;
            choice.staySelector = '#facebox .dirty-stay, #facebox .close, #facebox_overlay';
            choice.proceedSelector = '#facebox .dirty-proceed';
        },
		
		// Dialog Stashing Support
		
        close: function (proceeding, unstashing) {
			// Due to a bug in Facebox that causes it to
			// close the stashed dialog when it reappears, 
			// we skip the call to 'close.facebox' when
			// the next method call will be unstash().
            if (!unstashing) {
                $(document).trigger('close.facebox');
            }
        },
		
		// Selector for stashing the content of another dialog.
		// This should be the dialog that contains the form you want
		// to stash, not the Dirty Forms confirmation dialog.
        stashSelector: '#facebox .content',
		
		// Save the dialog content to the stash.
        stash: function () {
            var fb = $('#facebox');

			// Before returning the object that will be stashed, 
			// we check to see if there is any HTML and
			// whether the dialog is visible. We just get the
			// elements we need to save and then do a deep clone,
			// which will save the user's edits.
            return ($.trim(fb.html()) === '' || fb.css('display') != 'block') ?
               false :
               $('#facebox .content').children().clone(true);
        },
		
		// On the return trip (after the user takes the 
		// stay choice in the confirmation dialog), 
		// the jQuery object representing 
		// $('#facebox .content').children() is returned, so 
		// we just need to add it back to the dialog again.
		// This process may vary depending on the dialog framework.
        unstash: function (stash, ev) {
            $.facebox(stash);
        }
	};
	
});

Event Handler Customization

If you need Dirty Forms to work with either parent or child IFrames, you can attach custom events and override the default event handling code. This is useful if you want to monitor events of an IFrame or change the target frame that is redirected instead of using the default behavior.

You just need to hook the bind.dirtyforms event prior to the first call to .dirtyForms(). An events object is passed as the second parameter and it contains the following methods.

Event Object Methods

bind( window, document, data )

Binds all of the events for Dirty Forms.

window

The DOM window to use to bind the events to. The default uses the DOM window object from the Dirty Forms context.

document

The DOM document to use to bind the events to. The default uses the DOM document object from the Dirty Forms context.

data

The event data to be passed along to each of the event handlers. The default is an empty object ({});

bindForm( $form, data )

Binds the events to a form and sets the listening class so the :dirtylistening selector will include it.

$form

A jQuery object containing a form or multiple forms to bind. This method is called once for each form specified in the selector used when calling .dirtyForms().

data

The event data to be passed along to each of the event handlers. The default is an empty object ({});

onFocus( event )

The event handler for the focus and keydown events of each field that matches the fieldSelector.

event

A jQuery event object containing context from the element that caused the event.

onFieldChange( event )

The event handler for the change, input, propertychange, and keydown events of each field that matches the fieldSelector.

event

A jQuery event object containing context from the element that caused the event.

onReset( event )

The event handler for the reset event of each form.

event

A jQuery event object containing context from the element that caused the event.

onAnchorClick( event )

The event handler for the click event of each anchor that has a valid href and is not target="_blank".

event

A jQuery event object containing context from the element that caused the event.

onSubmit( event )

The event handler for the submit event of each form.

event

A jQuery event object containing context from the element that caused the event.

onBeforeUnload( event )

The event handler for the beforeunload event of the window object.

event

A jQuery event object containing context from the element that caused the event.

onRefireClick( event )

Called after the user decides to proceed after the dialog is shown (not including the browser dialog).

event

A jQuery event object containing context from the element that caused the event.

onRefireAnchorClick( event )

Called by onRefireClick() after attempting to execute the event. If the execution didn't cause the page to redirect or reload, we end up here and the assumption is that the user clicked an anchor.

event

A jQuery event object containing context from the element that caused the event.

clearUnload()

Detaches the beforeunload event from the window object. This is done when making a proceed choice from the dialog.

Example Usage

IMPORTANT: The handler for bind.dirtyforms must be declared before .dirtyForms() is called.

$(function() {

	// Add functionality to an event handler
	$(document).bind('bind.dirtyforms', function (ev, events) {
		var showAlert = function(when) {
			alert('hello world ' + when);
		};

		var originalBind = events.bind;

		events.bind = function (ev) {
			showAlert('before');
			originalBind(ev);
			showAlert('after');
		};
	});


	// Bind additional events to existing handlers
	$(document).bind('bind.dirtyforms', function (ev, events) {
		var originalBind = events.bind;

		events.bind = function (window, document, data) {
			originalBind(window, document, data);
			$('button.mybutton').bind('click', events.onAnchorClick);
		};
	});


	// Pass data between handlers via events
	$(document).bind('bind.dirtyforms', function (ev, events) {
		var originalOnAnchorClick = events.onAnchorClick;
		var originalOnRefireAnchorClick = events.onRefireAnchorClick;

		events.onAnchorClick = function (ev) {
			ev.data.hello = 'Hello there!';
			originalOnAnchorClick(ev);
		};
		events.onRefireAnchorClick = function (ev) {
			// Shows "Hello there!"
			alert(ev.data.hello);
			originalOnRefireAnchorClick(ev);
		};
	});


	// Watch the top (parent) document for clicks when hosted within an IFrame
	$(document).bind('bind.dirtyforms', function (ev, events) {
		events.bind(window.top, window.top.document);
	});


	// Watch the top document for clicks when hosted within an IFrame and
	// change the target window depending on whether the user clicked an anchor 
	// on the top page. If the anchor within an IFrame is non-local, redirect
	// the top browser window instead of within the IFrame.
	$(document).bind('bind.dirtyforms', function (ev, events) {
		events.bind(window.top, window.top.document, { isTopDocument: true });

		events.onRefireAnchorClick = function (ev) {
			var $a = $(ev.target).closest('[href]'),
				href = $a.attr('href'),
				isLocal = $a[0].host === window.location.host;

			if (ev.data.isTopDocument || !isLocal) {
				// For IFrame and non-local, redirect top document
				$.DirtyForms.dirtylog('Sending top location to ' + href);
				window.top.location.href = href;
			} else {
				$.DirtyForms.dirtylog('Sending location to ' + href);
				window.location.href = href;
			}
		};
	});


	// Advanced usage - watch the top document, keep track of its dirty
	// state, and block the exit of changes to the top document using a helper.
	$(document).bind('bind.dirtyforms', function (ev, events) {
		events.bind(window.top, window.top.document, { isTopDocument: true });

		// Locate all of the forms in the top document and bind them 
		// to listen for events that change the dirty status.
		var $forms = $(window.top.document).find('form');
		events.bindForm($forms, {});

		events.onRefireAnchorClick = function (ev) {
			var $a = $(ev.target).closest('[href]'),
				href = $a.attr('href'),
				isLocal = $a[0].host === window.location.host;

			if (ev.data.isTopDocument || !isLocal) {
				// For IFrame and non-local, redirect top document
				$.DirtyForms.dirtylog('Sending top location to ' + href);
				window.top.location.href = href;
			} else {
				$.DirtyForms.dirtylog('Sending location to ' + href);
				window.location.href = href;
			}
		};
	});

	// Continued: A helper to report the dirty status from the top document, 
	// and to allow setClean and rescan methods to affect the top document. 
	// We pass in true to the excludeHelpers parameter so we don't get 
	// a recursive loop.
	var topDocumentHelper = {
		isNotTopDocument: window.top !== window.self,
		isDirty: function ($node, index) {
			// Prevent from calling when the window is not in
			// an IFrame and there is no need to execute for every node.
			if (this.isNotTopDocument && index === 0) {
				return $(window.top.document).dirtyForms('isDirty', true);
			}
			return false;
		},
		setClean: function ($node, index, excludeIgnored) {
			if (this.isNotTopDocument && index === 0) {
				$(window.top.document).dirtyForms('setClean', excludeIgnored, true);
			}
		},
		rescan: function ($node, index, excludeIgnored) {
			if (this.isNotTopDocument && index === 0) {
				$(window.top.document).dirtyForms('rescan', excludeIgnored, true);
			}
		}
	};
	$.DirtyForms.helpers.push(topDocumentHelper);

});

jquery.dirtyforms's People

Contributors

gmcrist avatar hleumas avatar johnnaegle avatar marceltschoppch avatar mgroves avatar nickcoyne avatar nightowl888 avatar nitrodist avatar noreplymyget avatar sergeystoma avatar shawnachieve avatar slave2zeros avatar snikch avatar vitall avatar woto avatar ziykon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jquery.dirtyforms's Issues

Issue with DataTables for empty anchor links

I have a page which consists of User list on left side and its details appearing on right side on click of username. The details section is a form(being checked for dirty) which admin can change.

The problem is, on left hand side I have userlist in a DataTables with pagination feature. So now if I am adding values for new user which makes the form dirty and click on DataTables's pagination links (Next or Previous) before saving the user record, the dirtyForm warning is shown properly. If I click on "Continue" the page redirects to "/undefined". I found this thing when I enabled the dirtyLog.

According to me, its happening because the prev-next buttons in datatables are anchor tags without any "href" attribute whose value is being used by dirtyForms before showing the "Continue" - "Stop" popup.

I have fixed this thing for now by just checking for undefined value by replacing below line in jquery.dirtyforms.js file

location.href = anchor.attr('href');

with

if (anchor.attr('href')!=undefined) {
  location.href = anchor.attr('href');
}

Excluding a form

Hi,

First thank you for a great plugin.

Is it possible to enhance the plugin to exclude forms with a particular class, I want all forms checked except for those with 'FormPost' class. I realize I can add the ignoredirty class to the form but it would be nice to be able to exclude forms not to be checked using a single method call.

Thanks,

Etienne.

TinyMCE iFrames

Hello,

I have the dirtyforms up and working on my form, but I'm having issues with TinyMCE. I'm using the WYSIWYG module in Drupal 7, and I have multiple TinyMCE instances on a single page.

Any of the fields in the form that are radio buttons or non-TinyMCE fields are trowing isDirty just fine. Any of the TinyMCE fields that are inside of their iFrames aren't triggering properly no matter how significantly the content is changing.

An example of the TinyMCE rendered html is something like...
iframe
body contenteditable="true" onload="window.parent.tinyMCE.get('edit-field-field-2-comments-und-0-value').onLoad.dispatch();" class="mceContentBody " id="tinymce" spellcheck="false" dir="ltr">

asdf asdf asdf asdf asdf asdf

</body
/iframe

I've tried experimenting with the tinycme helper that comes with dirtyforms, but I don't really understand how to make it work, and it doesn't look like it is geared to look inside the iFrames in any case.

Any help would be much appreciated!

Thanks.

alert box shown when nothing changes are done in form

please tell me what to do with this issues when nothing changes is being done in form on which this plugin used and when user exits form it still shows alert box of unsaved changes .so please tell me how to fix this issue so that its alert box only apears when change is being done in the forms.

cleanDirty() function not working in IE9

Seems that it is working in Firefox 7, but not IE9.

I tested in Firebug with an input button to test for dirty status like this:

$('#dirtyCheck').click(function() {
    alert($.DirtyForms.isDirty());
});

It seems that when the button is clicked, the dirty class is put on the button instead of the input that is dirty. The code that is used to "clean" the form removes the class from the form, but not also from the button.

I am not sure if this is the root cause, but it definitely doesn't look right.

Undo changes

Is there an option to set the page as clean if the user undid all changes to the form?

ignoreClass not working correctly

See this: http://stackoverflow.com/questions/23679317/how-can-i-exclude-controls-from-making-form-dirty?noredirect=1#comment36380165_23679317

What I tried doing is adding some class names to my various controls so that they do not make my form dirty. class='ignoreDirty' and then I do the following:

$('form').dirtyForms({ ignoreClass : "ignoreDirty" });

However, this doesnt seem to work. After selecting my drop down whose class is ignoreDirty it raises the message that the form is dirty and I need to save changes. This filter does post back but that should not matter. See stackoverflow post for more info.

2 isDirty Methods?

Mal, I noticed that you added both of them in the initial commit, they are also both in the documentation:

$.DirtyForms.isDirty() will return true if any watched elements are considered dirty
if($.DirtyForms.isDirty())

$.fn.isDirty() will return true if the provided element is considered dirty
if($('form#accountform').isDirty())

It seems that we should be able to cascade one method to the other, but after checking the logic it is a bit different. So begs the question, why the separate logic?

Couldn't $.DirtyForms.isDirty() be rewritten as

isDirty : function(){
if (settings.disabled) return false;
return $('form').isDirty();
}

and if not, why?

Note also that the settings.disabled is missing from the other isDirty method, so if cascading the call it should most likely be moved as well.

Refiring 'click' only works for elements that have 'href' themselves

I'm using http://www.wizzud.com/jqDock/ which handles 'click' on an element that's inside an anchor. Therefore the triggering element does not have an "href" itself.

To handle such a scenario,
https://github.com/snikch/jquery.dirtyforms/blob/master/jquery.dirtyforms.js#L295
should read "get the closest element that has an 'href' attribute":

var anchor = $(e.target).closest('[href]');
dirtylog('Sending location to ' + anchor.attr('href'));
location.href = anchor.attr('href');

(I'm just defining anchor so it doesn't need to get determined twice)

http://api.jquery.com/closest/ says
"The .closest() method begins its search with the element itself before progressing up the DOM tree, and stops when item A matches the selector."
so that won't affect the behaviour for (the expected) a tags.

AJAX Control Toolkit TabControl compatibility

I have a problem when using DirtyForms with the AJAX TabControl inside of an update panel. The change detection only seems to be working on the first tab and not subsequent tabs.

I am performing an async postback when the tab changes (in order to keep track of the active tab) and I have also
included "livequery" which I understood to address this.

My code is as follows:

<script language="javascript" type="text/javascript"> // </script>

My form is made up of master pages (2) with the content control containing an update panel and then a tab control. The editable fields are then on the tab control.

Can you advise as to how I might resolve this please?

Getting the "Confirm Navigation" modal popup in Chrome v26

I switched out the dialog to use jQueryUI using the code on the home page. When I click "Leave this page" I get the Confirm Navigation modal popup from Chrome. I see in the decidingContinue function where the onbeforeunload = null, but Chrome still pops up the modal.

Any ideas?

Remove FaceBox and use browser "confirm"

For consistency with the rest of my application, and also with the "confirm" dialog that is used when closing the browser, I would like to remove the dependency on FaceBox (or any other HTML based dialog) and instead use the browser confirm dialog to display all messages.

I have looked at the code and the examples, but - not being very good at javascript - I cannot work out how this can be achieved.

Can you please provide assistance?

$.facebox is not a function

I tried setting the dialog to false this way:

$.DirtyForms.dialog = false;

But it didn't work. I still got the error in the subject. Am I doing it right?

beforeunload not cleared in jquery 1.9

I have set dialog to false in my dirty forms config so I can simply provide a builtin popup. I do this quite simply with the following code:

    $.DirtyForms.dialog = false;
    $('form.watch_changes').dirtyForms();
    $(window).bind('beforeunload', function(e) {
      if ($.DirtyForms.isDirty())
        return 'You have unsaved changes on this page, are you sure you want to leave?'
      return;
    }); 

In jquery 1.8, this works perfectly. However, in jquery 1.9 I just noticed that my own beforeunload event above gets called regardless of form submit. I.e. it gets triggered every single time a user tries to leave the page.

In browsing the dirty forms code, I noticed in the clearUnload method, that all beforeunload events are being wiped (supposedly due to a bug in jquery 1.7). I'm not 100% sure, but this doesn't seem to work in jquery 1.9. I don't see anything in the jquery changelog / upgrade guide to suggest this behaviour has changed.

In any case, I am not sold that this is a bug in dirty forms. It is doing the correct thing and only trying to remove it's own beforeunload event. It would make sense that any external events remain intact (my own one included).

For now, I have altered my dirtyforms so that https://github.com/snikch/jquery.dirtyforms/blob/master/jquery.dirtyforms.js#L379 will also $(ev.target).cleanDirty(), so that when a dirty form is submitted, it's essentially marked as clean. This works for my case, because by the time my own beforeunload is triggered, there are no more dirty forms, but it is probably less than ideal, since the form technically was dirty before it was submitted.

So I guess what I need is a way to ask dirty forms if a dirty form was submitted at any point?

Can't get dirty forms plugin installed and working.

I'm using jQuery 1.4.4, IE 8, running Java 6 jsp's with Struts2 tags. I downloaded the jquery.dirtyforms.js and facebox.js and made a relative reference to them in the jsp referencing our javascript folder. I didn't know if I needed anything else. I used this code in my jsp: $('form').dirtyForms(); that was wrapped in a jQuery function() statement.

I wasn't sure about the facebox code and how to bind it or if there was anything else I needed to do.

I think we can use this plugin in our app if I can get it configured and running, but the documentation didn't show installation or setup that I could find.

Javascript links shouldn't clearUnload()

I have links on my page with href "#" which have click handlers attached to them. They don't do much: collapse, expand, add/remove elements - they're not going anywhere or submitting a form.

I gave these links the 'ignore' class so that they don't show the dialog. However, once I click one of these links, clearUnload() is called and e.g. refreshing the browser won't be caught anymore. That should be easy to reproduce.

Maybe a new class is needed for this type of links, which leaves bindFn() without calling clearUnload() ?

Barfs on IE inside frames.

We have clients that use our pages inside iframes. DirtyForms uses top.document and the likes to try and capture stuff outside of the current frame. Due to the parent documents being on a different domain, this barfs completely and it becomes impossible to leave the page.

I'd like a way to tell dirty forms to only capture stuff inside the current document, I don't care about forms in parent frames or whatever. I can't see why anyone would.

Manually fire the dialog

Hi there!

Is there a way to manually fire the dialog?
My goal is to fire the dialog from other events than page unload (javascript new location for instance).

I can easily watch if the form is dirty with f($.DirtyForms.isDirty()) but I miss the possibility to call the dialog from there.

Thanks

settings using public options but not public themselves

I was wondering why I couldn't override DirtyForms.title and DirtyForms.message as documented.

Seems like bindFn() uses

settings.dialog.fire(settings.message, settings.title);

and "settings" is a private extension of $.DirtyForms.

I think it should use $.DirtyForms.dialog/message/title - only the "really" private attributes that are added don't need exposure.

Getting "Are you sure you want to leave this page? when closing form

I get the "Are you sure you want to leave this page?" question when ever I try to close the page, but, when clicking a link in my page to go to another page I get the modal that I've setup. Is this the "normal" behavior or did I set something up incorrectly. I am using jQuery UI dialog instead of faceBox. Below is my code:

    function (response, status, xhr) {
        $.showDialog($("<div>" + options.dialogMessage + "</div>"), {
            modal: true,
            html: options.dialogMessage,
            width: options.width,
            title: options.dialogTitle,
            buttons: [
                    { text: options.stayOnPageText,
                        click: function (e) {
                            $.DirtyForms.choiceContinue = false;
                            $(this).dialog('close');
                        }, id: 'dirtyStay'
                    },
                    { text: options.leavePageText,
                        click: function (e) {
                            $.DirtyForms.choiceContinue = true;
                            $(this).dialog('close');
                        }, id: 'dirtyLeave'
                    }
            ]
        })
        .bind('dialogclose', function (e) {
            // Execute the choice after the modal dialog closes
            $.DirtyForms.choiceCommit(e);
        });
    });

Thanks,
Mike

Reported Issue

": I was using Dirty Forms and I noticed that after you close facebox, if you perform another action that will take you away from the page (reload, close, etc.), it doesn't prompt you. It always lets you go through. Seems the facebox close functionality is breaking something. Just thought I'd let you know. "

via Email

working with jQuery datepicker

when I use the jQuery datepicker and this plugin, I can move forward and backwards through the months until I pick a value. Then if I try and change the month again I get the 'leaving page' dialog. I've tried to add the 'ignoreddirty' class to all the children elements of popup but to no avail. Surely I'm not the first to try and get these two to cooperate... Any help is appreciated.

I'm using jquery 1.7.1 and jquery ui version 1.8.11

Using pnotify for dialog with dirtyforms

I'm sharing my dialog section to get dirtyforms to work with pnotify. This creates a modal-confirmation notification.

dialog : {
refire : function(content, ev){
return false;
},
fire : function(message, title){
var content = {title: title,
hide: false,
text:'

' + message + '

ContinueStop',
before_open: function(pnotify) {
/* Make a modal screen overlay.*/
if (typeof modal_overlay != "undefined") modal_overlay.fadeIn("fast");
else modal_overlay = $("
")
.addClass("ui-widget-overlay")
.css( {
"display": "none",
"position": "fixed",
"top": "0",
"bottom": "0",
"right": "0",
"left": "0"
})
.appendTo("body").fadeIn("fast");
},
before_close: function(e) {
modal_overlay.fadeOut("fast");
//$.DirtyForms.choiceCommit(e);
}};
var notice = $.pnotify(content);
},
bind : function(){
$('.canceldirty, .ui-pnotify-container, #ui-widget-overlay').click($.DirtyForms.decidingCancel);
$('.continuedirty').click($.DirtyForms.decidingContinue);
},
stash : function(){
return false;
},
selector : '#ui-pnotify'
},

Getting the "Confirm Navigation" modal popup in Chrome v27.

Like issue #48. I added logging:
var decidingContinue = function (ev) {
dirtylog('Executing - window.onbeforeunload = null');
window.onbeforeunload = null; // fix for chrome

Below are logs from Chrome and Firefox. It's look like that " window.onbeforeunload = null;" doesn't work any more. I

Chrome ( Version 27.0.1453.94 m ):
[DirtyForms] Adding forms to watch
[DirtyForms] Adding form undefined to forms to watch
[DirtyForms] setDirty called
[DirtyForms] Entering: Leaving Event fired, type: click, element: x, class: undefined and id:
[DirtyForms] isDirty returned true
[DirtyForms] Setting deciding active
[DirtyForms] Saving dialog content
[DirtyForms] false
[DirtyForms] Deferring to the dialog
[DirtyForms] Entering: Leaving Event fired, type: click, element: xxx, class: btn and id:
[DirtyForms] Leaving: Already in the deciding process
[DirtyForms] Executing - window.onbeforeunload = null
[DirtyForms] Refiring click event
[DirtyForms] Entering: Leaving Event fired, type: click, element: x, class: undefined and id:[DirtyForms] Leaving: Already in the deciding process
[DirtyForms] Sending location to xxx
[DirtyForms] Entering: Leaving Event fired, type: beforeunload, element: [object HTMLDocument], class: undefined and id: undefined
[DirtyForms] isDirty returned true
[DirtyForms] Setting deciding active
[DirtyForms] Saving dialog content
[DirtyForms] false
[DirtyForms] Returning to beforeunload browser handler with: xxx
[DirtyForms] Before unload will be called, resetting

Firefox( Version 21.0)
[DirtyForms] Adding forms to watch
[DirtyForms] Adding form undefined to forms to watch
[DirtyForms] setDirty called
[DirtyForms] Entering: Leaving Event fired, type: click, element: xxx, class: undefined and id:
[DirtyForms] isDirty returned true
[DirtyForms] Setting deciding active
[DirtyForms] Saving dialog content
[DirtyForms] false
[DirtyForms] Deferring to the dialog
[DirtyForms] Entering: Leaving Event fired, type: click, element: xxx, class: btn and id:
[DirtyForms] Leaving: Already in the deciding process
[DirtyForms] Executing - window.onbeforeunload = null
[DirtyForms] Refiring click event
[DirtyForms] Entering: Leaving Event fired, type: click, element: xxx, class: undefined and id:
[DirtyForms] Leaving: Already in the deciding process
[DirtyForms] Sending location to xxx
[DirtyForms] Entering: Leaving Event fired, type: beforeunload, element: [object HTMLDocument], class: undefined and id: undefined
[DirtyForms] Leaving: Already in the deciding process

file uploads are ignored

Hi,
first of all thanks for the great plugin.

I have some problems with my file upload fields. DirtyForms will just check that there is a change when I click in the textfield for the path (if one exist) and not when clicking the "brows" button and upload a file.

Maybe you can give me a hand to solve the problem.

fileuploads

Does not seem to catch input of type="file"

input type="reset" does not "undirty" the form on FF 10.0.2

Hello,

To reproduce :

  • on a form that use this excellent plugin, there's an button.
  • user change the value of an input type="text"
  • using FF 10.0.2, the isDirty() method returns true on the said form
  • on chrome, the isDirty() method returns false (the right behaviour)

Is there something that can be done ?

dirtyForms is failing after user presses 'Esc' key while in 'Stay/Discard' popup.

When user has a dirty form and tries to navigate elsewhere the 'Save/Discard' popup can be acted using following actions.

  1. Click on close link of popup - Works fine
  2. Click on "Discard changes" - Works fine
  3. Click on "Stay on this page" - Works fine
  4. If user presses 'Esc' key and then try to navigate elsewhere, dirtyForm fails here.

Manually reseting form to "clean" leaves form in dirty state

The following set of actions causes the form to be left dirty:
click in a text box
enter a value (any value)
change focus away from the textbox (tab out or click out)
re-focus on the textbox
remove the value I entered (return it to the original clean state)
try to navigate away from the page
I get the dirty forms popup warning at this time.

I'm only using the jquery.dirtyForms.js file in an ASP MVC application. Is this expected behavior or do I have the dirtyForms configuration wrong?

checkboxes

I have a simple checkbox input. Is there something special I need to do to get this plug-in to detect change in checkbox checked state?

navigating jquery UI timepicker pop-up / iOS / touch punch - triggers dirty dialog

Yeah, ridiculous for me to even post this, perhaps.. but.. figured I'd throw it up here.

I'm using 'touch punch' so that iOS touch events will work with my jQuery UI widgets.
Seems to be working pretty good.

So, I have this jquery UI timepicker, and when I try to navigate through the calendar (specifically use the arrow buttons in the corners of the pop-up to change months), it must be interpreting falsely somehow that I'm leaving the page. The "are you sure you want to leave" dialog pops up.

The timepicker, to my best knowledge, performs no under-the-hood ajax / xhr requests and furthermore works flawlessly in other browsers I'm testing. If I get time, I'll see if I can post a jsfiddle that you would have to try on an iPad or some such...

A few issues

Hi,

Me again, sorry to bother you again but I've come across a couple of issues. I have to state that I am not a jQuery or Javascript expert as most my work is not in JS so I apologise if I've missed something obvious or my code is not correct:

1: I use images with links for a toolbar, e.g.

    <a href="/clients/new" title="New"><img alt="Tb_new" src="/images/tb_new.png?1285904278"></a>

The plugin was not picking up a href as the target was the image itself, to fix this I added code to the refire function:

    refire = function(e){
    $(document).trigger('beforeRefire.dirtyforms');
    switch(e.type){
        case 'click':
            dirtylog("Refiring click event");
            var event = new jQuery.Event('click');
            $(e.target).trigger(event);
            if(!event.isDefaultPrevented()){
                dirtylog('Sending location to ' + $(e.target).attr('href'));
                var href = $(e.target).attr('href');
                if (typeof  href == 'undefined') href = $(e.target).parent('a').attr('href');
                location.href = href;
                return;
            }
            break;

The above has the following modifications:

                var href = $(e.target).attr('href');
                if (typeof  href == 'undefined') href = $(e.target).parent('a').attr('href');
                location.href = href;

It seems to work ok and is picking up the correct href.

2: When using Ajax to update the page content when a particular link is clicked the dialog is displayed and when I press continue the page is updated. In my JS I capture the click to execute the Ajax call I then return false. The problem is that the deciding variable is never reset and subsequent clicks are ignored. I tried adding the following line to the refire event to reset the variable but this caused other porblems for non-ajax links.

Any ideas how I can reset the variable when using Ajax calls to refresh the page rather than refreshing the complete page by going to a particular href?

    settings.deciding = false;

3: In IE8 after the warning dialog is displayed a browser dialog is displayed saying:

Are you sure you want to navigate away from this page.
null
Press ok to continue, or cancel to stay on the current page.

Not sure what is causing this to happen or how to overcome it??

4: Some of my fields where not being picked up as dirty when I changed them, I tracked it down to following line:

    $('input:text, input:password, input:checkbox, input:radio, textarea, select, input', this).change(function(){

My fields have types on them (added by formtastic a rails plugin) for recognized types, in this example a telephone (type=tel)

    <input id="client_client_phone" maxlength="20" name="client[client_phone]" type="tel" value="phone by master">

As you can see I added input as another type to catch all inputs, it this safe or should I add each type, e.g. input:tel

Thanks,

Etienne.

Method to ignore certain fields or setClean with a helper

It seems dirtyForms doesn't place nicely with the latest Select2; it sees placeholder text as dirty. Since dirtyForms uses jQuery.on, it receives events from the added Select2 fields on the form that aren't actually submitted.

I looked at ignoreclass, but this is for ignoring anchors rather than a class of form field. I don't see a way to add to the inputSelector. The helpers also have the ability to set an element as dirty but not to clear the dirty status.

Is there another way around this?

Not marking as dirty if autofocused element is changed

I have a form (loaded via ajax, not sure if that is related) where I auto-focus the first field. If I type something in that field, the reload the page, it doesn't get flagged as dirty. If I click out of and into that field and change it it's fine. Likewise, if I tab to the next field, ctrl+tab back and type something it gets marked as dirty.

Feature request: Allow user to revert a change

At the moment, change() automatically sets a field dirty. It would be nice to allow the user to revert a change, thus making the field clean again. If that was the last dirty field the form becomes clean itself:

In $.fn.dirtyForms():

            $(core.fields, this).change(function(){
                            // A changed field is not necessarily dirty: Compare current with default value/selection
                            var is_dirty = false;
                            var element = $(this).get(0)   // corresponding DOM element
                            var type = element.type;
                            if (type == "checkbox" || type == "radio")
                            {
                                if (element.checked != element.defaultChecked) { is_dirty = true; }
                            }
                            else if (type == "hidden" || type == "password" || type == "text" || type == "textarea")
                            {
                                if (element.value != element.defaultValue) { is_dirty = true; }
                            }
                            else if (type == "select-one" || type == "select-multiple")
                            {
                                for (var j = 0; j < element.options.length; j++)
                                {
                                    if (element.options[j].selected != element.options[j].defaultSelected) { is_dirty = true; }
                                }
                            }
                            if (is_dirty)
                {
                    $(this).setDirty();
                }
                else
                {
                    $(this).unsetDirty();
                                // When the last dirty element becomes undirty the form becomes undirty
                                this.closest('form').determineDirty();
                };
            });
        });
    }

which would need the helpers

    $.fn.determineDirty = function(){
        // Call this on a form to make it determine (set/unset) its dirtyness
        // depending on whether there is at least one dirty form field
        var form = $(this);
            var form_still_dirty = false;
            dirtylog('checking if form is still dirty...');
            $(':input', form).each(function(index, element) {
                if ($(element).isDirty()) {
                    form_still_dirty = true;
                    return false;   // break: one dirty element is enough
                }
            });
            if (form_still_dirty)
            {
                dirtylog('form with id '+form.attr('id')+' is still dirty');
                return form.addClass($.DirtyForms.dirtyClass);
            }
            else
            {
                dirtylog('form with id '+form.attr('id')+' is not dirty anymore');
                return form.removeClass($.DirtyForms.dirtyClass);
            }
    }

        $.fn.unsetDirty = function(){
        dirtylog('unsetDirty called');
        this.each(function(e){
            $(this).removeClass($.DirtyForms.dirtyClass);
        });
        // Important: For performance reasons the form does not
        // automatically determine if it is now not dirty anymore.
        // If you're doing calls to unsetDirty() manually then
        // call determineDirty() on the closest form when done.
    }

IE9 "navigate away" dialog not showing message

When clicking the refesh or back button in the browser, the dialog pops up with the browser's "Are you sure you want to leave this page?" dialog. In the dialog it says: "Message from web page: null".

The best place to see this issue in action is on your own demo page with an IE9 browser: http://mal.co.nz/code/jquery-dirty-forms/

I checked the code against the docs (https://developer.mozilla.org/en/DOM/window.onbeforeunload and http://msdn.microsoft.com/en-us/library/ms536907(VS.85).aspx) and it seems the return value from beforeunloadBindFn is incorrect.

According to the docs, the function should either return a string to indicate not to leave the page or not return a value at all to indicate it is okay. Instead it returns either null, or true depending on which part of the logic is used. It looks like the doubleunloadfix code at the top of the bindFn() function is stopping the logic from ever reaching the code that is supposed to return a string (the message).

In addition, the event.returnValue property is not being set (unless jQuery is doing it - I didn't check). See the Mozilla link above for a good example of this.

License?

Hello there!

I'm interested in using this plugin in one of my projects. However, there's no license associated with it. I would recommend either an MIT or BSD license so others can use it.

Thanks for the work - I hope I can get to use it!

facebox not a function problem

I am sure that something I have done has caused this since I did have everything working.

However, now when I try to leave a "dirty" page I am getting "$.facebox is not a function"

Any clue as to what to look for?

can't get running with ajax

I've tried to get dirtyforms running with ajax.

In my web application there is a site-navigation with ajax based loading for the page edit-container that contains the forms.
When I try to refresh the site, the confirm window will appear as desired.

When I click onto another navigation element to request the related form container, the confirm window won't appear.

What am I doing wrong?
Here is my jquery test: https://gist.github.com/1055ab3e4346d3881959

Thanks in advance!

DirtyForms ignored hidden fields

I'm trying to use DirtyForms to catch when a user has changed elements in a form in a modal window. However, some of those elements are custom Bootstrap-style dropdown menus (because the client didn't like default select fields), which change a hidden field.

When jQuery changes the value of a hidden field, $.DirtyForms.isDirty() still returns false. Any thoughts on how to fix this?

Disable dirtyForms

Hi there!

Great plugin.
I was wondering if there is a way to disable dirtyforms after setting it.
If I set it with $('form').dirtyForms(); can I then remove it from $('form')?

Thanks

DirtyForm in conjunction with Jquery UI Tabs - how to fire external to form?

I have a page that uses jquery tabs, each tab has a form, and when you click on a tab I would like to fire the "dirtyForms" check to do what it needs to do.

Everything is set up and works great inside the forms, but I can not get the tabs to fire off the dirtyForm events.

I've tried several things, such as added code to these code blocks to try to pick up elements like my tabs:

// For jQuery 1.7+, use on()
if (typeof $(document).on === 'function') {
$(document).on('click', 'a', aBindFn);
$(document).on('submit','form',formBindFn);
if (inIframe) {
$(top.document).on('click','a',aBindFn);
$(top.document).on('submit','form',formBindFn);
}
} else { // For jQuery 1.4.2 - 1.7, use delegate()
$(document).delegate('a', 'click', formBindFn);
$(document).delegate('form','submit',formBindFn);
if (inIframe) {
$(top.document).delegate('a','click',aBindFn);
$(top.document).delegate('form','submit',formBindFn);
}
}

and I've tried to wire up an event to the tabs which fires off a dialog with the custom handclers (this works, but does not prevnt tabs from switching, even if I return $.dirtyforms.choiceContinue() Or isDirty:

$('#tabs').bind('tabsselect', function(event, ui) {


            var isValid = !$.DirtyForms.isDirty();
            event.preventDefault();

            var retValue = true;


            if (!isValid) {
                $("#dialog:ui-dialog").dialog("destroy"); // bug fix: don't remove!
                $("#dialog-confirm").dialog({
                    resizable: false,
                    height: 140,
                    modal: true,
                    buttons: {
                        "Stay": function() {
                            $.DirtyForms.decidingCancel(event);
                            retValue = false;
                            //$.DirtyForms.choiceContinue = false;
                            //$('form.simple').dirtyForms('setClean'); // reset to clean state
                            $(this).dialog("close");
                        },
                        Cancel: function() {
                            $.DirtyForms.decidingContinue(event);
                            retValue = true;
                            //$.DirtyForms.choiceContinue = true;
                            $(this).dialog("close"); //return false;
                        }
                    }
                }).bind('dialogclose', function() {
                    // Execute the choice after the modal dialog closes
                    $.DirtyForms.choiceCommit(event);
                });
            }

            return retValue;

         //   return $.DirtyForms.choiceContinue;

If I click on links inside the form, it works fine, as stated. Can you please recommend some way to fire off the dirty check dialog and block the tabs from doing what they do if the form is dirty?

Thanks A MILLION! If you can help me, you are a life saver!!! :D

--shawn

decidingContinue() doesn't function when used with modal jQuery UI dialog

After deciding I would rather have a modal dialog than a non-modal one, I discovered that the decidingContinue() method doesn't refire the event while the modal dialog is open.

Unfortunately, I cannot just swap the order of decidingContinue and $(this).dialog('close') because the latter will call the catch-all in the 'dialogclose' event. Which means the decidingCancel method would be called instead of decidingContinue.

I tried firing the dialog close method by responding to the decidingcontinued.dirtyforms custom event, but doing that crashes somewhere in the jQuery library.

I got it to work with a global boolean variable that holds the value of the decision like the following example, however, this means that there is no built-in way to support modal dialogs in dirtyForms (at least for modal jQuery UI dialogs).

    $('#unsavedChanges').bind('dialogclose', function (e) {
        // dContinue is a global boolean variable that holds the value of the decision (decided elsewhere)
        if (dContinue) {
            $.DirtyForms.decidingContinue(e);
        } else {
            $.DirtyForms.decidingCancel(e);
        }
    });

This got me thinking about putting this structure into dirtyForms, however having 2 different ways to accomplish the same thing, while sometimes handy can also be confusing.

So what I am wondering (with a severe lack of understanding of the various dialog plugins that are out there) is why can't the decision making be made into two phases instead of having 2 methods that should never be called twice. Something like this:

    bind: function() {
        $('#unsavedChanges').dialog('option', 'buttons',
            [
                {
                    text: "Go Back",
                    click: function(e) {
                        $(this).dialog('close');
                    }
                },
                {
                    text: "Continue",
                    click: function(e) {
                        $.DirtyForms.decisionContinue = true;
                        $(this).dialog('close');
                    }
                }
            ]
        ).bind('dialogclose', function(e) {
            // decisionContinue would default to false, and decisionCommit() would 
            // contain the same logic as my previous example.
            $.DirtyForms.decisionCommit(e);
        });
    }

While there is no doubt in my mind that this needs to be part of dirtyForms, would it make sense to deprecate the isDeciding(), decidingCancel() and decidingContinue() methods? I mean, is this pattern flexible enough for all of the dialog frameworks that you are aware of? I think it could work with facebox like so:

    bind: function() {
        $('#facebox .cancel, #facebox .close').click(function(e) {
            $(document).trigger('close.facebox');
        });
        $('#facebox .continue').click(function(e){
            $.DirtyForms.decisionContinue = true;
            $(document).trigger('close.facebox');
        });
        $(document).bind('afterClose.facebox', function(e){
            $.DirtyForms.decisionCommit(e);
        });
    }

So, to deprecate or not to deprecate, that is the question...

jQuery.DirtyForm - Usage with iFrame

Hi,

can you provide an example of how to detected dirty fields within an iFrame or in general using jquery.DirtyForm within iFrames. While googling, I did come across one of the stackoverflow questions http://stackoverflow.com/questions/155739/detecting-unsaved-changes which was answered by NightOwl888, in which it was mentioned that the plugin supports iFrame. However, with my limited knowledge of the plugin, I am unable to get it working while using within the iframe.

Regards,
Srikanth Naropanth

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.