Magicodes.WeiChat——自定义knockoutjs template、component实现微信自定义菜单
本人一向比较喜欢折腾,玩了这么久的knockoutjs,总觉得不够劲,于是又开始准备折腾自己了。
最近在完善Magicodes.WeiChat微信开发框架时,发现之前做的自定义菜单这块太不给力了,而各种第三方平台在这一块做得也比较渣,功能不全不说,界面还很不友好,于是决心重整一版,以满足需求。
下面先上图,新的UI界面如下所示:
如何实现这个功能呢?下面请等我一一道来吧。
左侧树形结构绑定
HTML模板如下所示:
<div class="dd" id="nestable2">
<ol class="dd-list" data-bind="foreach:Menus()">
<li class="dd-item lv1">
<div class="dd-handle">
<span class="pull-right">
<i class="fa fa-plus" data-bind="click:$root.AddClick"></i>
<i class="fa fa-times" data-bind="click:$root.RemoveItem"></i>
<i class="fa fa-pencil" data-bind="click:$root.ItemClick"></i>
</span>
<span>
<span class="label label-info"><i class="fa" data-bind="css:$root.getIconCssByType(type)"></i></span><span data-bind="text:name,click:$root.ItemClick"></span>
</span>
</div>
<!-- ko if:$data.sub_button !== undefined -->
<ol class="dd-list" data-bind="foreach:$data.sub_button">
<li class="dd-item lv2" data-id="2">
<div class="dd-handle">
<span class="pull-right">
<i class="fa fa-times" data-bind="click:$root.RemoveItem"></i>
<i class="fa fa-pencil" data-bind="click:$root.ItemClick"></i>
</span>
<span class="label label-success"><i class="fa" data-bind="css:$root.getIconCssByType(type)"></i></span> <span data-bind="text:name"></span>
</div>
</li>
</ol>
<!-- /ko -->
</li>
</ol>
</div>
这里我解释一下,上述模板用到了两个foreach循环,以便绑定这个两级列表。实际上如果数据结构支持的话,ko是可以递归的绑定的。ko的强大性是毋庸置疑的。然后注意这个注释:“<!-- ko if:$data.sub_button !== undefined -->”,这个真的不是注释,这个是有用的。为了不产生脏元素,ko支持这种绑定写法。这里先用if做了判断,然后再绑定子集。其余的,就是简单的data-bind语法了。
通过上述模板,我们注意到数据结构中两个关键点:Menus和sub_button,那我们就来看看viewModel。viewModel中定义了Menus = ko.observableArray([]),然后使用Ajax获取数据来填充:
//初始化,加载数据
this.Init = function () {
mwc.ui.setBusy();
self.Api.request('GET', {
url: '/api/Menus',
func: function (data) {
mwc.ui.clearBusy();
$.each(data, function (i, v) {
if (v.sub_button) {
$.each(v.sub_button, function (i1, v1) {
v.sub_button[i1] = $.extend(self.getModelTpl(), v1);
})
}
data[i] = $.extend(self.getModelTpl(), v);
});
self.Menus(ko.mapping.fromJS(data));
}
});
};
注意,因为方便,这里使用了knockout.mapping js,请注意ko.mapping.fromJS方法。
右侧编辑模板绑定
这块无疑是比较复杂的一块,我们先进行肢解:
- 通用模块:顶部按钮组、名称输入框、保存按钮
- 模板(按微信类型加载不同模板)
我们先来看看整体的编辑模板:
<div class="ibox-title">
<h5>按钮其他参数 </h5>
</div>
<div class="ibox-content" data-bind="with:EditModel" style="min-height: 600px;">
<form class="form-horizontal">
<!-- ko if:type() != 'empty' -->
<buttonschoices params="SelectsModel: $root.SelectTypes,SelectValue:type"></buttonschoices>
<div class="hr-line-dashed"></div>
<div class="form-group">
<label class="col-sm-2 control-label">名称</label>
<div class="col-sm-10">
<input type="text" class="form-control" data-bind="value:name" required>
</div>
</div>
<div class="hr-line-dashed"></div>
<!-- /ko -->
<div data-bind="template:{name:$root.GetEditTemplateName,data:$root.EditModel,afterRender:$root.afterEditTemplateRender}">
</div>
<!-- ko if:type() != 'empty' -->
<div>
<button class="btn btn-primary pull-right" type="button" data-bind="click:$root.Save">
<i class="fa fa-save"></i>
<strong>保存</strong>
</button>
</div>
<!-- /ko -->
</form>
</div>
由模板可知,整个编辑模块由类型按钮组、名称框、动态模板、保存按钮组成。接下来我就先介绍下类型按钮组的定义与绑定:
类型按钮组——knockout component
如上述代码中,使用了html标签buttonschoices。而这个标签就是我定义的knockout compoent。使用knockout compoent能做什么呢?就如上述代码中,我们可以知道以下几点:
- 返回HTML模板
- 传递参数,绑定compoent ViewModel
那么封装knockout compoent,有助于我们封装一些通用UI组件,就比如按钮组类型选择。我们先来一览代码:
//按钮组选择组件
ko.components.register('buttonschoices', {
viewModel: function (params) {
var self = this;
//所选值
this.SelectValue = ko.observable();
//text:文本
//value:值
//icon:图标
//des:描述
this.SelectItem = ko.observable({ text: "", value: "", icon: "", des: "" });
//选择模型
this.SelectsModel = ko.observableArray([]);
if (params && typeof (params.SelectsModel()) != "undefined") {
self.SelectsModel(params.SelectsModel());
if (typeof (params.SelectValue()) != "undefined") {
self.SelectValue(params.SelectValue());
self.SelectItem($.grep(self.SelectsModel(), function (v, i) { return v.value == self.SelectValue() })[0]);
}
}
this.GetActiveCss = function (item) {
return item.value == self.SelectValue() ? "active btn-primary" : "";
}
this.buttonClick = function (item) {
self.SelectValue(item.value);
self.SelectItem(item);
params.SelectValue(item.value);
}
},
template: '<div class="btn-group" data-bind="foreach: SelectsModel">' +
'<button class="btn btn-white" data-bind="css:$parent.GetActiveCss($data),click:$parent.buttonClick"><i class="fa" data-bind="css:icon"></i> <span data-bind="text:text"></span></button>' +
'</div>' +
'<div class="well" data-bind="with:SelectItem">' +
'<span data-bind="text:des"></span>' +
'</div>'
});
整个组件代码很简洁明了,通过ko.components.register注册组件,buttonschoices为组件名称,整个组件由两部分组成:
- viewModel:视图模型
- template:模板
其中,viewModel接收了传入参数,并且进行了处理。我们来依次解析这个viewModel:
- SelectValue:所选指。这个所选指会根据传入参数(还记得前面的“<buttonschoices params="SelectsModel: $root.SelectTypes,SelectValue:type"></buttonschoices>”吗,其中SelectValue:type就是传入了参数SelectValue)进行赋值,如右侧代码:self.SelectValue(params.SelectValue())。
- SelectItem:所选项。项结构为{ text: "", value: "", icon: "", des: "" },分别代表文本、值、图标和描述。
- SelectsModel:选择模型,就是列表模型。有多少个按钮,就看其有多少个项了。传入参数见“SelectsModel: $root.SelectTypes”。我们来看看这个$root.SelectTypes是怎么定义的:
//类型选择
this.SelectTypes = ko.observableArray([
{ text: "点击推事件", value: "click", icon: "fa-font", des: "用户点击此类型按钮后,微信服务器会通过消息接口推送消息类型为event 的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互" },
{ text: "跳转URL", value: "view", icon: "fa-link", des: "用户点击此类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。" },
{ text: "扫码推事件", value: "scancode_push", icon: "fa-qrcode", des: "用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后显示扫描结果(如果是URL,将进入URL),且会将扫码的结果传给开发者,开发者可以下发消息。" },
{ text: "扫码推事件且弹出“消息接收中”提示框", value: "scancode_waitmsg", icon: "fa-qrcode", des: "用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。" },
{ text: "弹出系统拍照发图", value: "pic_sysphoto", icon: "fa-camera", des: "用户点击按钮后,微信客户端将调起系统相机,完成拍照操作后,会将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。" },
{ text: "弹出拍照或者相册发图", value: "pic_photo_or_album", icon: "fa-camera", des: "用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。" },
{ text: "弹出微信相册发图器", value: "pic_weixin", icon: "fa-picture-o", des: "用户点击按钮后,微信客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。" },
{ text: "弹出地理位置选择器", value: "location_select", icon: "fa-map-marker", des: "用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操作后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。" },
{ text: "下发消息(除文本消息)", value: "media_id", icon: "fa-newspaper-o", des: "用户点击按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。" },
{ text: "跳转图文消息URL", value: "view_limited", icon: "fa-envelope", des: "用户点击按钮后,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL,永久素材类型只支持图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。" }
]);
众所周知,微信自定义菜单支持10中类型的按钮,那么这里是其类型的定义。这也说明,这个按钮组是完全通用的,你只要给予与上述结构一致的数据,其就能显示成当前效果。
- GetActiveCss:获取当前所选样式。选中返回选中样式,否则返回空。
- buttonClick:按钮点击事件,这里拿到的是数据项,ko就是这么方便。然后值得注意的是,参数是双向的,我们可以利用“params.SelectValue(item.value);”来回写值,这样编辑模型的类型值才会产生改变。
viewModel很简单,template也很简单,就是将刚才所说的viewModel绑定,用到了BootStrap按钮组样式“btn-group”,用foreach绑定SelectsModel,然后逐个绑定。
注意:
$parent表示父级对象,即乃父,因为foreach之后,其实对象已经指定到了乃父的儿子(SelectsModel)的某个儿子($data)上,而GetActiveCss是viewModel的女儿,自然要通过乃父来获取了,毕竟其乃父的儿子的子孙并不是她。
$data表示当前项,即乃父的儿子的某个儿子,用于循环中获取当前项数据。
with类似于using命名空间一样,用了它,下面的元素都可以省却改命名空间了。
是不是很简单的样子。我们再来说说模板:
动态加载模板
首先,我们先聚焦到以下代码:
<div data-bind="template:{name:$root.GetEditTemplateName,data:$root.EditModel,afterRender:$root.afterEditTemplateRender}">
</div>
首先我们得明确以下内容:
template语法用于绑定模板,其中name用于指定模板名称,这里绑定了$root.GetEditTemplateName方法,data用于指定模板的viewModel。
然后我们再来看看GetEditTemplateName怎么回事?如下所示:
//根据类型获取编辑模板
this.GetEditTemplateName = function (data) {
switch (data.type()) {
case "empty":
return "emptyTemplate";
case "media_id":
case "view_limited":
return "media_idTemplate";
case "view":
return "urlTemplate"
default:
return "keyTemplate";
}
};
看起来也蛮简单的样子,就返回了一个模板名称,那我们再继续来看看这些模板。
<script id="emptyTemplate" type="text/html">
<div class="well">
<h3>注意事项:</h3>
创建自定义菜单后,由于微信客户端缓存,需要24小时微信客户端才会展现出来。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。
</div>
</script>
<script id="keyTemplate" type="text/html">
<div class="form-group" id="buttonDetails_url_area">
<label class="col-sm-2 control-label">关键字</label>
<div class="col-sm-10">
<input type="text" class="form-control" data-bind="value:key" />
</div>
</div>
</script> <script id="urlTemplate" type="text/html">
<div class="form-group" id="buttonDetails_url_area">
<label class="col-sm-2 control-label">链接</label>
<div class="col-sm-10">
<input type="url" class="form-control" data-bind="value:url" />
</div>
</div>
</script>
<script id="media_idTemplate" type="text/html">
<news-choice-button params="value: media_id"></news-choice-button>
</script>
<div data-bind="with:EditModel">
<news-choice-modal params="value: media_id"></news-choice-modal>
</div>
模板的定义也蛮简单的,id和上面的字符串是一致的,类型必须为text/html。上面模板分别为空模板,关键字模板,链接模板和素材模板。
其中素材模板里面使用了自定义的component,和之前的buttonschoices一样,封装了多图文选择代码。
由于组件news-choice-button和news-choice-modal需要讲解的篇幅比较长,这里就暂不介绍了。
至于增删改查,对于ko来说,都是操作数据模型。比如左侧树形结构的增删,则是对Menus数组的增减操作,而编辑,则需要更新数组中的数据项。viewModel的修改,ko会自动重绘UI。这里就不多介绍了。
总结
通过使用knockoutjs 的动态模板,我们可以很方便的根据需要加载不同的模板进行绑定显示。而通过knockoutjs component的封装,我们可以很方便的实现对业务或者通用UI组件的封装,以达到重复使用的目的。
Magicodes.WeiChat——自定义knockoutjs template、component实现微信自定义菜单的更多相关文章
- Magicodes.WeiChat——使用OAuth 2.0获取微信用户信息
使用Magicodes.WeiChat,可以很方便的获取到微信用户的信息.在使用OAuth 2.0之前,你先需要做以下操作: 1)在开发者中心修改[网页授权获取用户基本信息],在弹出的界面输入自己的根 ...
- Magcodes.WeiChat——自定义CustomCreationConverter之实现微信自定义菜单的序列化
微信自定义菜单接口是一个比较麻烦的接口,往往开发的小伙伴们看到下面的这段返回JSON,整个人就会不好了: {"menu":{"button":[{" ...
- Magicodes.WeiChat——版本发布历史
购买地址:https://item.taobao.com/item.htm?id=520205558575 您可以在新标签页打开此图,以查看原始图片. Magicodes.WeiChat为湖南心莱信息 ...
- Magicodes.WeiChat——WeChatOAuthTest(网页授权获取用户基本信息)
Demo访问地址:http://wechat.magicodes.net/app/AppDemo/WeChatOAuthTest?tenantId=1 关于公众号如何获取用户信息,请参考此文档:htt ...
- Magicodes.WeiChat——利用纷纭打造云日志频道
纷纭,是个免费的渠道集成工具.这里我就不多介绍了,右侧是飞机票:https://lesschat.com/ 在开发或者在运维情况下,我们经常需要查看并关注服务器端日志以确保程序是否健康运行.尤其是在微 ...
- Magicodes.WeiChat——V3.0(多租户)版本发布
主要内容如下: 添加项目Magicodes.WeiChat.Data.Multitenant,全面支持多租户(基于EF已经ASP.NET Identity) 增加租户管理.租户成员管理.修改密码.公众 ...
- Magicodes.WeiChat——ASP.NET Scaffolding生成增删改查、分页、搜索、删除确认、批量操作、批量删除等业务代码
关于T4代码生成这块,我之前写过几篇帖子,如:<Magicodes.NET框架之路——让代码再飞一会(ASP.NET Scaffolding)>(http://www.cnblogs.co ...
- Magicodes.WeiChat——后台JS框架封装
Magicodes.WeiChat同时也致力于提高后台开发效率,因此对在后台前端这块也做了一定的封装.我们先来说说主要的框架JS——mwc.js和mwc_elements.js.这两个JS文件位于Sc ...
- Magicodes.WeiChat——多租户的设计与实现
概要 多租户(Multi Tenancy/Tenant)是一种软件架构,其定义是:在一台服务器上运行单个应用实例,它为多个租户提供服务. 本框架使用的是共享数据库.共享 Schema.共享数据表的数据 ...
随机推荐
- CSS 块状元素和内联元素的详解
我们先来分析一下块级元素.内联级元素的定义和解析: 块元素(block element)一般是其他元素的容器元素,块元素一般都从新行开始,它可以容纳内联元素和其他块元素,常见块元素是段落标签'P&q ...
- mac上抓iphone数据包
iOS 5后,apple引入了RVI remote virtual interface的特性,它只需要将iOS设备使用USB数据线连接到mac上,然后使用rvictl工具以iOS设备的UDID为参数在 ...
- Docker常用操作
启动容器并安装package docker run xxx apt-get -y xxx 其中-y要加上避免无法交互 批量删除容器 docker ps -a | awk '{print $1}' |x ...
- 英语语法 It all started the summer before second grade when our moving van pulled into her neighborhood
It all started the summer before second grade when our moving van pulled into herneighborhood It all ...
- 7.Mybatis关联表查询(这里主要讲的是一对一和一对多的关联查询)
在Mybatis中的管理表查询这里主要介绍的是一对一和一对多的关联查询的resultMap的管理配置查询,当然你也可以用包装类来实现.不过这里不说,做关联查询的步骤可以简单的总结为以下的几步: 1.分 ...
- visual studio installer制作安装包——Installer 类
链接:https://msdn.microsoft.com/zh-cn/library/system.configuration.install.installer.aspx Installer 类 ...
- javascript 命令方式 测试例子
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 数据库记录转换成json格式 (2011-03-13 19:48:37) (转)
http://blog.sina.com.cn/s/blog_621768f30100r6v7.html 数据库记录转换成json格式 (2011-03-13 19:48:37) 转载▼ 标签: 杂谈 ...
- 关于hql一些不常见但好用的技巧(个人总结)
最近一直在用spring-data-jpa这个东西,感觉方法上注解hql语句已经是很常用的方法了, 有一些关于hql的经验分享一下: 一.hql的join hql的优势就是直接的关联关系嘛,但是通过h ...
- JavaScript求最大数最小数
<!DOCTYPE html><html><head lang="en"> <meta charset="UTF-8" ...