最早接触到knockoutJs应该是2017年初了。彼时只是了解了一下大概信息,写了一些简单的例子,没有怎么实际使用过。现在有机会再项目中实战一下,记录一下。
1.项目架构
前端:bootstrap和layer负责页面展示,knockoutJs负责mvvm,引入jquery.validate做表单校验,还有一些其他不怎么重要的,这里没有涉及到,不介绍
后端:ssm框架
2.功能介绍
做一个工作流管理的菜单,没有采用图像化的做法,直接做数据库的增删改查。包括:工作流信息、工作流节点信息、节点关联关系
3.引入knockoutJs
因为是后期功能,因此没有在项目全局引用,只在涉及到的功能中引用了
<%--knockoutJS,mvvm框架的前端部分,可以实现页面数据和js对象的相互映射--%>
<script type="text/javascript" src="${pageContext.request.contextPath}/home/js/knockout-3.4.2.js"></script>
<%--knockoutJS的一个帮助工具:将普通的json对象转为knocoutJS所需的对象--%>
<script type="text/javascript" src="${pageContext.request.contextPath}/home/js/knockout.mapping.js"></script>
knockout.mapping.js是一个knockout的帮助工具。后边会说明,使用时,必须放在knockoutJS之后
4.前端JSP
基本结构如下
每一个div对应js中的一个ViewModel
代码如下,其中涉及到knockout的标签是data-bind=“”
data-bind="click: $root.init" 表示给元素绑定点击事件$root.init,$root代表的是绑定到该div的viewmodel,后边讲解js代码的时候会提到
data-bind="textinput:queryFlowName" 表示input中的value对应ko监控的值
queryFlowName,textinpu是在form中可以输入的元素中使用的,如果只是显示的元素,如td,则用text(data-bind="text: flowName")
foreach循环如下:
<tbody id="linkTbody" data-bind="foreach: linkItems">
<tr>
<td data-bind="text: $index() + 1"></td>
<td>
<a href="#" data-bind="click : $root.delLink">删除</a>
<a href="#" data-bind="click : $root.updLink">修改</a>
</td>
<td data-bind="text: linkName"></td>
<td data-bind="text: nodeName"></td>
<td data-bind="text: nextNodeName"></td>
<td data-bind="text: conditionStatus"></td>
</tr>
</tbody>
其中data-bind="foreach: linkItems"表示要迭代的数组名称是viewmodel中的linkItems
data-bind="text: $index() + 1" 循环时会有一个默认的索引$index(),从0开始
后边的linkName、nodeName等都是linkItems中的对象中拥有的key
绑定多个事件
data-bind="event : {click : $root.trClick, mouseover : $root.trMouseOver, mouseout : $root.trMouseout}"
<%--knockoutJS,mvvm框架的前端部分,可以实现页面数据和js对象的相互映射--%>
<script type="text/javascript" src="${pageContext.request.contextPath}/home/js/knockout-3.4.2.js"></script>
<%--knockoutJS的一个帮助工具:将普通的json对象转为knocoutJS所需的对象--%>
<script type="text/javascript" src="${pageContext.request.contextPath}/home/js/knockout.mapping.js"></script>
<style>
#linkEditDiv td:nth-child(odd){
text-align: right;
}
#linkEditDiv td:nth-child(even){
text-align: left;
}
#linkEditDiv tr{
height:50px;
}
</style>
<%--流程列表--%>
<div class="row">
<div class="col-md-12" style="text-align:left" id='sysFlowManagePage'>
<!-- 按钮工具栏 -->
<ol class="breadcrumb" style="height:25px;padding-top:0;">
<li>
<a href="javascript:void(0);" data-bind="click: $root.init">
<span class="glyphicon glyphicon-search" style=" font-weight:bold;"></span> 搜索
</a>
</li>
<li>
<a href="javascript:void(0);" data-bind="click: $root.addFlow">
<span class="glyphicon glyphicon-plus" style=" font-weight:bold;"></span> 新增
</a>
</li>
</ol>
<form id="queryPage">
<table style="width:100%;" align="center">
<caption class="table-caption"></caption>
<tbody>
<tr>
<td class="query_td_right">流程名称:</td>
<td class="query_td_left">
<input type="text" class="txt query-text" name="flowName" data-bind="textinput:queryFlowName">
</td>
</tr>
</tbody>
</table>
</form>
<br/>
<!-- 查询条件table end-->
<!-- 列表 -->
<div class="table-responsive">
<table id="cpaAcctAppList" align="center" class="table table-striped table-bordered table-hover table-fix">
<thead>
<tr>
<th width="50">序号 </th>
<th width="70">操作 </th>
<th width="150">流程名称</th>
<th width="150">流程描述</th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td data-bind="text: $index() + 1"></td>
<td>
<a href="#" data-bind="click: $root.updFlow">修改</a>
<a href="#" data-bind="click: $root.delFlow">删除</a>
<a href="#" data-bind="click: $root.opeFlowLink">流程连接</a>
<a href="#" data-bind="click: $root.opeFlowNode">流程节点</a>
</td>
<td data-bind="text: flowName"></td>
<td data-bind="text: flowDesc"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<%--修改流程信息--%>
<div id="flowEditDiv" style="display: none;height:140px;" class="layer_notice">
<ol class="breadcrumb" style="height:25px;padding-top:0;text-align: left;">
<li>
<a data-bind="click : $root.ok" href="javascript:void(0);">
<span class="glyphicon glyphicon-search" style=" font-weight:bold;"></span> 保存
</a>
</li>
<li>
<a data-bind="click : $root.no" href="javascript:void(0);">
<span class="glyphicon glyphicon-search" style=" font-weight:bold;"></span> 取消
</a>
</li>
</ol>
<form role="form" id="updFlowForm">
<div style="margin-top: 20px">
<label>流程名称:</label><input data-bind="textinput:flowItem.flowName" type="text" name="roleName" style="width: 180px;"/>
</div>
<div style="padding:10px 0 10px 0">
<label>流程描述:</label><input data-bind="textinput:flowItem.flowDesc" type="text" name="roleDesc" style="width: 180px;"/>
</div>
</form>
</div>
<%--显示连接列表--%>
<div id="flowLinkDiv" style="display: none;height:300px;margin:0 auto;" class="layer_notice">
<ol class="breadcrumb" style="height:25px;padding-top:0;text-align: left;">
<li>
<a data-bind="click : $root.addLink" href="javascript:void(0);">
<span class="glyphicon glyphicon-search" style=" font-weight:bold;"></span> 新增
</a>
</li>
</ol>
<div class="table-responsive" style="width:800px;margin:10px auto;">
<table id="linkTable" align="center" class="table table-striped table-bordered table-hover table-fix">
<thead>
<tr>
<th width="50">序号</th>
<th width="70">操作</th>
<th width="150">连接名称</th>
<th width="150">节点名称</th>
<th width="150">下一个节点名称</th>
<th width="100">条件状态</th>
</tr>
</thead>
<tbody id="linkTbody" data-bind="foreach: linkItems">
<tr>
<td data-bind="text: $index() + 1"></td>
<td>
<a href="#" data-bind="click : $root.delLink">删除</a>
<a href="#" data-bind="click : $root.updLink">修改</a>
</td>
<td data-bind="text: linkName"></td>
<td data-bind="text: nodeName"></td>
<td data-bind="text: nextNodeName"></td>
<td data-bind="text: conditionStatus"></td>
</tr>
</tbody>
</table>
</div>
</div>
<%--修改节点信息--%>
<div id="nodeEditDiv" style="display: none;height:190px;" class="layer_notice">
<ol class="breadcrumb" style="height:25px;padding-top:0;text-align: left;">
<li>
<a data-bind="click : $root.ok" href="javascript:void(0);">
<span class="glyphicon glyphicon-search" style=" font-weight:bold;"></span> 保存
</a>
</li>
<li>
<a data-bind="click : $root.no" href="javascript:void(0);">
<span class="glyphicon glyphicon-search" style=" font-weight:bold;"></span> 取消
</a>
</li>
</ol>
<form role="form" id="updNodeForm">
<table style="margin: 15px 40px;">
<tr style="height: 50px;">
<td>节点名称:</td>
<td>
<input data-bind="textinput:nodeItem.nodeName" type="text" name="" style="width: 180px;" required/>
</td>
<td>所属流程:</td>
<td>
<input data-bind="textinput:nodeItem.flowName" type="text" name="flowName" style="width: 180px;" readonly required/>
<span class="glyphicon glyphicon-search" data-bind="click:$root.selFlow"/>
</td>
</tr>
<tr style="height: 50px;">
<td>节点描述:</td>
<td><input data-bind="textinput:nodeItem.nodeDesc" type="text" name="nodeDesc" style="width: 180px;"/></td>
<td>节点类型:</td>
<td>
<input data-bind="textinput:nodeItem.nodeType" type="text" name="_nodeType" style="width: 180px;" required/>
</td>
</tr>
</table>
</form>
</div>
<%--修改连接信息--%>
<div id="linkEditDiv" style="display: none;height:190px;;" class="layer_notice">
<ol class="breadcrumb" style="height:25px;padding-top:0;text-align: left;">
<li>
<a data-bind="click : $root.ok" href="javascript:void(0);">
<span class="glyphicon glyphicon-search" style=" font-weight:bold;"></span> 保存
</a>
</li>
<li>
<a data-bind="click : $root.no" href="javascript:void(0);">
<span class="glyphicon glyphicon-search" style=" font-weight:bold;"></span> 取消
</a>
</li>
</ol>
<form role="form" id="updLinkForm">
<table style="margin: 15px 40px;">
<tr>
<td>流转名称:</td>
<td><input data-bind="textinput:item.linkName" type="text" name="linkName" style="width: 180px;" required/></td>
<td>本节点名称:</td>
<td>
<input data-bind="textinput:item.nodeName" type="text" name="_nodeName" style="width: 180px;" readonly required/>
<span class="glyphicon glyphicon-search" data-bind="click:$root.selNode" desc="node"/>
</td>
</tr>
<tr>
<td>流转描述:</td>
<td><input data-bind="textinput:item.linkDesc" type="text" name="linkDesc" style="width: 180px;"/></td>
<td>下个节点名称:</td>
<td>
<input data-bind="textinput:item.nextNodeName" type="text" name="nextNodeName" style="width: 180px;" readonly required/>
<span class="glyphicon glyphicon-search" data-bind="click:$root.selNextNode" desc="nextNode"/>
</td>
</tr>
<tr>
<td>条件状态:</td>
<td>
<input data-bind="textinput:item.conditionStatus" type="text" name="conditionStatus" style="width: 180px;" required/>
</td>
<td></td>
<td></td>
</tr>
</table>
</form>
</div>
<%--选择节点--%>
<div id="selNode" style="display: none;height:50px;margin:0 auto;" class="layer_notice">
<ol class="breadcrumb" style="height:25px;padding-top:0;text-align: left;">
<li>
<a data-bind="click : $root.addNode" href="javascript:void(0);">
<span class="glyphicon glyphicon-search" style=" font-weight:bold;"></span> 新增
</a>
</li>
</ol>
<div class="table-responsive" style="width: 800px;margin: 10px auto;">
<table id="nodeTable" align="center" class="table table-striped table-bordered table-hover table-fix">
<thead>
<tr>
<th width="50">序号</th>
<th width="60">操作</th>
<th width="150">节点名称</th>
<th width="50">节点类型</th>
<th width="150">节点描述</th>
</tr>
</thead>
<tbody id="nodeTbody" data-bind="foreach: nodeItems">
<tr data-bind="event : {click : $root.trClick, mouseover : $root.trMouseOver, mouseout : $root.trMouseout}">
<td data-bind="text: $index() + 1"></td>
<td>
<a href="javascript:void(0)" data-bind="click : $root.updNode">修改</a>
<a href="javascript:void(0)" data-bind="click : $root.delNode">删除</a>
</td>
<td data-bind="text: nodeName"></td>
<td data-bind="text: nodeType"></td>
<td data-bind="text: nodeDesc"></td>
</tr>
</tbody>
</table>
</div>
</div>
<%--选择流程--%>
<div id="selFlow" style="display: none;height:50px;margin:0 auto;" class="layer_notice">
<div class="table-responsive" style="width: 800px;margin: 10px auto;">
<table id="flowTable" align="center" class="table table-striped table-bordered table-hover table-fix">
<thead>
<tr>
<th width="50">序号</th>
<%--<th width="60">操作</th>--%>
<th width="150">流程名称</th>
<th width="150">流程描述</th>
</tr>
</thead>
<tbody id="flowTbody" data-bind="foreach: flowItems">
<tr data-bind="event : {click : $root.trClick, mouseover : $root.trMouseOver, mouseout : $root.trMouseout}">
<td data-bind="text: $index() + 1"></td>
<%--<td>--%>
<%--<a href="javascript:void(0)" data-bind="click : $root.updNode">修改</a>--%>
<%--<a href="javascript:void(0)" data-bind="click : $root.delNode">删除</a>--%>
<%--</td>--%>
<td data-bind="text: flowName"></td>
<td data-bind="text: flowDesc"></td>
</tr>
</tbody>
</table>
</div>
</div>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/sys_flow/sys_flow.js"></script>
5.前端js
基本结构如下
代码如下
(function () {
var linkUpdLayer,selNodeLayer,selFlowLayer,flowUpdLayer,
flowList ,
baseUrl = contentPath + '/sysFlow/';
$(function () {
var vm = new TableViewModel();
ko.applyBindings(vm, $('#sysFlowManagePage')[0]);
vm.init();
});
/**
* 流程节点ViewModel
* @param linkItemKo 连接信息
* @param flowItemKo 流程信息
* @param t 类型
* @constructor
*/
function NodeListViewModel(linkItemKo,flowItemKo,t) {
var self = this;
self.linkItem = linkItemKo;
self.flowItem = flowItemKo;
self.nodeItems = ko.observableArray([]);
//是否是流程操作
self.isLink = (function () {
return !(linkItemKo === null || !linkItemKo);
})();
//初始化节点列表
self.initNodeTable = function () {
$.postAjax(baseUrl+'queryFlowNode',{flowId:self.isLink?linkItemKo.flowId():flowItemKo.flowId()},function (data) {
self.nodeItems(data);
});
};
//鼠标移入事件
self.trMouseOver = function (item,event) {
if(!self.isLink){
return;
}
trMouseOver($(event.target));
};
//鼠标移出事件
self.trMouseout = function(){
if(!self.isLink){
return;
}
trMouseOut($(event.target));
};
//鼠标点击事件
self.trClick = function (item) {
if(!self.isLink){
return;
}
switch (t){
case 1:
self.linkItem.nodeName(item.nodeName);
self.linkItem.nodeId(item.nodeId);
break;
case 2:
self.linkItem.nextNodeName(item.nodeName);
self.linkItem.nextNodeId(item.nodeId);
break;
}
if(selNodeLayer){
layer.close(selNodeLayer);
}
};
self.addNode = function () {
var newNodeItem = {
flowId: '',flowName : '',
nodeName: '', nodeDesc: '',nodeType:''
};
var nodeItemKo = new NodeEditViewModel(ko.mapping.fromJS(newNodeItem),self);
nodeOpenCommon('新增节点信息',nodeItemKo);
};
//删除节点
self.delNode = function(item){
if($.isEmpty(item.nodeId)){
layer.alert('发生错误!');
return;
}
var con = layer.confirm('确定删除该记录吗?', {
btn: ['确定','取消'] //按钮
}, function(){
layer.close(con);
$.postAjax(baseUrl+'delNode',{nodeId : item.nodeId},function (state) {
if(state.success){
layer.msg('删除成功!');
self.initNodeTable();
}else{
layer.alert('删除失败!失败原因:' + state.message);
}
});
}, function(){});
};
//修改节点
self.updNode = function (item) {
if(flowList){
for(var i=0;i<flowList.length;i++){
if(item.flowId === flowList[i].flowId){
item.flowName = flowList[i].flowName;
break;
}
}
}
var updNodeVm = new NodeEditViewModel(ko.mapping.fromJS(item),self);
nodeOpenCommon('修改节点信息',updNodeVm);
};
/**
* 节点信息修改ViewModel
* @param nodeItemKo
* @param nodeTableViewModel
* @constructor
*/
function NodeEditViewModel(nodeItemKo,nodeTableViewModel) {
var nodeSelf = this;
nodeSelf.nodeItem = nodeItemKo;
//取消
nodeSelf.no = function () {
closeNodeOpenCommon();
};
//选择流程
nodeSelf.selFlow = function (item) {
var selFlow = document.getElementById('selFlow'),
$flowTbody = $('#flowTbody'),
flowTbodyHtml = $flowTbody.html(),
flowListViewModel = new FlowListViewModel(ko.mapping.fromJS(item));
selFlowLayer = layer.open({
title: '选择流程',
type: 1,
content: $(selFlow),
area: ['880px', '460px'],
success : function () {
ko.cleanNode(selFlow);
ko.applyBindings(flowListViewModel, selFlow);
flowListViewModel.initFlowTable();
},
end : function () {
$flowTbody.html(flowTbodyHtml);
$(selFlow).hide();
}
});
};
/**
* 选择流程
* @constructor
*/
function FlowListViewModel(nodeItemKo) {
var self = this;
self.nodeItem = nodeItemKo;
self.flowItems = ko.observableArray([]);
//初始化列表
self.initFlowTable = function () {
$.postAjax(baseUrl + 'queryFlow', null, function (data) {
self.flowItems(data);
});
};
//鼠标移入事件
self.trMouseOver = function () {
trMouseOver($(event.target));
};
//鼠标移出事件
self.trMouseout = function(){
trMouseOut($(event.target));
};
//鼠标点击事件
self.trClick = function (item) {
self.nodeItem.nodeItem.flowName(item.flowName);
self.nodeItem.nodeItem.flowId(item.flowId);
if(selFlowLayer){
layer.close(selFlowLayer);
}
};
}
//校验并保存
nodeSelf.ok = function (item) {
var validateOptions = Validation;
validateOptions.validateOptions = true;
validateOptions.messages = {
_nodeName: '请输入流转名称',
_nodeType : '请输入节点类型',
flowName : '请选择所属流程'
};
validateOptions.errorPlacement = function (label, element) {};
validateOptions.submitHandler=function (form) {};
var $updNodeForm = $('#updNodeForm');
$updNodeForm.validate(validateOptions);
var is = $updNodeForm.valid();
if(!is){
layer.alert('信息不完整');
return;
}
$.postAjax(baseUrl+'saveOrUpdFlowNode',ko.mapping.toJS(item.nodeItem),function (state) {
if(state.success){
layer.alert('保存成功!');
closeNodeOpenCommon();
nodeTableViewModel.initNodeTable();
}else{
layer.alert('保存失败!');
}
});
};
}
// nodeEditDiv
var nodeLayer,
nodeEditDiv = document.getElementById('nodeEditDiv');
/**
* 关闭nodeLayer
*/
function closeNodeOpenCommon() {
if(nodeLayer){
layer.close(nodeLayer);
}
}
/**
* 新增和修改节点信息的公用layer
* @param title
* @param nodeItemKo
*/
function nodeOpenCommon(title,nodeItemKo) {
nodeLayer = layer.open({
title: title,
type: 1,
content: $(nodeEditDiv),
area: ['600px', '250px'],
success : function () {
ko.cleanNode(nodeEditDiv);
ko.applyBindings(nodeItemKo, nodeEditDiv);
},
end : function () {
$(nodeEditDiv).hide();
}
});
}
}
/**
* 修改Link信息
* @constructor
*/
function LinkEditViewModel(linkItem,linkTableViewModel) {
var self = this;
self.item = ko.mapping.fromJS(linkItem);
//选择本节点
self.selNode = function () {
selNodeCommon(1,self.item,null,'选择本节点');
};
//选择下一个节点
self.selNextNode = function () {
selNodeCommon(2,self.item,null,'选择下一个节点');
};
//取消
self.no = function () {
closeLinkUpdLayer();
};
//保存
self.ok = function () {
var validateOptions = Validation;
validateOptions.validateOptions = true;
validateOptions.messages = {
linkName: '请输入流转名称',
conditionStatus : '请输入条件状态',
_nodeName : '请选择本节点名称',
nextNodeName : '请选择下一个节点名称'
};
validateOptions.errorPlacement = function (label, element) {};
validateOptions.submitHandler=function (form) {};
var $updLinkForm = $('#updLinkForm');
$updLinkForm.validate(validateOptions);
var is = $updLinkForm.valid();
if(!is){
layer.alert('信息不完整');
return;
}
$.postAjax(baseUrl+'saveOrUpdFlowLink',ko.mapping.toJS(self.item),function (state) {
if(state.success){
layer.alert('保存成功!');
closeLinkUpdLayer();
linkTableViewModel.initLinkTable();
}else{
layer.alert('保存失败!');
}
});
};
//关闭layer弹出框
function closeLinkUpdLayer() {
if(linkUpdLayer){
layer.close(linkUpdLayer);
}
}
}
/**
* 鼠标移入事件
*/
function trMouseOver() {
var $target = $(event.target);
if($target.is('td')){
$target = $target.parent();
}
$target.css('background-color','#CCFF80');
$target.css('cursor','pointer');
}
/**
* 鼠标移除事件
*/
function trMouseOut() {
var $target = $(event.target);
if($target.is('td')){
$target = $target.parent();
}
$target.css('background-color','white');
$target.css('cursor','pointer');
}
/**
* 流程节点
* @param t
* @param linkItemKo
* @param flowItemKo
* @param title
*/
function selNodeCommon(t,linkItemKo,flowItemKo,title) {
var selNode = document.getElementById('selNode'),
$nodeTbody = $('#nodeTbody'),
nodeTbodyHtml = $nodeTbody.html(),
nodeListViewModel = new NodeListViewModel(linkItemKo,flowItemKo,t);
selNodeLayer = layer.open({
title: title,
type: 1,
content: $(selNode),
area: ['880px', '460px'],
success : function () {
ko.cleanNode(selNode);
ko.applyBindings(nodeListViewModel, selNode);
nodeListViewModel.initNodeTable();
},
end : function () {
$nodeTbody.html(nodeTbodyHtml);
$(selNode).hide();
}
});
}
/**
* 查看流程Link信息的ViewModel
* @param flowItem
* @constructor
*/
function LinkTableViewModel(flowItem){
var self = this;
var linkEditDiv = document.getElementById('linkEditDiv'),flowId = $.trim(flowItem.flowId);
self.linkItems = ko.observableArray([]);
//初始化列表
self.initLinkTable = function () {
$.postAjax(baseUrl + 'selectLinkByFlowId', {flowId : flowId}, function (data) {
self.linkItems(data);
});
};
//新增
self.addLink = function(){
var newItem = {
flowId: flowId,
linkName: '', linkDesc: '',
nodeId: '', nodeName: '',
nextNodeId: '', nextNodeName: '',
conditionStatus: ''
};
var updLinkVm = new LinkEditViewModel(ko.mapping.fromJS(newItem),self);
linkOpenCommon('新增连接信息',updLinkVm);
};
//修改
self.updLink = function (item) {
var updLinkVm = new LinkEditViewModel(item,self);
linkOpenCommon('修改连接信息',updLinkVm);
};
//删除
self.delLink = function (item) {
if($.isEmpty(item.linkId)){
layer.alert('发生错误!');
return;
}
var con = layer.confirm('确定删除该记录吗?', {
btn: ['确定','取消'] //按钮
}, function(){
layer.close(con);
$.postAjax(baseUrl+'delLink',{linkId : item.linkId},function (state) {
if(state.success){
layer.msg('删除成功!');
self.initLinkTable();
}else{
layer.alert('删除失败!失败原因:' + state.message);
}
});
}, function(){});
};
/**
* link的layer弹出框公用部分
* @param title layer弹出框的标题
* @param updLinkVm
*/
function linkOpenCommon(title,updLinkVm) {
linkUpdLayer = layer.open({
title: title,
type: 1,
content: $(linkEditDiv),
area: ['600px', '250px'],
success : function () {
ko.cleanNode(linkEditDiv);//取消绑定
ko.applyBindings(updLinkVm, linkEditDiv);//绑定
},
end :function () {
$(linkEditDiv).hide();
}
});
}
}
/**
* 列表页的ViewModel
* @constructor
*/
function TableViewModel() {
var self = this,
flowEditDiv = document.getElementById('flowEditDiv');
self.items = ko.observableArray([]);
self.queryFlowName = ko.observable('');
self.delFlow = function (item) {
if($.isEmpty(item.flowId)){
layer.alert('发生错误!');
return;
}
//TODO:注意,此处尚未做级联删除
var con = layer.confirm('如果删除该记录,该记录下的节点信息将同时删除.<br>确定删除该记录吗?', {
btn: ['确定','取消'] //按钮
}, function(){
layer.close(con);
$.postAjax(baseUrl+'delFlow',{flowId : item.flowId},function (state) {
if(state.success){
layer.msg('删除成功!');
self.init();
}else{
layer.alert('删除失败!失败原因:' + state.message);
}
});
}, function(){});
};
//新增
self.addFlow = function () {
var addVm = ko.mapping.fromJS({flowName:'',flowDesc:''});
flowOpenCommon(flowEditDiv,addVm,'新增工作流信息');
};
//修改
self.updFlow = function (item) {
var updVm = ko.mapping.fromJS(item);
flowOpenCommon(flowEditDiv,updVm,'修改工作流信息');
};
function flowOpenCommon(flowEditDiv,updVm,title) {
flowUpdLayer = layer.open({
title: title,
type: 1,
content: $(flowEditDiv),
area: ['320px', '215px'],
success:function () {
ko.cleanNode(flowEditDiv);//取消绑定
var flowEditViewModel = new FlowEditViewModel(updVm);
ko.applyBindings(flowEditViewModel, flowEditDiv);//绑定
},
end:function () {
$(flowEditDiv).hide();
}
});
}
//初始化列表
self.init = function () {
$.postAjax(baseUrl + 'queryFlow', {flowName: $.trim(self.queryFlowName())}, function (data) {
flowList = data;
self.items(data);
});
};
//流程节点
self.opeFlowNode = function (item) {
selNodeCommon(null,null,ko.mapping.fromJS(item),'流程节点信息');
};
function FlowEditViewModel(flowItemKo) {
var flowSelf = this;
flowSelf.flowItem = flowItemKo;
flowSelf.ok = function () {
var validateOptions = Validation;
validateOptions.validateOptions = true;
validateOptions.messages = {
flowName: '请输入工作流名称'
};
validateOptions.errorPlacement = function (label, element) {};
validateOptions.submitHandler=function (form) {};
var $updFlowForm = $('#updFlowForm');
$updFlowForm.validate(validateOptions);
var is = $updFlowForm.valid();
if(!is){
layer.alert('信息不完整');
return;
}
$.postAjax(baseUrl+'saveOrUpdFlow',ko.mapping.toJS(flowSelf.flowItem),function (state) {
if(state.success){
layer.alert('保存成功!');
closeFlowLayer();
self.init();
}else{
layer.alert('保存失败!');
}
});
};
flowSelf.no = function () {
closeFlowLayer();
};
function closeFlowLayer() {
if(flowUpdLayer){
layer.close(flowUpdLayer);
}
}
}
//流程连接
self.opeFlowLink = function (item) {
//将tbody缓存起来,解决数据重复问题
var $linkTbody = $('#linkTbody'),
tbody = $linkTbody.html(),
flowLinkDiv = document.getElementById('flowLinkDiv');
linkUpdLayer = layer.open({
title: '工作流连接信息',
type: 1,
content: $(flowLinkDiv),
area: ['880px', '450px'],
success : function(){
var linkTableViewModel = new LinkTableViewModel(item);
ko.cleanNode(flowLinkDiv);//取消绑定
ko.applyBindings(linkTableViewModel, flowLinkDiv);
linkTableViewModel.initLinkTable();
},
end:function () {
$(flowLinkDiv).hide();
$linkTbody.html(tbody);
}
});
}
})();
关键代码说明
/**
* 声明一个TableViewModel
*/
function TableViewModel() {
var self = this,
flowEditDiv = document.getElementById('flowEditDiv');
//监控对象是一个数组
self.items = ko.observableArray([]);
//监控对象是一个字符串
self.queryFlowName = ko.observable('');
self.delFlow = function (item) {
if($.isEmpty(item.flowId)){
layer.alert('发生错误!');
return;
}
var con = layer.confirm('如果删除该记录,该记录下的节点信息将同时删除.<br>确定删除该记录吗?', {
btn: ['确定','取消'] //按钮
}, function(){
layer.close(con);
$.postAjax(baseUrl+'delFlow',{flowId : item.flowId},function (state) {
if(state.success){
layer.msg('删除成功!');
self.init();
}else{
layer.alert('删除失败!失败原因:' + state.message);
}
});
}, function(){});
};
//新增
self.addFlow = function () {
//注意这里的ko.mapping.fromJS,刚开始的时候,我们不是提到了一个ko.mapping.js吗?这里就是其中的一个功能
//ko监控的对象结构如下:
//{key:ko.observable(value)}
//也就是需要ko.observable声明一下,如果需要监控的对象键值对比较多,一个个转就太麻烦了
//使用ko.mapping.fromJS就可以直接转换了
//另外,ko.mapping.toJS则可以反过来将一个ko监控对象转为正常的json对象
var addVm = ko.mapping.fromJS({flowName:'',flowDesc:''});
flowOpenCommon(flowEditDiv,addVm,'新增工作流信息');
};
//修改
self.updFlow = function (item) {
var updVm = ko.mapping.fromJS(item);
flowOpenCommon(flowEditDiv,updVm,'修改工作流信息');
};
function flowOpenCommon(flowEditDiv,updVm,title) {
flowUpdLayer = layer.open({
title: title,
type: 1,
content: $(flowEditDiv),
area: ['320px', '215px'],
success:function () {
ko.cleanNode(flowEditDiv);//取消绑定
var flowEditViewModel = new FlowEditViewModel(updVm);
ko.applyBindings(flowEditViewModel, flowEditDiv);//绑定
},
end:function () {
$(flowEditDiv).hide();
}
});
}
//初始化列表
self.init = function () {
$.postAjax(baseUrl + 'queryFlow', {flowName: $.trim(self.queryFlowName())}, function (data) {
flowList = data;
//给ko监控对象赋值,不能使用=,
//因为ko监控对象的结构并不是简单的json对象格式
//需要使用()赋值,如下
self.items(data);
});
};
//流程节点
self.opeFlowNode = function (item) {
selNodeCommon(null,null,ko.mapping.fromJS(item),'流程节点信息');
};
function FlowEditViewModel(flowItemKo) {
var flowSelf = this;
flowSelf.flowItem = flowItemKo;
flowSelf.ok = function () {
var validateOptions = Validation;
validateOptions.validateOptions = true;
validateOptions.messages = {
flowName: '请输入工作流名称'
};
validateOptions.errorPlacement = function (label, element) {};
validateOptions.submitHandler=function (form) {};
var $updFlowForm = $('#updFlowForm');
$updFlowForm.validate(validateOptions);
var is = $updFlowForm.valid();
if(!is){
layer.alert('信息不完整');
return;
}
$.postAjax(baseUrl+'saveOrUpdFlow',ko.mapping.toJS(flowSelf.flowItem),function (state) {
if(state.success){
layer.alert('保存成功!');
closeFlowLayer();
self.init();
}else{
layer.alert('保存失败!');
}
});
};
flowSelf.no = function () {
closeFlowLayer();
};
function closeFlowLayer() {
if(flowUpdLayer){
layer.close(flowUpdLayer);
}
}
}
//流程连接
self.opeFlowLink = function (item) {
//将tbody缓存起来,解决数据重复问题
var $linkTbody = $('#linkTbody'),
tbody = $linkTbody.html(),
flowLinkDiv = document.getElementById('flowLinkDiv');
linkUpdLayer = layer.open({
title: '工作流连接信息',
type: 1,
content: $(flowLinkDiv),
area: ['880px', '450px'],
success : function(){
var linkTableViewModel = new LinkTableViewModel(item);
ko.cleanNode(flowLinkDiv);//取消绑定
ko.applyBindings(linkTableViewModel, flowLinkDiv);
linkTableViewModel.initLinkTable();
},
end:function () {
$(flowLinkDiv).hide();
$linkTbody.html(tbody);
}
});
}
//上边提到的$root指的既是这里new出来的TableViewModel
//$('#sysFlowManagePage')[0]要绑定的元素,也是TableViewModel的作用域,也就是说在$('#sysFlowManagePage')[0])中出现的data-bind归TableViewModel管
//注意,不能重复绑定,如果需要清除,使用ko.cleanNode($('#sysFlowManagePage')[0]);清除绑定
$(function () {
var vm = new TableViewModel();
ko.applyBindings(vm, $('#sysFlowManagePage')[0]);
vm.init();
});
6.后端接口
结构
比较简单,查出需要的数据返回前端,前端需要保存删除的数据后端删除等
7.注意点
ko监控对象的结构不再是简单的json结构,这也是引入ko.mapping的原因
注意重复绑定的问题,如果ko.applyBindings没有指定绑定作用域的话,默认是当前的document