/* --------------------------------------------------------------
modal.js 2016-02-23
Gambio GmbH
http://www.gambio.de
Copyright (c) 2016 Gambio GmbH
Released under the GNU General Public License (Version 2)
[http://www.gnu.org/licenses/gpl-2.0.html]
--------------------------------------------------------------
*/
/* globals Mustache */
jse.libs.modal = jse.libs.modal || {};
/**
* ## Modal Dialogs Library
*
* This library handles jQuery UI and Bootstrap modals and it is quite useful when it comes to display
* plain messages. Make sure to use the "showMessage" function only in pages where Bootstrap is loaded.
*
* Notice: Some library methods are deprecated and will be removed with JSE v1.5.
*
* Notice: Make sure that you load the require vendor files (Bootstrap or jQuery UI) before using this module.
*
* ### Examples
*
* **Display jQuery UI message.**
*
* ```javascript
* jse.libs.modal.message({
* title: 'My Title', // Required
* content: 'My Content' // Required
* buttons: { ... } // Optional
* // Other jQueryUI Dialog Widget Options
* });
* ```
*
* **Display Bootstrap message.**
*
* ```javascript
* jse.libs.modal.showMessage('Title', 'Content');
* ```
*
* @todo Refactor modal functionality, remove Mustache dependency and split jQuery UI and Bootstrap use.
*
* @module JSE/Libs/modal
* @exports jse.libs.modal
*
* @requires jQueryUI
* @requires Bootstrap
* @requires Mustache
*/
(function (exports) {
'use strict';
// ------------------------------------------------------------------------
// VARIABLES
// ------------------------------------------------------------------------
/**
* Contains Default Modal Buttons
*
* @type {Object}
*/
const buttons = {
'yes': {
'name': jse.core.lang.translate('yes', 'buttons'),
'type': 'success'
},
'no': {
'name': jse.core.lang.translate('no', 'buttons'),
'type': 'fail'
},
'abort': {
'name': jse.core.lang.translate('abort', 'buttons'),
'type': 'fail'
},
'ok': {
'name': jse.core.lang.translate('ok', 'buttons'),
'type': 'success'
},
'close': {
'name': jse.core.lang.translate('close', 'buttons'),
'type': 'fail'
}
};
// ------------------------------------------------------------------------
// PRIVATE FUNCTIONS
// ------------------------------------------------------------------------
/**
* Get Form Data
*
* Returns all form data, which is stored inside the layer.
*
* @param {object} $self jQuery selector of the layer.
* @param {bool} validateForm Flag that determines whether the form must be validated
* before we get the data.
*
* @return {json} Returns a JSON with all form data.
*
* @private
*/
var _getFormData = function ($self, validateForm) {
var $forms = $self
.filter('form')
.add($self.find('form')),
formData = {},
promises = [];
if ($forms.length) {
$forms.each(function () {
var $form = $(this);
if (validateForm) {
var localDeferred = $.Deferred();
promises.push(localDeferred);
$form.trigger('validator.validate', {
'deferred': localDeferred
});
}
var key = $form.attr('name') || $form.attr('id') || ('form_' + new Date().getTime() * Math.random());
formData[key] = window.jse.lib.form.getData($form);
});
}
return $.when
.apply(undefined, promises)
.then(function () {
return formData;
},
function () {
return formData;
})
.promise();
};
/**
* Reject Handler
*
* @param {object} $element Selector element.
* @param {object} deferred Deferred object.
*
* @private
*/
var _rejectHandler = function ($element, deferred) {
_getFormData($element).always(function (result) {
deferred.reject(result);
$element
.dialog('close')
.remove();
});
};
/**
* Resolve Handler
*
* @param {object} $element Selector element.
* @param {object} deferred Deferred object.
*
* @private
*/
var _resolveHandler = function ($element, deferred) {
_getFormData($element, true).done(function (result) {
deferred.resolve(result);
$element
.dialog('close')
.remove();
});
};
/**
* Generate Buttons
*
* Transforms the custom buttons object (which is incompatible with jQuery UI)
* to a jQuery UI compatible format and returns it.
*
* @param {object} dataset Custom buttons object for the dialog.
* @param {object} deferred Deferred-object to resolve/reject on close.
*
* @return {array} Returns a jQuery UI dialog compatible buttons array.
*
* @private
*/
var _generateButtons = function (dataset, deferred) {
var newButtons = [],
tmpButton = null;
// Check if buttons are available.
if (dataset) {
$.each(dataset, function (k, v) {
// Setup a new button.
tmpButton = {};
tmpButton.text = v.name || 'BUTTON';
// Setup click handler.
tmpButton.click = function () {
var $self = $(this);
// If a callback is given, execute it with the current scope.
if (typeof v.callback === 'function') {
v.callback.apply($self, []);
}
// Add the default behaviour for the close functionality. On fail,
// reject the deferred object, else resolve it.
switch (v.type) {
case 'fail':
_rejectHandler($self, deferred);
break;
case 'success':
_resolveHandler($self, deferred);
break;
default:
break;
}
};
// Add to the new buttons array.
newButtons.push(tmpButton);
});
}
return newButtons;
};
/**
* Get Template
*
* This method will return a promise object that can be used to execute code,
* once the template HTML of the modal is found.
*
* @param {object} options Options to be applied to the template.
*
* @return {object} Returns a deferred object.
*
* @private
*/
var _getTemplate = function (options) {
var $selection = [],
deferred = $.Deferred();
try {
$selection = $(options.template);
} catch (exception) {
jse.core.debug(jse.core.lang.templateNotFound(options.template));
}
if ($selection.length) {
deferred.resolve($selection.html());
} else {
window.jse.lib.ajax({
'url': options.template,
'dataType': 'html'
}).done(function (result) {
if (options.storeTemplate) {
var $append = $('<div />')
.attr('id', options.template)
.html(result);
$('body').append($append);
}
deferred.resolve(result);
}).fail(function () {
deferred.reject();
});
}
return deferred;
};
/**
* Create Modal Layer
*
* @param {object} options Extra modal options to be applied.
* @param {string} title Modal title
* @param {string} className Class name to be added to the modal element.
* @param {object} defaultButtons Modal buttons for the layer.
* @param {string} template Template name to be used for the modal.
*
* @return {object} Returns a modal promise object.
*
* @private
*/
var _createLayer = function (options, title, className, defaultButtons, template) {
// Setup defaults & deferred objects.
var deferred = $.Deferred(),
promise = deferred.promise(),
$template = '',
defaults = {
'title': title || '',
'dialogClass': className || '',
'modal': true,
'resizable': false,
'buttons': defaultButtons || [buttons.close],
'draggable': false,
'closeOnEscape': false,
'autoOpen': false,
'template': template || '#modal_alert',
'storeTemplate': false,
'closeX': true,
'modalClose': false
},
instance = null,
$forms = null;
// Merge custom settings with default settings
options = options || {};
options = $.extend({}, defaults, options);
options.buttons = _generateButtons(options.buttons, deferred);
_getTemplate(options).done(function (html) {
// Generate template
$template = $(Mustache.render(html, options));
if (options.validator) {
$template
.find('form')
.attr('data-gx-widget', 'validator')
.find('input')
.attr({
'data-validator-validate': options.validator.validate,
'data-validator-regex': options.validator.regex || ''
})
.addClass('validate');
}
// Setup dialog
$template.dialog(options);
try {
instance = $template.dialog('instance');
} catch (exception) {
instance = $template.data('ui-dialog');
}
// Add bootstrap button classes to buttonSet.
instance
.uiButtonSet
.children()
.addClass('btn btn-default');
// If the closeX-option is set to false, remove the button from the layout
// else bind an event listener to reject the deferred object.
if (options.closeX === false) {
instance
.uiDialogTitlebarClose
.remove();
} else {
instance
.uiDialogTitlebarClose
.html('×')
.one('click', function () {
_rejectHandler(instance.element, deferred);
});
}
// Add an event listener to the modal overlay if the option is set.
if (options.modalClose) {
$('body')
.find('.ui-widget-overlay')
.last()
.one('click', function () {
_rejectHandler(instance.element, deferred);
});
}
// Prevent submit on enter in inner forms
$forms = instance.element.find('form');
if ($forms.length) {
$forms.on('submit', function (event) {
event.preventDefault();
});
}
if (options.executeCode && typeof options.executeCode === 'function') {
options.executeCode.call($(instance.element));
}
// Add a close layer method to the promise.
promise.close = function (fail) {
if (fail) {
_rejectHandler(instance.element, deferred);
} else {
_resolveHandler(instance.element, deferred);
}
};
$template.dialog('open');
if (window.gx && window.jse.widgets && window.jse.widgets.init) {
window.jse.widgets.init($template);
window.jse.controllers.init($template);
window.jse.extensions.init($template);
}
}).fail(function () {
deferred.reject({
'error': 'Template not found'
});
});
return promise;
};
/**
* Create a warning log for the deprecated method.
*
* @param {String} method The method name to be included in the log.
*
* @private
*/
function _logDeprecatedMethod(method) {
jse.core.debug.warn(`Used deprecated modal method ${method} which will be removed in JSE v1.5.`);
}
// ------------------------------------------------------------------------
// PUBLIC FUNCTIONS
// ------------------------------------------------------------------------
/**
* Generates the default alert layer.
*
* @param {object} options Mix of jQuery UI dialog options and custom options
* @param {string} title Default title for the type of alert layer
* @param {string} className Default class for the type of alert layer
* @param {array} defbuttons Array wih the default buttons for the array type
* @param {string} template Selector for the jQuery-object used as template
*
* @return {object} Returns a promise object.
*
* @deprecated This method will be removed with JSE v1.5.
*/
exports.alert = function (options) {
_logDeprecatedMethod('jse.libs.modal.alert()');
var data = $.extend({}, {
'draggable': true
}, options);
return _createLayer(data, jse.core.lang.translate('hint', 'labels'), '', [buttons.ok]);
};
/**
* Returns a confirm layer.
*
* @param {object} options Mix of jQuery UI dialog options and custom options.
*
* @return {promise} Returns a promise
*
* @deprecated This method will be removed with JSE v1.5.
*/
exports.confirm = function (options) {
_logDeprecatedMethod('jse.libs.modal.confirm()');
var data = $.extend({}, {
'draggable': true
}, options);
return _createLayer(data, jse.core.lang.translate('confirm', 'labels'), 'confirm_dialog',
[buttons.no, buttons.yes]);
};
/**
* Returns a prompt layer.
*
* @param {object} options Mix of jQuery UI dialog options and custom options.
*
* @return {promise} Returns a promise object.
*
* @deprecated This method will be removed with JSE v1.5.
*/
exports.prompt = function (options) {
_logDeprecatedMethod('jse.libs.modal.prompt()');
var data = $.extend({}, {
'draggable': true
}, options);
return _createLayer(data, jse.core.lang.translate('prompt', 'labels'), 'prompt_dialog',
[buttons.abort, buttons.ok], '#modal_prompt');
};
/**
* Returns a success layer.
*
* @param {object} options Mix of jQuery UI dialog options and custom options.
*
* @return {object} Returns a promise object.
*
* @deprecated This method will be removed with JSE v1.5.
*/
exports.success = function (options) {
_logDeprecatedMethod('jse.libs.modal.success()');
var data = $.extend({}, {
'draggable': true
}, options);
return _createLayer(data, jse.core.lang.translate('success', 'labels'), 'success_dialog');
};
/**
* Returns an error layer.
*
* @param {object} options Mix of jQuery UI dialog options and custom options.
*
* @return {object} Returns a promise object.
*
* @deprecated This method will be removed with JSE v1.5.
*/
exports.error = function (options) {
_logDeprecatedMethod('jse.libs.modal.error()');
var data = $.extend({}, {
'draggable': true
}, options);
return _createLayer(data, jse.core.lang.translate('error', 'labels'), 'error_dialog');
};
/**
* Returns a warning layer.
*
* @param {object} options Mix of jQuery UI dialog options and custom options.
*
* @return {object} Returns a promise object.
*
* @deprecated This method will be removed with JSE v1.5.
*/
exports.warn = function (options) {
_logDeprecatedMethod('jse.libs.modal.warn()');
var data = $.extend({}, {
'draggable': true
}, options);
return _createLayer(data, jse.core.lang.translate('warning', 'labels'), 'warn_dialog');
};
/**
* Returns an info layer.
*
* @param {object} options Mix of jQuery UI dialog options and custom options.
*
* @return {promise} Returns a promise object.
*
* @deprecated This method will be removed with JSE v1.5.
*/
exports.info = function (options) {
_logDeprecatedMethod('jse.libs.modal.info()');
var data = $.extend({}, {
'draggable': true
}, options);
return _createLayer(data, jse.core.lang.translate('info', 'labels'), 'info_dialog');
};
/**
* Display jQuery UI message.
*
* This method provides an easy way to display a message to the user by using jQuery UI dialog widget.
*
* @param {Object} options Modal options are the same as the jQuery dialog widget.
*/
exports.message = function (options) {
// Create div element for modal dialog.
$('body').append('<div class="modal-layer">' + options.content + '</div>');
// Append options object with extra dialog options.
options.modal = true;
options.dialogClass = 'gx-container';
// Set default buttons, if option wasn't provided.
if (options.buttons === undefined) {
options.buttons = [
{
text: buttons.close.name,
click: function () {
$(this).dialog('close');
$(this).remove();
}
}
];
}
// Display message to the user.
$('.modal-layer:last').dialog(options);
};
/**
* Display Bootstrap modal message.
*
* {@link http://getbootstrap.com/javascript/#modals}
*
* Example:
*
* jse.libs.modal.showMessage('Title', 'Message', [
* {
* title: 'Send', // Button title
* class: 'btn btn-primary send', // (optional) Add a custom button class.
* callback: function(event) { ... } // (optional) Provide a click callback
* },
* {
* title: 'Close',
* closeModal: true // (optional) Modal will be closed upon click.
* }
* ]);
*
* You can close the modal by using the Bootstrap API: $modal.modal('hide');
*
* @param {String} title The message title.
* @param {String} content The message content.
* @param {Object[]} [buttons=null] Provide an array with objects which define the modal buttons.
*
* @return {jQuery} Returns the modal selector.
*/
exports.showMessage = function (title, content, buttons = null) {
// Generate the default close button definition.
if (!buttons) {
buttons = [
{
title: jse.core.lang.translate('CLOSE', 'general'),
class: 'btn btn-default',
callback: event => $(event.currentTarget).parents('.modal').modal('hide')
}
];
}
// Prepare the Bootstrap HTML markup.
const html = `<div class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title">${title}</h4>
</div>
<div class="modal-body">
${content}
</div>
<div class="modal-footer"></div>
</div>
</div>
</div>`;
const $modal = $(html).appendTo('body');
// Add the buttons to the modal.
buttons.forEach(button => {
const $button = $('<button/>');
$button
.text(button.title)
.attr('class', button.class || 'btn btn-default');
if (button.callback) {
$button.on('click', button.callback);
}
$button.appendTo($modal.find('.modal-footer'));
});
// Remove the modal element when its hidden.
$modal.on('hidden.bs.modal', () => $modal.remove());
// Display the modal to the user.
$modal.modal('show');
return $modal;
};
}(jse.libs.modal));