js/src/services/modal.service.js
import $ from 'external/jquery';
import atk from 'atk';
/**
* This is default setup for Fomantic-UI modal.
* Allow to manage URL pass to our modal and dynamically update content from this URL
* using the Fomantic-UI api function.
* Also keep track of created modals and display only the topmost modal.
*/
class ModalService {
constructor() {
this.modals = [];
}
getDefaultFomanticSettings() {
return [
{
duration: 100,
},
{
// never autoclose previously displayed modals, manage them thru this service only
allowMultiple: true,
// any change in modal DOM should automatically refresh cached positions
// allow modal window to add scrolling when content is added after modal is created
observeChanges: true,
onShow: this.onShow,
onHide: this.onHide,
onHidden: this.onHidden,
},
];
}
onShow() {
const s = atk.modalService;
for (const modal of s.modals) {
if (modal === this) {
throw new Error('Unexpected modal to show - modal is already active');
}
}
s.modals.push(this);
s.addModal($(this));
// recenter modal, needed even with observeChanges enabled
// https://github.com/fomantic/Fomantic-UI/issues/2920
// NOT https://github.com/fomantic/Fomantic-UI/issues/2476
$(this).modal('refresh');
}
onHide() {
const s = atk.modalService;
if (s.modals.length === 0 || s.modals.at(-1) !== this) {
throw new Error('Unexpected modal to hide - modal is not front');
}
s.modals.pop();
s.removeModal($(this));
return true;
}
onHidden() {
const $modal = $(this);
if ($modal.data('needRemove')) {
$modal.remove();
}
}
addModal($modal) {
// hide other modals
if (this.modals.length > 1) {
const $previousModal = $(this.modals.at(-2));
if ($previousModal.hasClass('visible')) {
$previousModal.css('visibility', 'hidden');
$previousModal.addClass('__hiddenNotFront');
$previousModal.removeClass('visible');
}
}
const data = $modal.data();
let args = {};
if (data.args) {
args = data.args;
}
// check for data type, usually JSON or HTML
if (data.type === 'json') {
args = $.extend(true, args, { __atk_json: 1 });
}
// does modal content need to be loaded dynamically
if (data.url) {
$modal.data('closeOnLoadingError', true);
const $content = $modal.find('.atk-dialog-content');
$content.html(this.getLoaderHtml(data.loadingLabel ?? ''));
$content.api({
on: 'now',
url: data.url,
data: args,
method: 'GET',
obj: $content,
onComplete: function (response, content) {
// prevent modal duplication
// TODO deduplicate in favor of api.service.js code only
if (response.html) {
const responseBody = new DOMParser().parseFromString('<body>' + response.html.trim() + '</body>', 'text/html').body;
const $modalsContainers = $('body > .ui.dimmer.modals.page, body > .atk-side-panels');
$(responseBody.childNodes[0]).find('.ui.modal[id], .atk-right-panel[id]').each((i, e) => {
$modalsContainers.find('#' + e.id).remove();
});
}
const result = content.html(response.html);
if (result.length === 0) {
// TODO this if should be removed
response.success = false;
response.isServiceError = true;
response.message = 'Modal service error: Empty HTML, unable to replace modal content from server response';
} else if (response.id) {
// content is replace no need to do it in api
response.id = null;
}
},
onSuccess: function () {
$modal.removeData('closeOnLoadingError');
},
});
}
}
removeModal($modal) {
// https://github.com/fomantic/Fomantic-UI/issues/2528
if ($modal.modal('get settings').transition) {
$modal.transition('stop all');
}
// hide other modals
if (this.modals.length > 0) {
const $previousModal = $(this.modals.at(-1));
if ($previousModal.hasClass('__hiddenNotFront')) {
$previousModal.css('visibility', '');
$previousModal.addClass('visible');
$previousModal.removeClass('__hiddenNotFront');
// recenter modal, needed even with observeChanges enabled
// https://github.com/fomantic/Fomantic-UI/issues/2476
$previousModal.modal('refresh');
}
}
}
getLoaderHtml(loaderText) {
return '<div class="ui active inverted dimmer">'
+ '<div class="ui text loader">' + loaderText + '</div>'
+ '</div>';
}
}
export default Object.freeze(new ModalService());