/* global log -- eslint */
/**
* @file Dialogs inspired by TES III: Morrowind. Long live House Telvanni.
* @module dialog
*/
/**
* The global ID of onscreen dialogs. Starts at 0.
* @type {number}
* @memberof module:dialog
*/
window.DIALOG_ID = 0;
/**
* Creates a dialog and puts it onscreen with various options and callbacks.
*
* @function dialog
* @param {Object} options - Configuration options for the dialog.
* @param {(string|HTMLElement)} [options.text] - The text or HTML to display in the dialog.
* Text is auto-converted to HTML.
* @param {string} [options.title] - The dialog's title.
* @param {boolean} [options.modal=false] - Whether to open a modal dialog. Implies draggable=false;
* dialogClass='ui-dialog-modal'. Note that modal dialogs hijack
* the entire screen and should only be used in specific cases.
* If IITC is running on mobile, modal will always be true.
* @param {string} [options.id] - A unique ID for this dialog. If a dialog with this ID is already open,
* it will be automatically closed.
* @param {Function} [options.closeCallback] - A callback to run on close.
* @param {Function} [options.collapseCallback] - A callback to run on dialog collapse.
* @param {Function} [options.expandCallback] - A callback to run on dialog expansion.
* @param {Function} [options.collapseExpandCallback] - A callback to run on both collapse and expand.
* Overrides collapseCallback and expandCallback.
* Receives a boolean argument `collapsing`.
* @param {Function} [options.focusCallback] - A callback to run when the dialog gains focus.
* @param {Function} [options.blurCallback] - A callback to run when the dialog loses focus.
* @returns {jQuery} The jQuery object representing the created dialog.
*
* @see {@link http://docs.jquery.com/UI/API/1.8/Dialog} for a list of all jQuery UI Dialog options.
* If you previously applied a class to your dialog after creating it with alert(),
* dialogClass may be particularly useful.
*/
window.dialog = function (options) {
// Override for smartphones. Preserve default behavior and create a modal dialog.
options = options || {};
// Build an identifier for this dialog
var id = 'dialog-' + (options.modal ? 'modal' : options.id ? options.id : 'anon-' + window.DIALOG_ID++);
var jqID = '#' + id;
var html = '';
// Convert text to HTML if necessary
if (options.text) {
html = window.convertTextToTableMagic(options.text);
} else if (options.html) {
html = options.html;
} else {
log.error('window.dialog: warning: no text in dialog');
html = window.convertTextToTableMagic('');
}
// Modal dialogs should not be draggable
if (options.modal) {
options.dialogClass = (options.dialogClass ? options.dialogClass + ' ' : '') + 'ui-dialog-modal';
options.draggable = false;
}
// Close out existing dialogs.
if (window.DIALOGS[id]) {
try {
var selector = $(window.DIALOGS[id]);
selector.dialog('close');
selector.remove();
} catch {
log.error('window.dialog: Tried to close nonexistent dialog ' + id);
}
}
// there seems to be a bug where width/height are set to a fixed value after moving a dialog
function sizeFix() {
if (dialog.data('collapsed')) return;
var options = dialog.dialog('option');
dialog.dialog('option', 'height', options.height);
dialog.dialog('option', 'width', options.width);
}
// Create the window, appending a div to the body
$('body').append('<div id="' + id + '"></div>');
var dialog = $(jqID).dialog(
$.extend(
true,
{
autoOpen: false,
modal: false,
draggable: true,
closeText: '',
title: '',
buttons: {
OK: function () {
$(this).dialog('close');
},
},
open: function () {
var titlebar = $(this).closest('.ui-dialog').find('.ui-dialog-titlebar');
titlebar.find('.ui-dialog-title').addClass('ui-dialog-title-active').addClass('text-overflow-ellipsis');
var close = titlebar.find('.ui-dialog-titlebar-close');
// Title should not show up on mouseover
close.removeAttr('title').addClass('ui-dialog-titlebar-button');
// re-center dialog on title dblclick
// jQuery-UI takes care about initial dialog position, but if content's height grows,
// then dialog's bottom may go beyond screen (e.g. 'Auto draw' with a bunch of bookmarks in folder).
// So this is just a nasty workaround for such issue.
// todo: watch height changes and adapt automatically
titlebar.dblclick(sizeFix);
if (!$(this).dialog('option', 'modal')) {
// Start out with a cloned version of the close button
var collapse = close.clone();
// Change it into a collapse button and set the click handler
collapse.addClass('ui-dialog-titlebar-button-collapse ui-dialog-titlebar-button-collapse-expanded');
collapse.click(
$.proxy(function () {
var collapsed = $(this).data('collapsed') === true;
// Toggle collapsed state
$(this).data('collapsed', !collapsed);
// Run callbacks if we have them
if ($(this).data('collapseExpandCallback')) {
$.proxy($(this).data('collapseExpandCallback'), this)(!collapsed);
} else {
if (!collapsed && $(this).data('collapseCallback')) {
$.proxy($(this).data('collapseCallback'), this)();
} else if (collapsed && $(this).data('expandCallback')) {
$.proxy($(this).data('expandCallback'), this)();
}
}
// Find the button pane and content dialog in this ui-dialog, and add or remove the 'hidden' class.
var dialog = $(this).closest('.ui-dialog');
var content = dialog.find('.ui-dialog-content');
var buttonpane = dialog.find('.ui-dialog-buttonpane');
var button = dialog.find('.ui-dialog-titlebar-button-collapse');
// Slide toggle
$(this).css('height', '');
$(content).slideToggle({
duration: window.DIALOG_SLIDE_DURATION,
complete: function () {
$(buttonpane).slideToggle({
duration: window.DIALOG_SLIDE_DURATION,
complete: sizeFix,
});
},
});
if (collapsed) {
$(button).removeClass('ui-dialog-titlebar-button-collapse-collapsed');
$(button).addClass('ui-dialog-titlebar-button-collapse-expanded');
} else {
$(button).removeClass('ui-dialog-titlebar-button-collapse-expanded');
$(button).addClass('ui-dialog-titlebar-button-collapse-collapsed');
}
}, this)
);
// Put it into the titlebar
titlebar.prepend(collapse);
close.addClass('ui-dialog-titlebar-button-close');
}
window.DIALOGS[$(this).data('id')] = this;
window.DIALOG_COUNT++;
log.log('window.dialog: ' + $(this).data('id') + ' (' + $(this).dialog('option', 'title') + ') opened. ' + window.DIALOG_COUNT + ' remain.');
},
close: function () {
// Run the close callback if we have one
if ($(this).data('closeCallback')) {
$.proxy($(this).data('closeCallback'), this)();
}
// Make sure that we don't keep a dead dialog in focus
if (window.DIALOG_FOCUS && $(window.DIALOG_FOCUS).data('id') === $(this).data('id')) {
window.DIALOG_FOCUS = null;
}
// Finalize
delete window.DIALOGS[$(this).data('id')];
window.DIALOG_COUNT--;
log.log('window.dialog: ' + $(this).data('id') + ' (' + $(this).dialog('option', 'title') + ') closed. ' + window.DIALOG_COUNT + ' remain.');
// remove from DOM and destroy
$(this).dialog('destroy').remove();
},
focus: function () {
if ($(this).data('focusCallback')) {
$.proxy($(this).data('focusCallback'), this)();
}
// Blur the window currently in focus unless we're gaining focus
if (window.DIALOG_FOCUS && $(window.DIALOG_FOCUS).data('id') !== $(this).data('id')) {
$.proxy(function () {
if ($(this).data('blurCallback')) {
$.proxy($(this).data('blurCallback'), this)();
}
$(this).closest('.ui-dialog').find('.ui-dialog-title').removeClass('ui-dialog-title-active').addClass('ui-dialog-title-inactive');
}, window.DIALOG_FOCUS)();
}
// This dialog is now in focus
window.DIALOG_FOCUS = this;
$(this).closest('.ui-dialog').find('.ui-dialog-title').removeClass('ui-dialog-title-inactive').addClass('ui-dialog-title-active');
},
},
options
)
);
dialog.on('dialogdragstop dialogresizestop', sizeFix);
// Set HTML and IDs
dialog.html(html);
dialog.data('id', id);
dialog.data('jqID', jqID);
// Set callbacks
dialog.data('closeCallback', options.closeCallback);
dialog.data('collapseCallback', options.collapseCallback);
dialog.data('expandCallback', options.expandCallback);
dialog.data('collapseExpandCallback', options.collapseExpandCallback);
dialog.data('focusCallback', options.focusCallback);
dialog.data('blurCallback', options.blurCallback);
if (options.modal) {
// ui-modal includes overrides for modal dialogs
dialog.parent().addClass('ui-modal');
} else {
// Enable snapping
dialog.dialog().parents('.ui-dialog').draggable('option', 'snap', true);
}
// Run it
dialog.dialog('open');
return dialog;
};
/**
* Creates an alert dialog with default settings. This function is a simplified wrapper around `window.dialog`.
* It provides a quick way to create basic alert dialogs with optional HTML content and a close callback.
*
* @function alert
* @param {string} text - The text or HTML content to display in the alert dialog.
* @param {boolean} [isHTML=false] - Specifies whether the `text` parameter should be treated as HTML.
* If `true`, the `text` will be inserted as HTML, otherwise as plain text.
* @param {Function} [closeCallback] - A callback function to be executed when the alert dialog is closed.
*
* @returns {jQuery} The jQuery object representing the created alert dialog.
*/
window.alert = function (text, isHTML, closeCallback) {
var obj = { closeCallback: closeCallback };
if (isHTML) {
obj.html = text;
} else {
obj.text = text;
}
return window.dialog(obj);
};
window.setupDialogs = function () {
window.DIALOG_ID = 0;
window.DIALOGS = {};
window.DIALOG_COUNT = 0;
window.DIALOG_FOCUS = null;
};