jquery-dirtyforms-rails 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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);