Socket
Socket
Sign inDemoInstall

jquery.dirtyforms

Package Overview
Dependencies
2
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.1.0 to 1.2.0

341

jquery.dirtyforms.js

@@ -23,3 +23,3 @@ /*!

$.fn.on = function (events, selector, data, handler) {
$(this).delegate(selector, events, data, handler);
return $(this).delegate(selector, events, data, handler);
};

@@ -34,3 +34,2 @@ } else {

DirtyForms: {
debug: false,
message: 'You\'ve made changes on this page which aren\'t saved. If you leave you will lose these changes.',

@@ -44,5 +43,3 @@ title: 'Are you sure you want to do that?',

dialog: {
refire: function (content, ev) {
$.facebox(content);
},
selector: '#facebox .content',
fire: function (message, title) {

@@ -55,9 +52,11 @@ var content = '<h1>' + title + '</h1><p>' + message + '</p><p><a href="#" class="ignoredirty button medium red continue">Continue</a><a href="#" class="ignoredirty button medium cancel">Stop</a>';

return function (e) {
e.preventDefault();
$(document).trigger('close.facebox');
decision(e);
if (e.type !== 'keydown' || (e.type === 'keydown' && e.keyCode === 27)) {
$(document).trigger('close.facebox');
decision(e);
}
};
};
$('#facebox .cancel, #facebox .close, #facebox_overlay').click(close(decidingCancel));
$('#facebox .continue').click(close(decidingContinue));
$(document).bind('keydown.facebox', close(settings.decidingCancel));
$('#facebox .cancel, #facebox .close, #facebox_overlay').click(close(settings.decidingCancel));
$('#facebox .continue').click(close(settings.decidingContinue));
},

@@ -70,5 +69,14 @@ stash: function () {

},
selector: '#facebox .content'
refire: function (content, ev) {
$.facebox(content);
}
},
/*<log>*/
debug: false,
dirtylog: function (msg) {
dirtylog(msg);
},
/*</log>*/
isDirty: function () {

@@ -78,4 +86,6 @@ return $(':dirtylistening').dirtyForms('isDirty');

// DEPRECATED: Duplicate functionality.
// Use $('html').addClass($.DirtyForms.ignoreClass); instead.
disable: function () {
settings.disabled = true;
$('html').addClass(settings.ignoreClass);
},

@@ -87,4 +97,12 @@

choiceCommit: function (e) {
choiceCommit(e);
choiceCommit: function (ev) {
if (settings.deciding) {
$(document).trigger('choicecommit.dirtyforms');
if (settings.choiceContinue) {
settings.decidingContinue(ev);
} else {
settings.decidingCancel(ev);
}
$(document).trigger('choicecommitAfter.dirtyforms');
}
},

@@ -96,12 +114,20 @@

decidingContinue: function (e) {
decidingContinue(e);
decidingContinue: function (ev) {
clearUnload(); // fix for chrome/safari
ev.preventDefault();
settings.dialogStash = false;
$(document).trigger('decidingcontinued.dirtyforms');
refire(settings.decidingEvent);
settings.deciding = settings.currentForm = settings.decidingEvent = false;
},
decidingCancel: function (e) {
decidingCancel(e);
},
dirtylog: function (msg) {
dirtylog(msg);
decidingCancel: function (ev) {
ev.preventDefault();
$(document).trigger('decidingcancelled.dirtyforms');
if (settings.dialog !== false && settings.dialogStash !== false && typeof settings.dialog.refire === 'function') {
dirtylog('Refiring the dialog with stashed content');
settings.dialog.refire(settings.dialogStash.html(), ev);
}
$(document).trigger('decidingcancelledAfter.dirtyforms');
settings.deciding = settings.currentForm = settings.decidingEvent = settings.dialogStash = false;
}

@@ -114,6 +140,6 @@ }

dirtylistening: function (a) {
return $(a).hasClass($.DirtyForms.listeningClass);
return $(a).hasClass(settings.listeningClass);
},
dirty: function (a) {
return $(a).hasClass($.DirtyForms.dirtyClass);
return $(a).hasClass(settings.dirtyClass);
}

@@ -125,28 +151,25 @@ });

init: function () {
var core = $.DirtyForms;
dirtylog('Adding forms to watch');
bindExit();
return this.each(function (e) {
if (!$(this).is('form')) return;
dirtylog('Adding form ' + $(this).attr('id') + ' to forms to watch');
$(this).addClass(core.listeningClass);
// exclude all HTML 4 except text and password, but include HTML 5 except search
var inputSelector = "textarea,input:not([type='checkbox'],[type='radio'],[type='button']," +
"[type='image'],[type='submit'],[type='reset'],[type='file'],[type='search'])";
// exclude all HTML 4 except text and password, but include HTML 5 except search
var inputSelector = "textarea,input:not([type='checkbox'],[type='radio'],[type='button']," +
"[type='image'],[type='submit'],[type='reset'],[type='file'],[type='search'])";
var selectionSelector = "input[type='checkbox'],input[type='radio'],select";
var resetSelector = "input[type='reset']";
// Initialize settings with the currently focused element (HTML 5 autofocus)
var $focused = $(document.activeElement);
if ($focused.is(inputSelector)) {
settings.focused.element = $focused;
settings.focused.value = $focused.val();
}
$(this).on('focus change', inputSelector, onFocus);
$(this).on('change', selectionSelector, onSelectionChange);
$(this).on('click', resetSelector, onReset);
return this.each(function (e) {
var $form = $(this);
if (!$form.is('form')) return;
// Initialize settings with the currently focused element (autofocus)
var focused = $(this).find(inputSelector).filter(':focus');
if (focused) {
settings.focused.element = focused;
settings.focused.value = focused.val();
}
dirtylog('Adding form ' + $form.attr('id') + ' to forms to watch');
$form.addClass(settings.listeningClass)
.on('focus change', inputSelector, onFocus)
.on('change', "input[type='checkbox'],input[type='radio'],select", onSelectionChange)
.on('click', "input[type='reset']", onReset);
});

@@ -156,5 +179,4 @@ },

isDirty: function () {
var isDirty = false;
var node = this;
if (settings.disabled) return false;
var isDirty = false,
node = this;
if (focusedIsDirty()) {

@@ -165,3 +187,3 @@ isDirty = true;

this.each(function (e) {
if ($(this).hasClass($.DirtyForms.dirtyClass)) {
if ($(this).hasClass(settings.dirtyClass)) {
isDirty = true;

@@ -171,3 +193,3 @@ return true;

});
$.each($.DirtyForms.helpers, function (key, obj) {
$.each(settings.helpers, function (key, obj) {
if ("isDirty" in obj) {

@@ -195,3 +217,3 @@ if (obj.isDirty(node)) {

return this.each(function (e) {
$(this).addClass($.DirtyForms.dirtyClass).parents('form').addClass($.DirtyForms.dirtyClass);
$(this).addClass(settings.dirtyClass).parents('form').addClass(settings.dirtyClass);
});

@@ -205,15 +227,15 @@ },

return this.each(function (e) {
var node = this;
var node = this, $node = $(this);
// remove the current dirty class
$(node).removeClass($.DirtyForms.dirtyClass);
$node.removeClass(settings.dirtyClass);
if ($(node).is('form')) {
if ($node.is('form')) {
// remove all dirty classes from children
$(node).find(':dirty').removeClass($.DirtyForms.dirtyClass);
$node.find(':dirty').removeClass(settings.dirtyClass);
} else {
// if this is last dirty child, set form clean
var $form = $(node).parents('form');
var $form = $node.parents('form');
if ($form.find(':dirty').length === 0) {
$form.removeClass($.DirtyForms.dirtyClass);
$form.removeClass(settings.dirtyClass);
}

@@ -223,3 +245,3 @@ }

// Clean helpers
$.each($.DirtyForms.helpers, function (key, obj) {
$.each(settings.helpers, function (key, obj) {
if ("setClean" in obj) {

@@ -261,3 +283,2 @@ obj.setClean(node);

watchParentDocs: true,
disabled: false,
exitBound: false,

@@ -269,4 +290,6 @@ formStash: false,

currentForm: false,
/*<log>*/
hasFirebug: "console" in window && "firebug" in window.console,
hasConsoleLog: "console" in window && "log" in window.console,
/*</log>*/
focused: { "element": false, "value": false }

@@ -283,3 +306,3 @@ }, $.DirtyForms);

var onSelectionChange = function () {
if ($(this).hasClass($.DirtyForms.ignoreClass)) return;
if (isIgnored($(this))) return;
$(this).dirtyForms('setDirty');

@@ -293,3 +316,3 @@ if (settings.onFormCheck) {

var $this = $(this);
if (focusedIsDirty() && !$this.hasClass($.DirtyForms.ignoreClass)) {
if (focusedIsDirty() && !isIgnored($this)) {
settings.focused.element.dirtyForms('setDirty');

@@ -310,4 +333,5 @@ if (settings.onFormCheck) {

/*<log>*/
var dirtylog = function (msg) {
if (!$.DirtyForms.debug) return;
if (!settings.debug) return;
msg = "[DirtyForms] " + msg;

@@ -322,17 +346,14 @@ if (settings.hasFirebug) {

};
/*</log>*/
var bindExit = function () {
if (settings.exitBound) return;
var inIframe = (top !== self);
$(document).on('click', 'a[href]', aBindFn);
$(document).on('submit', 'form', formBindFn);
if (settings.watchParentDocs && inIframe) {
$(top.document).on('click', 'a[href]', aBindFn);
$(top.document).on('submit', 'form', formBindFn);
}
$(document).on('click', 'a[href]', aBindFn)
.on('submit', 'form', formBindFn);
$(window).bind('beforeunload', beforeunloadBindFn);
if (settings.watchParentDocs && inIframe) {
$(top.document).on('click', 'a[href]', aBindFn)
.on('submit', 'form', formBindFn);
$(top.window).bind('beforeunload', beforeunloadBindFn);

@@ -344,18 +365,14 @@ }

var getIgnoreAnchorSelector = function () {
var result = '';
$.each($.DirtyForms.helpers, function (key, obj) {
var ignoredByHelpers = function () {
var $ignored = $();
$.each(settings.helpers, function (key, obj) {
if ("ignoreAnchorSelector" in obj) {
if (result.length > 0) { result += ','; }
result += obj.ignoreAnchorSelector;
$ignored = $ignored.add(obj.ignoreAnchorSelector);
}
});
return result;
return $ignored;
};
var aBindFn = function (ev) {
var a = $(this);
// Filter out any anchors the helpers wish to exclude
if (!a.is(getIgnoreAnchorSelector()) && typeof a.attr('href') != 'undefined') {
if (!$(this).is(ignoredByHelpers()) && !isDifferentTarget($(this))) {
bindFn(ev);

@@ -397,5 +414,12 @@ }

var bindFn = function (ev) {
dirtylog('Entering: Leaving Event fired, type: ' + ev.type + ', element: ' + ev.target + ', class: ' + $(ev.target).attr('class') + ' and id: ' + ev.target.id);
var $element = $(ev.target), eventType = ev.type;
dirtylog('Entering: Leaving Event fired, type: ' + eventType + ', element: ' + ev.target + ', class: ' + $element.attr('class') + ' and id: ' + ev.target.id);
if (ev.type == 'beforeunload' && settings.doubleunloadfix) {
// Important: Do this check before calling clearUnload()
if (ev.isDefaultPrevented()) {
dirtylog('Leaving: Event has been stopped elsewhere');
return false;
}
if (eventType == 'beforeunload' && settings.doubleunloadfix) {
dirtylog('Skip this unload, Firefox bug triggers the unload event multiple times');

@@ -406,7 +430,5 @@ settings.doubleunloadfix = false;

if ($(ev.target).hasClass(settings.ignoreClass) || isDifferentTarget(ev)) {
dirtylog('Leaving: Element has ignore class or has target=\'_blank\'');
if (!ev.isDefaultPrevented()) {
clearUnload();
}
if (isIgnored($element)) {
dirtylog('Leaving: Element has ignore class or a descendant of an ignored element');
clearUnload();
return false;

@@ -420,43 +442,32 @@ }

if (ev.isDefaultPrevented()) {
dirtylog('Leaving: Event has been stopped elsewhere');
return false;
}
if (!settings.isDirty()) {
dirtylog('Leaving: Not dirty');
if (!ev.isDefaultPrevented()) {
clearUnload();
}
clearUnload();
return false;
}
if (ev.type == 'submit' && $(ev.target).dirtyForms('isDirty')) {
if (eventType == 'submit' && $element.dirtyForms('isDirty')) {
dirtylog('Leaving: Form submitted is a dirty form');
if (!ev.isDefaultPrevented()) {
clearUnload();
}
clearUnload();
return true;
}
if (settings.dialog) {
settings.deciding = true;
settings.decidingEvent = ev;
dirtylog('Setting deciding active');
dirtylog('Saving dialog content');
settings.dialogStash = settings.dialog.stash();
dirtylog(settings.dialogStash);
}
// Callback for page access in current state
$(document).trigger('defer.dirtyforms');
if (ev.type == 'beforeunload') {
//clearUnload();
if (eventType == 'beforeunload') {
dirtylog('Returning to beforeunload browser handler with: ' + settings.message);
return settings.message;
}
if (!settings.dialog) {
return;
if (!settings.dialog) return;
// Using the GUI dialog...
settings.deciding = true;
settings.decidingEvent = ev;
dirtylog('Setting deciding active');
if (typeof settings.dialog.stash === 'function') {
dirtylog('Saving dialog content');
settings.dialogStash = settings.dialog.stash();
dirtylog(settings.dialogStash);
}

@@ -467,5 +478,5 @@

if ($(ev.target).is('form') && $(ev.target).parents(settings.dialog.selector).length > 0) {
if (typeof settings.dialog.selector === 'string' && $element.is('form') && $element.parents(settings.dialog.selector).length > 0) {
dirtylog('Stashing form');
settings.formStash = $(ev.target).clone(true).hide();
settings.formStash = $element.clone(true).hide();
} else {

@@ -476,47 +487,16 @@ settings.formStash = false;

dirtylog('Deferring to the dialog');
settings.dialog.fire($.DirtyForms.message, $.DirtyForms.title);
settings.dialog.bind();
settings.dialog.fire(settings.message, settings.title);
if (typeof settings.dialog.bind === 'function')
settings.dialog.bind();
};
var isDifferentTarget = function (ev) {
var aTarget = $(ev.target).attr('target');
if (typeof aTarget === 'string') {
aTarget = aTarget.toLowerCase();
}
return (aTarget === '_blank');
var isDifferentTarget = function ($element) {
var aTarget = $element.attr('target');
return typeof aTarget === 'string' ? aTarget.toLowerCase() === '_blank' : false;
};
var choiceCommit = function (ev) {
if (settings.deciding) {
$(document).trigger('choicecommit.dirtyforms');
if ($.DirtyForms.choiceContinue) {
decidingContinue(ev);
} else {
decidingCancel(ev);
}
$(document).trigger('choicecommitAfter.dirtyforms');
}
var isIgnored = function ($element) {
return $element.closest('.' + settings.ignoreClass).length > 0;
};
var decidingCancel = function (ev) {
ev.preventDefault();
$(document).trigger('decidingcancelled.dirtyforms');
if (settings.dialog !== false && settings.dialogStash !== false) {
dirtylog('Refiring the dialog with stashed content');
settings.dialog.refire(settings.dialogStash.html(), ev);
}
$(document).trigger('decidingcancelledAfter.dirtyforms');
settings.dialogStash = false;
settings.deciding = settings.currentForm = settings.decidingEvent = false;
};
var decidingContinue = function (ev) {
clearUnload(); // fix for chrome/safari
ev.preventDefault();
settings.dialogStash = false;
$(document).trigger('decidingcontinued.dirtyforms');
refire(settings.decidingEvent);
settings.deciding = settings.currentForm = settings.decidingEvent = false;
};
var clearUnload = function () {

@@ -532,33 +512,26 @@ // I'd like to just be able to unbind this but there seems

var refire = function (e) {
$(document).trigger('beforeRefire.dirtyforms');
$(document).trigger('beforeunload.dirtyforms');
switch (e.type) {
case 'click':
dirtylog("Refiring click event");
var event = new jQuery.Event('click');
$(e.target).trigger(event);
if (!event.isDefaultPrevented()) {
var anchor = $(e.target).closest('[href]');
dirtylog('Sending location to ' + anchor.attr('href'));
if (anchor.attr('href') !== undefined) {
location.href = anchor.attr('href');
}
return;
}
break;
default:
dirtylog("Refiring " + e.type + " event on " + e.target);
var target;
if (settings.formStash) {
dirtylog('Appending stashed form to body');
target = settings.formStash;
$('body').append(target);
}
else {
target = $(e.target);
if (!target.is('form'))
target = target.closest('form');
}
target.trigger(e.type);
break;
$(document).trigger('beforeRefire.dirtyforms')
.trigger('beforeunload.dirtyforms');
if (e.type === 'click') {
dirtylog("Refiring click event");
var event = new $.Event('click');
$(e.target).trigger(event);
if (!event.isDefaultPrevented()) {
var href = $(e.target).attr('href');
dirtylog('Sending location to ' + href);
location.href = href;
return;
}
} else {
dirtylog("Refiring " + e.type + " event on " + e.target);
var target;
if (settings.formStash) {
dirtylog('Appending stashed form to body');
target = settings.formStash;
$('body').append(target);
}
else {
target = $(e.target).closest('form');
}
target.trigger(e.type);
}

@@ -565,0 +538,0 @@ };

@@ -1,2 +0,2 @@

!function(e){"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof exports?module.exports=e(require("jquery")):e(jQuery)}(function(e){if("function"!=typeof e(document).on){if("function"!=typeof e(document).delegate)throw"jQuery 1.4.2 or higher is required by jquery.dirtyforms";e.fn.on=function(t,i,n,r){e(this).delegate(i,t,n,r)}}e.extend({DirtyForms:{debug:!1,message:"You've made changes on this page which aren't saved. If you leave you will lose these changes.",title:"Are you sure you want to do that?",dirtyClass:"dirty",listeningClass:"dirtylisten",ignoreClass:"ignoredirty",choiceContinue:!1,helpers:[],dialog:{refire:function(t,i){e.facebox(t)},fire:function(t,i){var n="<h1>"+i+"</h1><p>"+t+'</p><p><a href="#" class="ignoredirty button medium red continue">Continue</a><a href="#" class="ignoredirty button medium cancel">Stop</a>';e.facebox(n)},bind:function(){var t=function(t){return function(i){i.preventDefault(),e(document).trigger("close.facebox"),t(i)}};e("#facebox .cancel, #facebox .close, #facebox_overlay").click(t(y)),e("#facebox .continue").click(t(p))},stash:function(){var t=e("#facebox");return""===e.trim(t.html())||"block"!=t.css("display")?!1:e("#facebox .content").clone(!0)},selector:"#facebox .content"},isDirty:function(){return e(":dirtylistening").dirtyForms("isDirty")},disable:function(){i.disabled=!0},ignoreParentDocs:function(){i.watchParentDocs=!1},choiceCommit:function(e){h(e)},isDeciding:function(){return i.deciding},decidingContinue:function(e){p(e)},decidingCancel:function(e){y(e)},dirtylog:function(e){a(e)}}}),e.extend(e.expr[":"],{dirtylistening:function(t){return e(t).hasClass(e.DirtyForms.listeningClass)},dirty:function(t){return e(t).hasClass(e.DirtyForms.dirtyClass)}});var t={init:function(){var t=e.DirtyForms;return a("Adding forms to watch"),d(),this.each(function(s){if(e(this).is("form")){a("Adding form "+e(this).attr("id")+" to forms to watch"),e(this).addClass(t.listeningClass);var d="textarea,input:not([type='checkbox'],[type='radio'],[type='button'],[type='image'],[type='submit'],[type='reset'],[type='file'],[type='search'])",c="input[type='checkbox'],input[type='radio'],select",l="input[type='reset']";e(this).on("focus change",d,o),e(this).on("change",c,r),e(this).on("click",l,n);var u=e(this).find(d).filter(":focus");u&&(i.focused.element=u,i.focused.value=u.val())}})},isDirty:function(){var t=!1,n=this;return i.disabled?!1:s()?(t=!0,!0):(this.each(function(i){return e(this).hasClass(e.DirtyForms.dirtyClass)?(t=!0,!0):void 0}),e.each(e.DirtyForms.helpers,function(e,i){return"isDirty"in i&&i.isDirty(n)?(t=!0,!0):"isNodeDirty"in i&&i.isNodeDirty(n)?(t=!0,!0):void 0}),a("isDirty returned "+t),t)},setDirty:function(){return a("setDirty called"),this.each(function(t){e(this).addClass(e.DirtyForms.dirtyClass).parents("form").addClass(e.DirtyForms.dirtyClass)})},setClean:function(){return a("setClean called"),i.focused={element:!1,value:!1},this.each(function(t){var i=this;if(e(i).removeClass(e.DirtyForms.dirtyClass),e(i).is("form"))e(i).find(":dirty").removeClass(e.DirtyForms.dirtyClass);else{var n=e(i).parents("form");0===n.find(":dirty").length&&n.removeClass(e.DirtyForms.dirtyClass)}e.each(e.DirtyForms.helpers,function(e,t){"setClean"in t&&t.setClean(i)})})}};e.fn.dirtyForms=function(i){return t[i]?t[i].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof i&&i?void e.error("Method "+i+" does not exist on jQuery.dirtyForms"):t.init.apply(this,arguments)},e.fn.setDirty=function(){return this.dirtyForms("setDirty")},e.fn.isDirty=function(){return this.dirtyForms("isDirty")},e.fn.cleanDirty=function(){return this.dirtyForms("setClean")};var i=e.DirtyForms=e.extend({watchParentDocs:!0,disabled:!1,exitBound:!1,formStash:!1,dialogStash:!1,deciding:!1,decidingEvent:!1,currentForm:!1,hasFirebug:"console"in window&&"firebug"in window.console,hasConsoleLog:"console"in window&&"log"in window.console,focused:{element:!1,value:!1}},e.DirtyForms),n=function(){e(this).parents("form").dirtyForms("setClean"),i.onFormCheck&&i.onFormCheck()},r=function(){e(this).hasClass(e.DirtyForms.ignoreClass)||(e(this).dirtyForms("setDirty"),i.onFormCheck&&i.onFormCheck())},o=function(){var t=e(this);s()&&!t.hasClass(e.DirtyForms.ignoreClass)&&(i.focused.element.dirtyForms("setDirty"),i.onFormCheck&&i.onFormCheck()),i.focused.element=t,i.focused.value=t.val()},s=function(){return i.focused.element&&i.focused.element.val()!==i.focused.value},a=function(t){e.DirtyForms.debug&&(t="[DirtyForms] "+t,i.hasFirebug?console.log(t):i.hasConsoleLog?window.console.log(t):alert(t))},d=function(){if(!i.exitBound){var t=top!==self;e(document).on("click","a[href]",l),e(document).on("submit","form",u),i.watchParentDocs&&t&&(e(top.document).on("click","a[href]",l),e(top.document).on("submit","form",u)),e(window).bind("beforeunload",f),i.watchParentDocs&&t&&e(top.window).bind("beforeunload",f),i.exitBound=!0}},c=function(){var t="";return e.each(e.DirtyForms.helpers,function(e,i){"ignoreAnchorSelector"in i&&(t.length>0&&(t+=","),t+=i.ignoreAnchorSelector)}),t},l=function(t){var i=e(this);i.is(c())||"undefined"==typeof i.attr("href")||g(t)},u=function(e){i.currentForm=this,g(e)},f=function(e){var t=g(e);return t&&i.doubleunloadfix!==!0&&(a("Before unload will be called, resetting"),i.deciding=!1),i.doubleunloadfix=!0,setTimeout(function(){i.doubleunloadfix=!1},200),"string"==typeof t?(e=e||window.event,e&&(e.returnValue=t),t):void 0},g=function(t){return a("Entering: Leaving Event fired, type: "+t.type+", element: "+t.target+", class: "+e(t.target).attr("class")+" and id: "+t.target.id),"beforeunload"==t.type&&i.doubleunloadfix?(a("Skip this unload, Firefox bug triggers the unload event multiple times"),i.doubleunloadfix=!1,!1):e(t.target).hasClass(i.ignoreClass)||m(t)?(a("Leaving: Element has ignore class or has target='_blank'"),t.isDefaultPrevented()||v(),!1):i.deciding?(a("Leaving: Already in the deciding process"),!1):t.isDefaultPrevented()?(a("Leaving: Event has been stopped elsewhere"),!1):i.isDirty()?"submit"==t.type&&e(t.target).dirtyForms("isDirty")?(a("Leaving: Form submitted is a dirty form"),t.isDefaultPrevented()||v(),!0):(i.dialog&&(i.deciding=!0,i.decidingEvent=t,a("Setting deciding active"),a("Saving dialog content"),i.dialogStash=i.dialog.stash(),a(i.dialogStash)),e(document).trigger("defer.dirtyforms"),"beforeunload"==t.type?(a("Returning to beforeunload browser handler with: "+i.message),i.message):void(i.dialog&&(t.preventDefault(),t.stopImmediatePropagation(),e(t.target).is("form")&&e(t.target).parents(i.dialog.selector).length>0?(a("Stashing form"),i.formStash=e(t.target).clone(!0).hide()):i.formStash=!1,a("Deferring to the dialog"),i.dialog.fire(e.DirtyForms.message,e.DirtyForms.title),i.dialog.bind()))):(a("Leaving: Not dirty"),t.isDefaultPrevented()||v(),!1)},m=function(t){var i=e(t.target).attr("target");return"string"==typeof i&&(i=i.toLowerCase()),"_blank"===i},h=function(t){i.deciding&&(e(document).trigger("choicecommit.dirtyforms"),e.DirtyForms.choiceContinue?p(t):y(t),e(document).trigger("choicecommitAfter.dirtyforms"))},y=function(t){t.preventDefault(),e(document).trigger("decidingcancelled.dirtyforms"),i.dialog!==!1&&i.dialogStash!==!1&&(a("Refiring the dialog with stashed content"),i.dialog.refire(i.dialogStash.html(),t)),e(document).trigger("decidingcancelledAfter.dirtyforms"),i.dialogStash=!1,i.deciding=i.currentForm=i.decidingEvent=!1},p=function(t){v(),t.preventDefault(),i.dialogStash=!1,e(document).trigger("decidingcontinued.dirtyforms"),b(i.decidingEvent),i.deciding=i.currentForm=i.decidingEvent=!1},v=function(){a("Clearing the beforeunload event"),e(window).unbind("beforeunload",f),window.onbeforeunload=null,e(document).trigger("beforeunload.dirtyforms")},b=function(t){switch(e(document).trigger("beforeRefire.dirtyforms"),e(document).trigger("beforeunload.dirtyforms"),t.type){case"click":a("Refiring click event");var n=new jQuery.Event("click");if(e(t.target).trigger(n),!n.isDefaultPrevented()){var r=e(t.target).closest("[href]");return a("Sending location to "+r.attr("href")),void(void 0!==r.attr("href")&&(location.href=r.attr("href")))}break;default:a("Refiring "+t.type+" event on "+t.target);var o;i.formStash?(a("Appending stashed form to body"),o=i.formStash,e("body").append(o)):(o=e(t.target),o.is("form")||(o=o.closest("form"))),o.trigger(t.type)}}});
!function(e){"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof exports?module.exports=e(require("jquery")):e(jQuery)}(function(e){if("function"!=typeof e(document).on){if("function"!=typeof e(document).delegate)throw"jQuery 1.4.2 or higher is required by jquery.dirtyforms";e.fn.on=function(t,i,n,o){return e(this).delegate(i,t,n,o)}}e.extend({DirtyForms:{message:"You've made changes on this page which aren't saved. If you leave you will lose these changes.",title:"Are you sure you want to do that?",dirtyClass:"dirty",listeningClass:"dirtylisten",ignoreClass:"ignoredirty",choiceContinue:!1,helpers:[],dialog:{selector:"#facebox .content",fire:function(t,i){var n="<h1>"+i+"</h1><p>"+t+'</p><p><a href="#" class="ignoredirty button medium red continue">Continue</a><a href="#" class="ignoredirty button medium cancel">Stop</a>';e.facebox(n)},bind:function(){var t=function(t){return function(i){("keydown"!==i.type||"keydown"===i.type&&27===i.keyCode)&&(e(document).trigger("close.facebox"),t(i))}};e(document).bind("keydown.facebox",t(i.decidingCancel)),e("#facebox .cancel, #facebox .close, #facebox_overlay").click(t(i.decidingCancel)),e("#facebox .continue").click(t(i.decidingContinue))},stash:function(){var t=e("#facebox");return""===e.trim(t.html())||"block"!=t.css("display")?!1:e("#facebox .content").clone(!0)},refire:function(t,i){e.facebox(t)}},isDirty:function(){return e(":dirtylistening").dirtyForms("isDirty")},disable:function(){e("html").addClass(i.ignoreClass)},ignoreParentDocs:function(){i.watchParentDocs=!1},choiceCommit:function(t){i.deciding&&(e(document).trigger("choicecommit.dirtyforms"),i.choiceContinue?i.decidingContinue(t):i.decidingCancel(t),e(document).trigger("choicecommitAfter.dirtyforms"))},isDeciding:function(){return i.deciding},decidingContinue:function(t){m(),t.preventDefault(),i.dialogStash=!1,e(document).trigger("decidingcontinued.dirtyforms"),h(i.decidingEvent),i.deciding=i.currentForm=i.decidingEvent=!1},decidingCancel:function(t){t.preventDefault(),e(document).trigger("decidingcancelled.dirtyforms"),i.dialog!==!1&&i.dialogStash!==!1&&"function"==typeof i.dialog.refire&&i.dialog.refire(i.dialogStash.html(),t),e(document).trigger("decidingcancelledAfter.dirtyforms"),i.deciding=i.currentForm=i.decidingEvent=i.dialogStash=!1}}}),e.extend(e.expr[":"],{dirtylistening:function(t){return e(t).hasClass(i.listeningClass)},dirty:function(t){return e(t).hasClass(i.dirtyClass)}});var t={init:function(){d();var t="textarea,input:not([type='checkbox'],[type='radio'],[type='button'],[type='image'],[type='submit'],[type='reset'],[type='file'],[type='search'])",s=e(document.activeElement);return s.is(t)&&(i.focused.element=s,i.focused.value=s.val()),this.each(function(s){var d=e(this);d.is("form")&&d.addClass(i.listeningClass).on("focus change",t,r).on("change","input[type='checkbox'],input[type='radio'],select",o).on("click","input[type='reset']",n)})},isDirty:function(){var t=!1,n=this;return s()?(t=!0,!0):(this.each(function(n){return e(this).hasClass(i.dirtyClass)?(t=!0,!0):void 0}),e.each(i.helpers,function(e,i){return"isDirty"in i&&i.isDirty(n)?(t=!0,!0):"isNodeDirty"in i&&i.isNodeDirty(n)?(t=!0,!0):void 0}),t)},setDirty:function(){return this.each(function(t){e(this).addClass(i.dirtyClass).parents("form").addClass(i.dirtyClass)})},setClean:function(){return i.focused={element:!1,value:!1},this.each(function(t){var n=this,o=e(this);if(o.removeClass(i.dirtyClass),o.is("form"))o.find(":dirty").removeClass(i.dirtyClass);else{var r=o.parents("form");0===r.find(":dirty").length&&r.removeClass(i.dirtyClass)}e.each(i.helpers,function(e,t){"setClean"in t&&t.setClean(n)})})}};e.fn.dirtyForms=function(i){return t[i]?t[i].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof i&&i?void e.error("Method "+i+" does not exist on jQuery.dirtyForms"):t.init.apply(this,arguments)},e.fn.setDirty=function(){return this.dirtyForms("setDirty")},e.fn.isDirty=function(){return this.dirtyForms("isDirty")},e.fn.cleanDirty=function(){return this.dirtyForms("setClean")};var i=e.DirtyForms=e.extend({watchParentDocs:!0,exitBound:!1,formStash:!1,dialogStash:!1,deciding:!1,decidingEvent:!1,currentForm:!1,focused:{element:!1,value:!1}},e.DirtyForms),n=function(){e(this).parents("form").dirtyForms("setClean"),i.onFormCheck&&i.onFormCheck()},o=function(){g(e(this))||(e(this).dirtyForms("setDirty"),i.onFormCheck&&i.onFormCheck())},r=function(){var t=e(this);s()&&!g(t)&&(i.focused.element.dirtyForms("setDirty"),i.onFormCheck&&i.onFormCheck()),i.focused.element=t,i.focused.value=t.val()},s=function(){return i.focused.element&&i.focused.element.val()!==i.focused.value},d=function(){if(!i.exitBound){var t=top!==self;e(document).on("click","a[href]",a).on("submit","form",u),e(window).bind("beforeunload",f),i.watchParentDocs&&t&&(e(top.document).on("click","a[href]",a).on("submit","form",u),e(top.window).bind("beforeunload",f)),i.exitBound=!0}},c=function(){var t=e();return e.each(i.helpers,function(e,i){"ignoreAnchorSelector"in i&&(t=t.add(i.ignoreAnchorSelector))}),t},a=function(t){e(this).is(c())||y(e(this))||l(t)},u=function(e){i.currentForm=this,l(e)},f=function(e){var t=l(e);return t&&i.doubleunloadfix!==!0&&(i.deciding=!1),i.doubleunloadfix=!0,setTimeout(function(){i.doubleunloadfix=!1},200),"string"==typeof t?(e=e||window.event,e&&(e.returnValue=t),t):void 0},l=function(t){var n=e(t.target),o=t.type;return t.isDefaultPrevented()?!1:"beforeunload"==o&&i.doubleunloadfix?(i.doubleunloadfix=!1,!1):g(n)?(m(),!1):i.deciding?!1:i.isDirty()?"submit"==o&&n.dirtyForms("isDirty")?(m(),!0):(e(document).trigger("defer.dirtyforms"),"beforeunload"==o?i.message:void(i.dialog&&(i.deciding=!0,i.decidingEvent=t,"function"==typeof i.dialog.stash&&(i.dialogStash=i.dialog.stash()),t.preventDefault(),t.stopImmediatePropagation(),i.formStash="string"==typeof i.dialog.selector&&n.is("form")&&n.parents(i.dialog.selector).length>0?n.clone(!0).hide():!1,i.dialog.fire(i.message,i.title),"function"==typeof i.dialog.bind&&i.dialog.bind()))):(m(),!1)},y=function(e){var t=e.attr("target");return"string"==typeof t?"_blank"===t.toLowerCase():!1},g=function(e){return e.closest("."+i.ignoreClass).length>0},m=function(){e(window).unbind("beforeunload",f),window.onbeforeunload=null,e(document).trigger("beforeunload.dirtyforms")},h=function(t){if(e(document).trigger("beforeRefire.dirtyforms").trigger("beforeunload.dirtyforms"),"click"===t.type){var n=new e.Event("click");if(e(t.target).trigger(n),!n.isDefaultPrevented()){var o=e(t.target).attr("href");return void(location.href=o)}}else{var r;i.formStash?(r=i.formStash,e("body").append(r)):r=e(t.target).closest("form"),r.trigger(t.type)}}});
//# sourceMappingURL=jquery.dirtyforms.min.js.map
{
"name": "jquery.dirtyforms",
"version": "1.1.0",
"version": "1.2.0",
"description": "Dirty Forms is a jQuery plugin to help prevent users from losing data when editing forms.",

@@ -5,0 +5,0 @@ "main": "jquery.dirtyforms.min.js",

@@ -19,4 +19,22 @@ [![jquery-dirtyforms MyGet Build Status](https://www.myget.org/BuildSource/Badge/jquery-dirtyforms?identifier=193d9dab-a526-484e-8062-9a960322f246)](https://www.myget.org/)

Prerequisites
---------------------------------
## Features
- Works on forms of any size.
- Wide browser support.
- 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 common rich text editor frameworks (optional).
- Small size (about 6k when minified).
- Universal Module Definition (UMD) support for AMD, JSCommon, Browserify, etc.
- Hosted on jsDelivr CDN for easy combining of modules into a single HTTP request.
## Supported Browsers
- IE 8+
- Google Chrome (all versions since 2010)
- Firefox 4+
- Safari 5+
## Prerequisites
- [jQuery](http://jquery.com) (>= 1.4.2)

@@ -35,3 +53,3 @@ - [jquery.facebox](https://github.com/NightOwl888/facebox) (>= 1.2.0)

```HTML
<script type="text/javascript" src="//cdn.jsdelivr.net/jquery.dirtyforms/1.1.0/jquery.dirtyforms.min.js"></script>
<script type="text/javascript" src="//cdn.jsdelivr.net/jquery.dirtyforms/1.2.0/jquery.dirtyforms.min.js"></script>
```

@@ -41,3 +59,3 @@

```HTML
<script type="text/javascript" src="//cdn.jsdelivr.net/g/jquery@1.11.3,jquery.facebox,jquery.dirtyforms@1.1.0"></script>
<script type="text/javascript" src="//cdn.jsdelivr.net/g/jquery@1.11.3,jquery.facebox,jquery.dirtyforms@1.2.0"></script>
```

@@ -79,3 +97,3 @@

```HTML
<script type="text/javascript" src="//cdn.jsdelivr.net/jquery.dirtyforms/1.1.0/jquery.dirtyforms.min.js.map"></script>
<script type="text/javascript" src="//cdn.jsdelivr.net/jquery.dirtyforms/1.2.0/jquery.dirtyforms.min.js.map"></script>
```

@@ -85,3 +103,3 @@

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

@@ -92,7 +110,4 @@ ```

For NuGet, this file is not included in the package, but you can get it from [here](https://github.com/NightOwl888/jquery.dirtyforms.dist/blob/master/jquery.dirtyforms.min.js.map) if you really need it.
## Usage
Usage
---------------------------------
```javascript

@@ -116,81 +131,166 @@ // Enable for all forms

Options
---------------------------------
## Ignoring Things
Simply add the value of `$.DirtyForms.ignoreClass` to any elements you wish to ignore, and Dirty Forms will ignore them.
```javascript
$('#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.
```HTML
<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);
```
## Options
The following options are available to set via **$.DirtyForms.OPTIONNAME = OPTIONVALUE** or get via **OPTIONVALUE = $.DirtyForms.OPTIONNAME**
**debug**: set to true to log messages to the firebug console (or alert if you don't have firebug).
| Name | Type | Default | Description |
|---|---|---|---|
| **title** | string | `Are you sure you want to do that?` | Sets the title of the dialog (JavaScript/CSS dialog only). |
| **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). |
| **ignoreClass** | string | `ignoredirty` | 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. |
| **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. |
| **choiceContinue** | bool | `false` | Set to true from the dialog to indicate to continue execution of the link or button that was clicked or false to cancel. Execution of the choice will be deferred until `choiceCommit()` is called. |
| **helpers** | string | `[]` | An array for helper objects. See [Helpers](#helpers) below. |
| **dialog** | string | A Facebox dialog object | An object that will be used to fire the JavaScript/CSS dialog. See [Dialogs](#dialogs) below. |
| **debug** | bool | `false` | Set to true to log messages to the console (or firebug). If your browser doesn't support this, there will be alerts instead. |
**message**: The dialog message to be sent.
> **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.
**title**: The modal dialog title.
## Public Methods
**dirtyClass**: The class applied to elements when they're considered dirty.
#### ```$.DirtyForms.isDirty()```
**listeningClass**: The class applied to elements that are having their inputs monitored for change.
Returns true if any watched elements (that are not ignored) are considered dirty.
**ignoreClass**: The class applied to elements that you wish to allow the action to be continue even when the form is dirty.
**choiceContinue**: Set to true from the dialog to indicate to continue execution of the link or button that was clicked or false to cancel. Execution of the choice will be deferred until *choiceCommit()* is called.
#### ```$('form#my-watched-form').dirtyForms()```
**helpers**: An array for helper objects. See Helpers below.
Starts watching the supplied elements (forms) for descendant input changes. To watch all forms, simply use the `'form'` selector.
**dialog**: See Dialogs below.
```javascript
$('form').dirtyForms();
```
Public Methods
---------------------------------
**$.DirtyForms.isDirty()** will return true if any watched elements are considered dirty.
*Syntax:* ***if($.DirtyForms.isDirty())***
#### `var isDirty = $('form#my-watched-form').dirtyForms('isDirty')`
**$.fn.dirtyForms()** will start watching the supplied elements for descendant input changes.
*Syntax:* ***$('form').dirtyForms();***
Returns true if the provided element is considered dirty.
**$.fn.dirtyForms('isDirty')** will return true if the provided element is considered dirty.
*Syntax:* ***if($('form#accountform').dirtyForms('isDirty'))***
**$.fn.dirtyForms('setDirty')** will set the provided element as dirty.
*Syntax:* ***$('form#accountform').dirtyForms('setDirty');***
#### `$('form#my-watched-form').dirtyForms('setDirty')`
**$.fn.dirtyForms('setClean')** will mark the provided form or element as clean.
*Syntax:* ***$('form#accountform').dirtyForms('setClean');***
Marks the provided element as dirty by adding the `dirtyClass`. If the element is within a form, the form is also marked dirty with the `dirtyClass`.
**$.DirtyForms.choiceCommit()** should be called after the dialog is closed to commit the choice that was specified in *$.DirtyForms.choiceContinue*. This method will cascade the call to either *$.DirtyForms.decidingContinue()* or *$.DirtyForms.decidingCancel()* automatically, so there is no need to use them in conjunction with this method. An event object is required to be passed as a parameter.
*Syntax:* ***$.DirtyForms.choiceCommit(event);***
**$.DirtyForms.decidingContinue()** should be called from the dialog to refire the event and continue following the link or button that was clicked. An event object is required to be passed as a parameter.
*Syntax:* ***$.DirtyForms.decidingContinue(event);***
**$.DirtyForms.decidingCancel()** should be called from the dialog to indicate not to move on to the page of the button or link that was clicked. An event object is required to be passed as a parameter.
*Syntax:* ***$.DirtyForms.decidingCancel(event);***
#### `$('form#my-watched-form').dirtyForms('setClean')`
**$.DirtyForms.isDeciding()** will return true if the dialog has fired and neither *$.DirtyForms.decidingCancel()* or *$.DirtyForms.decidingContinue()* has yet been called.
*Syntax:* ***if($.DirtyForms.isDeciding())***
Marks the provided form or element as clean. In other words, removes the `dirtyClass`. If the operation marks all elements within a form clean, it will also mark the form clean.
Obsolete Public Methods
---------------------------------
**IMPORTANT**: The following methods have been completely removed from the public interface to avoid collisions with other JavaScript code. This is a **breaking change**. Please update your code before getting the current version.
#### `$.DirtyForms.choiceCommit( event )`
*decidingContinue()*
Please use ***$.DirtyForms.decidingContinue()*** instead
This method should be called after the dialog is closed to commit the choice that was specified in *$.DirtyForms.choiceContinue*. This method will cascade the call to either `$.DirtyForms.decidingContinue()` or `$.DirtyForms.decidingCancel()` automatically, so there is no need to use them in conjunction with this method. An event object is required to be passed as a parameter. See the [Dialogs](#dialogs) section for an example of how to use this method.
*decidingCancel()*
Please use ***$.DirtyForms.decidingCancel()*** instead
The following methods have been deprecated and will eventually be removed from dirtyForms. Please update your code to access the new methods as shown here. This was done to conform to jQuery plugin authoring guidelines (http://docs.jquery.com/Plugins/Authoring).
#### `$.DirtyForms.decidingContinue( event )`
*$.fn.isDirty()* -- former syntax: *$('form#accountform').isDirty()*
Please use ***$('form#accountform').dirtyForms('isDirty')*** instead
This method should be called from the dialog to refire the event and continue following the link or button that was clicked. An event object is required to be passed as a parameter.
*$.fn.setDirty()* -- former syntax: *$('form#accountform').setDirty()*
Please use ***$('form#accountform').dirtyForms('setDirty')*** instead
*$.fn.cleanDirty()* -- former syntax: *$('form#accountform').cleanDirty()*
Please use ***$('form#accountform').dirtyForms('setClean')*** instead
#### `$.DirtyForms.decidingCancel( event )`
Helpers
---------------------------------
This method should be called from the dialog to indicate not to move on to the page of the button or link that was clicked. An event object is required to be passed as a parameter. Note that this method will automatically cancel the event.
#### `$.DirtyForms.isDeciding()`
This method will return true if the dialog has fired and neither `$.DirtyForms.decidingCancel()` or `$.DirtyForms.decidingContinue()` has yet been called.
#### `$('form#my-watched-form').isDirty()` (Deprecated)
Please use `$('form#my-watched-form').dirtyForms('isDirty')` instead.
#### `$('form#my-watched-form').setDirty()` (Deprecated)
Please use `$('form#my-watched-form').dirtyForms('setDirty')` instead.
#### `$('form#my-watched-form').cleanDirty()` (Deprecated)
Please use `$('form#my-watched-form').dirtyForms('setClean')` instead.
#### `$.DirtyForms.disable()` (Deprecated)
Please use `$('html').addClass($.DirtyForms.ignoreClass)` instead.
## Triggers
Simply bind a function to any of these hooks to respond to the corresponding trigger.
```javascript
$(document).bind('choicecommit.dirtyforms', function() {
...stuff to do before commiting the user's choice...
});
```
**decidingcancelled.dirtyforms**: Raised when the *decidingCancel()* method is called before it runs any actions.
**decidingcancelledAfter.dirtyforms**: Raised when the *decidingCancel()* method is called after it runs all actions.
**decidingcontinued.dirtyforms**: Raised when the *decidingContinue()* method is called before it runs any actions.
**choicecommit.dirtyforms**: Raised when the *choiceCommit* method is called before it runs any actions.
**choicecommitAfter.dirtyforms**: Raised when the *choiceCommit* method is called after it runs all actions.
**defer.dirtyforms**: Raised prior to showing the dialog box to the user.
**beforeRefire.dirtyforms**: Raised before the original event is refired after a user chooses to leave the page.
**beforeunload.dirtyforms**: 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.
-----
You can attach callbacks to the **decidingcancelled.dirtyforms** and **decidingcontinued.dirtyforms** custom events. These events are called when the cancel, or continue method on the modal dialog is called (when the user clicks either continue, or cancel).
These triggers are not available when used with the browser fallback dialog method.
Also available is **defer.dirtyforms** for accessing elements on the page prior to the dialog box alerting the user is called, and **beforeRefire.dirtyforms**, called before the original event is refired after a user chooses to leave the page (useful if you need to do things like save data back to fields which is normally part of event propagation - ala tinyMce).
## Selectors
**:dirty** will select all elements with the dirty class attached. form:dirty would be all forms that are currently dirty for example.
**:dirtylistening** will select all elements that has the listening class attached. This should be all forms that are currently listening for change.
## 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, 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 when you're using replacement inputs or textarea, such as with tinymce. To enable the tinymce helper, simply include the helpers/tinymce.js file. Same goes for helpers/ckeditor.js.
This is useful when you're using replacement inputs or textarea, such as with TinyMCE or CKEditor. See [Available Helpers](#available-helpers).

@@ -203,19 +303,65 @@ #### Available Helpers

#### Documentation for Rolling-Your-Own Helper
#### Custom Helpers
**MEMBERS (All Optional)**
Helpers can be created by implementing and then pushing the helper to the `$.DirtyForms.helpers` array.
**isDirty(node)** - method - Should return the dirty status of the helper.
```javascript
$.DirtyForms.helpers.push(myHelper);
```
**setClean(node)** - method - Should reset the dirty status of the helper so *isDirty(node)* will return false the next time it is called.
##### Members (All Optional)
**ignoreAnchorSelector** - property - A jQuery selector of any anchor elements to exclude from activating the dialog. Non-anchors will be ignored. This works similarly to putting the ignoreClass on a specific anchor, but will always ignore the anchors if your helper is included.
#### `isDirty( form )`
Should return the dirty status of the helper. You can use jQuery to select all of the helpers within the form and test their dirty status.
The node parameter is typically an individual form element. To respect the way jQuery selectors work, all children of the node as well as the node itself should have your custom **isDirty()** and **setClean()** logic applied.
```javascript
isDirty: function (form) {
var isDirty = false;
// Search for all tinymce elements inside the given form
$(form).find(':tinymce').each(function () {
**IMPORTANT**: Support for the former *isNodeDirty(node)* method has been deprecated. Please update any custom helpers to use **isDirty(node)**. This change was made to make helpers easier to understand and use.
if ($(this).tinymce().isDirty()) {
isDirty = true;
return true;
}
});
return isDirty;
}
```
#### `setClean( form )`
Should reset the dirty status of the helper so `isDirty(form)` will return false the next time it is called.
```javascript
setClean: function (form) {
// Search for all tinymce elements inside the given form
$(form).find(':tinymce').each(function () {
if ($(this).tinymce().isDirty()) {
//Force not dirty state
$(this).tinymce().isNotDirty = 1;
}
});
}
```
#### `ignoreAnchorSelector` (Property)
A jQuery selector of any anchor elements to exclude from activating the dialog. Non-anchors will be ignored. This works similarly to putting the ignoreClass on a specific anchor, but will always ignore the anchors if your helper is included.
```javascript
ignoreAnchorSelector: '.mceEditor a,.mceMenu a'
```
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.
```javascript
// Example helper, the form is always considered dirty

@@ -257,32 +403,126 @@ (function($){

Dialogs
---------------------------------
The default facebox dialog can be overriden by setting a new dialog object.
See the [TinyMCE Helper Source Code](https://github.com/snikch/jquery.dirtyforms/blob/master/jquery.dirtyforms/helpers/tinymce.js) for another complete example.
The dialog object **requires** you to set four methods and a property.
## Dialogs
Methods:
The default facebox dialog can be overriden by setting a new dialog object, and providing implementations for the following members.
- **fire**
- **refire**
- **bind**
- **stash**
#### `fire(message, title)` (Required)
Property:
Opens the dialog.
- **selector**
##### message
The main message to show in the dialog.
##### title
The title for the header of the dialog.
#### `bind()` (Optional)
Binds the continue and cancel functions to the correct links. For some dialog frameworks, it is simpler just to do all of the work in the fire function. In that case, this function can be omitted. Note that this function is called immediately after fire is called.
It is important to trap the `ESC` key (character code 27) in the keydown event in order to ensure it doesn't cause erratic behavior.
```javascript
// Selector is a selector string for dialog content. Used to determine if event targets are inside a dialog
// Trap the escape key and force a close.
// Cancel it so the dialog framework doesn't intercept it.
$(document).keydown(function(e) {
// Look for the ESC key
if (e.keyCode == 27) {
// Cancel the event so it doesn't bubble
e.preventDefault();
// Close the dialog
dlg.dialog('close');
// Cancel the decision
$.DirtyForms.decidingCancel(e);
// Return false
return false;
}
// Make sure you don't return false here
});
```
#### `stash()` (Optional)
Stash returns the current contents of a dialog to be refired after the confirmation. Use to store the current dialog (from the application), when it's about to be replaced with the confirmation dialog. This function can be omitted (or return false) if you don't wish to stash anything.
See the [Modal Dialog Stashing](#modal-dialog-stashing) section for more information.
#### `refire(content, ev)` (Optional)
Refire handles closing an existing dialog AND fires a new one. You can omit this method (or return false) if you don't need to use stashing/refiring.
See the [Modal Dialog Stashing](#modal-dialog-stashing) section for more information.
#### `selector` (Optional Property)
A jQuery selector used to select the element that 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.
See the [Modal Dialog Stashing](#modal-dialog-stashing) section for more information.
#### `continueButtonText` (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 continue button.
If contributing a new dialog module, please include this property and set the default value to `Leave This Page`.
#### `cancelButtonText` (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 cancel button.
If contributing a new dialog module, please include this property and set the default value to `Stay Here`.
#### `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).
#### `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 doesn't make sense to override styles of an existing theme.
#### Dialog Example
```javascript
// Selector is a selector string for dialog content.
// Used to select the element that will be cloned and put into the stash.
selector : '#facebox .content',
// Fire starts the dialog
fire : function(message, title){
var content = '<h1>' + title + '</h1><p>' + message + '</p><p><a href="#" class="ignoredirty continue">Continue</a><a href="#" class="ignoredirty cancel">Stop</a>';
var content = '<h1>' + title + '</h1><p>' + message + '</p>' +
'<p>' +
'<a href="#" class="' + $.DirtyForms.ignoreClass + ' continue">Continue</a>' +
'<a href="#" class="' + $.DirtyForms.ignoreClass + ' cancel">Stop</a>' +
'</p>';
$.facebox(content);
},
// Bind binds the continue and cancel functions to the correct links
// Bind binds the continue and cancel functions to the correct links. For some dialog
// frameworks, it is simpler just to do all of the work in the fire function.
// In that case, this function can be omitted. Note that this function is called immediately
// after fire is called.
bind : function(){
$(document).bind('keydown.facebox', function (e) {
// Intercept the escape key and send the event to Dirty Forms
if (e.keyCode === 27) {
$(document).trigger('close.facebox');
$.DirtyForms.decidingCancel(e);
}
});
$('#facebox .cancel, #facebox .close').click($.DirtyForms.decidingCancel);

@@ -295,3 +535,14 @@ $('#facebox .continue').click($.DirtyForms.decidingContinue);

// Refire handles closing an existing dialog AND fires a new one
// Stash returns the current contents of a dialog to be refired after the confirmation
// Use to store the current dialog, when it's about to be replaced with the confirmation dialog.
// This function can be omitted (or return false) if you don't wish to stash anything.
stash : function(){
var fb = $('#facebox .content');
return ($.trim(fb.html()) == '' || fb.css('display') != 'block') ?
false :
fb.clone(true);
}
// Refire handles closing an existing dialog AND fires a new one.
// You can omit this method (or return false) if you don't need to use stashing/refiring.
refire : function(content){

@@ -305,17 +556,10 @@ var rebox = function(){

// Stash returns the current contents of a dialog to be refired after the confirmation
// Use to store the current dialog, when it's about to be replaced with the confirmation dialog.
// This function can return false if you don't wish to stash anything.
stash : function(){
var fb = $('#facebox .content');
return ($.trim(fb.html()) == '' || fb.css('display') != 'block') ?
false :
fb.clone(true);
}
```
**fire** accepts a message and title, and is responsible for creating the modal dialog. Note the two classes on each link. In the **bind** method you will see that we bind the *$.DirtyForms.decidingCancel* method to the .cancel link and the .close link, and we bind *$.DirtyForms.decidingContinue* to the .continue link. You must bind both *$.DirtyForms.decidingCancel* and *$.DirtyForms.decidingContinue* in the **bind** method.
`fire()` accepts a `message` and `title`, and is responsible for creating the modal dialog. Note the two classes on each link. In the `bind()` method you will see that we bind the `$.DirtyForms.decidingCancel(e)` method to the `.cancel` link and the `.close` link, and we bind `$.DirtyForms.decidingContinue(e)` to the `.continue` link. You should bind both `$.DirtyForms.decidingCancel` and `$.DirtyForms.decidingContinue` in the `bind()` method (or alternatively you can create the entire dialog in the `fire()` method).
Alternatively, another pattern is supported for modal dialogs where the continuing execution of the event is not allowed until after the dialog is closed (such as when using jQuery UI dialog in modal mode). The pattern uses a boolean property named **$.DirtyForms.choiceContinue** to indicate the dialog choice and a method named **$.DirtyForms.choiceCommit()** to execute the choice. Here is an example of that pattern in action using a modal jQuery UI dialog:
> Be sure to register the keydown event for the escape key and pass the call on to `$.DirtyForms.decidingCancel(e)` or the default browser fallback will fail when the user hits the escape key.
Alternatively, another pattern is supported for modal dialogs where the continuing execution of the event is not allowed until after the dialog is closed (such as when using jQuery UI dialog in modal mode). The pattern uses a boolean property named `$.DirtyForms.choiceContinue` to indicate the dialog choice and a method named `$.DirtyForms.choiceCommit(e)` to execute the choice. Here is an example of that pattern in action using a modal jQuery UI dialog:
```javascript

@@ -325,3 +569,3 @@ $.DirtyForms.dialog = {

fire: function(message, dlgTitle) {
$('#unsavedChanges').dialog({title: dlgTitle, width: 350, modal: true});
$('#unsavedChanges').dialog({title: dlgTitle, width: 400, modal: true});
$('#unsavedChanges').html(message);

@@ -333,5 +577,5 @@ },

{
text: "Stay Here",
text: "Leave This Page",
click: function(e) {
$.DirtyForms.choiceContinue = false;
$.DirtyForms.choiceContinue = true;
$(this).dialog('close');

@@ -341,5 +585,5 @@ }

{
text: "Leave This Page",
text: "Stay Here",
click: function(e) {
$.DirtyForms.choiceContinue = true;
$.DirtyForms.choiceContinue = false;
$(this).dialog('close');

@@ -353,70 +597,40 @@ }

});
},
refire: function(content) {
return false;
},
stash: function() {
return false;
// Intercept the escape key and cancel the event.
// Calling dialog('close') will fire the 'dialogclose' event,
// which will in turn commit the choice to Dirty Forms.
$(document).bind('keydown', function(e) {
if (e.keyCode == 27) {
e.preventDefault();
$.DirtyForms.choiceContinue = false;
$('#unsavedChanges').dialog('close');
}
});
}
}
// Dynamically add the div element to the page for the dialog (a hidden div).
$('body').append("<div id='unsavedChanges' style='display: none'></div>");
```
Note that calling *$.DirtyForms.choiceContinue = false;* isn't strictly necessary as false is the default, but is shown in the example to make it more clear. Also note that a choice will always be committed using this method whether the user clicks a button or uses the "X" icon to close the dialog because 'dialogclose' is called in every case the dialog is closed.
Note that calling `$.DirtyForms.choiceContinue = false;` isn't strictly necessary as `false` is the default, but is shown in the example to make it more clear. Also note that a choice will always be committed using this method whether the user clicks a button or uses the "X" icon to close the dialog because the `'dialogclose'` event is called in every case the dialog is closed.
The *$.DirtyForms.choiceCommit()* method automatically calls either *$.DirtyForms.decidingCancel()* or *$.DirtyForms.decidingContinue()* depending on the state of *$.DirtyForms.choiceContinue*, so there is no need to call them manually.
The `$.DirtyForms.choiceCommit()` method automatically calls either `$.DirtyForms.decidingCancel()` or `$.DirtyForms.decidingContinue()` depending on the state of `$.DirtyForms.choiceContinue`, so there is no need to call them manually.
If you don't want to use a dialog at all, simply pass in false instead of an object.
If you don't want to use a dialog at all, simply pass in `false` instead of an object. Dirty Forms will then use the default browser dialog.
```javascript
$.DirtyForms.dialog = {
selector : '#mylightbox .body',
fire : function (){ ... },
refire : function (){ ... },
stash : function (){ ... },
bind : function (){ ... }
}
$.DirtyForms.dialog = false;
```
Triggers
---------------------------------
### Modal Dialog Stashing
Simply bind a function to any of these hooks to respond to the corresponding trigger.
Stashing is meant for the following scenario.
```javascript
$(document).bind('choicecommit.dirtyforms', function() {
...stuff to do before commiting the user's choice...
});
```
1. A form is hosted inside a modal dialog.
2. The dialog framework you use doesn't allow overlaying a modal dialog on top of another modal dialog.
**decidingcancelled.dirtyforms**: Raised when the *decidingCancel()* method is called before it runs any actions.
You don't need to use stashing if either of the above (or both) of the items don't apply to you.
**decidingcancelledAfter.dirtyforms**: Raised when the *decidingCancel()* method is called after it runs all actions.
**decidingcontinued.dirtyforms**: Raised when the *decidingContinue()* method is called before it runs any actions.
**choicecommit.dirtyforms**: Raised when the *choiceCommit* method is called before it runs any actions.
**choicecommitAfter.dirtyforms**: Raised when the *choiceCommit* method is called after it runs all actions.
**defer.dirtyforms**: Raised prior to showing the dialog box to the user.
**beforeRefire.dirtyforms**: Raised before the original event is refired after a user chooses to leave the page.
**beforeunload.dirtyforms**: 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.
You can attach callbacks to the **decidingcancelled.dirtyforms** and **decidingcontinued.dirtyforms** custom events. These events are called when the cancel, or continue method on the modal dialog is called (when the user clicks either continue, or cancel).
These triggers are not available when used with the browser fallback dialog method.
Also available is **defer.dirtyforms** for accessing elements on the page prior to the dialog box alerting the user is called, and **beforeRefire.dirtyforms**, called before the original event is refired after a user chooses to leave the page (useful if you need to do things like save data back to fields which is normally part of event propagation - ala tinyMce).
Selectors
---------------------------------
**:dirty** will select all elements with the dirty class attached. form:dirty would be all forms that are currently dirty for example.
**:dirtylistening** will select all elements that has the listening class attached. This should be all forms that are currently listening for change
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.

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc