UNPKG

lu2

Version:

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

613 lines (534 loc) 20.7 kB
/** * @Dialog.js * @author zhangxinxu * @version * Created: 15-06-18 */ (function (global, factory) { if (typeof define === 'function' && (define.amd || define.cmd)) { define(factory); } else { global.Dialog = factory(); } }(this, function (require) { // require if (typeof require == 'function') { require('common/ui/Loading'); } /** * 弹框组件 * IE9+基于HTML5 <dialog>元素创建 * 本透明遮罩层和弹框在一个元素内 * @example * var myDialog = new Dialog(options); * $(button).click(function() { myDialog.remove(); }); */ // 类名前缀 var prefixGlobal = 'ui-'; var prefixDialog = 'ui-dialog-'; var joiner = '-'; // 关闭SVG var svg = window.addEventListener ? '<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>' : ''; // Is it webkit var isWebkit = 'WebkitAppearance' in document.documentElement.style || typeof document.webkitHidden != 'undefined'; // jquery语法 $.dialog = function (options) { return new Dialog(options); }; /** * 弹框实例方法 * @param {Object} options 纯对象,可选参数 */ var Dialog = function (options) { var defaults = { title: '', // 不同类别的内容类型 content: '', // 弹框的宽度 width: 'auto', // 不同类别的默认按钮 buttons: [], // 弹框显示、隐藏、移除的回调 onShow: $.noop, onHide: $.noop, onRemove: $.noop }; // 最终参数 var params = $.extend({}, defaults, options || {}); var el = {}; this.el = el; // 存储一些参数 this.width = params.width; this.callback = { show: params.onShow, hide: params.onHide, remove: params.onRemove }; // 各个元素创建 // 容器-含半透明遮罩背景 el.container = window.addEventListener ? $('<dialog class="' + prefixDialog + 'container"></dialog>') : $('<div class="' + prefixDialog + 'container"></div>'); // IE10+增加CSS3动画支持 if (history.pushState) { el.container.get(0).addEventListener(isWebkit ? 'webkitAnimationEnd' : 'animationend', function(event) { if (event.target.tagName.toLowerCase() == 'dialog') { this.classList.remove(prefixDialog + 'animation'); } }); } // 弹框主体 el.dialog = $('<div class="' + prefixGlobal + 'dialog"></div>').css('width', params.width).attr({ tabindex: '-1', role: 'dialog' }); // 标题 el.title = $('<div class="' + prefixDialog + 'title" role="heading"></div>').html(params.title); // 关闭按钮 var idClose = ('id_' + Math.random()).replace('0.', ''); el.close = $('<a href="javascript:" class="' + prefixDialog + 'close ESC"></a>').attr({ role: 'button', 'aria-label': '关闭', id: idClose, 'data-target': idClose }).html(svg).click($.proxy(function(event) { event.preventDefault(); // 有其他可ESC元素存在时候,弹框不关闭 var activeElement = document.activeElement; var attrActiveElement = activeElement.getAttribute('data-target'); var targetElement = attrActiveElement && document.getElementById(attrActiveElement); if (window.isKeyEvent && targetElement && activeElement != el.close[0] && document.querySelector('a[data-target="' + attrActiveElement + '"],input[data-target="' + attrActiveElement + '"]') && targetElement.clientWidth > 0) { return; } // 关闭弹框 this[this.closeMode](); }, this)); // 主体 var content = params.content; // content可以是函数 if ($.isFunction(content)) { content = content(); } if (!content.size) { this.closeMode = 'remove'; } else { this.closeMode = 'hide'; } el.body = $('<div class="' + prefixDialog + 'body"></div>')[content.size ? 'append' : 'html'](content); // 底部 el.footer = $('<div class="' + prefixDialog + 'footer"></div>'); // 按钮 this.button(params.buttons); // 组装 el.container.append(el.dialog.append(el.close).append(el.title).append(el.body).append(el.footer)); // IE7 额外辅助元素 if (!document.querySelector) { el.container.append('<i class="' + prefixDialog + 'after"></i>'); } // zIndex动态计算 // 所有dialog的前面 var dialog = $('.' + prefixDialog + 'container'); if (dialog.length) { // 页面前置 dialog.eq(0).before(el.container.css({ // 动态计算zIndex值 zIndex: dialog.eq(0).css('z-index') * 1 + 1 })); } else { // 页面后入 (params.container || $(document.body)).append(el.container); } this.display = false; if (params.content) { this.show(); } return this; }; /** * 弹框按钮元素 * @param {Array} data 数组,数组项为弹框按钮相关的数据对象 * @return {Object} 返回当前实例对象 */ Dialog.prototype.button = function(data) { var el = this.el; var dialog = this; // 先清空弹框的footer el.footer.empty(); // 遍历按钮数据生成按钮 $.each(data, function(i, btn) { // 避免btn为null等值报错 btn = btn || {}; // 按钮的默认参数 var type = i ? (btn.type || '') : (btn.type || 'primary'); var value = i ? (btn.value || '取消') : (btn.value || '确定'); // 事件处理,如果直接function类型,则认为是点击方法 var events = btn.events || { click: function() { dialog[dialog.closeMode](); } }; if ($.isFunction(events)) { events = { click: events }; } // 按钮的类名值 var cl = prefixGlobal + 'button'; if (type) cl = cl + ' ' + cl + joiner + type; if (!btn['for']) { el['button' + i] = $('<a href="javascript:" class="' + cl + '" role="button">' + value + '</a>'); } else { el['button' + i] = $('<label for="' + btn['for'] + '" class="' + cl + '" role="button">' + value + '</label>'); } $.each(events, function (eventType, fn) { el['button' + i].bind(eventType, { dialog: dialog }, fn); el['button' + i].on('focus', function () { $(this).css('outline', window.isKeyEvent ? '' : 'none'); }); }); el.footer.append(el['button' + i]); }); return dialog; }, /** * loading弹框,通常用在ajax请求弹框数据之前使用 * loading结束后可以直接调用弹框实例的open()方法显示 * @return {Object} 返回当前实例对象 */ Dialog.prototype.loading = function () { if (!$().loading) { window.console && window.console.error('need Loading.js'); return dialog; } var el = this.el; var dialog = this; if (el) { el.container.attr('class', [prefixDialog + 'container', prefixDialog + 'loading'].join(' ')); el.body.loading(); dialog.show(); } return dialog; }; /** * loading结束调用,主要包含内容展开的动画交互效果 * @param {number|0|false} time 参见Loading.js中的time参数 * @return {Object} 返回当前实例对象 */ Dialog.prototype.unloading = function (time) { if (!$().unloading) { window.console && window.console.error('need Loading.js'); return dialog; } var el = this.el; var dialog = this; if (el) { el.container.removeClass(prefixDialog + 'loading'); el.body.unloading(time); } return dialog; }; /** * 打开类型弹框的底层方法 * @param {String} html 弹框字符内容 * @param {Object} options 可选参数,这里只支持标题和按钮的自定义 * @return {Object} 返回当前实例对象 */ Dialog.prototype.open = function (html, options) { var el = this.el; var dialog = this; // 默认参数 var defaults = { title: '', buttons: [] }; // 最终参数 var params = $.extend({}, defaults, options || {}); // 替换当前弹框的内容,包括按钮等 if (el && html) { el.container.attr('class', [prefixDialog + 'container'].join(' ')); el.title.html(params.title); el.body.html(html); dialog.button(params.buttons).show(); } return dialog; }; /** * alert类型的弹框,默认仅一个“确定”按钮 * @param {String} html 提示文字或者提示HTML片段 * @param {Object} options 提示可选参数 * @return {Object} 返回当前实例对象 */ Dialog.prototype.alert = function (html, options) { var el = this.el; var dialog = this; // alert框的默认参数 var defaults = { title: '', // 类型, 'remind', 'success', 'warning', 或者任意 'custom' type: 'remind', buttons: [{}] }; // 最终参数 var params = $.extend({}, defaults, options || {}); if (!params.buttons[0].type && params.type != 'remind') { params.buttons[0].type = params.type; } // 替换当前弹框的内容,包括按钮等 if (el && html) { el.container.attr('class', [prefixDialog + 'container', prefixDialog + 'alert'].join(' ')); el.dialog.width('auto').attr({ role: 'alertdialog' }); el.title.html(params.title); // 如果是纯文本 if (/<[\w\W]+>/.test(html) == false) { html = '<p>' + html + '</p>'; } el.body.html('<div class="' + prefixDialog + params.type + ' ' + prefixDialog + 'alert">' + html + '</div>'); this.button(params.buttons).show(); if (el.button0) { el.button0.focus(); } } return dialog; }; /** * confirm类型的弹框,默认有一个“确定”和一个“取消”按钮 * @param {String} html 提示文字或者提示HTML片段 * @param {Object} options 提示可选参数 * @return {Object} 返回当前实例对象 */ Dialog.prototype.confirm = function (html, options) { var el = this.el; var dialog = this; // confirm框的默认参数 var defaults = { title: '', type: 'warning', buttons: [{ type: 'warning' }, { // 取消按钮使用默认配置 }] }; // 最终参数 var params = $.extend({}, defaults, options || {}); // warning类型的按钮可缺省 if (params.buttons.length && !params.buttons[0].type) { params.buttons[0].type = 'warning'; } // 替换当前弹框的内容,包括按钮等 if (el && html) { el.container.attr('class', [prefixDialog + 'container', prefixDialog + 'confirm'].join(' ')); el.dialog.width('auto'); el.title.html(params.title); // 如果是纯文本 if (/<[\w\W]+>/.test(html) == false) { html = '<p>' + html + '</p>'; } el.body.html('<div class="' + prefixDialog + params.type + ' ' + prefixDialog + 'confirm">' + html + '</div>'); this.button(params.buttons).show(); if (el.button0) { el.button0.focus(); } } return dialog; }; /** * 直接ajax请求数据并弹框显示处理 * @param {Object} ajaxOptions ajax请求参数,同$.ajax()参数 * @param {Object} options 弹框相关的可选参数 * @return {Object} 返回当前实例对象 */ Dialog.prototype.ajax = function (ajaxOptions, options) { var dialog = this; // ajax请求的一些默认参数 // 类似timeout等参数这里缺省了 // url直接注释了,就是jQuery的Ajax参数 var ajaxDefaults = { // 必须参数,请求地址 // url: '', dataType: 'JSON', timeout: 30000, error: function(xhr, status) { var msgSorry = '<h6>尊敬的用户,很抱歉您刚才的操作没有成功!</h6>'; var msgReason = ''; if (status == 'timeout') { msgReason = '<p>主要是由于请求时间过长,数据没能成功加载,这一般是由于网速过慢导致,您可以稍后重试!</p>'; } else if (status == 'parsererror') { msgReason = '<p>原因是请求的数据含有不规范的内容。一般出现这样的问题是开发人员没有考虑周全,欢迎向我们反馈此问题!</p>'; } else { msgReason = '<p>一般是网络出现了异常,如断网;或是网络临时阻塞,您可以稍后重试!如依然反复出现此问题,欢迎向我们反馈!</p>'; } dialog.alert(msgSorry + msgReason, { type: 'warning' }).unloading(); } }; var ajaxParams = $.extend({}, ajaxDefaults, ajaxOptions || {}); // ajax的url地址是必须参数 if (!ajaxParams.url) { return this; } // 下面是弹框的一些默认参数 var defaults = { title: '', content: function(data) { // 如果ajax的类型是'JSON', 此方法用来返回格式化的HTML数据 // 如果是HTML, 默认返回HTML if (typeof data == 'string') { return data; } return '看见我说明没使用\'options.content\'做JSON解析'; }, buttons: [] }; var params = $.extend({}, defaults, options || {}); // 存储ajax时候的activeElement // var lastActiveElement = this.lastActiveElement; // ajax走起,记录当前时间戳 var timer = new Date().getTime(); // ajax success回调走合并 var success = ajaxParams.success; ajaxParams.success = function (data) { dialog.open(params.content(data), params); // 移除菊花态 // 如果时间戳小于100毫秒 // 弹框直接打开,无动画 if (new Date().getTime() - timer < 100) { dialog.unloading(0); } else { dialog.unloading(); } // 这是用户设置的成功回调 if ($.isFunction(success)) { success.apply(this, arguments); } }; // 开始执行Ajax请求 // 稍等…… // 请求之前,我们还需要做一件事件 // 开菊花 dialog.loading(); // ajax走起 // 下面的定时器测试用 // setTimeout(function() { $.ajax(ajaxParams); // }, 150); return dialog; }; /** * 弹框出现与不出现,背景锁定同时页面不晃动的处理 * @return {Object} 返回当前实例对象 */ Dialog.prototype.scroll = function () { var containers = $('.' + prefixDialog + 'container'); // 是否有显示的弹框 var isDisplayed = false; containers.each(function() { if ($(this).css('display') == 'block') { isDisplayed = true; } }); // 因为去掉了滚动条,所以宽度需要偏移,保证页面内容没有晃动 if (isDisplayed) { var widthScrollbar = 17; if (this.display != true && typeof window.innerWidth == 'number') { widthScrollbar = window.innerWidth - document.documentElement.clientWidth; } document.documentElement.style.overflow = 'hidden'; if (this.display != true) { $(document.body).css('border-right', widthScrollbar + 'px solid transparent'); } } else { document.documentElement.style.overflow = ''; $(document.body).css('border-right', ''); } return this; }; /** * 键盘可访问的细节处理 * @return {Object} 返回当前实例对象 */ Dialog.prototype.tabindex = function () { var dialog = this.el.dialog; var lastActiveElement = this.lastActiveElement; if (this.display == true) { var activeElement = document.activeElement; if (dialog[0] !== activeElement) { this.lastActiveElement = activeElement; } // 键盘索引起始位置变为在弹框元素上 if (dialog) { dialog.focus(); } } else if (lastActiveElement && lastActiveElement.tagName.toLowerCase() != 'body') { // 键盘焦点元素还原 lastActiveElement.focus(); lastActiveElement.blur(); this.lastActiveElement = null; } return dialog; }; /** * 弹框显示方法 * @return {Object} 返回当前实例对象 */ Dialog.prototype.show = function () { var container = this.el && this.el.container; if (container) { // 面板显示 container.css('display', 'block'); if (this.display != true) { container.addClass(prefixDialog + 'animation'); } this.scroll(); this.display = true; this.tabindex(); if ($.isFunction(this.callback.show)) { this.callback.show.call(this, container); } } return this; }; /** * 弹框隐藏方法 * @return {Object} 返回当前实例对象 */ Dialog.prototype.hide = function () { var container = this.el.container; if (container) { container.hide(); this.scroll(); this.display = false; // 键盘焦点元素还原 this.tabindex(); if ($.isFunction(this.callback.hide)) { this.callback.hide.call(this, container); } } return this; }; /** * 弹框移除方法 * @return {Object} 返回当前实例对象 */ Dialog.prototype.remove = function () { var container = this.el && this.el.container; // var lastActiveElement = this.lastActiveElement; if (container) { container.remove(); this.scroll(); this.display = false; // 键盘焦点元素还原 this.tabindex(); if ($.isFunction(this.callback.remove)) { this.callback.remove.call(this, container); } } return this; }; return Dialog; }));