MDL-31655 Warn users of unblurred but modified fields
[moodle.git] / lib / yui / formchangechecker / formchangechecker.js
CommitLineData
88939271
ARN
1YUI.add('moodle-core-formchangechecker',
2 function(Y) {
3 // The CSS selectors we use
4 var CSS = {
5 };
6
7 var FORMCHANGECHECKERNAME = 'core-formchangechecker';
8
9 var FORMCHANGECHECKER = function() {
10 FORMCHANGECHECKER.superclass.constructor.apply(this, arguments);
11 }
12
13 Y.extend(FORMCHANGECHECKER, Y.Base, {
14 /**
15 * Initialize the module
16 */
17 initializer : function(config) {
18 var formid = 'form#' + this.get('formid');
19
20 // Add change events to the form elements
21 Y.all(formid + ' input').once('change', M.core_formchangechecker.set_form_changed, this);
22 Y.all(formid + ' textarea').once('change', M.core_formchangechecker.set_form_changed, this);
23 Y.all(formid + ' select').once('change', M.core_formchangechecker.set_form_changed, this);
24
9188a9a0
ARN
25 // Add a focus event to check for changes which are made without triggering a change event
26 Y.all(formid + ' input').on('focus', this.store_initial_value, this);
27 Y.all(formid + ' textarea').on('focus', this.store_initial_value, this);
28 Y.all(formid + ' select').on('focus', this.store_initial_value, this);
29
88939271
ARN
30 // We need any submit buttons on the form to set the submitted flag
31 Y.one(formid).on('submit', M.core_formchangechecker.set_form_submitted, this);
32
33 // YUI doesn't support onbeforeunload properly so we must use the DOM to set the onbeforeunload. As
34 // a result, the has_changed must stay in the DOM too
35 window.onbeforeunload = M.core_formchangechecker.report_form_dirty_state;
36 },
9188a9a0
ARN
37
38 /**
39 * Store the initial value of the currently focussed element
40 *
41 * If an element has been focussed and changed but not yet blurred, the on change
42 * event won't be fired. We need to store it's initial value to compare it in the
43 * get_form_dirty_state function later.
44 */
45 store_initial_value : function(e) {
46 if (M.core_formchangechecker.get_form_dirty_state()) {
47 // Clear the store_initial_value listeners as the form is already dirty so
48 // we no longer need to call this function
49 var formid = 'form#' + this.get('formid');
50
51 Y.all(formid + ' input').detach('focus', this.store_initial_value, this);
52 Y.all(formid + ' textarea').detach('focus', this.store_initial_value, this);
53 Y.all(formid + ' select').detach('focus', this.store_initial_value, this);
54 return;
55 }
56
57 // Make a note of the current element so that it can be interrogated and
58 // compared in the get_form_dirty_state function
59 M.core_formchangechecker.stateinformation.focused_element = {
60 element : e.target,
61 initial_value : e.target.get('value')
62 }
63 }
88939271
ARN
64 },
65 {
66 NAME : FORMCHANGECHECKERNAME,
67 ATTRS : {
68 formid : {
69 'value' : ''
70 }
71 }
72 }
73 );
74
75 M.core_formchangechecker = M.core_formchangechecker || {};
76
77 // We might have multiple instances of the form change protector
78 M.core_formchangechecker.instances = M.core_formchangechecker.instances || [];
79 M.core_formchangechecker.init = function(config) {
80 var formchangechecker = new FORMCHANGECHECKER(config);
81 M.core_formchangechecker.instances.push(formchangechecker);
82 return formchangechecker;
83 }
84
85 // Store state information
86 M.core_formchangechecker.stateinformation = [];
87
88 /**
89 * Set the form changed state to true
90 */
91 M.core_formchangechecker.set_form_changed = function() {
92 M.core_formchangechecker.stateinformation.formchanged = 1;
9188a9a0
ARN
93
94 // Once the form has been marked as dirty, we no longer need to keep track of form elements
95 // which haven't yet blurred
96 delete M.core_formchangechecker.stateinformation.focused_element;
88939271
ARN
97 }
98
99 /**
100 * Set the form submitted state to true
101 */
102 M.core_formchangechecker.set_form_submitted = function() {
103 M.core_formchangechecker.stateinformation.formsubmitted = 1;
104 }
105
106 /**
107 * Attempt to determine whether the form has been modified in any way and
108 * is thus 'dirty'
109 *
110 * @return Integer 1 is the form is dirty; 0 if not
111 */
112 M.core_formchangechecker.get_form_dirty_state = function() {
113 var state = M.core_formchangechecker.stateinformation;
114
115 // If the form was submitted, then return a non-dirty state
116 if (state.formsubmitted) {
117 return 0;
118 }
119
120 // If any fields have been marked dirty, return a dirty state
121 if (state.formchanged) {
122 return 1;
123 }
124
9188a9a0
ARN
125 // If a field has been focused and changed, but still has focus then the browser won't fire the
126 // onChange event. We check for this eventuality here
127 if (state.focused_element) {
128 if (state.focused_element.element.get('value') != state.focused_element.initial_value) {
129 return 1;
130 }
131 }
132
88939271
ARN
133 // Handle TinyMCE editor instances
134 // We can't add a listener in the initializer as the editors may not have been created by that point
135 // so we do so here instead
136 if (typeof tinyMCE != 'undefined') {
137 for (var editor in tinyMCE.editors) {
138 if (tinyMCE.editors[editor].isDirty()) {
139 return 1;
140 }
141 }
142 }
143
144 // If we reached here, then the form hasn't met any of the dirty conditions
145 return 0;
146 };
147
148 /**
149 * Return a suitable message if changes have been made to a form
150 */
151 M.core_formchangechecker.report_form_dirty_state = function(e) {
152 if (!M.core_formchangechecker.get_form_dirty_state()) {
153 // the form is not dirty, so don't display any message
154 return;
155 }
156
157 // This is the error message that we'll show to browsers which support it
158 var warningmessage = M.util.get_string('changesmadereallygoaway', 'moodle');
159
160 // Most browsers are happy with the returnValue being set on the event
161 // But some browsers do not consistently pass the event
162 if (e) {
163 e.returnValue = warningmessage;
164 }
165
166 // But some require it to be returned instead
167 return warningmessage;
168 };
169 },
170 '@VERSION@', {
171 requires : ['base']
172 }
173);