Semantic UI是一个逼格很高的前端框架,用了他,可以让你随时啪啪打设计经理、产品经理的脸。
但Semantic UI也是一个上手难度非常高的框架,尤其是他零散的className,而且这些className各自又能互相组合使用,所以,没有一定的耐性,可能会觉得他是一个巨啰嗦的框架。
虽然,Semantic UI也提供了很多Module View的套路样式封装,然而他的思想和Bootstrap、UIKit等完全不是一路的。这些套路都是又他的基本元素构造而成,你既可以单独将这些元素抽取出来用,也可以将不同的套路混搭使用,呃,真是五花八门。
最大的区别是,Semantic UI对空间、距离、字号,是使用em来处理的,这也是我2年前会看上他的一个重要因素。所以,本质上说,Semantic UI是通过字号来控制空间和距离的。当然其实你具体的去重载他的className,用具体的px,也是可以的。
好吧,多的就不啰嗦了,回到主题,Modal。
弹出层,是一个经常使用的东西,Semantic UI的Modal,样式做得很精致,包括弹出、消失的过度过程也非常流畅、充分,唯独有一样东西不好,就是控制太费解了,我看了老半天,都没闹明白他丫到底要干嘛,比如以下这个官网的例子:
$('.coupled.modal')
.modal({
allowMultiple: true
})
;
// open second modal on first modal buttons
$('.second.modal')
.modal('attach events', '.first.modal .button')
;// show first immediately
$('.first.modal')
.modal('show')
;
attach events,好吧,我觉得他的设想可能很好,可是现实中应该不会有人去这么用吧。Modal的按钮点击,一般会直接跟具体的业务操作,比如发起ajax,操作别的什么元素之类的。虽然这里你可以附加一个闭包函数,可是我想吐槽的是,你能不用字符串做为接口或者属性名吗?这样真的让事情变复杂了。好吧我知道你还有API,不过我想说那个也让问题变得好好复杂。
官网的接口文档也没有直接给出如何能直接调用到Modal的实例的方法,只给了几个简单的配置参数,和modal方法访问的常用关键字字符……呃,实在忍不住了,扒拉了一下他的modal.js的源代码,呃,其实问题本来可以很简单,源代码封装得好好的,为啥就不把接口暴露出来呢?你也是够了,故作简易。
简单说,通过jQuery捕获到任意DOM对象,执行modal方法,可以理解为对这个modal进行初始化。
$('#hello_world').modal({
// ...
});
只要你不调用modal('show'),他是不会乱来的。
那么如何取得他内部创建的这个Modal实例呢?其实他有一个默认的namespace,以module为前缀,modal访问名是:module-modal:
var modal = $('#hello_world').data('module-modal');
console.log(modal);
输出一下这个实例看看,其实里面的方法很完善,封装的好极了,可是为什么就是不暴露出来呢?
对于一般的modal的操作,只要知道:
modal.showModal(callback);
modal.hideModal(callback, keepDimmer);
modal.is.active();
其他的方法,说有用也有用,但上层应用一般也不会直接用到了。而他的Modal操作,我个人的看法是,就没必要用他的了,实在不好理解——关键是项目执行时间内,也没时间去理解他为啥要那么蹩脚。
所以,有了以下这么一个简单粗暴的封装:
define('module/UI', ['jquery', 'lodash'], function ($, _) {
var Modal = function (options) {
this.options = {
prefix: 'modal_box_',
namespace: 'semantic-modal',
closable: true,
cls: null,
// default message
icon: '',
header: '',
content: '',
actions: {}
};
if (_.isObject(options))
_.merge(this.options, options);
this.id = _.uniqueId(this.options.prefix);
this.body = false;
this.modal = false;
this.buttons = {};
this.bodyActionsBox = null;
};
Modal.prototype.hasBody = function () {
if (this.body !== false)
return true;
var el = document.getElementById(this.id);
if (el) {
this.body = $(el);
return true;
}
return false;
};
Modal.prototype.initBody = function () {
if ($('#' + this.id).get(0))
return this;
var closeIcon = !!this.options.closable ? '<i class="close icon"></i>' : '';
this.body = $('<div id="' + this.id + '" class="ui modal">' + closeIcon + '<div class="header"><i class="icon"></i><span class="inner"></span></div><div class="content"></div></div>');
this.body.modal(this.options);
if (this.options.cls)
this.body.addClass(this.options.cls);
this.initActions(this.options.actions);
// 先在内存中把整个modal初始化完成了,这样可以不用牺牲页面渲染的性能
$('body').append(this.body);
return this;
};
Modal.prototype.getBody = function () {
if (!this.hasBody())
this.initBody();
return this.body;
};
Modal.prototype.getModal = function () {
if (!this.modal) {
this.modal = this.getBody().data('module-' + this.options.namespace);
}
return this.modal;
};
Modal.prototype.hasActions = function () {
return !!this.bodyActionsBox;
};
Modal.prototype.removeActions = function () {
if (this.bodyActionsBox) {
this.bodyActionsBox.remove();
this.bodyActionsBox = false;
}
return this;
};
Modal.prototype.initActions = function (actions) {
if (_.size(actions) <= 0)
return this.removeActions();
var me = this;
this.bodyActionsBox = $('<div class="actions"></div>');
_.forOwn(actions, function (action, key) {
me.initAction(key, action);
});
this.body.append(this.bodyActionsBox);
return this;
};
Modal.prototype.initAction = function (name, action) {
action = _.merge({
text: null,
cls: null,
icon: null,
click: null
}, action || {});
var me = this
, text = action.text
, icon = action.icon
, cls = action.cls || '';
if (!_.isString(text) || text === '')
text = name;
if (_.isString(icon) && icon !== '') {
cls += ' icon labeled';
text += '<i class="icon ' + icon + '"></i>';
}
var button = this.buttons[name] = $('<button type="button" class="ui button ' + cls + '">' + text + '</button>');
button.prop('action', name);
button.click(function () {
me.action(button.prop('action'));
});
me.on(name, action.click);
this.bodyActionsBox.append(button);
return this;
};
Modal.prototype.on = function (event, fn, isOne) {
var me = this, $me = $(this), handle = !!isOne ? $me.one : $me.on;
if (_.isString(event)) {
if (_.isFunction(fn))
handle.call($me, event, fn);
else if (_.isArray(fn) && fn.length > 0) {
_.each(fn, function (item) {
me.on(event, item, isOne);
});
}
}
else if (_.isObject(event)) {
_.forOwn(event, function (value, name) {
me.on(name, value, isOne);
});
}
return this;
};
Modal.prototype.one = function (event, fn) {
return this.on(event, fn, true);
};
Modal.prototype.action = function (name) {
var isBreak = $(this).triggerHandler(name);
if (isBreak !== false)
this.hide();
return this;
};
Modal.prototype.isShow = function () {
return this.getModal().is.active();
};
Modal.prototype.isHide = function () {
return !this.isShow();
};
Modal.prototype.show = function (message) {
if (_.isFunction(message))
message = {then: message};
else if (typeof message !== 'undefined' && (_.isString(message) || _.isObject(message)))
this.setMessage(message);
if (this.isHide())
this.getModal().showModal(message && message.then);
return this;
};
Modal.prototype.hide = function (then, keepDimmer) {
var me = this;
if (this.isShow()) {
this.getModal().hideModal(then, keepDimmer);
}
return this;
};
Modal.prototype.setMessage = function (message) {
if (_.isString(message))
message = {content: message};
this.setIcon(message.icon || null);
this.setHeader(message.header || null);
this.setContent(message.content || null);
if (message.actions)
this.on(message.actions, null, true);
return this;
};
Modal.prototype.setIcon = function (icon) {
var item = this.getBody().find('.header .icon');
if (!icon || icon == '')
icon = this.options.icon;
if (!icon || icon == '')
item.css('display', 'none');
else {
item.css('display', '');
item.addClass(icon);
}
return this;
};
Modal.prototype.setHeader = function (content) {
var item = this.getBody().find('.header .inner');
if (!content || content.length == '')
content = this.options.header;
item.html(content);
return this;
};
Modal.prototype.setContent = function (content) {
var item = this.getBody().find('.content');
if (!content || content.length == '')
content = this.options.content;
item.html(content);
return this;
};
var globalModals = {};
var stdModalOptions = [
{
access: 'alert',
closable: false,
cls: 'small',
icon: 'comment outline',
header: '消息提示',
actions: {
'ok': {
text: '好的',
cls: 'green right',
icon: 'checkmark'
}
}
},
{
access: 'confirm',
closable: false,
cls: 'small',
icon: 'warning',
header: '请确认',
content: '你确定吗?',
actions: {
'yes': {
text: '我知道了',
cls: 'green',
icon: 'checkmark'
},
'no': {
text: '容我想想',
cls: 'red',
icon: 'remove'
}
}
}
];
var exports = {};
exports.ModalClass = Modal;
var getModal = exports.modal = function (id, options) {
if (!globalModals[id])
globalModals[id] = new Modal(options);
return globalModals[id];
};
_.each(stdModalOptions, function(options) {
var name = options.access;
exports[name] = function (message, show) {
var modal = getModal(name, options).setMessage(message);
if (typeof show === 'undefined')
show = true;
if (!!show)
modal.show(show);
return modal;
}
});
return exports;
});
他依赖于jQuery和lodash作为基本。
首先,这个封装,公布了两个标准的Modal,分别为alert和confirm
UI.alert('你好');
UI.alert({
content: '你不被授权访问以下页面',
// ok为alert默认的action
actions: {
ok: function(event) {
return false; // 将中断隐藏的操作
}
}
});
UI.confirm({
icon: 'info', // 换一个标题如何
header: '这是标题',
content: '这是内容',
// contrim默认的actions
actions: {
yes: [
function(event) {
},
function(event) {
}
],
no: function(event) {
}
}
})
// 不直接显示
UI.confirm('你确定吗?', false).show();
// 绑定自己的action
UI.confirm('你确定吗?', false).on('yes', function() {
return false;
}).show();
其次,暴露出ModalClass,和modal方法,通过ModalCalss你可以创建自己的modal实例,并且自行对实例进行管理。而modal则是实例托管的调用。
// my_modal是全局访问的名称
// 第二个参数是配置
UI.modal('my_modal', {
closable: true, // 是否可任意关闭
cls: 'large', // 给modal附加的class
icon: 'warning', // 默认的header的icon
header: '请确认', // 默认的header内容
content: '你确定吗?', // 默认的content内容
actions: {
'confirm': {
text: '确认', // 按钮显示文字
cls: 'green', // 按钮附加样式
icon: 'checkmark' // 按钮的icon
click: function() { // 绑定初始的事件
}
},
'wait': {
text: '容我想想',
cls: 'red',
icon: 'remove'
}
}
});
// 调用实例
UI.modal('my_modal').show();
如果你已经注册了某个Modal,想直接执行他的某个action,可以:
UI.modal('my_modal').action('confirm');
这个函数实际上就是触发事件。
嗯,其实以前使用UIKit的时候,封装过一个类似的,除了上述的操作,还有一个自动加载Ajax或者图片的需求,不过暂时够用了。
忘记说了,Semantic的官网来说,从他的例子来看,是把多个Modal写好,输出在HTML页面中,然后直接捕获这个元素执行modal方法。不过我想说,真的够了,谁没事为了弹个提示写那么多html呢。所以这个Modal的封装,就不需要在页面写个Modal了,直接调用就可以了。