UNPKG

lu2

Version:

Simple and flexible UI component library based on native HTML and JavaScript

752 lines (624 loc) 23.7 kB
/** * @Dialog.js * @author zhangxinxu * @version * @created 15-06-18 * @edited 19-11-01 */ /* global module */ (function (global, factory) { if (typeof exports === 'object' && typeof module !== 'undefined') { module.exports = factory(); } else if (typeof define === 'function' && (define.amd || define.cmd)) { define(factory); } else { global.Dialog = factory(); } // eslint-disable-next-line }((typeof global !== 'undefined') ? global // eslint-disable-next-line : ((typeof window !== 'undefined') ? window : ((typeof self !== 'undefined') ? self : this)), function () { /** * 弹框组件 * IE9+基于HTML5 <dialog>元素创建 * 透明遮罩层和弹框在一个元素内 * @example * var myDialog = new Dialog(options); * button.addEventListener('click', function() { myDialog.remove(); }); */ // 类名前缀 var DIALOG = 'dialog'; var CL = { add: function () { return ['ui', DIALOG].concat([].slice.call(arguments)).join('-'); }, toString: function () { return 'ui-' + DIALOG; } }; // 关闭SVG var strCloseSvg = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200"><path d="M116.152,99.999l36.482-36.486c2.881-2.881,2.881-7.54,0-10.42 l-5.215-5.215c-2.871-2.881-7.539-2.881-10.42,0l-36.484,36.484L64.031,47.877c-2.881-2.881-7.549-2.881-10.43,0l-5.205,5.215 c-2.881,2.881-2.881,7.54,0,10.42l36.482,36.486l-36.482,36.482c-2.881,2.881-2.881,7.549,0,10.43l5.205,5.215 c2.881,2.871,7.549,2.871,10.43,0l36.484-36.488L137,152.126c2.881,2.871,7.549,2.871,10.42,0l5.215-5.215 c2.881-2.881,2.881-7.549,0-10.43L116.152,99.999z"/></svg>'; var isSupportDialog = false; /** * 弹框实例方法 * @param {Object} options 纯对象,可选参数 */ var Dialog = function (options) { var defaults = { title: '', // 不同类别的内容类型 content: '', // 弹框的宽度 width: 'auto', // 弹框高度 height: 'auto', // 不同类别的默认按钮 buttons: [], // 弹框显示、隐藏、移除的回调 onShow: function () {}, onHide: function () {}, onRemove: function () {} }; // 最终参数 var objParams = Object.assign({}, defaults, options || {}); // 各个元素创建 // 容器-含半透明遮罩背景 var eleContainer = document.createElement(DIALOG); eleContainer.classList.add(CL.add('container')); // 使该元素也可以被focus eleContainer.setAttribute('tabindex', '-1'); // 是否支持原生弹框 isSupportDialog = ('open' in eleContainer); // 不支持的浏览器设置无障碍访问信息 if (isSupportDialog == false) { eleContainer.setAttribute('role', DIALOG); } // 弹框主体 var eleDialog = document.createElement('div'); eleDialog.classList.add(CL); // 设置尺寸和键盘访问特性 // 如果宽度设置的是纯数值,则认为是px单位 if (/^\d+$/.test(objParams.width)) { eleDialog.style.width = objParams.width + 'px'; } else { eleDialog.style.width = objParams.width; } // 高度 if (/^\d+$/.test(objParams.height)) { eleDialog.style.height = objParams.height + 'px'; } else if (objParams.height == 'stretch') { eleDialog.classList.add(CL.add(objParams.height)); } else if (objParams.height != 'auto') { eleDialog.style.height = objParams.height; } // 标题 var eleTitle = document.createElement('h4'); eleTitle.classList.add(CL.add('title')); eleTitle.innerHTML = objParams.title; // 关闭按钮 // 随机id,ESC快捷键关闭弹框用到 var strIdClose = ('lulu_' + Math.random()).replace('0.', ''); // 关闭按钮元素创建 var eleClose = document.createElement('button'); eleClose.classList.add(CL.add('close')); eleClose.classList.add('ESC'); // 无障碍支持 eleClose.setAttribute('aria-label', '关闭'); eleClose.id = strIdClose; eleClose.setAttribute('data-target', strIdClose); eleClose.innerHTML = strCloseSvg; // 主体内容 var dataContent = objParams.content || ''; // content可以是函数 if (typeof dataContent == 'function') { dataContent = dataContent(); } // 基于内容的数据类型,使用不同的默认的弹框关闭方式 if (typeof dataContent == 'string') { this.closeMode = 'remove'; } else { this.closeMode = 'hide'; } // 主体内容元素 var eleBody = document.createElement('div'); eleBody.classList.add(CL.add('body')); if (this.closeMode == 'remove') { eleBody.innerHTML = dataContent; } else { eleBody.appendChild(dataContent); } // 底部元素 var eleFooter = document.createElement('div'); eleFooter.classList.add(CL.add('footer')); // 组装 eleDialog.appendChild(eleClose); eleDialog.appendChild(eleTitle); eleDialog.appendChild(eleBody); eleDialog.appendChild(eleFooter); eleContainer.appendChild(eleDialog); // 插入的细节 // 1. 插在所有dialog的前面 // 2. 如果没有,则放在页面后面 var eleAllDialog = document.querySelectorAll(DIALOG); if (eleAllDialog.length) { eleAllDialog[0].insertAdjacentElement('beforebegin', eleContainer); } else { (objParams.container || document.body).appendChild(eleContainer); } // 暴露元素 this.element = { container: eleContainer, dialog: eleDialog, close: eleClose, title: eleTitle, body: eleBody, footer: eleFooter }; // 暴露一些参数 this.params = { title: objParams.title, width: objParams.width, buttons: objParams.buttons, content: dataContent }; this.callback = { show: objParams.onShow, hide: objParams.onHide, remove: objParams.onRemove }; this.display = false; // 按钮处理 this.button(); // 事件 this.events(); if (dataContent) { this.show(); } return this; }; /** * 弹框按钮需要单独处理 * @return {[type]} [description] */ Dialog.prototype.button = function () { var objParams = this.params; var objElement = this.element; objElement.footer.innerHTML = ''; for (var keyElement in objElement) { if (/^button/.test(keyElement)) { delete objElement[keyElement]; } } // 按钮元素创建 objParams.buttons.forEach(function (objButton, numIndex) { // objButton可能是null等 objButton = objButton || {}; // 按钮类型和值的处理 var strType = objButton.type; var strValue = objButton.value; if (strType == 'remind' || (!strType && numIndex == 0)) { strType = 'primary'; } if (!strValue) { strValue = ['确定', '取消'][numIndex]; } var eleButton = document.createElement('button'); if (objButton['for']) { eleButton = document.createElement('label'); eleButton.setAttribute('for', objButton['for']); } // 自定义的类名 if (objButton.className) { eleButton.className = objButton.className; } // 按钮样式 eleButton.classList.add(String(CL).replace(DIALOG, 'button')); if (strType) { eleButton.setAttribute('data-type', strType); } // 按钮内容 eleButton.innerHTML = strValue; // 放在底部元素中 objElement.footer.appendChild(eleButton); // 对外暴露 objElement['button' + numIndex] = eleButton; }); // 按钮事件 // 底部确定取消按钮 objParams.buttons.forEach(function (objButton, numIndex) { var eleButton = objElement['button' + numIndex]; if (!eleButton || objButton['for']) { return; } var objEvents = objButton.events || { click: function () { this[this.closeMode](); }.bind(this) }; if (typeof objEvents == 'function') { objEvents = { click: objEvents }; } for (var strEventType in objEvents) { eleButton.addEventListener(strEventType, function (event) { // 把实例对象传入 event.dialog = this; // 事件执行 objEvents[strEventType](event); }.bind(this)); } // 额外的focus事件支持 eleButton.addEventListener('focus', function () { if (window.isKeyEvent) { this.style.outline = ''; } else { this.style.outline = 'none'; } }); }.bind(this)); }; /** * 弹框相关事件处理 * @return {[type]} [description] */ Dialog.prototype.events = function () { var objElement = this.element; // IE10+增加CSS3动画支持 var eleContainer = objElement.container; eleContainer.addEventListener('animationend', function (event) { if (event.target.tagName.toLowerCase() == DIALOG) { eleContainer.classList.remove(CL.add('animation')); } }); if (isSupportDialog == true) { eleContainer.addEventListener('close', function () { // 滚动条处理 this.scrollbar(); // 显示状态切换 this.display = false; // 键盘焦点元素还原 this.tabindex(); }.bind(this)); } // 关闭弹框按钮 var eleClose = objElement.close; if (eleClose) { eleClose.addEventListener('click', function () { // 有其他可ESC元素存在时候,弹框不关闭 var eleActiveElement = document.activeElement; var attrActiveElement = eleActiveElement.getAttribute('data-target'); var eleTargetElement = null; if (attrActiveElement) { eleTargetElement = document.getElementById(attrActiveElement); } // 如果是其他元素的键盘访问 if (window.isKeyEvent && eleTargetElement && eleActiveElement != eleClose && document.querySelector('a[data-target="' + attrActiveElement + '"],input[data-target="' + attrActiveElement + '"],button[data-target="' + attrActiveElement + '"]') && eleTargetElement.clientWidth > 0) { return; } // 关闭弹框 this[this.closeMode](); }.bind(this)); } return this; }; /** * 打开类型弹框的底层方法 * @param {String} content 弹框字符内容 * @param {Object} options 可选参数,这里只支持标题和按钮的自定义 * @return {Object} 返回当前实例对象 */ Dialog.prototype.open = function (content, options) { var objElement = this.element; // 默认参数 // 只支持标题和按钮的自定义 var defaults = { title: '', buttons: [] }; var dataContent = content || ''; // content可以是函数 if (typeof dataContent == 'function') { dataContent = dataContent(); } // 基于内容的数据类型,使用不同的默认的弹框关闭方式 if (typeof dataContent == 'string') { this.closeMode = 'remove'; } else { this.closeMode = 'hide'; } // 最终参数 this.params = Object.assign({}, defaults, options || {}, { content: dataContent }); // 类名还原为初始 objElement.container.className = CL.add('container'); // 替换当前弹框的内容,包括按钮等 if (this.closeMode == 'remove') { objElement.body.innerHTML = dataContent; } else { objElement.body.appendChild(dataContent); } objElement.title.innerHTML = this.params.title; // 按钮 this.button(); // 显示 this.show(); return this; }; /** * loading弹框,通常用在ajax请求之前使用 * loading结束后可以直接调用弹框实例的open()方法显示 * @return {Object} 返回当前实例对象 */ Dialog.prototype.loading = function () { var objElement = this.element; objElement.container.className = [CL.add('container'), CL.add('loading')].join(' '); objElement.body.innerHTML = '<ui-loading rows="10" size="3"></ui-loading>'; this.show(); return this; }; /** * alert类型的弹框,默认仅一个“确定”按钮 * @param {String} content 提示文字或者提示HTML片段 * @param {Object} options 提示可选参数 * @return {Object} 返回当前实例对象 */ Dialog.prototype.alert = function (content, options) { var objElement = this.element; if (!content) { return; } var strContent = content; // alert框的默认参数 var defaults = { title: '', // 类型, 'remind', 'success', 'warning', danger', 或者任意 'custom' type: 'remind', buttons: [{}] }; // 最终参数 var objParams = Object.assign({}, defaults, options || {}); if (objParams.type == 'error' || objParams.type == 'fail') { objParams.type = 'danger'; } if (objParams.type == 'primary') { objParams.type = 'remind'; } if (objParams.buttons.length && !objParams.buttons[0].type) { objParams.buttons[0].type = objParams.type; // 如果是自定义类型,则使用'primary'作为按钮类型 if (/^remind|success|warning|danger$/.test(objParams.type) == false) { objParams.buttons[0].type = defaults.type; } } // 替换当前弹框的内容,包括按钮等 objElement.container.className = [CL.add('container'), CL.add('container', 'alert')].join(' '); // 尺寸 objElement.dialog.style.width = 'auto'; objElement.title.innerHTML = objParams.title; // 如果是纯文本 if (/<[\w\W]+>/.test(strContent) == false) { strContent = '<p>' + strContent + '</p>'; } objElement.body.innerHTML = '<div class="' + CL.add(objParams.type) + ' ' + CL.add('alert') + '">' + strContent + '</div>'; this.params = { width: 'auto', title: objParams.title, buttons: objParams.buttons, content: strContent }; this.button(); this.show(); if (objElement.button0) { objElement.button0.focus(); } return this; }; /** * confirm类型的弹框,默认有一个“确定”和一个“取消”按钮 * @param {String} content 提示文字或者提示HTML片段 * @param {Object} options 提示可选参数 * @return {Object} 返回当前实例对象 */ Dialog.prototype.confirm = function (content, options) { var objElement = this.element; if (!content) { return; } var strContent = content; // confirm框的默认参数 var defaults = { title: '', type: 'danger', buttons: [{}, {}] }; // 最终参数 var objParams = Object.assign({}, defaults, options || {}); if (objParams.type == 'error' || objParams.type == 'fail') { objParams.type = 'danger'; } if (objParams.type == 'primary') { objParams.type = 'remind'; } // danger类型的按钮可缺省 if (objParams.buttons.length && !objParams.buttons[0].type) { objParams.buttons[0].type = objParams.type; // 如果是自定义类型,则使用'primary'作为按钮类型 if (/^remind|success|warning|danger$/.test(objParams.type) == false) { objParams.buttons[0].type = defaults.type; } } // 替换当前弹框的内容,包括按钮等 objElement.container.className = [CL.add('container'), CL.add('container', 'confirm')].join(' '); // 尺寸 objElement.dialog.style.width = 'auto'; objElement.title.innerHTML = objParams.title; // 如果是纯文本 if (/<[\w\W]+>/.test(strContent) == false) { strContent = '<p>' + strContent + '</p>'; } objElement.body.innerHTML = '<div class="' + CL.add(objParams.type) + ' ' + CL.add('confirm') + '">' + strContent + '</div>'; this.params = { width: 'auto', title: objParams.title, buttons: objParams.buttons, content: strContent }; this.button(); this.show(); if (objElement.button0) { objElement.button0.focus(); } return this; }; /** * 弹框出现与不出现,背景锁定同时页面不晃动的处理 * @return {Object} 返回当前实例对象 */ Dialog.prototype.scrollbar = function () { var eleAllDialog = document.querySelectorAll(DIALOG); // 是否有显示的弹框 var isDisplayed = [].slice.call(eleAllDialog).some(function (eleDialog) { return window.getComputedStyle(eleDialog).display != 'none'; }); // 因为去掉了滚动条,所以宽度需要偏移,保证页面内容没有晃动 if (isDisplayed) { var widthScrollbar = 17; if (this.display != true) { widthScrollbar = window.innerWidth - document.documentElement.clientWidth; } // 所有PC浏览器都滚动锁定 document.documentElement.style.overflow = 'hidden'; if (this.display != true) { document.body.style.borderRight = widthScrollbar + 'px solid transparent'; } } else { document.documentElement.style.overflow = ''; document.body.style.borderRight = ''; } return this; }; /** * 键盘可访问的细节处理 * @return {Object} 返回当前实例对象 */ Dialog.prototype.tabindex = function () { var eleDialog = this.element.dialog; var eleLastActiveElement = this.lastActiveElement; if (this.display == true) { var eleActiveElement = document.activeElement; if (eleDialog != eleActiveElement) { this.lastActiveElement = eleActiveElement; } // 键盘索引起始位置变为在弹框元素上 if (eleDialog) { eleDialog.focus(); } } else if (eleLastActiveElement && eleLastActiveElement.tagName.toLowerCase() != 'body') { // 键盘焦点元素还原 eleLastActiveElement.focus(); eleLastActiveElement.blur(); this.lastActiveElement = null; } return this; }; /** * 弹框元素zIndex实时最大化 * @return {[type]} [description] */ Dialog.prototype.zIndex = function () { var eleContainer = this.element.container; // 返回eleTarget才是的样式计算对象 var objStyleTarget = window.getComputedStyle(eleContainer); // 此时元素的层级 var numZIndexTarget = objStyleTarget.zIndex; // 用来对比的层级,也是最小层级 var numZIndexNew = 19; // 只对<body>子元素进行层级最大化计算处理 document.body.childNodes.forEach(function (eleChild) { if (eleChild.nodeType != 1) { return; } var objStyleChild = window.getComputedStyle(eleChild); var numZIndexChild = objStyleChild.zIndex * 1; if (numZIndexChild && eleContainer != eleChild && objStyleChild.display != 'none') { numZIndexNew = Math.max(numZIndexChild + 1, numZIndexNew); } }); if (numZIndexNew != numZIndexTarget) { eleContainer.style.zIndex = numZIndexNew; } }; /** * 弹框显示方法 * @return {Object} 返回当前实例对象 */ Dialog.prototype.show = function () { var eleContainer = this.element.container; // 面板显示 if (isSupportDialog) { if (!eleContainer.open) { eleContainer.show(); } } else { eleContainer.setAttribute('open', 'open'); // 层级最大 this.zIndex(); } if (this.display != true) { eleContainer.classList.add(CL.add('animation')); } this.scrollbar(); this.display = true; this.tabindex(); if (typeof this.callback.show == 'function') { this.callback.show.call(this, eleContainer); } return this; }; /** * 弹框隐藏方法 * @return {Object} 返回当前实例对象 */ Dialog.prototype.hide = function () { var eleContainer = this.element.container; if (isSupportDialog) { eleContainer.close(); } else { eleContainer.removeAttribute('open'); } // 滚动条处理 this.scrollbar(); // 显示状态切换 this.display = false; // 键盘焦点元素还原 this.tabindex(); if (typeof this.callback.hide == 'function') { this.callback.hide.call(this, eleContainer); } return this; }; /** * 弹框移除方法 * @return {Object} 返回当前实例对象 */ Dialog.prototype.remove = function () { var eleContainer = this.element.container; eleContainer.remove(); // 滚动条处理 this.scrollbar(); // 显示状态切换 this.display = false; // 键盘焦点元素还原 this.tabindex(); if (typeof this.callback.remove == 'function') { this.callback.remove.call(this, eleContainer); } return this; }; return Dialog; }));