Newer
Older
import { $gettext } from '../lib/gettext';
/**
* This file provides a set of global handlers.
*/
var proxy_elements_selector = ':checkbox[data-proxyfor], :radio[data-proxyfor]';
var proxied_elements_selector = ':checkbox[data-proxiedby], :radio[data-proxiedby]';
function connectProxyAndProxied() {
$(proxy_elements_selector).each(function () {
const proxy = $(this);
const proxyId = proxy.uniqueId().attr('id');
const proxied = proxy.data('proxyfor');
// The following seems like a hack but works perfectly fine.
$(proxied).each(function () {
const proxiedBy = ($(this).attr('data-proxiedby') || '').split(',').filter(a => a.length > 0);
if (!proxiedBy.includes(`#${proxyId}`)) {
proxiedBy.push(`#${proxyId}`);
}
$(this)
.attr('data-proxiedby', proxiedBy.join(','))
.data('proxiedby', `#${proxyId}`);
});
}).trigger('update.proxy');
}
// Use a checkbox as a proxy for a set of other checkboxes. Define
// proxied elements by a css selector in attribute "data-proxyfor".
$(document).on('change', proxy_elements_selector, function (event, force) {
// Detect if event was triggered natively (triggered events have no
// originalEvent)
if (event.originalEvent !== undefined || !!force) {
const proxied = $(this).data('proxyfor');
$(proxied)
.filter(':not(:disabled)')
.prop('checked', this.checked)
.prop('indeterminate', false)
.each((index, element) => {
const proxiedBy = $(element).attr('data-proxiedby');
$(proxiedBy)
.filter((idx, item) => item !== this)
.trigger('update.proxy');
const event = new Event('change');
element.dispatchEvent(event);
if (element.matches('[data-proxyfor],[data-activates],[data-deactivates],[data-hides],[data-shows]')) {
$(element).trigger('change', [true]);
}
}
}).on('update.proxy', proxy_elements_selector, function () {
const proxied = $(this).data('proxyfor');
const $proxied = $(proxied).filter(':not(:disabled)');
const $checked = $proxied.filter(':checked');
const $indeterminate = $proxied.filter(function () {
return $(this).prop('indeterminate');
});
$(this).prop('checked', $proxied.length > 0 && $proxied.length === $checked.length);
$(this).prop(
'indeterminate',
($checked.length > 0 && $checked.length < $proxied.length) || $indeterminate.length > 0
);
$(this).trigger('change');
}).on('change', proxied_elements_selector, function () {
//In case of radio buttons in a group that are deselected,
//we must trigger the update.proxy event for each radio
//button in the group, if the proxy is another element
//than the proxy for "this" element.
if ($(this).is(':radio')) {
const proxy = $(this).data('proxiedby');
const name = $(this).attr('name');
const radio_button_group = $(`:radio[name="${name}"]`);
$(radio_button_group).each(function () {
const button_proxy = $(this).data('proxiedby');
if (button_proxy !== proxy) {
$(button_proxy).trigger('update.proxy');
}
});
} else {
const proxy = $(this).attr('data-proxiedby');
$(proxy).trigger('update.proxy');
}
});
STUDIP.ready(connectProxyAndProxied);
$(document).on('refresh-handlers', connectProxyAndProxied);
// Use a checkbox or radiobox as a toggle switch for the disabled attribute of
// another set of elements. Define set of elements to disable/enable if item is
// neither :checked nor :indeterminate by a css selector in attribute
// "data-activates" / "deactivates".
$(document).on('change', '[data-activates],[data-deactivates]', function() {
if (!$(this).is(':checkbox,:radio')) {
return;
}
['activates', 'deactivates'].forEach((type) => {
var selector = $(this).data(type);
if (selector === undefined || $(this).prop('disabled')) {
return;
}
var state = $(this).prop('checked') || $(this).prop('indeterminate') || false;
$(selector).each(function() {
Jan-Hendrik Willms
committed
var condition = $(this).data(`${type}Condition`),
toggle = state && (!condition || $(condition).length > 0);
$(this)
.attr('disabled', type === 'activates' ? !toggle : toggle)
.trigger('update.proxy');
});
});
});
STUDIP.ready((event) => {
$('[data-activates],[data-deactivates]', event.target).trigger('change');
});
// Use a select as a toggle switch for the disabled attribute of another
// element. Define element to disable if select has a value different from
// an empty string by a css selector in attribute "data-activates".
$(document).on('change update.proxy', 'select[data-activates]', function() {
Jan-Hendrik Willms
committed
var activates = $(this).data('activates'),
disabled = $(this).is(':disabled') || $(this).val().length === 0;
$(activates).attr('disabled', disabled);
});
STUDIP.ready((event) => {
$('select[data-activates]', event.target).trigger('change');
});
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
//
$(document).on('change', '[data-hides],[data-shows]', function () {
if (!$(this).is(':checkbox,:radio')) {
return;
}
['hides', 'shows'].forEach((type) => {
var selector = $(this).data(type);
if (selector === undefined || $(this).prop('disabled')) {
return;
}
var state = $(this).prop('checked') || $(this).prop('indeterminate') || false;
$(selector).each(function() {
var condition = $(this).data(`${type}Condition`),
toggle = state && (!condition || $(condition).length > 0);
$(this)
.toggle(type === 'shows' ? toggle : !toggle)
.trigger('update.proxy');
});
});
});
STUDIP.ready(event => {
$('[data-hides],[data-shows]', event.target).trigger('change');
});
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// Enable the user to set the checked state on a subset of related
// checkboxes by clicking the first checkbox of the subset and then
// clicking the last checkbox of the subset while holding down the shift
// key, thus toggling all the checkboxes in between.
// This only works if the first and last checkbox of the subset are set
// to the same state.
var last_element = null;
$(document).on('click', '[data-shiftcheck] :checkbox', function(event) {
if (!event.originalEvent || last_element === event.target) {
return;
}
if (last_element !== null && event.shiftKey) {
var $this = $(event.target),
$form = $this.closest('form'),
name = $this.attr('name'),
state = $this.prop('checked'),
$last = $(last_element),
children,
idx0,
idx1,
tmp;
if ($form.is($last.closest('form')) && name === $last.attr('name') && state === $last.prop('checked')) {
children = $form.find(':checkbox[name="' + name + '"]:not(:disabled)');
idx0 = children.index(event.target);
idx1 = children.index(last_element);
if (idx0 > idx1) {
tmp = idx0;
idx0 = idx1;
idx1 = tmp;
}
children.slice(idx0, idx1).prop('checked', state);
}
}
last_element = event.target;
});
// Lets the user confirm a specific action (submit or click event).
function confirmation_handler(event) {
if (!event.isDefaultPrevented()) {
event.stopPropagation();
event.preventDefault();
var element = $(event.currentTarget).closest('[data-confirm]'),
question =
element.data().confirm ||
element.attr('title') ||
element.find('[title]:first').attr('title') ||
$gettext('Wollen Sie die Aktion wirklich ausführen?');
STUDIP.Dialog.confirm(question).done(function() {
var content = element.data().confirm;
// We need to trigger the native event because for
// some reason, jQuery's .trigger() won't always
// work. Thus the data-confirm attribute will be removed
// so that the original event can be executed
element
.removeAttr('data-confirm')
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
// Reapply the data-confirm attribute
window.setTimeout(function() {
element.attr('data-confirm', content);
}, 0);
});
}
}
$(document).on(
'click',
'a[data-confirm],input[data-confirm],button[data-confirm],img[data-confirm]',
confirmation_handler
);
$(document).on('submit', 'form[data-confirm]', confirmation_handler);
// Ensures an element has the same value as another element.
$(document).on('change', 'input[data-must-equal]', function() {
var value = $(this).val(),
rel = $(this).data().mustEqual,
other = $(rel).val(),
labels = $.map([this, rel], function(element) {
var label = $(element)
.closest('label')
.text();
label = label || $('label[for="' + $(element).attr('id') + '"]').text();
return $.trim(label.split(':')[0]);
}),
error_message = $gettext('Die beiden Werte "$1" und "$2" stimmen nicht überein. '),
matches = error_message.match(/\$\d/g);
$.each(matches, function(i) {
error_message = error_message.replace(this, labels[i]);
});
if (value !== other) {
this.setCustomValidity(error_message);
} else {
this.setCustomValidity('');
}
});
//Generalisation: The enter-accessible class allows an element to be accessible via keyboard
//by triggering the click event when the enter key is pressed.
$(document).on('keydown', '.enter-accessible', function(event) {
if (event.code === 'Enter') {
//The enter key has been pressed.
Moritz Strohm
committed
event.preventDefault();
$(this).trigger('click');
}
});
$(document).on('click keydown', '[data-toggles]', function (event) {
if ((event.type === 'keydown' && event.key === 'Enter') || event.type === 'click') {
const target = event.currentTarget.dataset.toggles;
//Check if the target is a checkbox. These have to be toggled differently than
//other elements:
if ($(target).is(':checkbox')) {
//Toggle the checked state of the checkbox:
$(target).prop('checked', !$(target).prop('checked'));
} else {
//Do the normal toggle operation:
$(target).toggle();
}

Thomas Hackl
committed
const controls = $(event.currentTarget).attr('aria-controls');
if (controls) {
// Find elements which control the expanded status of the same element.
const elements = $('[aria-controls="' + controls + '"]');
const expanded = $(event.currentTarget).attr('aria-expanded') === 'true';
// Set the aria-expanded status accordingly.
elements.attr('aria-expanded', !expanded);
}