diff options
| author | Jan-Hendrik Willms <tleilax+studip@gmail.com> | 2025-10-01 11:30:47 +0200 |
|---|---|---|
| committer | Jan-Hendrik Willms <tleilax+studip@gmail.com> | 2025-10-01 11:30:47 +0200 |
| commit | c1a659702122401c9c197aa5fb64d62767c4dead (patch) | |
| tree | 90fd080cba9f366dd40f5c8a88bd1142b9507f2e /resources/assets/javascripts | |
| parent | 13027d9c79b37d9da74ae759a9c510c67b1c2c20 (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.js | 1263 |
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; |
