/* ======================================================================== * SaRibe: eModal.js v1.2.67 * http://saribe.github.io/eModal * ======================================================================== * Copyright Samuel Ribeiro. * Licensed under MIT. * ======================================================================== */ /** * Easy Modal for bootstrap, is a simple way to create modal dialogs using javascript. * @params allowed elements: * buttons {array}: An array of objects to configure buttons to modal footer; only able if message == string * css {object}: CSS object try apply into message body; only able if message == string * data {object}: Used for iframe with post data parameters * loading {boolean}: Set loading progress as message. * message {string|object}: The body message string or the HTML element. e.g.: $(selector); * size {string}: sm || lg || xl -> define the modal size. * subtitle {string}: The header subtitle string. This appear in a smaller text. * title {string}: The header title string. * useBin {boolean}: Set message as recyclable. * </summary> * <param name="params" >Modal options parameters of string body message.</param> * <param name="title" >The string header title or a flag to set default parameters.</param> * <returns type="Promise">{ then, element }.</returns> */ ; (function (define) { define(['jquery'], function ($, root) { var $modal; var BIN = 'recycle-bin'; var DIV = '<div>'; var EMPTY = ''; var EVENT_CLICK = 'click'; var EVENT_HIDE = 'hide'; var EVENT_SHOW = 'shown.bs.modal'; var EVENT_SUBMIT = 'submit'; var FOOTER_ID = 'eFooter'; var HIDE = EVENT_HIDE + '.bs.modal'; var INPUT = 'input'; var KEY_DANGER = 'danger'; var LABEL = { OK: 'Cancel', True: 'False', Yes: 'No' }; var lastParams = {}; var MODAL_BODY = 'modal-body'; var MODAL_DIALOG = '.modal-dialog'; var options = {}; var REC_MODAL_CONTENT = 'modal-rec'; var SIZE = { sm: 'sm', lg: 'lg', xl: 'xl' }; var TMP_MODAL_CONTENT = 'modal-tmp'; var defaultSettings = { allowContentRecycle: true, confirmLabel: Object.keys(LABEL)[0], labels: LABEL, loadingHtml: '<h5>Loading...</h5><div class=progress><div class="progress-bar progress-bar-striped active" style="width: 100%"></div></div>', size: EMPTY, title: 'Attention', autofocus: false }; root = root || {}; root.addLabel = addLabel; root.ajax = ajax; root.alert = alert; root.close = close; root.confirm = confirm; root.emptyBin = emptyBin; root.iframe = iframe; root.prompt = prompt; root.setId = setId; root.setEModalOptions = setEModalOptions; root.setModalOptions = setModalOptions; root.size = SIZE; root.version = '1.2.67'; return root; //#region /////////////////////////* Private Logic *///////////////////////// /** * Find modal body and append message to it. * @param {String | DOM} message */ function _build(message) { $modal .modal(options) .find('.modal-content') .append(message); } /** * Will find what Promises approach the developer is using. * Will use Promises A+ if Q.js is present, otherwise will use Promises A from jQuery. * @returns {Promise} */ function _createDeferred() { var defer; // try native promise //if (Promise) defer = Promise.defer(); var q; try { q = require('Q'); } // Load option Q by require if exist catch (e) { q = window.Q; } if (q) { // try Q defer = q.defer(); } else { // Use jQuery :( defer = $.Deferred(); defer.promise = defer.promise(); } defer.promise.element = _getModalInstance(true).find(MODAL_DIALOG); return defer; } /** * Will create modal DOM footer with all buttons. * @param {Array} buttons - all custom buttons, if none, will generate defaults * @returns {$DOM} footer DOM element */ function _getFooter(buttons) { if (buttons === false) { return EMPTY; } var messageFotter = $(DIV).addClass('modal-footer').prop('id', FOOTER_ID); if (buttons) { for (var i = 0, k = buttons.length; i < k; i++) { var btnOp = buttons[i]; var btn = $('<button>').addClass('btn btn-' + (btnOp.style || 'primary')); for (var index in btnOp) { if (btnOp.hasOwnProperty(index)) { switch (index) { case 'close': //add close event if (btnOp[index]) { btn.attr('data-dismiss', 'modal') .addClass('x'); } break; case EVENT_CLICK: //click event var fn = btnOp.click.bind(_getModalInstance(true).find('.modal-content')); btn.click(fn); break; case 'text': btn.html(btnOp[index]); break; default: //all other possible HTML attributes to button element btn.attr(index, btnOp[index]); } } } messageFotter.append(btn); } } else { //if no buttons defined by user, add a standard close button. messageFotter.append('<button class="x btn btn-primary" data-dismiss=modal type=button>Close</button>'); } return messageFotter; } /** * Extract message from arguments. * @param {Object | String} data - this can be the message string or the full detailed object * @returns {$DOM} */ function _getMessage(data) { var $message; var content = data.loading ? defaultSettings.loadingHtml : (data.message || data); if (content.on || content.onclick) { $message = data.clone === true ? $(content).clone() : $(content); $message.addClass(data.useBin && !data.loading ? REC_MODAL_CONTENT : TMP_MODAL_CONTENT); } else { $message = $(DIV) .attr('style', 'position:relative;word-wrap:break-word;') .addClass(MODAL_BODY) .html(content); } return data.css && (data.css !== $message.css && $message.css(data.css)), $message; } /** * Return a new modal object if is the first request or the already created model. * @param {boolean} skipEventChageIfExists * @returns {jQuery Object} */ function _getModalInstance(skipEventChageIfExists) { if (!$modal) { //add recycle bin container to document if (!$('#' + BIN).length) { $('body').append($(DIV).prop('id', BIN).hide()); } $modal = createModalElement(); } if (skipEventChageIfExists && $modal) { return $modal; } if (defaultSettings.autofocus) { $modal .on(EVENT_SHOW, function () { $(this).find(INPUT).first().focus(); }); } return $modal; function createModalElement() { return $('<div class="modal fade" tabindex="-1"><style>.modal-title{display:inline;}.modal-xl{width:96%;}.modal-body{max-height: calc(100vh - 145px);overflow-y: auto;}</style>' + '<div class=modal-dialog>' + '<div class=modal-content>' + ' <div class=modal-header><h5 class=modal-title></h5><button type=button class="x close" data-dismiss=modal aria-label="Close"><span aria-hidden=true>×</span></button></div>' + '</div>' + '</div>' + '</div>') .on('hidden.bs.modal', _recycleModal) .on(EVENT_CLICK, 'button.x', function (ev) { var btn = $(ev.currentTarget); if (btn.prop('type') !== EVENT_SUBMIT) return $modal.modal(EVENT_HIDE); try { if (btn.closest('form')[0].checkValidity()) return close(); } catch (e) { return close(); } return $modal; }); } } /** * @param {String} version * @returns {Boolean} */ function _jQueryMinVersion(version) { var $ver = $.fn.jquery.split('.'); var ver = version.split('.'); var $major = $ver[0]; var $minor = $ver[1]; var $patch = $ver[2]; var major = ver[0]; var minor = ver[1]; var patch = ver[2]; return !( (major > $major) || (major === $major && minor > $minor) || (major === $major && minor === $minor && patch > $patch) ); } /** * Move content to recycle bin if is a DOM object defined by user, * delete it if is a simple string message. * All modal messages can be deleted if default setting "allowContentRecycle" = false. */ function _recycleModal() { if (!$modal) return; var $content = $modal.find('.' + REC_MODAL_CONTENT).removeClass(REC_MODAL_CONTENT) .appendTo('#' + BIN); $modal .off(HIDE) .off(EVENT_SHOW) .find('.modal-content > div:not(:first-child)') .remove(); if (!defaultSettings.allowContentRecycle || lastParams.clone) { $content.remove(); } } /** * Handle default values and toggle between {Object | String}. * Create or get Modal element * @param {Object | String} data - this can be the message string or the full detailed object * @param {String} title - the string that will be shown in modal header * @returns {Promise} Promise with modal $DOM element */ function _setup(params, title) { if (!params) throw new Error('Invalid parameters!'); _recycleModal(); lastParams = params; // Lazy loading var $ref = _getModalInstance(); var size = 'modal-' + (params.size || defaultSettings.size); // Change size $ref.find(MODAL_DIALOG) .removeClass('modal-sm modal-lg modal-xl') .addClass(params.size || defaultSettings.size ? size : null); // Change title $ref.find('.modal-title') .html((params.title || title || defaultSettings.title) + ' ') .append($('<small>').html(params.subtitle || EMPTY)); $ref.on(HIDE, params.onHide); } //#endregion //#region /////////////////////////* Public Methods *///////////////////////// function addLabel(yes, no) { LABEL[yes] = no; } /** * Gets data from URL to eModal body * @param {Object | String} data - this can be the message string or the full detailed object * @param {String} title - the string that will be shown in modal header * @returns {Promise} Promise with modal $DOM element */ function ajax(data, title) { var dfd = _createDeferred(); if (typeof data === 'object') { setEModalOptions(data); } var params = { async: true, deferred: dfd, xhrFields: { withCredentials: false }, loading: true, title: title || defaultSettings.title, url: data.url || data, dataType: data.dataType || 'text' }; if (data.url) { $.extend(params, data); } $.ajax(params) .done(ok) .fail(error); return alert(params, title); function ok(html) { $modal .find('.' + MODAL_BODY) .html(data.success ? data.success(html) : html); return dfd.resolve($modal); } function error(responseText, textStatus) { var msg = data.error ? data.error(responseText, textStatus, params) : '<div class="alert alert-danger">' + '<strong>XHR Fail: </strong>URL [ ' + params.url + '] load fail.' + '</div>'; $modal .find('.' + MODAL_BODY) .html(msg); return dfd.reject(responseText); } } /** * Non blocking alert whit bootstrap. * @param {Object | String} data - this can be the message string or the full detailed object. * @param {String} title - the string that will be shown in modal header. * @returns {Promise} Promise with modal $DOM element */ function alert(data, title) { _setup(data, title); var dfd = data.deferred || _createDeferred(); if (typeof data === 'object') { setEModalOptions(data); } var $message = $(DIV).append(_getMessage(data), _getFooter(data.buttons)); _build($message); if (!data.async) { $modal.on(EVENT_SHOW, dfd.resolve); } return dfd.promise; } /** * Non blocking confirm dialog with bootstrap. * @param {Object | String} data - this can be the message string or the full detailed object. * @param {String} title - the string that will be shown in modal header. * @returns {Promise} Promise with modal $DOM element */ function confirm(data, title) { var dfd = _createDeferred(); if (typeof data === 'object') { setEModalOptions(data); } return alert({ async: true, buttons: [{ close: true, click: click, text: LABEL[data.label] ? LABEL[data.label] : LABEL[defaultSettings.confirmLabel], style: data.style && data.style[0] || KEY_DANGER }, { close: true, click: click, text: LABEL[data.label] ? data.label : defaultSettings.confirmLabel, style: data.style && data.style[1] }], deferred: dfd, message: data.message || data, onHide: click, size: data.size, title: data.title || title }); function click(ev) { close(); var key = $(ev.currentTarget).html(); return LABEL[key] ? dfd.resolve() : dfd.reject(); } } /** * Will load a URL in iFrame inside the modal body. * @param {Object | String} data - this can be the URL string or the full detailed object. * @param {String} title - the string that will be shown in modal header. * @returns {Promise} Promise with modal $DOM element */ function iframe(params, title) { var dfd = _createDeferred(); if (typeof data === 'object') { setEModalOptions(data); } var postData = params.data ? Object.keys(params.data).map(mapData).join(' ') : ''; var html = ('<div class=modal-body style="position: absolute;width: 100%;background-color: rgba(255,255,255,0.8);height: 100%;">%1%</div>' + '<iframe class="embed-responsive-item" frameborder=0 src="%0%" %2% style="width:100%;height:75vh;display:block;"/>') .replace('%0%', params.message || params.url || params) .replace('%1%', defaultSettings.loadingHtml) .replace('%2%', postData); var message = _jQueryMinVersion('3.0.0') ? $(html).on('load', iframeReady): $(html).load(iframeReady); return alert({ async: true, buttons: params.buttons || false, deferred: dfd, message: message, size: params.size || SIZE.xl, title: params.title || title }); ////////// function mapData(item) { return item + '="' + params.data[item] + '"'; } function iframeReady() { $(this) .parent() .find('div.' + TMP_MODAL_CONTENT) .fadeOut(function () { $(this).remove(); }); return dfd.resolve(); } } /** * Remove all Dom elements in recycle bin. * @returns {Array} All removed elements */ function emptyBin() { return $('#' + BIN + ' > *').remove(); } /** * Provides one value form. * @param {Object | String} data - this can be the value string label or the full detailed object. * @param {String} title - the string that will be shown in modal header. * @returns {Promise} Promise with modal $DOM element */ function prompt(data, title) { var dfd = _createDeferred(); if (typeof data === 'object') { setEModalOptions(data); } var params = { deferred: dfd }; if (typeof data === 'object') { $.extend(params, data); } else { params.message = data; params.title = title; } params.async = true; if (params.buttons) { var btn; for (var i = 0, k = params.buttons.length; i < k; i++) { btn = params.buttons[i]; btn.style = (btn.style || 'default') + ' pull-left'; btn.type = btn.type || 'button'; } } var buttons = _getFooter([{ close: true, type: 'reset', text: LABEL.OK, style: KEY_DANGER }, { close: false, type: EVENT_SUBMIT, text: defaultSettings.confirmLabel }].concat(params.buttons || [])); params.buttons = false; params.onHide = submit; params.message = $('<form role=form style="margin-bottom:0;">' + '<div class=modal-body>' + '<label for=prompt-input class=control-label>' + (params.message || EMPTY) + '</label>' + '<input type=text class=form-control required autocomplete="on" value="' + (params.value || EMPTY) + (params.pattern ? '" pattern="' + params.pattern : EMPTY) + '">' + '</div></form>') .append(buttons) .on(EVENT_SUBMIT, submit); return alert(params); function submit(ev) { var value = $modal.find(INPUT).val(); close(); //TODO: ev.type !== EVENT_SUBMIT ? dfd.reject(value) : dfd.resolve(value); return false; } } function setId(id) { _getModalInstance(true) .find(MODAL_DIALOG) .prop('id', id); } /** * Set or change eModal options. * @param {Object} overrideOptions * @returns {Object} merged eModal options */ function setEModalOptions(overrideOptions) { return $.extend(defaultSettings, overrideOptions); } /** * Set or change bootstrap modal options. * @param {Object} overrideOptions * @returns {Object} merged eModal options */ function setModalOptions(overrideOptions) { $modal && $modal.remove(); return $.extend(options, overrideOptions); } /** * Close the current open eModal * @returns {$DOM} eModal DOM element */ function close() { if ($modal) { $modal.off(HIDE).modal(EVENT_HIDE); } return $modal; } //#endregion }); }(typeof define == 'function' && define.amd ? define : function (args, mName) { this.eModal = typeof module != 'undefined' && module.exports ? mName(require(args[0], {}), module.exports) : mName(window.$); }));