- 根因:页面(_MainLayout 等)加载的是 bundle.js 等由 bundleconfig.json 打包的生成产物,
但 csproj 未接入 BundlerMinifier,dotnet build 不会重生它们,Jenkins 只是把 git 里
陈旧的 bundle.js 原样发布 -> 改了 fastVue.base.js 后回车不生效(部署的 bundle 仍是无
enter 的旧版)。且源文件带 UTF-8 BOM,拼接后每个 BOM 落入文件中间成为 ZWNBSP,提交
diff 充满不可见字符(历史上曾多次在产物上手工清 BOM 打补丁)。
- 修复:
1. YLErpWeb.csproj 新增 BuildBundlerMinifier(3.2.449);其 BundleMinify 目标在
BeforeCompile 自动按 bundleconfig.json 重建全部 bundle,Jenkins 构建即自动产出正确版本,
不再需要手工重建并提交 bundle.js。
2. .gitignore 忽略 8 个生成产物(jquery.js/bundle.js/vue.js/bundle.css/bundleV2.css/
bundleV2.js/bundle.min.css/bundleV2.min.css) 并 git rm --cached 取消跟踪;这些产物
永不再入版本库、永不陈旧。
3. 剥离 15 个喂给 bundle 的源文件(及 css)开头的 UTF-8 BOM,使重建产物不再含 ZWNBSP。
- 验证:本地 dotnet build -t:BundleMinify 重建后 8 个 bundle 全部 BOM=0,bundle.js 含完整
enter 链路(_enterFired/isEnter/$emit('enter'));删除 bundle.min.css 后构建可自动重建。
641 lines
22 KiB
JavaScript
641 lines
22 KiB
JavaScript
/* ========================================================================
|
|
* 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.$);
|
|
})); |