Skip to content

Commit 5cf8bf9

Browse files
authored
Merge pull request #2882 from drgrice1/set-list-mass-edit-date
Add the capability to mass edit a particular set date when editing sets in the sets manager.
2 parents 9d60716 + 5c5cabb commit 5cf8bf9

3 files changed

Lines changed: 124 additions & 37 deletions

File tree

htdocs/js/ProblemSetList/problemsetlist.js

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
for (const id of ids) elements.push(document.getElementById(id));
88
for (const element of elements) {
99
if (element?.id.endsWith('_err_msg')) {
10-
element?.classList.remove('d-none');
11-
} else {
12-
element?.classList.add('is-invalid');
10+
element.classList.remove('d-none');
11+
} else if (element) {
12+
element.classList.add('is-invalid');
1313
if (!(element.id in event_listeners)) {
1414
event_listeners[element.id] = hide_errors([], elements);
15-
element?.addEventListener('change', event_listeners[element.id]);
15+
element.addEventListener('change', event_listeners[element.id]);
1616
}
1717
}
1818
}
@@ -23,17 +23,17 @@
2323
for (const id of ids) elements.push(document.getElementById(id));
2424
for (const element of elements) {
2525
if (element?.id.endsWith('_err_msg')) {
26-
element?.classList.add('d-none');
26+
element.classList.add('d-none');
2727
if (element.id === 'select_set_err_msg' && 'set_table_id' in event_listeners) {
2828
document
2929
.getElementById('set_table_id')
3030
?.removeEventListener('change', event_listeners.set_table_id);
3131
delete event_listeners.set_table_id;
3232
}
33-
} else {
34-
element?.classList.remove('is-invalid');
33+
} else if (element) {
34+
element.classList.remove('is-invalid');
3535
if (element.id in event_listeners) {
36-
element?.removeEventListener('change', event_listeners[element.id]);
36+
element.removeEventListener('change', event_listeners[element.id]);
3737
delete event_listeners[element.id];
3838
}
3939
}
@@ -174,10 +174,15 @@
174174
'zh-HK': 'yyyy/L/d ah:mm'
175175
};
176176

177-
// Initialize the date/time picker for the import form.
177+
// Initialize the date/time picker for the import form and common date editor.
178+
const dateInputs = [];
178179
const importDateShift = document.getElementById('import_date_shift');
179-
if (importDateShift) {
180-
luxon.Settings.defaultLocale = importDateShift.dataset.locale ?? 'en';
180+
if (importDateShift) dateInputs.push(importDateShift);
181+
const commonDateInput = document.getElementById('common-date');
182+
if (commonDateInput) dateInputs.push(commonDateInput);
183+
184+
for (const dateInput of dateInputs) {
185+
luxon.Settings.defaultLocale = dateInput.dataset.locale ?? 'en';
181186

182187
// Compute the time difference between a time in the browser timezone and the same time in the course timezone.
183188
// flatpickr gives the time in the browser's timezone, and this is used to adjust to the course timezone.
@@ -189,17 +194,17 @@
189194
new Date(dateTime.toLocaleString('en-US')).getTime() -
190195
new Date(
191196
dateTime.toLocaleString('en-US', {
192-
timeZone: importDateShift.dataset.timezone ?? 'America/New_York'
197+
timeZone: dateInput.dataset.timezone ?? 'America/New_York'
193198
})
194199
).getTime()
195200
);
196201
};
197202

198-
let fallbackDate = importDateShift.value
199-
? new Date(parseInt(importDateShift.value) * 1000 - timezoneAdjustment(parseInt(importDateShift.value)))
203+
let fallbackDate = dateInput.value
204+
? new Date(parseInt(dateInput.value) * 1000 - timezoneAdjustment(parseInt(dateInput.value)))
200205
: new Date();
201206

