-
Notifications
You must be signed in to change notification settings - Fork 118
Expand file tree
/
Copy pathedit.js
More file actions
202 lines (197 loc) · 8.33 KB
/
edit.js
File metadata and controls
202 lines (197 loc) · 8.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import {
isObject,
isNumber,
isString,
} from '../utils/isType';
import { Tinymce } from '../utils/tinymce.js';
import debounce from '../utils/debounce';
import { updateSectionProgress, getQuestionDiv , deleteAllAnswersForQuestion } from '../utils/sectionUpdate';
import datePicker from '../utils/datePicker';
import TimeagoFactory from '../utils/timeagoFactory.js.erb';
$(() => {
if ($('form.form-answer').length > 0) {
const editorClass = 'tinymce_answer';
const showSavingMessage = (jQuery) => jQuery.closest('.question-form').find('[data-status="saving"]').show();
const hideSavingMessage = (jQuery) => jQuery.closest('.question-form').find('[data-status="saving"]').hide();
const closestErrorSavingMessage = (jQuery) => jQuery.closest('.question-form').find('[data-status="error-saving"]');
const questionId = (jQuery) => jQuery.closest('.form-answer').attr('data-autosave');
const isStale = (jQuery) => jQuery.closest('.question-form').find('.answer-locking').text().trim().length !== 0;
const isReadOnly = () => $('.form-answer fieldset:disabled').length > 0;
const showOrHideQuestions = (data) => {
data.section_data.forEach((section) => {
updateSectionProgress(section.sec_id, section.no_ans, section.no_qns);
});
data.qn_data.to_hide.forEach((questionid) => {
deleteAllAnswersForQuestion(questionid);
getQuestionDiv(questionid).slideUp();
});
data.qn_data.to_show.forEach((questionid) => {
getQuestionDiv(questionid).slideDown();
});
};
/*
* A map of debounced functions, one for each input, textarea or select change at any
* form with class form-answer. The key represents a question id and the value holds
* the debounced function for a given input, textarea or select. Note, this map is
* populated on demand, i.e. the first time a change is made at a given input, textarea
* or select within the form, a new key-value should be created. Succesive times, the
* debounced function should be retrieved instead.
*/
const debounceMap = {};
const autoSaving = (jQuery) => {
if (!isStale(jQuery)) {
jQuery.closest('.form-answer').trigger('submit');
}
};
const doneCallback = (data, jQuery) => {
const form = jQuery.closest('form');
// Validation for the data object received
if (isObject(data)) {
if (isObject(data.question)) { // Object related to question within data received
if (isNumber(data.question.id)) {
if (isString(data.question.answer_status)) {
$(`#answer-status-${data.question.id}`).html(data.question.answer_status);
TimeagoFactory.render($('time.timeago'));
}
if (isString(data.question.locking)) { // When an answer is stale...
// Removes event handlers for the saved form
detachEventHandlers(form); // eslint-disable-line no-use-before-define
// Reflesh form view with the new partial form received
$(`#answer-form-${data.question.id}`).html(data.question.form);
// Retrieves the newly form added to the DOM
const newForm = $(`#answer-form-${data.question.id}`).find('form');
// Attaches event handlers for the new form
attachEventHandlers(newForm); // eslint-disable-line no-use-before-define
// Refresh optimistic locking view with the form that caused the locking
$(`#answer-locking-${data.question.id}`).html(data.question.locking);
} else { // When answer is NOT stale...
$(`#answer-locking-${data.question.id}`).html('');
if (isNumber(data.question.answer_lock_version)) {
form.find('#answer_lock_version').val(data.question.answer_lock_version);
}
}
}
}// End Object related to question within data received
if (isObject(data.plan)) { // Object related to plan within data received
if (isString(data.plan.progress)) {
$('.progress').html(data.plan.progress);
}
}
showOrHideQuestions(data);
}
};
const failCallback = (error, jQuery) => {
closestErrorSavingMessage(jQuery).html(
(isObject(error.responseJSON) && isString(error.responseJSON.detail))
? error.responseJSON.detail : error.statusText,
).show();
};
const changeHandler = (e) => {
const target = $(e.target);
const id = questionId(target);
if (!debounceMap[id]) {
debounceMap[id] = debounce(autoSaving);
}
debounceMap[id](target);
};
const submitHandler = (e) => {
e.preventDefault();
const target = $(e.target);
const form = target.closest('form');
const id = questionId(target);
if (debounceMap[id]) {
// Cancels the delated execution of autoSaving
// (e.g. user clicks the button before the delay is met)
debounceMap[id].cancel();
}
$.ajax({
method: form.attr('method'),
url: form.attr('action'),
data: form.serializeArray(),
beforeSend: () => {
showSavingMessage(target);
},
complete: () => {
hideSavingMessage(target);
},
}).done((data) => {
doneCallback(data, target);
}).fail((error) => {
failCallback(error, target);
});
};
const blurHandler = (editor) => {
const target = $(editor.getElement());
const id = questionId(target);
if (editor.isDirty()) {
editor.save(); // Saves contents from editor to the textarea element
if (!debounceMap[id]) {
debounceMap[id] = debounce(autoSaving);
}
debounceMap[id](target);
}
};
const focusHandler = (editor) => {
const id = questionId($(editor.getElement()));
if (debounceMap[id]) {
/* Cancels the delayed execution of autoSaving, either because user
* transitioned from an option_based question to the comment or
* because the target element triggered blur and focus before
* the delayed execution of autoSaving.
*/
debounceMap[id].cancel();
}
};
const formHandlers = ({ jQuery, attachment = 'off' }) => {
// Listeners to change and submit for a form
jQuery[attachment]('change', changeHandler);
jQuery[attachment]('submit', submitHandler);
};
const editorHandlers = (editor) => {
// Listeners to blur and focus events for a tinymce instance
editor.on('Blur', () => blurHandler(editor));
editor.on('Focus', () => focusHandler(editor));
};
/*
Detaches events from a specific form including its tinymce editor
@param { objecg } - jQueryForm to remove events
*/
const detachEventHandlers = (jQueryForm) => {
formHandlers({ jQuery: jQueryForm, attachment: 'off' });
const tinymceId = jQueryForm.find(`.${editorClass}`).attr('id');
Tinymce.destroyEditorById(tinymceId);
};
/*
Attaches events for a specific form including its tinymce editor
@param { objecg } - jQueryForm to add events
*/
const attachEventHandlers = (jQueryForm) => {
formHandlers({ jQuery: jQueryForm, attachment: 'on' });
const tinymceId = jQueryForm.find(`.${editorClass}`).attr('id');
Tinymce.init({ selector: `#${tinymceId}` });
editorHandlers(Tinymce.findEditorById(tinymceId));
};
// Initial load
TimeagoFactory.render($('time.timeago'));
Tinymce.init({ selector: `.${editorClass}` });
if (!isReadOnly()) {
// Attaches form and tinymce event handlers
Tinymce.findEditorsByClassName(editorClass).forEach(editorHandlers);
formHandlers({ jQuery: $('.form-answer'), attachment: 'on' });
} else {
// Sets the editor mode for each editor to readonly
Tinymce.findEditorsByClassName(editorClass).forEach((editor) => {
editor.mode.set('readonly');
});
}
datePicker();
// Clicking the 'Comments & Guidance' div should toggle the guidance & comments section
$(document).on('click', '.toggle-guidance-section', (e) => {
const target = $(e.currentTarget);
target.parents('.question-body').find('.guidance-section').toggle();
target.find('span.fa-chevron-right, span.fa-chevron-left')
.toggleClass('fa-chevron-right')
.toggleClass('fa-chevron-left');
});
}
});