aboutsummaryrefslogtreecommitdiff
path: root/resources/assets/javascripts
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+studip@gmail.com>2025-10-01 11:30:47 +0200
committerJan-Hendrik Willms <tleilax+studip@gmail.com>2025-10-01 11:30:47 +0200
commitc1a659702122401c9c197aa5fb64d62767c4dead (patch)
tree90fd080cba9f366dd40f5c8a88bd1142b9507f2e /resources/assets/javascripts
parent13027d9c79b37d9da74ae759a9c510c67b1c2c20 (diff)
restructure dialog js file so that typescript won't complain, fixes #5906
Closes #5906 Merge request studip/studip!4503
Diffstat (limited to 'resources/assets/javascripts')
-rw-r--r--resources/assets/javascripts/lib/dialog.js1263
1 files changed, 627 insertions, 636 deletions
diff --git a/resources/assets/javascripts/lib/dialog.js b/resources/assets/javascripts/lib/dialog.js
index 3d6f56a..4081a45 100644
--- a/resources/assets/javascripts/lib/dialog.js
+++ b/resources/assets/javascripts/lib/dialog.js
@@ -1,4 +1,4 @@
-import { $gettext } from '../lib/gettext';
+import { $gettext } from './gettext';
import parseOptions from './parse_options.js';
import extractCallback from './extract_callback.js';
import Overlay from './overlay.js';
@@ -15,9 +15,7 @@ import Report from './report.ts';
* @copyright 2014 Stud.IP Core Group
* @todo Handle file uploads <http://goo.gl/PnSra8>
*/
-
-
-var dialog_margin = 0;
+let dialog_margin = 0;
/**
* Extract buttons from given element.
@@ -63,12 +61,10 @@ function extractButtons(element, callback = null) {
const Dialog = {
instances: {},
stack: [],
- hasInstance: function(id) {
- id = id || 'default';
+ hasInstance(id = 'default') {
return this.instances[id] !== undefined;
},
- getInstance: function(id) {
- id = id || 'default';
+ getInstance(id = 'default') {
if (!this.hasInstance(id)) {
this.instances[id] = {
open: false,
@@ -82,737 +78,732 @@ const Dialog = {
}
return this.instances[id];
},
- removeInstance: function(id) {
- id = id || 'default';
+ removeInstance(id = 'default') {
if (this.hasInstance(id)) {
delete this.instances[id];
- var index = this.stack.indexOf(id);
+ const index = this.stack.indexOf(id);
this.stack.splice(index, 1);
}
},
- /**
- * legacy method, remove in future
- * @return bool
- */
- shouldOpen: function() {
- return true;
-// return !$('html').is('.responsive-display') && $(window).innerHeight() >= 400;
- },
- handlers: {
- header: {}
- }
-};
-
-// Handler for HTTP header X-Location: Relocate to another location
-Dialog.handlers.header['X-Location'] = function(location, options) {
- location = decodeURI(location);
- if (document.location.href === location) {
- document.location.reload(true);
- } else {
- $(window)
- .on('hashchange', function() {
- document.location.reload(true);
- })
- .on('beforeunload', function() {
- $(window).off('hashchange');
- });
- }
+ // Creates a dialog from an anchor, a button or a form element.
+ // Will update the dialog if it is already open
+ fromElement(element, options = {}) {
+ if ($(element).is(':disabled') || !Dialog.shouldOpen()) {
+ return;
+ }
- Dialog.close(options);
- document.location = location;
+ if (options.close) {
+ Dialog.close(options);
+ return;
+ }
- return false;
-};
-// Handler for HTTP header X-Dialog-Execute: Execute arbitrary function
-Dialog.handlers.header['X-Dialog-Execute'] = function(value, options, xhr) {
- var callback = window,
- payload = xhr.getResponseHeader('Content-Type').match(/json/)
- ? $.parseJSON(xhr.responseText)
- : xhr.responseText;
+ if (!$(element).is('a,button,form,input[type=image],input[type=submit]')) {
+ throw 'Dialog.fromElement called on an unsupported element.';
+ }
- // Try to parse value as JSON (value might be {func: 'foo', payload: {}})
- try {
- value = $.parseJSON(value);
- } catch {
- value = { func: value };
- }
+ options.origin = element;
+ options.title =
+ options.title ||
+ Dialog.getInstance(options.id).options.title ||
+ $(element).attr('title') ||
+ $(element).find('[title]').first().attr('title') ||
+ $(element).filter('a,button').text();
+ options.method = 'get';
+ options.data = {};
+
+ let url;
+ let fd;
+
+ // Predefine options
+ if ($(element).is('form,button,input')) {
+ url = $(element).attr('formaction') ||
+ $(element).closest('form').data('formaction') ||
+ $(element).closest('form').attr('action');
+ options.method = $(element).closest('form').attr('method');
+ options.data = $(element).closest('form').serializeArray();
+
+ if ($(element).is('button,input')) {
+ options.data.push({
+ name: $(element).attr('name'),
+ value: $(element).val()
+ });
+ } else if ($(element).data().triggeredBy) {
+ options.data.push($(element).data().triggeredBy);
+ }
+ $(element).closest('form').removeData('formaction');
- // Check for invalid call
- if (value.func === undefined) {
- throw 'Dialog: Invalid value for X-Dialog-Execute';
- }
+ if ($(element).closest('form').attr('enctype') === 'multipart/form-data') {
+ options.processData = false;
- // Populate payload if not set
- if (value.payload === undefined) {
- value.payload = xhr.getResponseHeader('Content-Type').match(/json/)
- ? $.parseJSON(xhr.responseText)
- : xhr.responseText;
- }
+ fd = new FormData();
+ options.data.forEach(function(item) {
+ fd.append(item.name, item.value);
+ });
- // Find callback
- callback = extractCallback(value.func, payload);
+ $(element).closest('form').find('input[type=file]').each(function() {
+ let name = $(this).attr('name');
+ for (let i = 0; i < this.files.length; i += 1) {
+ fd.append(name, this.files[i]);
+ }
+ });
- // Check callback
- if (typeof callback !== 'function') {
- throw 'Dialog: Given callback is not a valid function';
- }
+ options.data = fd;
+ }
+ } else {
+ url = $(element).attr('href');
+ }
- // Execute callback
- return callback(value.payload, xhr);
-};
-// Handler for HTTP header X-Dialog-Close: Close the dialog
-Dialog.handlers.header['X-Dialog-Close'] = function(value, options) {
- Dialog.close(options);
- return false;
-};
-// Handler for HTTP header X-Wikilink: Set the options' wiki link
-Dialog.handlers.header['X-Wikilink'] = function(link, options) {
- options.wiki_link = link;
-};
-// Handler for HTTP header X-Title: Set the dialog title
-Dialog.handlers.header['X-Title'] = function(title, options) {
- title = decodeURIComponent(title);
- if (title !== $('title').data().original) {
- options.title = title || options.title;
- }
-};
-// Handler for HTTP header X-No-Buttons: Decide whether to show dialog buttons
-Dialog.handlers.header['X-No-Buttons'] = function(value, options) {
- options.buttons = false;
- options.dialogClass = 'no-default-buttons';
-};
-// Handler for HTTP header X-Dialog-Size: Adjust the size of the dialog
-Dialog.handlers.header['X-Dialog-Size'] = function (value, options) {
- options.size = value;
-};
+ return Dialog.fromURL(url, options);
+ },
-// Creates a dialog from an anchor, a button or a form element.
-// Will update the dialog if it is already open
-Dialog.fromElement = function(element, options) {
- options = options || {};
+ // Creates a dialog from a passed url
+ fromURL(url, options = {}) {
+ // Append overlay
+ if (Dialog.getInstance(options.id).open) {
+ Overlay.show(true, Dialog.getInstance(options.id).element.parent());
+ } else {
+ Overlay.show(true);
+ }
- if ($(element).is(':disabled') || !Dialog.shouldOpen()) {
- return;
- }
+ // Send ajax request
+ $.ajax({
+ url: url,
+ type: (options.method || 'get').toUpperCase(),
+ data: options.data || {},
+ headers: { 'X-Dialog': true },
+ cache: false,
+ contentType:
+ options.processData !== undefined && !options.processData
+ ? false
+ : 'application/x-www-form-urlencoded; charset=UTF-8',
+ processData: options.processData ?? true
+ })
+ .done(function(response, status, xhr) {
+ let advance = true;
- if (options.close) {
- Dialog.close(options);
- return;
- }
+ // Trigger event
+ $(options.origin || document).trigger('dialog-load', { xhr: xhr, options: options });
- if (!$(element).is('a,button,form,input[type=image],input[type=submit]')) {
- throw 'Dialog.fromElement called on an unsupported element.';
- }
+ // Execute all defined header handlers
+ const handlers = Object.assign(
+ Dialog.handlers.header,
+ STUDIP.Dialog.handlers.header
+ );
+ $.each(handlers, (header, handler) => {
+ let value = xhr.getResponseHeader(header);
+ let result = true;
+ if (value !== null) {
+ result = handler(value, options, xhr);
+ }
+ advance = advance && result !== false;
+ return result;
+ });
- options.origin = element;
- options.title =
- options.title ||
- Dialog.getInstance(options.id).options.title ||
- $(element).attr('title') ||
- $(element).find('[title]').first().attr('title') ||
- $(element).filter('a,button').text();
- options.method = 'get';
- options.data = {};
-
- var url, fd;
-
- // Predefine options
- if ($(element).is('form,button,input')) {
- url = $(element).attr('formaction') ||
- $(element).closest('form').data('formaction') ||
- $(element).closest('form').attr('action');
- options.method = $(element).closest('form').attr('method');
- options.data = $(element).closest('form').serializeArray();
-
- if ($(element).is('button,input')) {
- options.data.push({
- name: $(element).attr('name'),
- value: $(element).val()
- });
- } else if ($(element).data().triggeredBy) {
- options.data.push($(element).data().triggeredBy);
- }
- $(element).closest('form').removeData('formaction');
+ Overlay.hide(0);
- if ($(element).closest('form').attr('enctype') === 'multipart/form-data') {
- options.processData = false;
+ if (advance) {
+ Dialog.show(response, options);
+ }
+ })
+ .fail((jqXHR, textStatus, errorThrown) => {
+ Report.error($gettext('Es ist ein Fehler aufgetreten.'), jqXHR.responseJSON?.message ?? errorThrown);
+ Overlay.hide();
- fd = new FormData();
- options.data.forEach(function(item) {
- fd.append(item.name, item.value);
});
- $(element).closest('form').find('input[type=file]').each(function() {
- var name = $(this).attr('name'),
- i;
- for (i = 0; i < this.files.length; i += 1) {
- fd.append(name, this.files[i]);
- }
- });
+ return true;
+ },
- options.data = fd;
- }
- } else {
- url = $(element).attr('href');
- }
+ // Opens or updates the dialog
+ show(content, options = {}) {
+ options = Object.assign({}, Dialog.options, options);
- return Dialog.fromURL(url, options);
-};
+ options.wikilink = options.wikilink === undefined ? true : options.wikilink;
-// Creates a dialog from a passed url
-Dialog.fromURL = function(url, options) {
- options = options || {};
+ const scripts = $('<div>' + content + '</div>').filter('script'); // Extract scripts
+ let dialog_options = {};
+ const instance = Dialog.getInstance(options.id);
- // Check if dialog should actually open
- if (!Dialog.shouldOpen()) {
- location.href = url;
- }
+ if (instance.open) {
+ options.title = options.title || instance.element.dialog('option', 'title');
+ }
- // Append overlay
- if (Dialog.getInstance(options.id).open) {
- Overlay.show(true, Dialog.getInstance(options.id).element.parent());
- } else {
- Overlay.show(true);
- }
+ if (options['center-content']) {
+ content = '<div class="studip-dialog-centered-helper">' + content + '</div>';
+ }
- // Send ajax request
- $.ajax({
- url: url,
- type: (options.method || 'get').toUpperCase(),
- data: options.data || {},
- headers: { 'X-Dialog': true },
- cache: false,
- contentType:
- options.processData !== undefined && !options.processData
- ? false
- : 'application/x-www-form-urlencoded; charset=UTF-8',
- processData: options.processData ?? true
- })
- .done(function(response, status, xhr) {
- var advance = true;
-
- // Trigger event
- $(options.origin || document).trigger('dialog-load', { xhr: xhr, options: options });
-
- // Execute all defined header handlers
- var handlers = Object.assign(
- Dialog.handlers.header,
- STUDIP.Dialog.handlers.header
- );
- $.each(handlers, (header, handler) => {
- var value = xhr.getResponseHeader(header),
- result = true;
- if (value !== null) {
- result = handler(value, options, xhr);
+ // Hide and update container
+ instance.element.hide().html(content);
+
+ // Store options and dimensions
+ instance.options = options;
+ instance.dimensions = Dialog.calculateDimensions(instance, content, options);
+ instance.previous_title = instance.previous_title || PageLayout.title;
+
+ // Set dialog options
+ dialog_options = $.extend(dialog_options, {
+ width: instance.dimensions.width,
+ height: instance.dimensions.height,
+ dialogClass: Dialog.getClasses(options),
+ classes: {
+ 'ui-dialog-content': 'studip-dialog-content',
+ },
+ buttons: options.buttons || {},
+ title: options.title,
+ modal: true,
+ resizable: options.resize ?? true,
+ create(event) {
+ $(event.target)
+ .parent()
+ .css('position', 'fixed');
+ },
+ resizeStop(event, ui) {
+ const position = [
+ Math.floor(ui.position.left) - $(window).scrollLeft(),
+ Math.floor(ui.position.top) - $(window).scrollTop()
+ ];
+ $(event.target)
+ .parent()
+ .css('position', 'fixed');
+ $(event.target).dialog('option', 'position', position);
+
+ instance.fixedDimensions = true;
+ instance.dimensions = ui.size;
+ },
+ open(event) {
+ PageLayout.title = dialog_options.title;
+
+ // Prevent tooltips from being automatically focussed
+ if (event.target.querySelector('[autofocus]') === null) {
+ $(':tabbable.tooltip:focus').data('tooltipObject')?.hide();
+ $(':tabbable:not(.tooltip):first', event.target).trigger('focus');
}
- advance = advance && result !== false;
- return result;
- });
- Overlay.hide(0);
+ // Adjust titlebar of dialog
+ const helpbar_element = $('.helpbar a[href*="hilfe.studip.de"]');
+ const tooltip = helpbar_element.text();
+ const link = options.wiki_link || helpbar_element.attr('href');
+ const element = $('<a class="ui-dialog-titlebar-wiki"' + ' target="_blank" rel="noopener noreferrer">')
+ .attr('href', link)
+ .attr('title', tooltip);
+
+ if (options.wikilink) {
+ $(this)
+ .siblings('.ui-dialog-titlebar')
+ .addClass('with-wiki-link')
+ .find('.ui-dialog-titlebar-close')
+ .before(element);
+ }
- if (advance) {
- Dialog.show(response, options);
- }
- })
- .fail((jqXHR, textStatus, errorThrown) => {
- Report.error($gettext('Es ist ein Fehler aufgetreten.'), jqXHR.responseJSON?.message ?? errorThrown);
- Overlay.hide();
+ $(this).parent().find('.ui-dialog-title').attr({
+ title: options.title,
+ role: 'heading',
+ 'aria-level': 2
+ });
+ $(this).parents('.studip-dialog').attr('aria-modal', 'true');
- });
+ instance.open = true;
+ // Execute scripts
+ $('head').append(scripts);
- return true;
-};
+ $(options.origin || document).trigger('dialog-open', {dialog: this, options: options});
+ },
+ close() {
+ $(options.origin || document).trigger('dialog-close', {dialog: this, options: options});
-// Opens or updates the dialog
-Dialog.show = function(content, options = {}) {
- options = Object.assign({}, Dialog.options, options);
+ PageLayout.title = instance.previous_title;
- options.wikilink = options.wikilink === undefined ? true : options.wikilink;
+ Dialog.close(options);
+ }
+ });
- var scripts = $('<div>' + content + '</div>').filter('script'); // Extract scripts
- var dialog_options = {};
- var instance = Dialog.getInstance(options.id);
+ // Create buttons
+ if (
+ options.buttons === undefined
+ || (options.buttons && !$.isPlainObject(options.buttons))
+ ) {
+ // Create observer to detect changes on disabled attribute
+ const observer = new MutationObserver((mutations) => {
+ mutations.forEach(mutation => {
+ const id = mutation.target.id;
+ const buttonIndex = dialog_options.buttons.findIndex(button => button.id === id);
+
+ if (mutation.attributeName === 'disabled') {
+ dialog_options.buttons[buttonIndex].disabled = mutation.target.disabled;
+ } else if (mutation.attributeName === 'class') {
+ const classes = mutation.target.classList.toString();
+ dialog_options.buttons[buttonIndex].class = classes.replace(/\bbutton\b/, '');
+ }
- if (instance.open) {
- options.title = options.title || instance.element.dialog('option', 'title');
- }
+ instance.element.dialog('option', 'buttons', dialog_options.buttons);
+ });
+ });
- if (options['center-content']) {
- content = '<div class="studip-dialog-centered-helper">' + content + '</div>';
- }
+ dialog_options.buttons = extractButtons(instance.element, (button) => {
+ observer.observe(button, {
+ attributes: true,
+ attributeFilter: ['class', 'disabled'],
+ });
+ });
- // Hide and update container
- instance.element.hide().html(content);
-
- // Store options and dimensions
- instance.options = options;
- instance.dimensions = Dialog.calculateDimensions(instance, content, options);
- instance.previous_title = instance.previous_title || PageLayout.title;
-
- // Set dialog options
- dialog_options = $.extend(dialog_options, {
- width: instance.dimensions.width,
- height: instance.dimensions.height,
- dialogClass: Dialog.getClasses(options),
- classes: {
- 'ui-dialog-content': 'studip-dialog-content',
- },
- buttons: options.buttons || {},
- title: options.title,
- modal: true,
- resizable: options.resize ?? true,
- create(event) {
- $(event.target)
- .parent()
- .css('position', 'fixed');
- },
- resizeStop(event, ui) {
- var position = [
- Math.floor(ui.position.left) - $(window).scrollLeft(),
- Math.floor(ui.position.top) - $(window).scrollTop()
- ];
- $(event.target)
- .parent()
- .css('position', 'fixed');
- $(event.target).dialog('option', 'position', position);
-
- instance.fixedDimensions = true;
- instance.dimensions = ui.size;
- },
- open(event) {
- PageLayout.title = dialog_options.title;
-
- // Prevent tooltips from being automatically focussed
- if (event.target.querySelector('[autofocus]') === null) {
- $(':tabbable.tooltip:focus').data('tooltipObject')?.hide();
- $(':tabbable:not(.tooltip):first', event.target).trigger('focus');
+ // Create 'close' button
+ const cancelButton = dialog_options.buttons.find(button => button.class.split(' ').includes('cancel'));
+ if (!cancelButton) {
+ dialog_options.buttons.push({
+ text: $gettext('Schließen'),
+ class: 'cancel',
+ click: () => Dialog.close(options),
+ });
+ } else {
+ cancelButton.click = () => Dialog.close(options);
}
+ }
- // Adjust titlebar of dialog
- const helpbar_element = $('.helpbar a[href*="hilfe.studip.de"]');
- const tooltip = helpbar_element.text();
- const link = options.wiki_link || helpbar_element.attr('href');
- const element = $('<a class="ui-dialog-titlebar-wiki"' + ' target="_blank" rel="noopener noreferrer">')
- .attr('href', link)
- .attr('title', tooltip);
+ // Create/update dialog
+ instance.element.dialog(dialog_options);
+ instance.element.scrollTo(0, 0);
- if (options.wikilink) {
- $(this)
- .siblings('.ui-dialog-titlebar')
- .addClass('with-wiki-link')
- .find('.ui-dialog-titlebar-close')
- .before(element);
- }
+ // Trigger update event on document since options.origin might have been removed
+ $(document).trigger('dialog-update', { dialog: instance.element, options: options });
+ },
- $(this).parent().find('.ui-dialog-title').attr({
- title: options.title,
- role: 'heading',
- 'aria-level': 2
- });
- $(this).parents('.studip-dialog').attr('aria-modal', 'true');
+ // Closes the dialog for good
+ close(options = {}) {
+ if (Dialog.hasInstance(options.id)) {
+ const instance = Dialog.getInstance(options.id);
- instance.open = true;
- // Execute scripts
- $('head').append(scripts);
+ if (instance.open) {
+ instance.open = false;
+ try {
+ instance.element.dialog('close');
+ instance.open = instance.element.dialog('isOpen');
+ } catch {
+ // No action necessary
+ }
- $(options.origin || document).trigger('dialog-open', {dialog: this, options: options});
- },
- close() {
- $(options.origin || document).trigger('dialog-close', {dialog: this, options: options});
+ // Apparently the close event has been canceled, so don't force
+ // a close
+ if (instance.open) {
+ return false;
+ }
- PageLayout.title = instance.previous_title;
+ try {
+ instance.element.dialog('destroy');
+ instance.element.remove();
+ } catch {
+ // No action necessary
+ }
+ }
- Dialog.close(options);
+ Dialog.removeInstance(options.id);
}
- });
-
- // Create buttons
- if (
- options.buttons === undefined
- || (options.buttons && !$.isPlainObject(options.buttons))
- ) {
- // Create observer to detect changes on disabled attribute
- const observer = new MutationObserver((mutations) => {
- mutations.forEach(mutation => {
- const id = mutation.target.id;
- const buttonIndex = dialog_options.buttons.findIndex(button => button.id === id);
-
- if (mutation.attributeName === 'disabled') {
- dialog_options.buttons[buttonIndex].disabled = mutation.target.disabled;
- } else if (mutation.attributeName === 'class') {
- const classes = mutation.target.classList.toString();
- dialog_options.buttons[buttonIndex].class = classes.replace(/\bbutton\b/, '');
- }
- instance.element.dialog('option', 'buttons', dialog_options.buttons);
- });
- });
+ if (options['reload-on-close'] && options['is-reloading'] === undefined) {
+ window.location.reload();
+ options['is-reloading'] = true;
+ }
+ },
- dialog_options.buttons = extractButtons(instance.element, (button) => {
- observer.observe(button, {
- attributes: true,
- attributeFilter: ['class', 'disabled'],
- });
- });
+ getClasses(options) {
+ const classes = ['studip-dialog'];
- // Create 'close' button
- const cancelButton = dialog_options.buttons.find(button => button.class.split(' ').includes('cancel'));
- if (!cancelButton) {
- dialog_options.buttons.push({
- text: $gettext('Schließen'),
- class: 'cancel',
- click: () => Dialog.close(options),
- });
- } else {
- cancelButton.click = () => Dialog.close(options);
+ if (options.dialogClass) {
+ classes.push(options.dialogClass);
+ } else if (options['center-content']) {
+ classes.push('studip-dialog-centered');
}
- }
-
- // Create/update dialog
- instance.element.dialog(dialog_options);
- instance.element.scrollTo(0, 0);
- // Trigger update event on document since options.origin might have been removed
- $(document).trigger('dialog-update', { dialog: instance.element, options: options });
-};
+ return classes.join(' ');
+ },
-// Closes the dialog for good
-Dialog.close = function(options) {
- options = options || {};
+ calculateDimensions(instance, content, options) {
+ const previous = instance.previous !== false ? Dialog.getInstance(instance.previous) : false;
+ let width = options.width || ($(window).width() * 2) / 3;
+ let height = options.height || ($(window).height() * 2) / 3;
+ let max_width = $(window).width() * 0.95;
+ let max_height = $(window).height() * 0.9;
+ let helper;
+ let temp;
+
+ if (instance.fixedDimensions) {
+ return instance.dimensions;
+ }
- if (Dialog.hasInstance(options.id)) {
- var instance = Dialog.getInstance(options.id);
+ if ($('html').is('.responsive-display')) {
+ max_width = $(window).width() - 6; // Subtract border
+ max_height = $(window).height();
- if (instance.open) {
- instance.open = false;
- try {
- instance.element.dialog('close');
- instance.open = instance.element.dialog('isOpen');
- } catch {
- // No action necessary
+ if (options.width === undefined) {
+ width = $(window).width() * 0.95;
+ height = $(window).height() * 0.98;
}
+ }
- // Apparently the close event has been canceled, so don't force
- // a close
- if (instance.open) {
- return false;
+ // Adjust size if neccessary
+ if (!options.size) {
+ width = instance.dimensions?.width ?? width;
+ height = instance.dimensions?.height ?? height;
+ } else if (options.size === 'auto' || options.size === 'fit') {
+ // Render off screen
+ helper = $('<div class="ui-dialog ui-widget ui-widget-content">');
+ helper.addClass(Dialog.getClasses(options));
+
+ const helper_title = $('<span class="ui-dialog-title">')
+ .text(options.title)
+ .appendTo(helper)
+ .wrap('<div class="ui-dialog-titlebar ui-helper-clearfix">')
+ .after('<button class="ui-button ui-button-icon-only ui-dialog-titlebar-close">close</button>');
+ if (options.wikilink) {
+ helper_title.parent().append('<a class="ui-dialog-titlebar-wiki"></a>').addClass('with-wiki-link');
}
- try {
- instance.element.dialog('destroy');
- instance.element.remove();
- } catch {
- // No action necessary
+
+ $('<div class="ui-dialog-content">').html($.parseHTML(content)).appendTo(helper);
+ // Prevent buttons from wrapping
+ $('[data-dialog-button]', helper).css('white-space', 'nowrap');
+ // Add cancel button if missing
+ if ((options.buttons === undefined || options.buttons !== false)) {
+ $('<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"></div>')
+ .append('<div class="ui-dialog-buttonset"><button class="ui-button ui-widget ui-corner-all cancel">Foo</button></div>')
+ .appendTo(helper)
}
- }
- Dialog.removeInstance(options.id);
- }
+ helper.css({
+ position: 'absolute',
+ left: '-10000px',
+ top: '-10000px',
+ width: 'auto'
+ }).appendTo('body');
- if (options['reload-on-close'] && options['is-reloading'] === undefined) {
- window.location.reload();
- options['is-reloading'] = true;
- }
-};
+ // Calculate width and height
+ width = Math.min(helper.outerWidth(true) + dialog_margin, max_width);
+ height = Math.min(helper.outerHeight(true), max_height);
-Dialog.getClasses = function (options) {
- var classes = ['studip-dialog'];
+ if (options.size === 'auto') {
+ width = Math.max(300, width);
+ height = Math.max(200, height);
+ }
+ // Remove helper element
+ helper.remove();
+ } else if (options.size === 'big') {
+ width = $(window).width() * 0.9;
+ height = $(window).height() * 0.8;
+ } else if (options.size === 'medium') {
+ width = $(window).width() * 0.6;
+ height = $(window).height() * 0.5;
+ } else if (options.size === 'medium-43') {
+ //Medium size in 4:3 aspect ratio
+ height = $(window).height() * 0.8;
+ width = parseInt(height) * 1.33333333;
+ if (width > $(window).width()) {
+ width = $(window).width() * 0.9;
+ }
+ } else if (options.size === 'small') {
+ width = 300;
+ height = 200;
+ } else if (options.size.match(/^\d+x\d+$/)) {
+ temp = options.size.split('x');
+ width = temp[0];
+ height = temp[1];
+ } else if (!options.size.match(/\D/)) {
+ width = height = options.size;
+ }
- if (options.dialogClass) {
- classes.push(options.dialogClass);
- } else if (options['center-content']) {
- classes.push('studip-dialog-centered');
- }
+ // Ensure dimensions fit in viewport
+ width = Math.min(width, max_width);
+ height = Math.min(height, max_height);
+ if (
+ previous &&
+ previous.dimensions !== undefined &&
+ width > previous.dimensions.width &&
+ height > previous.dimensions.height
+ ) {
+ width = width > previous.dimensions.width ? previous.dimensions.width * 0.95 : width;
+ height = height > previous.dimensions.height ? previous.dimensions.height * 0.95 : height;
+ }
- return classes.join(' ');
-};
+ return {
+ width: width,
+ height: height
+ };
+ },
-Dialog.calculateDimensions = function (instance, content, options) {
- var previous = instance.previous !== false ? Dialog.getInstance(instance.previous) : false;
- var width = options.width || ($(window).width() * 2) / 3;
- var height = options.height || ($(window).height() * 2) / 3;
- var max_width = $(window).width() * 0.95;
- var max_height = $(window).height() * 0.9;
- var helper;
- var temp;
-
- if (instance.fixedDimensions) {
- return instance.dimensions;
- }
+ // Specialized confirmation dialog
+ confirm(question, yes_callback, no_callback) {
+ return $.Deferred(function(defer) {
+ if (question === true) {
+ defer.resolve();
+ } else if (question === false) {
+ defer.reject();
+ } else {
+ Dialog.show(_.escape(question).replace("\n", '<br>'), {
+ id: 'confirmation-dialog',
+ title: $gettext('Bitte bestätigen Sie die Aktion'),
+ size: 'fit',
+ wikilink: false,
+ dialogClass: 'studip-confirmation',
+ buttons: {
+ accept: {
+ text: $gettext('Ja'),
+ click: defer.resolve,
+ class: 'accept'
+ },
+ cancel: {
+ text: $gettext('Nein'),
+ click: defer.reject,
+ class: 'cancel'
+ }
+ }
+ });
+ }
+ $(document).one('dialog-close', function() {
+ if (defer.state() === 'pending') {
+ defer.reject();
+ }
+ });
+ })
+ .then(yes_callback, no_callback)
+ .always(function() {
+ Dialog.close({ id: 'confirmation-dialog' });
+ })
+ .promise();
+ },
- if ($('html').is('.responsive-display')) {
- max_width = $(window).width() - 6; // Subtract border
- max_height = $(window).height();
+ confirmAsPost(question, action) {
+ const form = $('<form/>', {
+ action: action,
+ method: 'post'
+ });
+ $('<input/>', {
+ type: 'hidden',
+ name: STUDIP.CSRF_TOKEN.name,
+ value: STUDIP.CSRF_TOKEN.value
+ }).appendTo(form);
- if (options.width === undefined) {
- width = $(window).width() * 0.95;
- height = $(window).height() * 0.98;
- }
- }
+ $('body').append(form);
- // Adjust size if neccessary
- if (!options.size) {
- width = instance.dimensions?.width ?? width;
- height = instance.dimensions?.height ?? height;
- } else if (options.size === 'auto' || options.size === 'fit') {
- // Render off screen
- helper = $('<div class="ui-dialog ui-widget ui-widget-content">');
- helper.addClass(Dialog.getClasses(options));
-
- var helper_title = $('<span class="ui-dialog-title">')
- .text(options.title)
- .appendTo(helper)
- .wrap('<div class="ui-dialog-titlebar ui-helper-clearfix">')
- .after('<button class="ui-button ui-button-icon-only ui-dialog-titlebar-close">close</button>');
- if (options.wikilink) {
- helper_title.parent().append('<a class="ui-dialog-titlebar-wiki"></a>').addClass('with-wiki-link');
- }
+ Dialog.confirm(question).done(() => {
+ form.submit();
+ });
+ return false;
+ },
- $('<div class="ui-dialog-content">').html($.parseHTML(content)).appendTo(helper);
- // Prevent buttons from wrapping
- $('[data-dialog-button]', helper).css('white-space', 'nowrap');
- // Add cancel button if missing
- if ((options.buttons === undefined || options.buttons !== false)) {
- $('<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"></div>')
- .append('<div class="ui-dialog-buttonset"><button class="ui-button ui-widget ui-corner-all cancel">Foo</button></div>')
- .appendTo(helper)
+ registerHeaderHandler(header, handler) {
+ Dialog.handlers.header[header] = handler;
+ },
+ removeHeaderHandler(header) {
+ if (Dialog.handlers.header[header] !== undefined) {
+ delete Dialog.handlers.header[header];
}
+ },
- helper.css({
- position: 'absolute',
- left: '-10000px',
- top: '-10000px',
- width: 'auto'
- }).appendTo('body');
-
- // Calculate width and height
- width = Math.min(helper.outerWidth(true) + dialog_margin, max_width);
- height = Math.min(helper.outerHeight(true), max_height);
+ initialize() {
+ function checkValidity(element) {
+ if (element.matches('a')) {
+ return true;
+ }
- if (options.size === 'auto') {
- width = Math.max(300, width);
- height = Math.max(200, height);
- }
- // Remove helper element
- helper.remove();
- } else if (options.size === 'big') {
- width = $(window).width() * 0.9;
- height = $(window).height() * 0.8;
- } else if (options.size === 'medium') {
- width = $(window).width() * 0.6;
- height = $(window).height() * 0.5;
- } else if (options.size === 'medium-43') {
- //Medium size in 4:3 aspect ratio
- height = $(window).height() * 0.8;
- width = parseInt(height) * 1.33333333;
- if (width > $(window).width()) {
- width = $(window).width() * 0.9;
+ const form = element.closest('form');
+ if (form === null) {
+ return true;
+ }
+ return form.checkValidity();
}
- } else if (options.size === 'small') {
- width = 300;
- height = 200;
- } else if (options.size.match(/^\d+x\d+$/)) {
- temp = options.size.split('x');
- width = temp[0];
- height = temp[1];
- } else if (!options.size.match(/\D/)) {
- width = height = options.size;
- }
- // Ensure dimensions fit in viewport
- width = Math.min(width, max_width);
- height = Math.min(height, max_height);
- if (
- previous &&
- previous.dimensions !== undefined &&
- width > previous.dimensions.width &&
- height > previous.dimensions.height
- ) {
- width = width > previous.dimensions.width ? previous.dimensions.width * 0.95 : width;
- height = height > previous.dimensions.height ? previous.dimensions.height * 0.95 : height;
- }
+ // Actual dialog handler
+ function dialogHandler(event) {
+ if (!event.isDefaultPrevented() && checkValidity(event.currentTarget)) {
+ let target = $(event.target).closest('[data-dialog]');
+ let options = target.data().dialog;
+
+ if (
+ target.is('form')
+ && event.originalEvent?.submitter
+ && $(event.originalEvent.submitter).attr('formaction')
+ ) {
+ target.data('formaction', $(event.originalEvent.submitter).attr('formaction'));
+ }
- return {
- width: width,
- height: height
- };
-};
+ if (Dialog.fromElement(target, parseOptions(options))) {
+ event.preventDefault();
+ }
+ }
+ }
-// Specialized confirmation dialog
-Dialog.confirm = function(question, yes_callback, no_callback) {
- return $.Deferred(function(defer) {
- if (question === true) {
- defer.resolve();
- } else if (question === false) {
- defer.reject();
- } else {
- Dialog.show(_.escape(question).replace("\n", '<br>'), {
- id: 'confirmation-dialog',
- title: $gettext('Bitte bestätigen Sie die Aktion'),
- size: 'fit',
- wikilink: false,
- dialogClass: 'studip-confirmation',
- buttons: {
- accept: {
- text: $gettext('Ja'),
- click: defer.resolve,
- class: 'accept'
- },
- cancel: {
- text: $gettext('Nein'),
- click: defer.reject,
- class: 'cancel'
- }
+ function clickHandler(event) {
+ if (!event.isDefaultPrevented() && checkValidity(event.currentTarget)) {
+ const element = $(event.target).closest(':submit,input[type="image"]');
+ const form = element.closest('form');
+ const action = element.attr('formaction');
+ form.data('triggeredBy', {
+ name: $(event.target).attr('name'),
+ value: $(event.target).val()
+ });
+ if (action) {
+ form.data('formaction', action);
}
- });
+ }
}
- $(document).one('dialog-close', function() {
- if (defer.state() === 'pending') {
- defer.reject();
+
+ // Calculate dialogs margins (outer width - inner width of the dialog) in
+ // order to properly calculated needed dialog widths. Otherwise horizontal
+ // scrollbars will occur. This is located here because it is only
+ // used in Dialog.show().
+ const temp = $('<div class="ui-dialog" style="position: absolute;left:-1000px;top:-1000px;"></div>');
+ temp.html('<div class="ui-dialog-content ui-widget-content"><div style="width: 100%">foo</div></div>');
+ temp.appendTo('body');
+ dialog_margin = temp.outerWidth(true) - $('.ui-dialog-content', temp).width();
+ temp.remove();
+
+ // Handle links, buttons and forms
+ $(document)
+ .on(
+ 'click',
+ 'a[data-dialog],button[data-dialog],input[type=image][data-dialog],input[type=submit][data-dialog]',
+ dialogHandler
+ )
+ .on('click', 'form[data-dialog] :submit', clickHandler)
+ .on('click', 'form[data-dialog] input[type=image]', clickHandler)
+ .on('submit', 'form[data-dialog]', dialogHandler);
+
+ // Close dialog on click outside of it
+ $(document).on('click', '.ui-widget-overlay', function() {
+ if ($('.ui-dialog').length > 0 && Dialog.stack.length) {
+ Dialog.close({
+ id: Dialog.stack[0]
+ });
}
});
- })
- .then(yes_callback, no_callback)
- .always(function() {
- Dialog.close({ id: 'confirmation-dialog' });
- })
- .promise();
+
+ // Recalculate dialog dimensions upon window resize. This is throttled
+ // since the resize event keeps on firing during the resizing.
+ let timeout = null;
+ $(window).on('resize', (event) => {
+ if (event.target !== window) {
+ return;
+ }
+
+ clearTimeout(timeout);
+ timeout = setTimeout(() => {
+ Dialog.stack.forEach((id) => {
+ const instance = Dialog.getInstance(id);
+ instance.dimensions = Dialog.calculateDimensions(
+ instance,
+ $(instance.element).html(),
+ instance.options
+ );
+
+ $(instance.element).dialog('option', 'width', instance.dimensions.width);
+ $(instance.element).dialog('option', 'height', instance.dimensions.height);
+ });
+ }, 10);
+ });
+ },
+
+ /**
+ * legacy method, remove in future
+ * @return boolean
+ */
+ shouldOpen() {
+ return true;
+// return !$('html').is('.responsive-display') && $(window).innerHeight() >= 400;
+ },
+ handlers: {
+ header: {}
+ }
};
-Dialog.confirmAsPost = function(question, action) {
- var form = $('<form/>', {
- action: action,
- method: 'post'
- });
- $('<input/>', {
- type: 'hidden',
- name: STUDIP.CSRF_TOKEN.name,
- value: STUDIP.CSRF_TOKEN.value
- }).appendTo(form);
+// Handler for HTTP header X-Location: Relocate to another location
+Dialog.registerHeaderHandler('X-Location', (location, options) => {
+ location = decodeURI(location);
- $('body').append(form);
+ if (document.location.href === location) {
+ document.location.reload(true);
+ } else {
+ $(window)
+ .on('hashchange', function() {
+ document.location.reload(true);
+ })
+ .on('beforeunload', function() {
+ $(window).off('hashchange');
+ });
+ }
- Dialog.confirm(question).done(function() {
- form.submit();
- });
+ Dialog.close(options);
+ document.location = location;
return false;
-};
+});
-Dialog.registerHeaderHandler = function (header, handler) {
- Dialog.handlers.header[header] = handler;
-};
-Dialog.removeHeaderHandler = function (header) {
- if (Dialog.handlers.header[header] !== undefined) {
- delete Dialog.handlers.header[header];
+// Handler for HTTP header X-Dialog-Execute: Execute arbitrary function
+Dialog.registerHeaderHandler('X-Dialog-Execute', (value, options, xhr) => {
+ let callback = window;
+ const payload = xhr.getResponseHeader('Content-Type').match(/json/)
+ ? JSON.parse(xhr.responseText)
+ : xhr.responseText;
+
+ // Try to parse value as JSON (value might be {func: 'foo', payload: {}})
+ try {
+ value = JSON.parse(value);
+ } catch {
+ value = { func: value };
}
-};
-Dialog.initialize = function() {
- function checkValidity(element) {
- if (element.matches('a')) {
- return true;
- }
+ // Check for invalid call
+ if (value.func === undefined) {
+ throw 'Dialog: Invalid value for X-Dialog-Execute';
+ }
- const form = element.closest('form');
- if (form === null) {
- return true;
- }
- return form.checkValidity();
+ // Populate payload if not set
+ if (value.payload === undefined) {
+ value.payload = xhr.getResponseHeader('Content-Type').match(/json/)
+ ? JSON.parse(xhr.responseText)
+ : xhr.responseText;
}
- // Actual dialog handler
- function dialogHandler(event) {
- if (!event.isDefaultPrevented() && checkValidity(event.currentTarget)) {
- let target = $(event.target).closest('[data-dialog]');
- let options = target.data().dialog;
-
- if (
- target.is('form')
- && event.originalEvent?.submitter
- && $(event.originalEvent.submitter).attr('formaction')
- ) {
- target.data('formaction', $(event.originalEvent.submitter).attr('formaction'));
- }
+ // Find callback
+ callback = extractCallback(value.func, payload);
- if (Dialog.fromElement(target, parseOptions(options))) {
- event.preventDefault();
- }
- }
+ // Check callback
+ if (typeof callback !== 'function') {
+ throw 'Dialog: Given callback is not a valid function';
}
- function clickHandler(event) {
- if (!event.isDefaultPrevented() && checkValidity(event.currentTarget)) {
- var element = $(event.target).closest(':submit,input[type="image"]');
- var form = element.closest('form');
- var action = element.attr('formaction');
- form.data('triggeredBy', {
- name: $(event.target).attr('name'),
- value: $(event.target).val()
- });
- if (action) {
- form.data('formaction', action);
- }
- }
- }
+ // Execute callback
+ return callback(value.payload, xhr);
+});
- // Calculate dialogs margins (outer width - inner width of the dialog) in
- // order to properly calculated needed dialog widths. Otherwise horizontal
- // scrollbars will occur. This is located here because it is only
- // used in Dialog.show().
- var temp = $('<div class="ui-dialog" style="position: absolute;left:-1000px;top:-1000px;"></div>');
- temp.html('<div class="ui-dialog-content ui-widget-content"><div style="width: 100%">foo</div></div>');
- temp.appendTo('body');
- dialog_margin = temp.outerWidth(true) - $('.ui-dialog-content', temp).width();
- temp.remove();
-
- // Handle links, buttons and forms
- $(document)
- .on(
- 'click',
- 'a[data-dialog],button[data-dialog],input[type=image][data-dialog],input[type=submit][data-dialog]',
- dialogHandler
- )
- .on('click', 'form[data-dialog] :submit', clickHandler)
- .on('click', 'form[data-dialog] input[type=image]', clickHandler)
- .on('submit', 'form[data-dialog]', dialogHandler);
-
- // Close dialog on click outside of it
- $(document).on('click', '.ui-widget-overlay', function() {
- if ($('.ui-dialog').length > 0 && Dialog.stack.length) {
- Dialog.close({
- id: Dialog.stack[0]
- });
- }
- });
+// Handler for HTTP header X-Dialog-Close: Close the dialog
+Dialog.registerHeaderHandler('X-Dialog-Close', (value, options) => {
+ Dialog.close(options);
+ return false;
+});
- // Recalculate dialog dimensions upon window resize. This is throttled
- // since the resize event keeps on firing during the resizing.
- var timeout = null;
- $(window).on('resize', (event) => {
- if (event.target !== window) {
- return;
- }
+// Handler for HTTP header X-Wikilink: Set the options' wiki link
+Dialog.registerHeaderHandler('X-Wikilink', (link, options)=> {
+ options.wiki_link = link;
+});
- clearTimeout(timeout);
- setTimeout(() => {
- Dialog.stack.forEach((id) => {
- var instance = Dialog.getInstance(id);
- instance.dimensions = Dialog.calculateDimensions(
- instance,
- $(instance.element).html(),
- instance.options
- );
+// Handler for HTTP header X-Title: Set the dialog title
+Dialog.registerHeaderHandler('X-Title', (title, options) => {
+ title = decodeURIComponent(title);
+ if (title !== $('title').data().original) {
+ options.title = title || options.title;
+ }
+});
- $(instance.element).dialog('option', 'width', instance.dimensions.width);
- $(instance.element).dialog('option', 'height', instance.dimensions.height);
- });
- }, 10);
- });
-};
+// Handler for HTTP header X-No-Buttons: Decide whether to show dialog buttons
+Dialog.registerHeaderHandler('X-No-Buttons', (value, options) => {
+ options.buttons = false;
+ options.dialogClass = 'no-default-buttons';
+});
+
+// Handler for HTTP header X-Dialog-Size: Adjust the size of the dialog
+Dialog.registerHeaderHandler('X-Dialog-Size', (value, options) => {
+ options.size = value;
+});
export default Dialog;