jquery-dirtyforms-rails 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +45 -0
- data/Rakefile +2 -0
- data/lib/jquery-dirtyforms-rails.rb +10 -0
- data/lib/jquery-dirtyforms-rails/version.rb +7 -0
- data/vendor/assets/javascripts/jquery.dirtyforms.dialogs.blockui.js +97 -0
- data/vendor/assets/javascripts/jquery.dirtyforms.dialogs.bootstrap.js +110 -0
- data/vendor/assets/javascripts/jquery.dirtyforms.dialogs.facebox.js +126 -0
- data/vendor/assets/javascripts/jquery.dirtyforms.dialogs.jquery-ui.js +96 -0
- data/vendor/assets/javascripts/jquery.dirtyforms.dialogs.pnotify.js +159 -0
- data/vendor/assets/javascripts/jquery.dirtyforms.helpers.alwaysdirty.js +27 -0
- data/vendor/assets/javascripts/jquery.dirtyforms.helpers.ckeditor.js +67 -0
- data/vendor/assets/javascripts/jquery.dirtyforms.helpers.tinymce.js +158 -0
- data/vendor/assets/javascripts/jquery.dirtyforms.js +596 -0
- metadata +60 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
/*!
|
2
|
+
PNotify dialog module (for jQuery Dirty Forms) | v | github.com/snikch/jquery.dirtyforms
|
3
|
+
(c) 2015-2016 Shad Storhaug
|
4
|
+
License MIT
|
5
|
+
*/
|
6
|
+
|
7
|
+
(function($, window, document, undefined) {
|
8
|
+
// Can't use ECMAScript 5's strict mode because several apps
|
9
|
+
// including ASP.NET trace the stack via arguments.caller.callee
|
10
|
+
// and Firefox dies if you try to trace through "use strict" call chains.
|
11
|
+
// See jQuery issue (#13335)
|
12
|
+
// Support: Firefox 18+
|
13
|
+
//"use strict";
|
14
|
+
|
15
|
+
var modal_overlay,
|
16
|
+
notice,
|
17
|
+
isPN1 = typeof PNotify !== 'function';
|
18
|
+
|
19
|
+
$.DirtyForms.dialog = {
|
20
|
+
|
21
|
+
// Custom properties and methods to allow overriding (may differ per dialog)
|
22
|
+
title: 'Are you sure you want to do that?',
|
23
|
+
class: 'dirty-dialog',
|
24
|
+
proceedButtonText: 'Leave This Page',
|
25
|
+
stayButtonText: 'Stay Here',
|
26
|
+
styling: 'bootstrap3',
|
27
|
+
width: '330',
|
28
|
+
|
29
|
+
// Typical Dirty Forms Properties and Methods
|
30
|
+
open: function (choice, message, ignoreClass) {
|
31
|
+
var content = $.extend(true, {}, {
|
32
|
+
title: this.title,
|
33
|
+
hide: false,
|
34
|
+
styling: this.styling,
|
35
|
+
width: this.width,
|
36
|
+
|
37
|
+
// 3.x and 2.x confirm buttons
|
38
|
+
confirm: {
|
39
|
+
confirm: true,
|
40
|
+
align: 'center',
|
41
|
+
buttons: [
|
42
|
+
{
|
43
|
+
text: this.proceedButtonText,
|
44
|
+
addClass: 'dirty-proceed ' + ignoreClass
|
45
|
+
},
|
46
|
+
{
|
47
|
+
text: this.stayButtonText,
|
48
|
+
addClass: 'dirty-stay ' + ignoreClass
|
49
|
+
}
|
50
|
+
]
|
51
|
+
},
|
52
|
+
// 3.x don't use history
|
53
|
+
history: {
|
54
|
+
history: false
|
55
|
+
},
|
56
|
+
// 3.x modal dialog
|
57
|
+
addclass: 'stack-modal ' + this.class,
|
58
|
+
stack: { 'dir1': 'down', 'dir2': 'right', 'modal': true },
|
59
|
+
|
60
|
+
// 3.x and 2.x hide closer and sticker
|
61
|
+
buttons: {
|
62
|
+
closer: false,
|
63
|
+
sticker: false
|
64
|
+
},
|
65
|
+
// 1.x hide closer and sticker
|
66
|
+
closer: false,
|
67
|
+
sticker: false,
|
68
|
+
|
69
|
+
// NOTE: Animate does not seem to work in 3.x in conjunction with confirm,
|
70
|
+
// but this is being added so the settings can be supplied externally should
|
71
|
+
// this issue be fixed. https://github.com/sciactive/pnotify/issues/224
|
72
|
+
animate: this.animate === undefined ? undefined : this.animate,
|
73
|
+
|
74
|
+
text: !isPN1 ? message :
|
75
|
+
'<span class="' + this.class + '">' +
|
76
|
+
'<p>' + message + '</p>' +
|
77
|
+
'<span style="display:block;text-align:center;">' +
|
78
|
+
'<button type="button" class="btn btn-default dirty-proceed ' + ignoreClass + '">' + this.proceedButtonText + '</button> ' +
|
79
|
+
'<button type="button" class="btn btn-default dirty-stay ' + ignoreClass + '">' + this.stayButtonText + '</button>' +
|
80
|
+
'</span>' +
|
81
|
+
'</span>',
|
82
|
+
|
83
|
+
// Not supported in 3.x
|
84
|
+
before_open: function (PNotify) {
|
85
|
+
if (!isPN1) {
|
86
|
+
// Position this notice in the center of the screen.
|
87
|
+
PNotify.get().css({
|
88
|
+
"top": ($(window).height() / 2) - (PNotify.get().height() / 2),
|
89
|
+
"left": ($(window).width() / 2) - (PNotify.get().width() / 2)
|
90
|
+
});
|
91
|
+
}
|
92
|
+
|
93
|
+
// Make a modal screen overlay.
|
94
|
+
if (modal_overlay) modal_overlay.fadeIn("fast");
|
95
|
+
else modal_overlay = $("<div />", {
|
96
|
+
"class": "ui-widget-overlay",
|
97
|
+
"css": {
|
98
|
+
"display": "none",
|
99
|
+
"position": "fixed",
|
100
|
+
"top": "0",
|
101
|
+
"bottom": "0",
|
102
|
+
"right": "0",
|
103
|
+
"left": "0"
|
104
|
+
}
|
105
|
+
}).appendTo("body").fadeIn("fast");
|
106
|
+
}
|
107
|
+
});
|
108
|
+
|
109
|
+
// Patch for PNotify 1.x
|
110
|
+
notice = !isPN1 ? new PNotify(content) : $.pnotify(content);
|
111
|
+
|
112
|
+
// Bind Events
|
113
|
+
choice.bindEnterKey = true;
|
114
|
+
choice.proceedSelector = '.' + this.class + ' .dirty-proceed';
|
115
|
+
choice.staySelector = '.' + this.class + ' .dirty-stay,.ui-widget-overlay';
|
116
|
+
|
117
|
+
// Support for Dirty Forms < 2.0
|
118
|
+
if (choice.isDF1) {
|
119
|
+
var close = function (decision) {
|
120
|
+
return function (e) {
|
121
|
+
if (e.type !== 'keydown' || (e.type === 'keydown' && (e.which == 27 || e.which == 13))) {
|
122
|
+
notice.remove();
|
123
|
+
if (modal_overlay) modal_overlay.fadeOut("fast");
|
124
|
+
decision(e);
|
125
|
+
return false;
|
126
|
+
}
|
127
|
+
};
|
128
|
+
};
|
129
|
+
// Trap the escape key and force a close. Cancel it so PNotify doesn't intercept it.
|
130
|
+
var decidingCancel = $.DirtyForms.decidingCancel;
|
131
|
+
$(document).keydown(close(decidingCancel));
|
132
|
+
$(choice.staySelector).click(close(decidingCancel));
|
133
|
+
$(choice.proceedSelector).click(close($.DirtyForms.decidingContinue));
|
134
|
+
}
|
135
|
+
},
|
136
|
+
close: function () {
|
137
|
+
notice.remove();
|
138
|
+
if (modal_overlay) modal_overlay.fadeOut("fast");
|
139
|
+
},
|
140
|
+
|
141
|
+
// Support for Dirty Forms < 2.0
|
142
|
+
fire: function (message, title) {
|
143
|
+
this.title = title;
|
144
|
+
this.open({ isDF1: true }, message, $.DirtyForms.ignoreClass);
|
145
|
+
},
|
146
|
+
|
147
|
+
// Support for Dirty Forms < 1.2
|
148
|
+
bind: function () {
|
149
|
+
},
|
150
|
+
stash: function () {
|
151
|
+
return false;
|
152
|
+
},
|
153
|
+
refire: function () {
|
154
|
+
return false;
|
155
|
+
},
|
156
|
+
selector: 'no-op'
|
157
|
+
};
|
158
|
+
|
159
|
+
})(jQuery, window, document);
|
@@ -0,0 +1,27 @@
|
|
1
|
+
/*!
|
2
|
+
Always dirty helper module (for jQuery Dirty Forms) | v | github.com/snikch/jquery.dirtyforms
|
3
|
+
(c) 2012-2016 Mal Curtis
|
4
|
+
License MIT
|
5
|
+
*/
|
6
|
+
|
7
|
+
// Example helper, the form is always considered dirty
|
8
|
+
|
9
|
+
(function($, window, document, undefined) {
|
10
|
+
// Can't use ECMAScript 5's strict mode because several apps
|
11
|
+
// including ASP.NET trace the stack via arguments.caller.callee
|
12
|
+
// and Firefox dies if you try to trace through "use strict" call chains.
|
13
|
+
// See jQuery issue (#13335)
|
14
|
+
// Support: Firefox 18+
|
15
|
+
//"use strict";
|
16
|
+
|
17
|
+
// Create a new object, with an isDirty method
|
18
|
+
var alwaysDirty = {
|
19
|
+
isDirty: function (node) {
|
20
|
+
// Perform dirty check on a given node (usually a form element)
|
21
|
+
return true;
|
22
|
+
}
|
23
|
+
};
|
24
|
+
// Push the new object onto the helpers array
|
25
|
+
$.DirtyForms.helpers.push(alwaysDirty);
|
26
|
+
|
27
|
+
})(jQuery, window, document);
|
@@ -0,0 +1,67 @@
|
|
1
|
+
/*!
|
2
|
+
CkEditor helper module (for jQuery Dirty Forms) | v | github.com/snikch/jquery.dirtyforms
|
3
|
+
(c) 2012-2016 Mal Curtis
|
4
|
+
License MIT
|
5
|
+
*/
|
6
|
+
|
7
|
+
(function($, window, document, undefined) {
|
8
|
+
// Can't use ECMAScript 5's strict mode because several apps
|
9
|
+
// including ASP.NET trace the stack via arguments.caller.callee
|
10
|
+
// and Firefox dies if you try to trace through "use strict" call chains.
|
11
|
+
// See jQuery issue (#13335)
|
12
|
+
// Support: Firefox 18+
|
13
|
+
//"use strict";
|
14
|
+
|
15
|
+
var ignoreSelector = '.cke_dialog_ui_button,.cke_tpl_list a';
|
16
|
+
|
17
|
+
var ckeditor = {
|
18
|
+
ignoreSelector: ignoreSelector,
|
19
|
+
isDirty: function ($form) {
|
20
|
+
var $editors = ckeditors($form),
|
21
|
+
isDirty = false;
|
22
|
+
if ($editors.length > 0) {
|
23
|
+
$.DirtyForms.dirtylog('Checking ' + $editors.length + ' ckeditors for dirtyness.');
|
24
|
+
$editors.each(function (editorIndex) {
|
25
|
+
if (this.checkDirty()) {
|
26
|
+
isDirty = true;
|
27
|
+
|
28
|
+
$.DirtyForms.dirtylog('CKEditor with index ' + editorIndex + ' was dirty, exiting...');
|
29
|
+
// Return false to break out of the .each() function
|
30
|
+
return false;
|
31
|
+
}
|
32
|
+
});
|
33
|
+
}
|
34
|
+
return isDirty;
|
35
|
+
},
|
36
|
+
setClean: function ($form) {
|
37
|
+
ckeditors($form).each(function () { this.resetDirty(); });
|
38
|
+
},
|
39
|
+
|
40
|
+
// Support for Dirty Forms < 2.0
|
41
|
+
ignoreAnchorSelector: ignoreSelector
|
42
|
+
};
|
43
|
+
var ckeditors = function (form) {
|
44
|
+
var $form = form.jquery ? form : $(form);
|
45
|
+
var editors = [];
|
46
|
+
if (!window.CKEDITOR || !window.CKEDITOR.instances) {
|
47
|
+
return $(editors);
|
48
|
+
}
|
49
|
+
try {
|
50
|
+
for (var key in window.CKEDITOR.instances) {
|
51
|
+
if (window.CKEDITOR.instances.hasOwnProperty(key)) {
|
52
|
+
var editor = window.CKEDITOR.instances[key];
|
53
|
+
if ($(editor.element.$).parents().index($form) != -1) {
|
54
|
+
$.DirtyForms.dirtylog('Adding CKEditor with key ' + key);
|
55
|
+
editors.push(editor);
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
catch (e) {
|
61
|
+
// Ignore, means there was no CKEDITOR variable
|
62
|
+
}
|
63
|
+
return $(editors);
|
64
|
+
};
|
65
|
+
$.DirtyForms.helpers.push(ckeditor);
|
66
|
+
|
67
|
+
})(jQuery, window, document);
|
@@ -0,0 +1,158 @@
|
|
1
|
+
/*!
|
2
|
+
TinyMCE helper module (for jQuery Dirty Forms) | v | github.com/snikch/jquery.dirtyforms
|
3
|
+
(c) 2013-2016 Mal Curtis
|
4
|
+
License MIT
|
5
|
+
*/
|
6
|
+
|
7
|
+
(function($, window, document, undefined) {
|
8
|
+
// Can't use ECMAScript 5's strict mode because several apps
|
9
|
+
// including ASP.NET trace the stack via arguments.caller.callee
|
10
|
+
// and Firefox dies if you try to trace through "use strict" call chains.
|
11
|
+
// See jQuery issue (#13335)
|
12
|
+
// Support: Firefox 18+
|
13
|
+
//"use strict";
|
14
|
+
|
15
|
+
var tinymceSelector = ':tinymce:not(.dirty-forms-temp)',
|
16
|
+
ignoreSelector = '.mceEditor a,.mceMenu a,[name^="mce_"]';
|
17
|
+
|
18
|
+
// Create a new object, with an isDirty method
|
19
|
+
var tinymce = {
|
20
|
+
// Dirty Forms properties and methods
|
21
|
+
ignoreSelector: ignoreSelector,
|
22
|
+
isDirty: function ($node) {
|
23
|
+
var isDirty = false;
|
24
|
+
if (hasTinyMCE($node)) {
|
25
|
+
// Search the current node and all descendant nodes that match the selector
|
26
|
+
$node.filter(tinymceSelector).add($node.find(tinymceSelector)).each(function () {
|
27
|
+
var $field = $(this);
|
28
|
+
|
29
|
+
$.DirtyForms.dirtylog('Checking node ' + $field.attr('id'));
|
30
|
+
if (typeof $field.data('df-tinymce-orig') === 'undefined') {
|
31
|
+
// For Dirty Forms < 2.0 and TinyMCE elements that were added via AJAX,
|
32
|
+
// we default to using TinyMCE's isDirty behavior (which is stateless).
|
33
|
+
if ($field.tinymce().isDirty()) {
|
34
|
+
isDirty = true;
|
35
|
+
$.DirtyForms.dirtylog('Node was totally dirty.');
|
36
|
+
// Return false to stop iterating.
|
37
|
+
return false;
|
38
|
+
}
|
39
|
+
} else {
|
40
|
+
// For Dirty Forms >= 2.0, we compare hash codes with the original content
|
41
|
+
var content = getTinyMceContent($field);
|
42
|
+
$.DirtyForms.dirtylog('TinyMCE content: ' + content);
|
43
|
+
var hash = getHashCode(content);
|
44
|
+
$.DirtyForms.dirtylog('TinyMCE hash: ' + hash);
|
45
|
+
var originalHash = $field.data('df-tinymce-orig');
|
46
|
+
$.DirtyForms.dirtylog('Original TinyMCE hash: ' + originalHash);
|
47
|
+
|
48
|
+
if (hash !== originalHash) {
|
49
|
+
isDirty = true;
|
50
|
+
$.DirtyForms.dirtylog('Node was totally dirty.');
|
51
|
+
// Return false to stop iterating.
|
52
|
+
return false;
|
53
|
+
}
|
54
|
+
}
|
55
|
+
});
|
56
|
+
}
|
57
|
+
return isDirty;
|
58
|
+
},
|
59
|
+
setClean: function ($node) {
|
60
|
+
if (hasTinyMCE($node)) {
|
61
|
+
|
62
|
+
// Search the current node and all descendant nodes that match the selector
|
63
|
+
$node.filter(tinymceSelector).add($node.find(tinymceSelector)).each(function () {
|
64
|
+
var $field = $(this);
|
65
|
+
|
66
|
+
// Set TinyMCE clean
|
67
|
+
if ($field.tinymce().isDirty()) {
|
68
|
+
$.DirtyForms.dirtylog('Resetting isDirty on node ' + $field.attr('id'));
|
69
|
+
$field.tinymce().isNotDirty = 1; //Force not dirty state
|
70
|
+
}
|
71
|
+
|
72
|
+
// Forget the original value and reset to the current state
|
73
|
+
storeOriginalValue($field);
|
74
|
+
});
|
75
|
+
}
|
76
|
+
},
|
77
|
+
rescan: function ($node) {
|
78
|
+
if (hasTinyMCE($node)) {
|
79
|
+
$node.filter(tinymceSelector).add($node.find(tinymceSelector)).each(function () {
|
80
|
+
var $field = $(this);
|
81
|
+
|
82
|
+
if (typeof $field.data('df-tinymce-orig') !== 'undefined') {
|
83
|
+
storeOriginalValue($field);
|
84
|
+
}
|
85
|
+
});
|
86
|
+
}
|
87
|
+
},
|
88
|
+
|
89
|
+
// Patch for Dirty Forms < 2.0
|
90
|
+
ignoreAnchorSelector: ignoreSelector
|
91
|
+
};
|
92
|
+
|
93
|
+
// Push the new object onto the helpers array
|
94
|
+
$.DirtyForms.helpers.push(tinymce);
|
95
|
+
|
96
|
+
// Fix: tinymce throws an error if the selector doesn't match anything
|
97
|
+
// (such as when there are no textareas on the current page)
|
98
|
+
var hasTinyMCE = function ($node) {
|
99
|
+
try {
|
100
|
+
return $node.filter(tinymceSelector).length > 0 || $node.find(tinymceSelector).length > 0;
|
101
|
+
}
|
102
|
+
catch (e) {
|
103
|
+
return false;
|
104
|
+
}
|
105
|
+
};
|
106
|
+
|
107
|
+
var getTinyMceContent = function ($field) {
|
108
|
+
// Hack: TinyMCE puts an extra <br> tag at the end of a paragraph when it is edited, so ignore that case.
|
109
|
+
return $field.tinymce().getContent({ format: 'raw' }).replace(/<br><\/p>/mg, '</p>');
|
110
|
+
};
|
111
|
+
|
112
|
+
var storeOriginalValue = function ($field) {
|
113
|
+
var content = getTinyMceContent($field);
|
114
|
+
$.DirtyForms.dirtylog('Original TinyMCE content: ' + content);
|
115
|
+
|
116
|
+
var hash = getHashCode(content);
|
117
|
+
$.DirtyForms.dirtylog('Original TinyMCE hash: ' + hash);
|
118
|
+
|
119
|
+
$field.data('df-tinymce-orig', hash);
|
120
|
+
};
|
121
|
+
|
122
|
+
// When TinyMCE is found, store the original value as a hash so we can see if there are changes later.
|
123
|
+
var init = function ($node) {
|
124
|
+
if (hasTinyMCE($node)) {
|
125
|
+
$node.filter(tinymceSelector).add($node.find(tinymceSelector)).each(function () {
|
126
|
+
storeOriginalValue($(this));
|
127
|
+
});
|
128
|
+
}
|
129
|
+
};
|
130
|
+
|
131
|
+
$(document).bind('scan.dirtyforms', function (ev) {
|
132
|
+
// Hack: TinyMCE doesn't have a global init event. So, we create a new
|
133
|
+
// TinyMCE editor within an invisible div and respond to its init event.
|
134
|
+
// There doesn't seem to be a reasonable way
|
135
|
+
// to remove the control again, so we simply ignore it.
|
136
|
+
var $form = $(ev.target);
|
137
|
+
var $editor = $('<div style="display:none;" class="dirty-forms-temp"></div>');
|
138
|
+
$form.append($editor);
|
139
|
+
$editor.tinymce({
|
140
|
+
oninit: function () {
|
141
|
+
init($form);
|
142
|
+
}
|
143
|
+
});
|
144
|
+
});
|
145
|
+
|
146
|
+
// Simple way to hash a string: http://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery
|
147
|
+
var getHashCode = function (str) {
|
148
|
+
var hash = 0, i, chr, len;
|
149
|
+
if (str.length === 0) return hash;
|
150
|
+
for (i = 0, len = str.length; i < len; i++) {
|
151
|
+
chr = str.charCodeAt(i);
|
152
|
+
hash = ((hash << 5) - hash) + chr;
|
153
|
+
hash |= 0; // Convert to 32bit integer
|
154
|
+
}
|
155
|
+
return hash;
|
156
|
+
};
|
157
|
+
|
158
|
+
})(jQuery, window, document);
|
@@ -0,0 +1,596 @@
|
|
1
|
+
/*!
|
2
|
+
Dirty Forms jQuery Plugin | v | github.com/snikch/jquery.dirtyforms
|
3
|
+
(c) 2010-2016 Mal Curtis
|
4
|
+
License MIT
|
5
|
+
*/
|
6
|
+
|
7
|
+
(function($, window, document, undefined) {
|
8
|
+
// Can't use ECMAScript 5's strict mode because several apps
|
9
|
+
// including ASP.NET trace the stack via arguments.caller.callee
|
10
|
+
// and Firefox dies if you try to trace through "use strict" call chains.
|
11
|
+
// See jQuery issue (#13335)
|
12
|
+
// Support: Firefox 18+
|
13
|
+
//"use strict";
|
14
|
+
|
15
|
+
if (!$.fn.on) {
|
16
|
+
// Patch jQuery 1.4.2 - 1.7 with an on function (that uses delegate).
|
17
|
+
$.fn.on = function (events, selector, data, handler) {
|
18
|
+
return this.delegate(selector, events, data, handler);
|
19
|
+
};
|
20
|
+
}
|
21
|
+
|
22
|
+
$.fn.dirtyForms = function (method) {
|
23
|
+
// Method calling logic
|
24
|
+
if (methods[method]) {
|
25
|
+
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
|
26
|
+
} else if (typeof method === 'object' || !method) {
|
27
|
+
return methods.init.apply(this, arguments);
|
28
|
+
} else {
|
29
|
+
$.error('Method ' + method + ' does not exist on jQuery.dirtyForms');
|
30
|
+
}
|
31
|
+
};
|
32
|
+
|
33
|
+
// Public Element methods ( $('form').dirtyForms('methodName', args) )
|
34
|
+
var methods = {
|
35
|
+
init: function (options) {
|
36
|
+
var data = {};
|
37
|
+
|
38
|
+
if (!state.initialized) {
|
39
|
+
// Override any default options
|
40
|
+
$.extend(true, $.DirtyForms, options);
|
41
|
+
|
42
|
+
$(document).trigger('bind.dirtyforms', [events]);
|
43
|
+
events.bind(window, document, data);
|
44
|
+
|
45
|
+
state.initialized = true;
|
46
|
+
}
|
47
|
+
|
48
|
+
this.filter('form').not(':dirtylistening').each(function () {
|
49
|
+
var $form = $(this);
|
50
|
+
dirtylog('Adding form ' + $form.attr('id') + ' to forms to watch');
|
51
|
+
|
52
|
+
// Store original values of the fields
|
53
|
+
$form.find($.DirtyForms.fieldSelector).each(function () {
|
54
|
+
storeOriginalValue($(this));
|
55
|
+
});
|
56
|
+
|
57
|
+
$form.trigger('scan.dirtyforms');
|
58
|
+
events.bindForm($form, data);
|
59
|
+
});
|
60
|
+
return this;
|
61
|
+
},
|
62
|
+
// Returns true if any of the selected elements or their children are dirty
|
63
|
+
isDirty: function (excludeHelpers) {
|
64
|
+
var ignoreSelector = getIgnoreSelector(),
|
65
|
+
dirtyClass = $.DirtyForms.dirtyClass,
|
66
|
+
isDirty = false;
|
67
|
+
|
68
|
+
this.each(function (index) {
|
69
|
+
var $node = $(this),
|
70
|
+
ignored = isFieldIgnored($node, ignoreSelector);
|
71
|
+
|
72
|
+
if ($node.hasClass(dirtyClass) && !ignored) {
|
73
|
+
isDirty = true;
|
74
|
+
// Exit out of the .each() function
|
75
|
+
return false;
|
76
|
+
}
|
77
|
+
|
78
|
+
// Check any descendant nodes (if this is a container element)
|
79
|
+
$node.find('.' + dirtyClass).each(function () {
|
80
|
+
if (!isFieldIgnored($(this), ignoreSelector)) {
|
81
|
+
isDirty = true;
|
82
|
+
// Exit out of the .each() function
|
83
|
+
return false;
|
84
|
+
}
|
85
|
+
});
|
86
|
+
// Exit out of the .each() function
|
87
|
+
if (isDirty) return false;
|
88
|
+
|
89
|
+
if (!ignored && !excludeHelpers) {
|
90
|
+
// Test helpers for this node.
|
91
|
+
$.each($.DirtyForms.helpers, function (i, helper) {
|
92
|
+
if (helper.isDirty && helper.isDirty($node, index)) {
|
93
|
+
isDirty = true;
|
94
|
+
// Exit out of the .each() function
|
95
|
+
return false;
|
96
|
+
}
|
97
|
+
});
|
98
|
+
|
99
|
+
// Exit out of the .each() function
|
100
|
+
if (isDirty) return false;
|
101
|
+
}
|
102
|
+
});
|
103
|
+
|
104
|
+
return isDirty;
|
105
|
+
},
|
106
|
+
// Marks the element(s) and any helpers within the element not dirty.
|
107
|
+
// If all of the fields in a form are marked not dirty, the form itself will be marked not dirty even
|
108
|
+
// if it is not included in the selector. Also resets original values to the current state -
|
109
|
+
// essentially "forgetting" the node or its descendants are dirty.
|
110
|
+
setClean: function (excludeIgnored, excludeHelpers) {
|
111
|
+
dirtylog('setClean called');
|
112
|
+
|
113
|
+
var doSetClean = function () {
|
114
|
+
var $field = $(this);
|
115
|
+
|
116
|
+
// Reset by storing the original value again
|
117
|
+
storeOriginalValue($field);
|
118
|
+
|
119
|
+
// Remove the dirty class
|
120
|
+
setDirtyStatus($field, false);
|
121
|
+
};
|
122
|
+
|
123
|
+
elementsInRange(this, $.DirtyForms.fieldSelector, excludeIgnored)
|
124
|
+
.each(doSetClean)
|
125
|
+
.parents('form').trigger('setclean.dirtyforms', [excludeIgnored]);
|
126
|
+
|
127
|
+
if (excludeHelpers) return this;
|
128
|
+
return fireHelperMethod(this, 'setClean', excludeIgnored, getIgnoreSelector());
|
129
|
+
},
|
130
|
+
// Scans the selected elements and descendants for any new fields and stores their original values.
|
131
|
+
// Ignores any original values that had been set previously. Also resets the dirty status of all fields
|
132
|
+
// whose ignore status has changed since the last scan.
|
133
|
+
rescan: function (excludeIgnored, excludeHelpers) {
|
134
|
+
dirtylog('rescan called');
|
135
|
+
|
136
|
+
var doRescan = function () {
|
137
|
+
var $field = $(this);
|
138
|
+
|
139
|
+
// Skip previously added fields
|
140
|
+
if (!hasOriginalValue($field)) {
|
141
|
+
// Store the original value
|
142
|
+
storeOriginalValue($field);
|
143
|
+
}
|
144
|
+
|
145
|
+
// Set the dirty status
|
146
|
+
setDirtyStatus($field, isFieldDirty($field));
|
147
|
+
};
|
148
|
+
|
149
|
+
elementsInRange(this, $.DirtyForms.fieldSelector, excludeIgnored)
|
150
|
+
.each(doRescan)
|
151
|
+
.parents('form').trigger('rescan.dirtyforms', [excludeIgnored]);
|
152
|
+
|
153
|
+
if (excludeHelpers) return this;
|
154
|
+
return fireHelperMethod(this, 'rescan', excludeIgnored, getIgnoreSelector());
|
155
|
+
}
|
156
|
+
};
|
157
|
+
|
158
|
+
// Custom selectors $('form:dirty')
|
159
|
+
$.extend($.expr[":"], {
|
160
|
+
dirty: function (element) {
|
161
|
+
var $element = $(element);
|
162
|
+
return $element.hasClass($.DirtyForms.dirtyClass) && !$element.is(':dirtyignored');
|
163
|
+
},
|
164
|
+
dirtylistening: function (element) {
|
165
|
+
return $(element).hasClass($.DirtyForms.listeningClass);
|
166
|
+
},
|
167
|
+
dirtyignored: function (element) {
|
168
|
+
return isFieldIgnored($(element), false);
|
169
|
+
}
|
170
|
+
});
|
171
|
+
|
172
|
+
// Public General Plugin properties and methods $.DirtyForms
|
173
|
+
$.DirtyForms = {
|
174
|
+
message: "You've made changes on this page which aren't saved. If you leave you will lose these changes.",
|
175
|
+
dirtyClass: 'dirty',
|
176
|
+
listeningClass: 'dirtylisten',
|
177
|
+
ignoreClass: 'dirtyignore',
|
178
|
+
ignoreSelector: '',
|
179
|
+
// exclude all HTML 4 except checkbox, option, text and password, but include HTML 5 except search
|
180
|
+
fieldSelector: "input:not([type='button'],[type='image'],[type='submit']," +
|
181
|
+
"[type='reset'],[type='file'],[type='search']),select,textarea",
|
182
|
+
/*<log>*/
|
183
|
+
debug: false,
|
184
|
+
dirtylog: function (msg) {
|
185
|
+
dirtylog(msg);
|
186
|
+
},
|
187
|
+
/*</log>*/
|
188
|
+
helpers: [],
|
189
|
+
dialog: false
|
190
|
+
};
|
191
|
+
|
192
|
+
// Private State Management
|
193
|
+
var state = {
|
194
|
+
initialized: false,
|
195
|
+
formStash: false,
|
196
|
+
dialogStash: false,
|
197
|
+
deciding: false,
|
198
|
+
decidingEvent: false
|
199
|
+
};
|
200
|
+
|
201
|
+
// Dialog Decision Management
|
202
|
+
var choice;
|
203
|
+
|
204
|
+
var bindKeys = function (ev) {
|
205
|
+
if (ev.data.bindEscKey && ev.which == 27 || ev.data.bindEnterKey && ev.which == 13) {
|
206
|
+
return doCommit(ev, false);
|
207
|
+
}
|
208
|
+
};
|
209
|
+
|
210
|
+
var bindDialog = function (choice) {
|
211
|
+
var staySelector = choice.staySelector,
|
212
|
+
proceedSelector = choice.proceedSelector;
|
213
|
+
|
214
|
+
if (staySelector !== '') {
|
215
|
+
$(staySelector).unbind('click', doCommit)
|
216
|
+
.click(doCommit);
|
217
|
+
}
|
218
|
+
if (proceedSelector !== '') {
|
219
|
+
$(proceedSelector).unbind('click', doProceed)
|
220
|
+
.click(doProceed);
|
221
|
+
}
|
222
|
+
if (choice.bindEscKey || choice.bindEnterKey) {
|
223
|
+
$(document).unbind('keydown', bindKeys)
|
224
|
+
.keydown(choice, bindKeys);
|
225
|
+
}
|
226
|
+
};
|
227
|
+
|
228
|
+
var callDialogClose = function (proceeding, unstashing) {
|
229
|
+
if ($.isFunction($.DirtyForms.dialog.close)) {
|
230
|
+
dirtylog('Calling dialog close');
|
231
|
+
$.DirtyForms.dialog.close(proceeding, unstashing);
|
232
|
+
}
|
233
|
+
};
|
234
|
+
|
235
|
+
var doProceed = function (ev) {
|
236
|
+
return doCommit(ev, true);
|
237
|
+
};
|
238
|
+
|
239
|
+
var doCommit = function (ev, proceeding) {
|
240
|
+
if (!state.deciding) return;
|
241
|
+
ev.preventDefault();
|
242
|
+
|
243
|
+
if (proceeding === true) {
|
244
|
+
var refireEvent = state.decidingEvent;
|
245
|
+
$(document).trigger('proceed.dirtyforms', [refireEvent]);
|
246
|
+
events.clearUnload(); // fix for chrome/safari
|
247
|
+
callDialogClose(proceeding, false);
|
248
|
+
refire(refireEvent);
|
249
|
+
} else {
|
250
|
+
$(document).trigger('stay.dirtyforms');
|
251
|
+
var isUnstashing = $.DirtyForms.dialog !== false && state.dialogStash !== false && $.isFunction($.DirtyForms.dialog.unstash);
|
252
|
+
callDialogClose(proceeding, isUnstashing);
|
253
|
+
if (isUnstashing) {
|
254
|
+
dirtylog('Refiring the dialog with stashed content');
|
255
|
+
$.DirtyForms.dialog.unstash(state.dialogStash, ev);
|
256
|
+
}
|
257
|
+
$(document).trigger('afterstay.dirtyforms');
|
258
|
+
}
|
259
|
+
|
260
|
+
state.deciding = state.decidingEvent = state.dialogStash = state.formStash = false;
|
261
|
+
return false;
|
262
|
+
};
|
263
|
+
|
264
|
+
// Event management
|
265
|
+
var events = {
|
266
|
+
bind: function (window, document, data) {
|
267
|
+
$(window).bind('beforeunload', data, events.onBeforeUnload);
|
268
|
+
$(document).on('click', 'a:not([target="_blank"])', data, events.onAnchorClick)
|
269
|
+
.on('submit', 'form', data, events.onSubmit);
|
270
|
+
},
|
271
|
+
bindForm: function ($form, data) {
|
272
|
+
var dirtyForms = $.DirtyForms;
|
273
|
+
|
274
|
+
// Test whether we are dealing with IE < 10
|
275
|
+
var isIE8_9 = ('onpropertychange' in document.createElement('input'));
|
276
|
+
var inputEvents = 'change input' + (isIE8_9 ? ' keyup selectionchange cut paste' : '');
|
277
|
+
$form.addClass(dirtyForms.listeningClass)
|
278
|
+
.on('focus keydown', dirtyForms.fieldSelector, data, events.onFocus)
|
279
|
+
.on(inputEvents, dirtyForms.fieldSelector, data, events.onFieldChange)
|
280
|
+
.bind('reset', data, events.onReset);
|
281
|
+
},
|
282
|
+
// For any fields added after the form was initialized, store the value when focused.
|
283
|
+
onFocus: function (ev) {
|
284
|
+
var $field = $(ev.target);
|
285
|
+
if (!hasOriginalValue($field)) {
|
286
|
+
storeOriginalValue($field);
|
287
|
+
}
|
288
|
+
},
|
289
|
+
onFieldChange: function (ev) {
|
290
|
+
var $field = $(ev.target);
|
291
|
+
if (ev.type !== 'change') {
|
292
|
+
delay(function () { setFieldStatus($field); }, 100);
|
293
|
+
} else {
|
294
|
+
setFieldStatus($field);
|
295
|
+
}
|
296
|
+
},
|
297
|
+
onReset: function (ev) {
|
298
|
+
var $form = $(ev.target).closest('form');
|
299
|
+
// Need a delay here because reset is called before the state of the form is reset.
|
300
|
+
setTimeout(function () { $form.dirtyForms('setClean'); }, 100);
|
301
|
+
},
|
302
|
+
onAnchorClick: function (ev) {
|
303
|
+
bindFn(ev);
|
304
|
+
},
|
305
|
+
onSubmit: function (ev) {
|
306
|
+
bindFn(ev);
|
307
|
+
},
|
308
|
+
onBeforeUnload: function (ev) {
|
309
|
+
var result = bindFn(ev);
|
310
|
+
|
311
|
+
if (result && state.doubleunloadfix !== true) {
|
312
|
+
dirtylog('Before unload will be called, resetting');
|
313
|
+
state.deciding = false;
|
314
|
+
}
|
315
|
+
|
316
|
+
state.doubleunloadfix = true;
|
317
|
+
setTimeout(function () { state.doubleunloadfix = false; }, 200);
|
318
|
+
|
319
|
+
// Only return the result if it is a string, otherwise don't return anything.
|
320
|
+
if (typeof result === 'string') {
|
321
|
+
// For IE and Firefox prior to version 4, set the returnValue.
|
322
|
+
ev.returnValue = result;
|
323
|
+
return result;
|
324
|
+
}
|
325
|
+
},
|
326
|
+
onRefireClick: function (ev) {
|
327
|
+
var event = new $.Event('click');
|
328
|
+
$(ev.target).trigger(event);
|
329
|
+
if (!event.isDefaultPrevented()) {
|
330
|
+
events.onRefireAnchorClick(ev);
|
331
|
+
}
|
332
|
+
},
|
333
|
+
onRefireAnchorClick: function (ev) {
|
334
|
+
var href = $(ev.target).closest('a[href]').attr('href');
|
335
|
+
if (href !== undefined) {
|
336
|
+
dirtylog('Sending location to ' + href);
|
337
|
+
window.location.href = href;
|
338
|
+
}
|
339
|
+
},
|
340
|
+
clearUnload: function () {
|
341
|
+
// I'd like to just be able to unbind this but there seems
|
342
|
+
// to be a bug in jQuery which doesn't unbind onbeforeunload
|
343
|
+
dirtylog('Clearing the beforeunload event');
|
344
|
+
$(window).unbind('beforeunload', events.onBeforeUnload);
|
345
|
+
window.onbeforeunload = null;
|
346
|
+
$(document).trigger('beforeunload.dirtyforms');
|
347
|
+
}
|
348
|
+
};
|
349
|
+
|
350
|
+
var elementsInRange = function ($this, selector, excludeIgnored) {
|
351
|
+
var $elements = $this.filter(selector).add($this.find(selector));
|
352
|
+
if (excludeIgnored) {
|
353
|
+
$elements = $elements.not(':dirtyignored');
|
354
|
+
}
|
355
|
+
return $elements;
|
356
|
+
};
|
357
|
+
|
358
|
+
var fireHelperMethod = function ($this, method, excludeIgnored, ignoreSelector) {
|
359
|
+
return $this.each(function (index) {
|
360
|
+
var $node = $(this);
|
361
|
+
|
362
|
+
if (!excludeIgnored || !isFieldIgnored($node, ignoreSelector)) {
|
363
|
+
$.each($.DirtyForms.helpers, function (i, helper) {
|
364
|
+
if (helper[method]) { helper[method]($node, index, excludeIgnored); }
|
365
|
+
});
|
366
|
+
}
|
367
|
+
});
|
368
|
+
};
|
369
|
+
|
370
|
+
var getFieldValue = function ($field) {
|
371
|
+
var value;
|
372
|
+
if ($field.is('select')) {
|
373
|
+
value = '';
|
374
|
+
$field.find('option').each(function () {
|
375
|
+
var $option = $(this);
|
376
|
+
if ($option.is(':selected')) {
|
377
|
+
if (value.length > 0) { value += ','; }
|
378
|
+
value += $option.val();
|
379
|
+
}
|
380
|
+
});
|
381
|
+
} else if ($field.is(":checkbox,:radio")) {
|
382
|
+
value = $field.is(':checked');
|
383
|
+
} else {
|
384
|
+
value = $field.val();
|
385
|
+
}
|
386
|
+
|
387
|
+
return value;
|
388
|
+
};
|
389
|
+
|
390
|
+
var storeOriginalValue = function ($field) {
|
391
|
+
dirtylog('Storing original value for ' + $field.attr('name'));
|
392
|
+
$field.data('df-orig', getFieldValue($field));
|
393
|
+
var isEmpty = ($field.data('df-orig') === undefined);
|
394
|
+
$field.data('df-empty', isEmpty);
|
395
|
+
};
|
396
|
+
|
397
|
+
var hasOriginalValue = function ($field) {
|
398
|
+
return ($field.data('df-orig') !== undefined || $field.data('df-empty') === true);
|
399
|
+
};
|
400
|
+
|
401
|
+
var getIgnoreSelector = function () {
|
402
|
+
var dirtyForms = $.DirtyForms,
|
403
|
+
result = dirtyForms.ignoreSelector;
|
404
|
+
$.each(dirtyForms.helpers, function (key, obj) {
|
405
|
+
if ('ignoreSelector' in obj) {
|
406
|
+
if (result.length > 0) { result += ','; }
|
407
|
+
result += obj.ignoreSelector;
|
408
|
+
}
|
409
|
+
});
|
410
|
+
return result;
|
411
|
+
};
|
412
|
+
|
413
|
+
var isFieldIgnored = function ($field, ignoreSelector) {
|
414
|
+
if (!ignoreSelector) {
|
415
|
+
ignoreSelector = getIgnoreSelector();
|
416
|
+
}
|
417
|
+
return $field.is(ignoreSelector) || $field.closest('.' + $.DirtyForms.ignoreClass).length > 0;
|
418
|
+
};
|
419
|
+
|
420
|
+
var isFieldDirty = function ($field, ignoreSelector) {
|
421
|
+
if (!hasOriginalValue($field) || isFieldIgnored($field, ignoreSelector)) return false;
|
422
|
+
return (getFieldValue($field) != $field.data('df-orig'));
|
423
|
+
};
|
424
|
+
|
425
|
+
var setFieldStatus = function ($field, ignoreSelector) {
|
426
|
+
if (isFieldIgnored($field, ignoreSelector)) return;
|
427
|
+
|
428
|
+
// Option groups are a special case because they change more than the current element.
|
429
|
+
if ($field.is(':radio[name]')) {
|
430
|
+
var name = $field.attr('name'),
|
431
|
+
$form = $field.parents('form');
|
432
|
+
|
433
|
+
$form.find(":radio[name='" + name + "']").each(function () {
|
434
|
+
var $radio = $(this);
|
435
|
+
setDirtyStatus($radio, isFieldDirty($radio, ignoreSelector));
|
436
|
+
});
|
437
|
+
} else {
|
438
|
+
setDirtyStatus($field, isFieldDirty($field, ignoreSelector));
|
439
|
+
}
|
440
|
+
};
|
441
|
+
|
442
|
+
var setDirtyStatus = function ($field, isDirty) {
|
443
|
+
dirtylog('Setting dirty status to ' + isDirty + ' on field ' + $field.attr('id'));
|
444
|
+
var dirtyClass = $.DirtyForms.dirtyClass,
|
445
|
+
$form = $field.parents('form');
|
446
|
+
|
447
|
+
// Mark the field dirty/clean
|
448
|
+
$field.toggleClass(dirtyClass, isDirty);
|
449
|
+
var changed = (isDirty !== ($form.hasClass(dirtyClass) && $form.find(':dirty').length === 0));
|
450
|
+
|
451
|
+
if (changed) {
|
452
|
+
dirtylog('Setting dirty status to ' + isDirty + ' on form ' + $form.attr('id'));
|
453
|
+
$form.toggleClass(dirtyClass, isDirty);
|
454
|
+
|
455
|
+
if (isDirty) $form.trigger('dirty.dirtyforms');
|
456
|
+
if (!isDirty) $form.trigger('clean.dirtyforms');
|
457
|
+
}
|
458
|
+
};
|
459
|
+
|
460
|
+
// A delay to keep the key events from slowing down when changing the dirty status on the fly.
|
461
|
+
var delay = (function () {
|
462
|
+
var timer = 0;
|
463
|
+
return function (callback, ms) {
|
464
|
+
clearTimeout(timer);
|
465
|
+
timer = setTimeout(callback, ms);
|
466
|
+
};
|
467
|
+
})();
|
468
|
+
|
469
|
+
var bindFn = function (ev) {
|
470
|
+
var $element = $(ev.target),
|
471
|
+
eventType = ev.type,
|
472
|
+
dirtyForms = $.DirtyForms;
|
473
|
+
dirtylog('Entering: Leaving Event fired, type: ' + eventType + ', element: ' + ev.target + ', class: ' + $element.attr('class') + ' and id: ' + ev.target.id);
|
474
|
+
|
475
|
+
// Important: Do this check before calling events.clearUnload()
|
476
|
+
if (ev.isDefaultPrevented()) {
|
477
|
+
dirtylog('Leaving: Event has been stopped elsewhere');
|
478
|
+
return false;
|
479
|
+
}
|
480
|
+
|
481
|
+
if (eventType == 'beforeunload' && state.doubleunloadfix) {
|
482
|
+
dirtylog('Skip this unload, Firefox bug triggers the unload event multiple times');
|
483
|
+
state.doubleunloadfix = false;
|
484
|
+
return false;
|
485
|
+
}
|
486
|
+
|
487
|
+
if ($element.is(':dirtyignored')) {
|
488
|
+
dirtylog('Leaving: Element has ignore class or a descendant of an ignored element');
|
489
|
+
events.clearUnload();
|
490
|
+
return false;
|
491
|
+
}
|
492
|
+
|
493
|
+
if (state.deciding) {
|
494
|
+
dirtylog('Leaving: Already in the deciding process');
|
495
|
+
return false;
|
496
|
+
}
|
497
|
+
|
498
|
+
if (!$('form:dirtylistening').dirtyForms('isDirty')) {
|
499
|
+
dirtylog('Leaving: Not dirty');
|
500
|
+
events.clearUnload();
|
501
|
+
return false;
|
502
|
+
}
|
503
|
+
|
504
|
+
if (eventType == 'submit' && $element.dirtyForms('isDirty')) {
|
505
|
+
dirtylog('Leaving: Form submitted is a dirty form');
|
506
|
+
events.clearUnload();
|
507
|
+
return true;
|
508
|
+
}
|
509
|
+
|
510
|
+
// Callback for page access in current state
|
511
|
+
$(document).trigger('defer.dirtyforms');
|
512
|
+
|
513
|
+
if (eventType == 'beforeunload') {
|
514
|
+
dirtylog('Returning to beforeunload browser handler with: ' + dirtyForms.message);
|
515
|
+
return dirtyForms.message;
|
516
|
+
}
|
517
|
+
if (!dirtyForms.dialog) return;
|
518
|
+
|
519
|
+
// Using the GUI dialog...
|
520
|
+
ev.preventDefault();
|
521
|
+
ev.stopImmediatePropagation();
|
522
|
+
|
523
|
+
dirtylog('Setting deciding active');
|
524
|
+
state.deciding = true;
|
525
|
+
state.decidingEvent = ev;
|
526
|
+
|
527
|
+
// Stash the dialog (with a form). This is done so it can be shown again via unstash().
|
528
|
+
if ($.isFunction(dirtyForms.dialog.stash)) {
|
529
|
+
dirtylog('Stashing dialog content');
|
530
|
+
state.dialogStash = dirtyForms.dialog.stash();
|
531
|
+
dirtylog('Dialog Stash: ' + state.dialogStash);
|
532
|
+
}
|
533
|
+
|
534
|
+
// Stash the form from the dialog. This is done so we can fire events on it if the user makes a proceed choice.
|
535
|
+
var stashSelector = dirtyForms.dialog.stashSelector;
|
536
|
+
if (typeof stashSelector === 'string' && $element.is('form') && $element.parents(stashSelector).length > 0) {
|
537
|
+
dirtylog('Stashing form');
|
538
|
+
state.formStash = $element.clone(true).hide();
|
539
|
+
} else {
|
540
|
+
state.formStash = false;
|
541
|
+
}
|
542
|
+
|
543
|
+
dirtylog('Deferring to the dialog');
|
544
|
+
|
545
|
+
// Create a new choice object
|
546
|
+
choice = {
|
547
|
+
proceed: false,
|
548
|
+
commit: function (ev) {
|
549
|
+
return doCommit(ev, choice.proceed);
|
550
|
+
},
|
551
|
+
bindEscKey: true,
|
552
|
+
bindEnterKey: false,
|
553
|
+
proceedSelector: '',
|
554
|
+
staySelector: ''
|
555
|
+
};
|
556
|
+
|
557
|
+
dirtyForms.dialog.open(choice, dirtyForms.message, dirtyForms.ignoreClass);
|
558
|
+
bindDialog(choice);
|
559
|
+
};
|
560
|
+
|
561
|
+
var refire = function (ev) {
|
562
|
+
if (ev.type === 'click') {
|
563
|
+
dirtylog("Refiring click event");
|
564
|
+
events.onRefireClick(ev);
|
565
|
+
} else {
|
566
|
+
dirtylog("Refiring " + ev.type + " event on " + ev.target);
|
567
|
+
var target;
|
568
|
+
if (state.formStash) {
|
569
|
+
dirtylog('Appending stashed form to body');
|
570
|
+
target = state.formStash;
|
571
|
+
$('body').append(target);
|
572
|
+
}
|
573
|
+
else {
|
574
|
+
target = $(ev.target).closest('form');
|
575
|
+
}
|
576
|
+
target.trigger(ev.type);
|
577
|
+
}
|
578
|
+
};
|
579
|
+
|
580
|
+
/*<log>*/
|
581
|
+
var dirtylog = function (msg) {
|
582
|
+
if (!$.DirtyForms.debug) return;
|
583
|
+
var hasFirebug = 'console' in window && 'firebug' in window.console,
|
584
|
+
hasConsoleLog = 'console' in window && 'log' in window.console;
|
585
|
+
msg = '[DirtyForms] ' + msg;
|
586
|
+
if (hasFirebug) {
|
587
|
+
console.log(msg);
|
588
|
+
} else if (hasConsoleLog) {
|
589
|
+
window.console.log(msg);
|
590
|
+
} else {
|
591
|
+
alert(msg);
|
592
|
+
}
|
593
|
+
};
|
594
|
+
/*</log>*/
|
595
|
+
|
596
|
+
})(jQuery, window, document);
|