202-
const fp = flatpickr(importDateShift.parentNode, {
207+
const fp = flatpickr(dateInput.parentNode, {
203208
allowInput: true,
204209
enableTime: true,
205210
minuteIncrement: 1,
@@ -216,15 +221,15 @@
216221
disableMobile: true,
217222
wrap: true,
218223
plugins: [
219-
new confirmDatePlugin({ confirmText: importDateShift.dataset.doneText, showAlways: true }),
224+
new confirmDatePlugin({ confirmText: dateInput.dataset.doneText, showAlways: true }),
220225
new ShortcutButtonsPlugin({
221226
button: [
222227
{
223-
label: importDateShift.dataset.todayText ?? 'Today',
228+
label: dateInput.dataset.todayText ?? 'Today',
224229
attributes: { class: 'btn btn-sm btn-secondary ms-auto me-1 mb-1' }
225230
},
226231
{
227-
label: importDateShift.dataset.nowText ?? 'Now',
232+
label: dateInput.dataset.nowText ?? 'Now',
228233
attributes: { class: 'btn btn-sm btn-secondary mx-auto mb-1' }
229234
}
230235
],
@@ -251,6 +256,10 @@
251256

252257
// Make the alternate input left-to-right even for right-to-left languages.
253258
this.altInput.dir = 'ltr';
259+
260+
// Move the id of the now hidden input onto the added input so the labels still work.
261+
this.altInput.id = this.input.id;
262+
this.input.removeAttribute('id');
254263
},
255264
parseDate(datestr, format) {
256265
// Deal with the case of a unix timestamp. The timezone needs to be adjusted back as this is for
@@ -278,11 +287,46 @@
278287
}
279288
});
280289

281-
importDateShift.nextElementSibling.addEventListener('keydown', (e) => {
290+
dateInput.nextElementSibling.addEventListener('keydown', (e) => {
282291
if (e.key === ' ' || e.key === 'Enter') {
283292
e.preventDefault();
284293
fp.open();
285294
}
286295
});
287296
}
297+
298+
if (commonDateInput) {
299+
document.getElementById('apply-common-date')?.addEventListener('click', () => {
300+
const dateTypeInput = document.getElementById('set-date-choice');
301+
if (!dateTypeInput?.value) {
302+
show_errors(['choose_date_type_err_msg'], [dateTypeInput]);
303+
return;
304+
}
305+
306+
if (!commonDateInput.value) {
307+
show_errors(
308+
['choose_common_date_err_msg'],
309+
[commonDateInput.parentNode?._flatpickr?.input, commonDateInput.parentNode?._flatpickr?.altInput]
310+
);
311+
return;
312+
}
313+
314+
const selectedSets = Array.from(document.getElementsByName('apply_date_sets')).filter((c) => c.checked);
315+
if (!selectedSets.length) {
316+
show_errors(['select_set_err_msg'], []);
317+
event_listeners.set_table_id = hide_errors(
318+
['set_table_id'],
319+
[document.getElementById('select_set_err_msg')]
320+
);
321+
document.getElementById('set_table_id')?.addEventListener('change', event_listeners.set_table_id);
322+
}
323+
324+
for (const set of selectedSets) {
325+
const inputPicker = document.getElementsByName(`set.${set.value}.${dateTypeInput.value}`)[0]?.parentNode
326+
?._flatpickr;
327+
inputPicker?.setDate(commonDateInput.value, true);
328+
inputPicker?.close(); // The picker isn't actually open, but this triggers the onClose handler.
329+
}
330+
});
331+
}
288332
})();

templates/ContentGenerator/Instructor/ProblemSetList/set_list_row.html.ep

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<tr>
1717
%
1818
% if ($c->{editMode}) {
19+
<td><%= check_box apply_date_sets => $set_id, class => 'form-check-input' =%></td>
1920
<td dir="ltr">
2021
% if ($iconClass) {
2122
<i class="<%= $iconClass =%>" title="<%= $iconTitle =%>" alt="<%= $iconTitle =%>"></i>
@@ -71,9 +72,9 @@
7172
% for my $field (@$fieldNames) {
7273
% next unless defined $fieldTypes->{$field};
7374
<td>
74-
<span class="d-inline-block w-100 text-center text-nowrap <%= $visibleClass %>">
75+
<div class="d-inline-block w-100 text-center text-nowrap <%= $visibleClass %>">
7576
<%= include 'ContentGenerator/Instructor/ProblemSetList/set_list_field',
7677
name => "set.$set_id.$field", value => $set->$field, type => $fieldTypes->{$field} =%>
77-
</span>
78+
</div>
7879
</td>
7980
% }

templates/ContentGenerator/Instructor/ProblemSetList/set_list_table.html.ep

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,50 @@
1010
% answer_date => maketext('Answer Date')
1111
% );
1212
%
13+
% if ($c->{editMode}) {
14+
<div class="row">
15+
<label class="col-auto col-form-label col-form-label-sm" for="set-date-choice">Set</label>
16+
<div class="col-auto">
17+
<select class="form-select form-select-sm" id="set-date-choice">
18+
<option value="" selected><%= maketext('Choose set date type') %></option>
19+
<option value="open_date"><%= maketext('Open Date') %></option>
20+
% if ($c->ce->{pg}{ansEvalDefaults}{enableReducedScoring}) {
21+
<option value="reduced_scoring_date"><%= maketext('Reduced Scoring Date') %></option>
22+
% }
23+
<option value="due_date"><%= maketext('Close Date') %></option>
24+
<option value="answer_date"><%= maketext('Answer Date') %></option>
25+
</select>
26+
</div>
27+
<label class="col-auto col-form-label col-form-label-sm" for="common-date">to</label>
28+
<div class="col-auto input-group input-group-sm flatpickr flex-nowrap" style="max-width: 200px">
29+
<%= text_field 'common-date' => '',
30+
id => 'common-date', class => 'form-control',
31+
data => {
32+
input => undef,
33+
done_text => maketext('Done'),
34+
today_text => maketext('Today'),
35+
now_text => maketext('Now'),
36+
locale => $ce->{language},
37+
timezone => $ce->{siteDefaults}{timezone}
38+
} =%>
39+
<a class="btn btn-secondary btn-sm" data-toggle role="button" tabindex="0"
40+
aria-label="<%= maketext('Pick date and time') =%>">
41+
<i class="fas fa-calendar-alt"></i>
42+
</a>
43+
</div>
44+
<div class="col-auto">
45+
<button id="apply-common-date" type="button" class="btn btn-secondary btn-sm">
46+
<%= maketext('Apply to Selected Sets') %>
47+
</button>
48+
</div>
49+
</div>
50+
<div id="choose_date_type_err_msg" class="alert alert-danger p-1 mb-0 mt-2 d-inline-flex d-none">
51+
<%= maketext('Please choose a set date type.') %>
52+
</div>
53+
<div id="choose_common_date_err_msg" class="alert alert-danger p-1 mb-0 mt-2 d-inline-flex d-none">
54+
<%= maketext('Please select a date.') %>
55+
</div>
56+
% }
1357
<div id="select_set_err_msg" class="alert alert-danger p-1 mb-0 mt-2 d-inline-flex d-none">
1458
<%= maketext('Please select at least one set.') %>
1559
</div>
@@ -21,22 +65,20 @@
2165
%
2266
<thead class="table-group-divider">
2367
<tr>
24-
% if (!$c->{editMode}) {
25-
<th>
26-
<%= label_for 'select-all', begin =%>
27-
<%= check_box 'select-all' => '', id => 'select-all',
28-
class => 'select-all form-check-input set-id-tooltip',
29-
'aria-label' => maketext('Select all sets'),
30-
data => {
31-
select_group => 'selected_sets',
32-
bs_toggle => 'tooltip',
33-
bs_placement => 'right',
34-
bs_title => maketext('Select all sets')
35-
} =%>
36-
<i class="fa-solid fa-check-double" aria-hidden="true"></i>
37-
<% end =%>
38-
</th>
39-
% }
68+
<th>
69+
<%= label_for 'select-all', begin =%>
70+
<%= check_box 'select-all' => '', id => 'select-all',
71+
class => 'select-all form-check-input set-id-tooltip',
72+
'aria-label' => maketext('Select all sets'),
73+
data => {
74+
select_group => $c->{editMode} ? 'apply_date_sets' : 'selected_sets',
75+
bs_toggle => 'tooltip',
76+
bs_placement => 'right',
77+
bs_title => maketext('Select all sets')
78+
} =%>
79+
<i class="fa-solid fa-check-double" aria-hidden="true"></i>
80+
<% end =%>
81+
</th>
4082
% for (@$fieldNames) {
4183
<th id="<%= $_ %>_header">
4284
% if (!$c->{editMode} && $sortableFields->{$_}) {

0 commit comments

Comments
 (0)