/* --------------------------------------------------------------
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
*
* Library that handles modal dialogs within the app. This function depends on jQuery & jQuery UI
* dialog widget. You are able to select the HTML template to be displayed as a modal and set other
* parameters to readjust the modal behavior. If no template option is provided the library will search
* for a default "#modal_alert" element. Place the markup from the following example into your page
* to quickly display messages to the user.
*
* ### Quick Usage Example (No Configuration)
*
* ```javascript
* Modal.message({
* title: 'My Title', // Required
* content: 'My Content' // Required
* buttons: { ... } // Optional
* // Other jQueryUI Dialog Widget Options
* });
* ```
*
* ### Example With Default HTML
*
* Insert the following HTML in your page:
*
* ```html
* <div id="modal_alert">
* <div>
* {{#content}}
* <div class="icon"> </div>
* <p>{{{.}}}</p>
* {{/content}}
* </div>
* </div>
*```
*
* If you don't specify a template the library will use the "#modal_alert" element.
*
* ```javascript
* Modal.alert({
* title: 'My Modal Title',
* content: 'My modal content.',
* position: { my: 'center', at: 'center', of: $('#parent-element-id') }
* });
* ```
*
* @todo Refactor the file and remove the methods that are not needed (like alert).
*
* @module JSE/Libs/modal
* @exports jse.libs.modal
*/
(function (exports) {
'use strict';
var
/**
* Body Element Selector
*
* @type {object}
*/
$body = $('body'),
/**
* Contains Default Modal Buttons
*
* @type {object}
*/
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'
}
};
/**
* 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 to the
* @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.
// @todo Test that ...
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;
};
/**
* 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.
*/
var _alert = function (options) {
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
*/
var _confirm = function (options) {
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.
*/
var _prompt = function (options) {
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.
*/
var _success = function (options) {
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.
*/
var _error = function (options) {
var data = $.extend({}, {
'draggable': true
}, options);
return _createLayer(data, jse.core.lang.translate('error', 'labels'), 'error_dialog');
};
/**
* Returns a warning layer.
*
* @name Core/Modal.warn
* @public
* @method
*
* @param {object} options Mix of jQuery UI dialog options and custom options.
*
* @return {object} Returns a promise object.
*/
var _warn = function (options) {
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.
*/
var _info = function (options) {
var data = $.extend({}, {
'draggable': true
}, options);
return _createLayer(data, jse.core.lang.translate('info', 'labels'), 'info_dialog');
};
/**
* ## Quickly display a message without setting up view files or JavaScript code.
*
* This method provides an easy way to display a message to the user without
* having to worry about templates and other dependencies of the library.
*
* @param {object} options Modal options are the same as the jQuery dialog widget.
*/
var _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);
};
// ------------------------------------------------------------------------
// VARIABLE EXPORT
// ------------------------------------------------------------------------
exports.error = _error;
exports.warn = _warn;
exports.info = _info;
exports.success = _success;
exports.alert = _alert;
exports.prompt = _prompt;
exports.confirm = _confirm;
exports.custom = _createLayer;
exports.message = _message;
}(jse.libs.modal));