MDL-54796 forms: Editor restore event created.
[moodle.git] / lib / yui / src / formchangechecker / js / formchangechecker.js
1 /**
2  * A utility to check for form changes before navigating away from a page.
3  *
4  * @module moodle-core-formchangechecker
5  */
7 /**
8  * A utility to check for form changes before navigating away from a page.
9  *
10  * @class M.core.formchangechecker
11  * @constructor
12  */
14 var FORMCHANGECHECKERNAME = 'core-formchangechecker',
16     FORMCHANGECHECKER = function() {
17         FORMCHANGECHECKER.superclass.constructor.apply(this, arguments);
18     };
20 Y.extend(FORMCHANGECHECKER, Y.Base, {
22         // The delegated listeners we need to detach after the initial value has been stored once
23         initialvaluelisteners : [],
25         /**
26          * Initialize the module
27          *
28          * @method initializer
29          */
30         initializer : function() {
31             var formid = 'form#' + this.get('formid'),
32                 currentform = Y.one(formid);
34             if (!currentform) {
35                 // If the form was not found, then we can't check for changes.
36                 return;
37             }
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);
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);
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));
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);
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         },
60         /**
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          */
70         store_initial_value : function(e) {
71             var thisevent;
72             if (e.target.hasClass('ignoredirty')) {
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                 }
83                 return;
84             }
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 = {
89                 element : e.target,
90                 initial_value : e.target.get('value')
91             };
92         }
93     },
94     {
95         NAME : FORMCHANGECHECKERNAME,
96         ATTRS : {
97             formid : {
98                 'value' : ''
99             }
100         }
101     }
102 );
104 M.core_formchangechecker = M.core_formchangechecker || {};
106 // We might have multiple instances of the form change protector
107 M.core_formchangechecker.instances = M.core_formchangechecker.instances || [];
108 M.core_formchangechecker.init = function(config) {
109     var formchangechecker = new FORMCHANGECHECKER(config);
110     M.core_formchangechecker.instances.push(formchangechecker);
111     return formchangechecker;
112 };
114 // Store state information
115 M.core_formchangechecker.stateinformation = [];
117 /*
118  * Set the form changed state to true
119  */
120 M.core_formchangechecker.set_form_changed = function(e) {
121     if (e && e.target && e.target.hasClass('ignoredirty')) {
122         // Don't warn on elements with the ignoredirty class
123         return;
124     }
125     M.core_formchangechecker.stateinformation.formchanged = 1;
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 };
132 /*
133  * Set the form submitted state to true
134  */
135 M.core_formchangechecker.set_form_submitted = function() {
136     M.core_formchangechecker.stateinformation.formsubmitted = 1;
137 };
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  */
145 M.core_formchangechecker.get_form_dirty_state = function() {
146     var state = M.core_formchangechecker.stateinformation,
147         editor;
149     // If the form was submitted, then return a non-dirty state
150     if (state.formsubmitted) {
151         return 0;
152     }
154     // If any fields have been marked dirty, return a dirty state
155     if (state.formchanged) {
156         return 1;
157     }
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     }
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
170     if (typeof window.tinyMCE !== 'undefined') {
171         for (editor in window.tinyMCE.editors) {
172             if (window.tinyMCE.editors[editor].isDirty()) {
173                 return 1;
174             }
175         }
176     }
178     // If we reached here, then the form hasn't met any of the dirty conditions
179     return 0;
180 };
182 /*
183  * Reset the form state
184  */
185 M.core_formchangechecker.reset_form_dirty_state = function() {
186     M.core_formchangechecker.stateinformation.formsubmitted = false;
187     M.core_formchangechecker.stateinformation.formchanged = false;
188 };
190 /*
191  * Return a suitable message if changes have been made to a form
192  */
193 M.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     }
199     // This is the error message that we'll show to browsers which support it
200     var warningmessage = M.util.get_string('changesmadereallygoaway', 'moodle');
202     if (M.cfg.behatsiterunning) {
203         // If the behat site is running we don't want browser alerts.
204         return;
205     }
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     }
213     // But some require it to be returned instead
214     return warningmessage;
215 };