Skip to content

Commit 5c5cabb

Browse files
committed
Add the capability to mass edit a particular set date when editing sets in the sets manager.
When editing sets in the sets manager, there are now some new inputs and such above the set list table. There is a select to choose which set date to change (the open date, reduced scoring date, close date, or answer date), and which date to set that to for all selected sets. Obviously the reduced scoring date is not shown if not enabled for the course. Then there is an "Apply to Selected Sets" button which if clicked will set the selected date type to the chosen date for all selected sets. There now needs to be the set check boxes on the edit page (these were not shown in edit mode before). Internally those checks have a different input name so that they don't conflict with the behavior of the usual checks and since they are for a different purpose. There is validation, and if the button is used when the inputs haven't been set or if no sets are selected, then messages are displayed informing the user of what is needed. Note that the usual ordering of dates applies. So if a set date is out of order then the other dates are adjusted just as if that date were edited directly for the particular set. This is how I envision what was requested in https://forums.openwebwork.org/mod/forum/discuss.php?d=8795#p22520. I don't see the suggested way of this working in that forum post as actually being feasible or even really making sense. The suggestion was to add a button that would copy a date to all sets below it, but the order of sets in the list is not a reasonable way of managing this since that order could depend on the set dates to begin with.
1 parent 744e06a commit 5c5cabb

File tree

3 files changed

+124
-37
lines changed

3 files changed

+124
-37
lines changed

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)