MDL-56462 boost: Fix assign grading options form
[moodle.git] / lib / yui / src / formchangechecker / js / formchangechecker.js
CommitLineData
1f777e5c
AN
1/**
2 * A utility to check for form changes before navigating away from a page.
3 *
4 * @module moodle-core-formchangechecker
5 */
6
7/**
8 * A utility to check for form changes before navigating away from a page.
9 *
10 * @class M.core.formchangechecker
11 * @constructor
12 */
13
0653469e
ARN
14var FORMCHANGECHECKERNAME = 'core-formchangechecker',
15
16 FORMCHANGECHECKER = function() {
17 FORMCHANGECHECKER.superclass.constructor.apply(this, arguments);
18 };
19
20Y.extend(FORMCHANGECHECKER, Y.Base, {
21
22 // The delegated listeners we need to detach after the initial value has been stored once
5bb4f444 23 initialvaluelisteners: [],
0653469e
ARN
24
25 /**
1f777e5c
AN
26 * Initialize the module
27 *
28 * @method initializer
29 */
5bb4f444 30 initializer: function() {
0653469e
ARN
31 var formid = 'form#' + this.get('formid'),
32 currentform = Y.one(formid);
33
34 if (!currentform) {
35 // If the form was not found, then we can't check for changes.
36 return;
37 }
38
b5594973
AG
39 // Add a listener here for an editor restore event.
40 Y.on(M.core.event.EDITOR_CONTENT_RESTORED, M.core_formchangechecker.reset_form_dirty_state, this);
41
0653469e
ARN
42 // Add change events to the form elements
43 currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'input', this);
44 currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'textarea', this);
45 currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'select', this);
46
47 // Add a focus event to check for changes which are made without triggering a change event
48 this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'input', this));
49 this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'textarea', this));
50 this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'select', this));
51
52 // We need any submit buttons on the form to set the submitted flag
53 Y.one(formid).on('submit', M.core_formchangechecker.set_form_submitted, this);
54
55 // YUI doesn't support onbeforeunload properly so we must use the DOM to set the onbeforeunload. As
56 // a result, the has_changed must stay in the DOM too
57 window.onbeforeunload = M.core_formchangechecker.report_form_dirty_state;
58 },
59
60 /**
1f777e5c
AN
61 * Store the initial value of the currently focussed element
62 *
63 * If an element has been focussed and changed but not yet blurred, the on change
64 * event won't be fired. We need to store it's initial value to compare it in the
65 * get_form_dirty_state function later.
66 *
67 * @method store_initial_value
68 * @param {EventFacade} e
69 */
5bb4f444 70 store_initial_value: function(e) {
0653469e 71 var thisevent;
58488d0f 72 if (e.target.hasClass('ignoredirty') || e.target.ancestor('.ignoredirty')) {
0653469e
ARN
73 // Don't warn on elements with the ignoredirty class
74 return;
75 }
76 if (M.core_formchangechecker.get_form_dirty_state()) {
77 // Detach all listen events to prevent duplicate initial value setting
78 while (this.initialvaluelisteners.length) {
79 thisevent = this.initialvaluelisteners.shift();
80 thisevent.detach();
81 }
82
83 return;
84 }
85
86 // Make a note of the current element so that it can be interrogated and
87 // compared in the get_form_dirty_state function
88 M.core_formchangechecker.stateinformation.focused_element = {
5bb4f444
DP
89 element: e.target,
90 initial_value: e.target.get('value')
0653469e
ARN
91 };
92 }
93 },
94 {
5bb4f444
DP
95 NAME: FORMCHANGECHECKERNAME,
96 ATTRS: {
97 formid: {
98 'value': ''
0653469e
ARN
99 }
100 }
101 }
102);
103
104M.core_formchangechecker = M.core_formchangechecker || {};
105
106// We might have multiple instances of the form change protector
107M.core_formchangechecker.instances = M.core_formchangechecker.instances || [];
108M.core_formchangechecker.init = function(config) {
109 var formchangechecker = new FORMCHANGECHECKER(config);
110 M.core_formchangechecker.instances.push(formchangechecker);
111 return formchangechecker;
112};
113
114// Store state information
115M.core_formchangechecker.stateinformation = [];
116
1f777e5c
AN
117/*
118 * Set the form changed state to true
119 */
0653469e 120M.core_formchangechecker.set_form_changed = function(e) {
58488d0f 121 if (e && e.target && (e.target.hasClass('ignoredirty') || e.target.ancestor('.ignoredirty'))) {
0653469e
ARN
122 // Don't warn on elements with the ignoredirty class
123 return;
124 }
125 M.core_formchangechecker.stateinformation.formchanged = 1;
126
127 // Once the form has been marked as dirty, we no longer need to keep track of form elements
128 // which haven't yet blurred
129 delete M.core_formchangechecker.stateinformation.focused_element;
130};
131
1f777e5c
AN
132/*
133 * Set the form submitted state to true
134 */
0653469e
ARN
135M.core_formchangechecker.set_form_submitted = function() {
136 M.core_formchangechecker.stateinformation.formsubmitted = 1;
137};
138
1f777e5c
AN
139/*
140 * Attempt to determine whether the form has been modified in any way and
141 * is thus 'dirty'
142 *
143 * @return Integer 1 is the form is dirty; 0 if not
144 */
0653469e
ARN
145M.core_formchangechecker.get_form_dirty_state = function() {
146 var state = M.core_formchangechecker.stateinformation,
147 editor;
148
149 // If the form was submitted, then return a non-dirty state
150 if (state.formsubmitted) {
151 return 0;
152 }
153
154 // If any fields have been marked dirty, return a dirty state
155 if (state.formchanged) {
156 return 1;
157 }
158
159 // If a field has been focused and changed, but still has focus then the browser won't fire the
160 // onChange event. We check for this eventuality here
161 if (state.focused_element) {
162 if (state.focused_element.element.get('value') !== state.focused_element.initial_value) {
163 return 1;
164 }
165 }
166
167 // Handle TinyMCE editor instances
168 // We can't add a listener in the initializer as the editors may not have been created by that point
169 // so we do so here instead
557f44d9
AN
170 if (typeof window.tinyMCE !== 'undefined') {
171 for (editor in window.tinyMCE.editors) {
172 if (window.tinyMCE.editors[editor].isDirty()) {
0653469e
ARN
173 return 1;
174 }
175 }
176 }
177
178 // If we reached here, then the form hasn't met any of the dirty conditions
179 return 0;
180};
181
584dd1b3
CB
182/*
183 * Reset the form state
184 */
185M.core_formchangechecker.reset_form_dirty_state = function() {
186 M.core_formchangechecker.stateinformation.formsubmitted = false;
187 M.core_formchangechecker.stateinformation.formchanged = false;
188};
189
1f777e5c
AN
190/*
191 * Return a suitable message if changes have been made to a form
192 */
0653469e
ARN
193M.core_formchangechecker.report_form_dirty_state = function(e) {
194 if (!M.core_formchangechecker.get_form_dirty_state()) {
195 // the form is not dirty, so don't display any message
196 return;
197 }
198
199 // This is the error message that we'll show to browsers which support it
200 var warningmessage = M.util.get_string('changesmadereallygoaway', 'moodle');
201
4415b667
DM
202 if (M.cfg.behatsiterunning) {
203 // If the behat site is running we don't want browser alerts.
204 return;
205 }
206
0653469e
ARN
207 // Most browsers are happy with the returnValue being set on the event
208 // But some browsers do not consistently pass the event
209 if (e) {
210 e.returnValue = warningmessage;
211 }
212
213 // But some require it to be returned instead
214 return warningmessage;
215};