首页 > 代码库 > EasyUI+Knockout实现经典表单的查看、编辑
EasyUI+Knockout实现经典表单的查看、编辑
此文章是基于
1. 搭建SpringMVC+Spring+Hibernate平台
2. 自制xml实现SQL动态参数配置
3. 利用DetachedCriteria构建HQL参数动态匹配
4. 常用日期处理方法工具类
5. 常用类型转换方法工具类
一. 准备工作
注:子项目 mms 全称:material management system;子项目 sys :基础信息、权限管理系统
1. 点击此下载 ims.rar,解压缩并把文件放到 ims 工程对应的文件夹下
二. 前端框架特色
1. js库为 jquery-1.8.1.min.js
2. UI选用 jquery-easyui-1.3.2
3. 封装了一大部分比较实用的控件和组件,如自动完成控件、弹出控件、日期控件等
4. 选 knockoutjs 为前端mvvm框架
采用Knockout,提供了一个数据模型与用户UI界面进行关联的高层次方式(采用行为驱动开发)
5. css框架选用 960gs、sexybuttons、css3 btn
三. 相关文件介绍
1. jquery.easyui.fix.js
/** * 模块名:easyui方法修改 * 程序名: easyui.fix.js **/ var easyuifix = {}; /** * for easyloader.js * add after row 13 usage: easyuifix.addLoadModules(_1); */ easyuifix.easyloader_addLoadModules = function (modules) { $.extend(modules, { juidatepick: { js: rootPath+"/content/js/jquery-plugin/jquery-ui/js/jquery-ui-datepick.min.js", css: rootPath+"/content/js/jquery-plugin/jquery-ui/css/jquery-ui.css" }, daterange: { js: rootPath+"/content/js/jquery-plugin/daterange/jquery.daterange.js", css: rootPath+"/content/js/jquery-plugin/daterange/jquery.daterange.css", dependencies: ["juidatepick"] }, lookup: { js: rootPath+"/content/js/jquery-plugin/lookup/jquery.lookup.js", dependencies: ["combo"] }, autocomplete: { js: rootPath+"/content/js/jquery-plugin/autocomplete/jquery.autocomplete.js", dependencies: ["combo"] } }); }; /** * for easyloader.js * add after row 181 usage: easyuifix.easyloader_setting(easyloader, src); */ easyuifix.easyloader_setting = function (easyloader,src) { easyloader.theme = "default"; easyloader.locale = "zh_CN"; }; /** * for jquery.parser.js * add after row 89 usage: easyuifix.parser_addplugins($.parser.plugins); */ easyuifix.parser_addplugins = function (plugins) { var arr = ["daterange", "lookup", "autocomplete"]; for (var i in arr) plugins.push(arr[i]); }; /** * for jquery.tabs.js * add after row 415 usage: easyuifix.tabs_showtabonselect(_5d); */ easyuifix.tabs_showtabonselect =function(tab){ tab.show(); //打开时其它页签先隐藏,,提升用户体验,点击时再显示 } /** * for easyui-lang-zh_CN.js * add last row: easyuifix.lang_zh_CN(); */ easyuifix.lang_zh_CN = function () { if ($.fn.lookup) { $.fn.lookup.defaults.missingMessage = ‘该输入项为必输项‘; } }; /** * for jquery.datagrid.js * add after row 1137 usage: easyuifix.datagrid_beforeDestroyEditor(_10a, _10b, row); * for jquery.easyui.min.js * add after row 7622 usage: easyuifix.datagrid_beforeDestroyEditor(_530, _531, row); */ easyuifix.datagrid_beforeDestroyEditor = function (jq, rowIndex, row) { var opts = $.data(jq, "datagrid").options; if (opts.OnBeforeDestroyEditor) { var editors = {}, list = $(jq).datagrid(‘getEditors‘, rowIndex) || []; $.each(list, function () { editors[this.field] = this; }); opts.OnBeforeDestroyEditor(editors, row, rowIndex, jq); } }; /** * for jquery.datagrid.js * add after row 1101 usage: easyuifix.datagrid_afterCreateEditor(_104, _105, row); * for jquery.easyui.min.js * add after row 7586 usage: easyuifix.datagrid_afterCreateEditor(_52a, _52b, row); */ easyuifix.datagrid_afterCreateEditor = function (jq, rowIndex, row) { var opts = $.data(jq, "datagrid").options; if (opts.OnAfterCreateEditor) { var editors = {}, list = $(jq).datagrid(‘getEditors‘, rowIndex) || []; $.each(list, function () { editors[this.field] = this; }); opts.OnAfterCreateEditor(editors, row, rowIndex, jq); } }; /** * for jquery.combo.js to convert disable to readonly * add after row 210 usage: easyuifix.combo_disableToReadonly(_32, _33); */ easyuifix.combo_disableToReadonly = function (jq, b) { var options = $.data(jq, "combo").options; var combo = $.data(jq, "combo").combo; if (b) { options.disabled = true; $(jq).attr("readonly", true); combo.find(".combo-value").attr("readonly", true).addClass("readonly"); combo.find(".combo-text").attr("readonly", true).addClass("readonly"); } else { options.disabled = false; $(jq).removeAttr("readonly"); combo.find(".combo-value").removeAttr("readonly").removeClass("readonly"); combo.find(".combo-text").removeAttr("readonly").removeClass("readonly"); } }; /** * for jquery.combobox.js to showblank * add after row 138 usage: easyuifix.combobox_showblank(_30, _2e); */ easyuifix.combobox_showblank = function (combobox, b) { if (combobox.showblank) { b.unshift(combobox.blankdefault); } } /** * for jquery.combobox.js to showblank * add after row 350 usage: easyuifix.combobox_defaultblank(); */ easyuifix.combobox_defaultblank = function () { $.fn.combobox.defaults = $.extend($.fn.combobox.defaults, { showblank: false, blankdefault: { value: ‘‘, text: ‘== 请选择 ==‘ } }); }
easyUI方法修改,改善方法,提升用户体验;注释中提示方法作用的位置
2. knockout.bindings.js
/// <reference path="knockout-3.4.1.js" /> /** * 程序名: knockout自定义绑定 **/ (function ($, ko) { var jqElement = function (element) { var jq = $(element); if ($(document).find(element).length == 0) { //处理元素在父页面执行的情况 if ($(parent.document).find(element).length > 0) jq = parent.$(element); } return jq; }; /** * value binding * 例如:comboboxValue、lookupValue、combotreeValue */ ko.creatEasyuiValueBindings = function (o) { o = $.extend({ type: ‘‘, event: ‘‘, getter: ‘getValue‘, setter: ‘setValue‘, fix: $.noop,formatter: function (v) { return v; }}, o); var customBinding = { init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { var jq = jqElement(element), handler = jq[o.type](‘options‘)[o.event], opt = {}; //handle the field changing opt[o.event] = function () { handler.apply(element, arguments); var value =http://www.mamicode.com/ jq[o.type](o.getter); if (valueAccessor() == null) throw "viewModel中没有页面绑定的字段"; valueAccessor()(value); }; //handle disposal (if KO removes by the template binding) ko.utils.domNodeDisposal.addDisposeCallback(element, function () { jq[o.type]("destroy"); }); o.fix(element, valueAccessor); jq[o.type](opt); }, update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { value = ko.utils.unwrapObservable(valueAccessor()); jqElement(element)[o.type](o.setter, o.formatter(value)); } }; ko.bindingHandlers[o.type + ‘Value‘] = customBinding; }; ko.creatEasyuiValueBindings({ type: ‘combobox‘, event: ‘onSelect‘ }); ko.creatEasyuiValueBindings({ type: ‘combotree‘, event: ‘onChange‘ }); ko.creatEasyuiValueBindings({ type: ‘datebox‘ , event: ‘onSelect‘ , formatter: com.formatDate }); ko.creatEasyuiValueBindings({ type: ‘lookup‘ , event: ‘onChange‘ }); ko.creatEasyuiValueBindings({ type: ‘numberbox‘ , event: ‘onChange‘ }); /* * enable、disable binding * 例如:linkbuttonEnable、linkbuttonDisable */ ko.creatEasyuiEnableBindings = function (o) { o = $.extend({ type: ‘‘,method:‘Enable‘, options:function(v){return ‘disabled‘;}, value: function (v) { return !v; } }, o); var customBinding = { update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { var enable = ko.toJS(valueAccessor()); $(element)[o.type](o.options(enable), o.value(enable)); } }; ko.bindingHandlers[o.type + o.method] = customBinding; }; ko.creatEasyuiEnableBindings({ type: ‘linkbutton‘, options: function (b) { return {disabled:!b}; } }); ko.creatEasyuiEnableBindings({ type: ‘linkbutton‘,method:‘Disable‘, options: function (b) { return { disabled: b }; } }); _readOnlyHandles = {}; _readOnlyHandles.defaults = function (obj, b) { b ? obj.addClass("readonly").attr("readonly", true) : obj.removeClass("readonly").removeAttr("readonly"); }; _readOnlyHandles.combo = function (obj, b) { obj.combo(b ? "disable" : "enable"); }; /** * readonly binding * 例如:numberboxReadOnly、comboboxReadOnly,或者直接readOnly */ ko.creatEasyuiReadOnlyBindings = function (o) { o = $.extend({ type: ‘‘, handler: _readOnlyHandles.defaults }, o); var customBinding = { update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { o.handler($(element),ko.toJS(valueAccessor())); } }; ko.bindingHandlers[o.type ? (o.type + ‘ReadOnly‘): ‘readOnly‘] = customBinding; }; ko.creatEasyuiReadOnlyBindings(); //default readonly ko.creatEasyuiReadOnlyBindings({ type: ‘numberbox‘ }); ko.creatEasyuiReadOnlyBindings({ type: ‘combobox‘, handler: _readOnlyHandles.combo }); ko.creatEasyuiReadOnlyBindings({ type: ‘datebox‘, handler: _readOnlyHandles.combo }); ko.creatEasyuiReadOnlyBindings({ type: ‘combotree‘, handler: _readOnlyHandles.combo }); ko.creatEasyuiReadOnlyBindings({ type: ‘lookup‘, handler: _readOnlyHandles.combo }); /** * datasource bingding */ ko.bindingHandlers.datasource = { init: function (element, valueAccessor) { var jq = jqElement(element); var ds = ko.toJS(valueAccessor()); if ($.isFunction(ds)) ds = ds.call(); if (jq.data(‘treegrid‘)) jq.treegrid(‘loadData‘, ds); else if (jq.data(‘datagrid‘)) jq.datagrid(‘loadData‘, ds); else if (jq.data(‘combotree‘)) jq.combotree(‘loadData‘, ds); else if (jq.data(‘combobox‘)) { var val = jq.combobox(‘getValue‘), ds = ds.rows || ds; jq.combobox(‘clear‘).combobox(‘loadData‘,ds).combobox(‘setValue‘, val); } else if (jq.data(‘tree‘)) jq.tree(‘loadData‘, ds.rows || ds); } }; /** * grid binding * datagrid、treegrid */ ko.creatEasyuiGridBindings = function (gridType) { var gridBinding = { update: function (element, valueAccessor, allBindingsAccessor, viewModel) { var grid = jqElement(element), opts = ko.toJS(valueAccessor()), url = opts.url; var customLoader = function (queryParams) { if (opts.pagination) { var paging = grid.datagrid(‘getPager‘).pagination(‘options‘); queryParams = $.extend(queryParams, { page: paging.pageNumber, rows: paging.pageSize }); } com.ajax({ type: ‘get‘, url: url, data: queryParams, success: function (d) { if ($.isFunction(opts.afterCustomSuccess)) opts.afterCustomSuccess(d); grid[gridType](‘loadData‘, d); } }); }; if (opts.treeField && opts.parentField) { //only for treegrid opts.loadFilter = function (data) { return utils.toTreeData(data.rows || data, opts.idField||opts.treeField, opts.parentField, "children"); }; } var handler = function () { if (grid.data(gridType)) { if (opts.customLoad) customLoader(opts.queryParams); else grid[gridType](opts); } else { var defaults = { iconCls: ‘icon icon-list‘, nowrap: true, //折行 rownumbers: true, //行号 striped: true, //隔行变色 singleSelect: true, //单选 remoteSort: true, //后台排序 pagination: false, //翻页 pageSize: 20, contentType: "application/json", method: "POST" }; var winsize = function (size) { var ret = {}; if (size && size.w) ret.width = jqElement(window).width() - size.w; if (size && size.h) ret.height = jqElement(window).height() - size.h; return ret; }; opts = $.extend(defaults, opts, winsize(opts.size)); if (opts.customLoad) { if (opts.remoteSort) opts.onSortColumn = function (sort, order) { customLoader($.extend(opts.queryParams, { sort: sort, order: order })); }; grid[gridType](opts); customLoader(opts.queryParams); if (opts.pagination) grid[gridType](‘getPager‘).pagination({ onSelectPage: customLoader }); } else grid[gridType](opts); valueAccessor()[gridType] = function () { return grid[gridType].apply(grid, arguments); } valueAccessor()[‘$element‘] = function () { return grid; }; if (opts.size) jqElement(window).resize(function () { grid[gridType](‘resize‘, winsize(opts.size)); }); } }; grid[gridType] ? handler() : using([gridType], function () { handler(); if (gridType==‘treegrid‘) com.loadCss(‘/content/images/icon/icon.css‘, document, true);//解决图标被tree.css覆盖的问题 }); } }; return gridBinding; }; ko.bindingHandlers.datagrid = ko.creatEasyuiGridBindings(‘datagrid‘); ko.bindingHandlers.treegrid = ko.creatEasyuiGridBindings(‘treegrid‘); /** * easyui binding * 例如:easyuiCombotree、easyuiCombobox、easyuiLinkbutton */ ko.createEasyuiInitBindings = function (o) { o = $.extend({ type: ‘‘ },o); var customBinding = { init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { var jq = jqElement(element), opt = ko.mapping.toJS(valueAccessor()); var handler = function () { jq[o.type](opt); }; jq[o.type] ? handler() : using(o.type, handler); //handle disposal (if KO removes by the template binding) ko.utils.domNodeDisposal.addDisposeCallback(element, function () { jq[o.type]("destroy"); }); valueAccessor()["$element"] = function () { return jq }; }, update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { var jq = jqElement(element), opt = ko.mapping.toJS(valueAccessor()); if (jq[o.type]) jq[o.type](opt); } }; var bindName = ‘easyui‘ + o.type.replace(/(^|\s+)\w/g, function (s) {return s.toUpperCase();}); ko.bindingHandlers[bindName] = customBinding; }; ko.createEasyuiInitBindings({ type: ‘combotree‘ }); ko.createEasyuiInitBindings({ type: ‘combobox‘ }); ko.createEasyuiInitBindings({ type: ‘linkbutton‘ }); /** * inputwidth、autoheight、autowidth binding */ ko.bindingHandlers.inputwidth = { update: function (element, valueAccessor, allBindingsAccessor, viewModel) { var that = $(element), widthStr = ko.toJS(valueAccessor()); var calcWidth = function (w) { var rate = 1 if (typeof w == ‘string‘) { if (w.indexOf("px") > -1 || w.indexOf("PX") > -1) w = w.replace("px", "").replace("PX", ""); if (w.indexOf("%") > -1) w = w.replace("%", "") / 100; } if (w > 0 && w <= 1) { rate = w; w = $(".val").width(); } return w * rate; }; var resizeWidth = function () { var width = calcWidth(widthStr); $.each([ { selector: ‘input.txtBox‘, handler: function (jq) { jq.width(width - 8); } }, //pading3*2 + border1*2 = 8 { selector: ‘input[comboname],input.combo-f‘, handler: function (jq) { jq.combo(‘resize‘, width); } } ], function () { var jq = that.find(this.selector); if (jq.length > 0) this.handler(jq); if (that.is(this.selector)) this.handler(that); }); }; resizeWidth(); $(window).resize(resizeWidth); } }; ko.bindingHandlers.autoheight = { update: function (element, valueAccessor, allBindingsAccessor, viewModel) { var that = $(element), obserable = valueAccessor(), height = $.isFunction(obserable) ? obserable() : obserable; that.height($(window).height() - height); $(window).resize(function () { that.height($(window).height() - height); }); } }; ko.bindingHandlers.autowidth = { update: function (element, valueAccessor, allBindingsAccessor, viewModel) { var that = $(element), obserable = valueAccessor(), width = $.isFunction(obserable) ? obserable() : obserable; that.width($(window).width() - width); $(window).resize(function () { that.width($(window).width() - width); }); } }; /** * 实现js与页面绑定 */ ko.bindingViewModel = function (viewModelInstance,node) { using(‘parser‘, function () { $.parser.onComplete = function () { ko.applyBindings(viewModelInstance, node || document.body); }; }); }; })(jQuery, ko);
自定义绑定,使 knockout 和 easyUI 关联,作用于页面中
3. receive.jsp
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>材料收料单</title> <%@ include file="/common/head.jsp"%> </head> <body> <div class="toolbar"> <a href="#" plain="true" class="easyui-linkbutton" icon="icon-arrow_refresh" title="刷新" data-bind="click:refreshClick">刷新</a> <a href="#" plain="true" class="easyui-linkbutton" icon="icon-add" title="新增" data-bind="click:addClick" >新增</a> <a href="#" plain="true" class="easyui-linkbutton" icon="icon-edit" title="编辑" data-bind="click:editClick" >编辑</a> <a href="#" plain="true" class="easyui-linkbutton" icon="icon-cross" title="删除" data-bind="click:deleteClick" >删除</a> <a href="#" plain="true" class="easyui-linkbutton" icon="icon-user-accept" title="审核" data-bind="click:auditClick" >审核</a> </div> <div class="container_12" style="position:relative;"> <div class="grid_1 lbl">收料单号</div> <div class="grid_2 val"><input type="text" data-bind="value:form.billNo" class="easyui-autocomplete txtBox" data-options="url:rootPath+‘/mms/receive!getBillNo.do‘"/></div> <div class="grid_1 lbl">供应商</div> <div class="grid_2 val"><input type="text" data-bind="value:form.supplierName" class="easyui-autocomplete txtBox" data-options="url:rootPath+‘/mms/merchants!getNames.do‘"/></div> <div class="grid_1 lbl">合同名称</div> <div class="grid_2 val"><input type="text" data-bind="value:form.contractCode" class="txtBox"/></div> <div class="clear"></div> <div class="grid_1 lbl">库房</div> <div class="grid_2 val"><input type="text" data-bind="comboboxValue:form.warehouseCode,datasource:dataSource.warehouseItems" class="easyui-combobox txtBox " data-options="showblank:true"/></div> <div class="grid_1 lbl">供应类型</div> <div class="grid_2 val"><input type="text" data-bind="comboboxValue:form.supplyType,datasource:dataSource.supplyType" class="easyui-combobox txtBox " data-options="showblank:true" /></div> <div class="grid_1 lbl">收料日期</div> <div class="grid_2 val"><input type="text" data-bind="value:form.receiveDate,readOnly:true" class="txtBox easyui-daterange"/></div> <div class="clear"></div> <div class="prefix_9" style="position:absolute;top:5px;height:0;"> <a id="search" href="#" class="buttonHuge button-blue" data-bind="click:searchClick" style="margin:0 15px;">查询</a> <a id="reset" href="#" class="buttonHuge button-blue" data-bind="click:clearClick">清空</a> </div> </div> <table data-bind="datagrid:grid"> <thead> <tr> <th field="billNo" sortable="true" align="left" width="90" >收料单号 </th> <th field="supplierName" sortable="true" align="left" width="200" >供应商 </th> <th field="supplyType" sortable="true" align="center" width="70" formatter="com.formateSupplyType" >供应类型 </th> <th field="contractCode" sortable="true" align="left" width="100" >合同名称 </th> <th field="warehouseName" sortable="true" align="left" width="100" >库房 </th> <th field="receiveDate" sortable="true" align="center" width="120" formatter="com.formatTime" >收料日期 </th> <th field="totalMoney" sortable="true" align="right" width="70" formatter="com.formatMoney" >金额 </th> <th field="originalNum" sortable="true" align="left" width="90" >原始票号 </th> <th field="doPerson" sortable="true" align="left" width="60" >经办人 </th> <th field="remark" sortable="true" align="left" width="150" >备注 </th> </tr> </thead> </table> <%@ include file="/common/foot.jsp"%> <script type="text/javascript" src="viewModel/mms/mms.com.js"></script> <script type="text/javascript" src="viewModel/mms/receive.js"></script> <script type="text/javascript"> using([‘messager‘, ‘dialog‘], function(){ var data = ${data}; ko.bindingViewModel(new viewModelReceive(data)); }); </script> </body> </html>
材料收料单列表界面,应用 data-bind 与数据模型进行绑定
4. receive.js
function viewModelReceive(data){ com.formateSupplyType = utils.fnValueToText(data.dataSource.supplyType); var self = this; this.dataSource = data.dataSource; this.form = ko.mapping.fromJS(data.form); delete this.form.__ko_mapping__; this.grid = { size: { w: 4, h: 94 }, url: rootPath + ‘/mms/receive!list.do‘, queryParams: ko.observable(), pagination: true, customLoad: false }; this.grid.queryParams(data.form); this.refreshClick = function () { window.location.reload(); }; this.addClick = function () { window.location.href = rootPath + ‘/mms/receive!add.do‘; }; this.deleteClick = function () { var row = self.grid.datagrid(‘getSelected‘); if (!row) return com.message(‘warning‘, ‘请先选择一条收料单!‘); com.message(‘confirm‘, ‘确定要删除选中的收料单吗?‘, function (b) { if (b) { com.ajax({ type: ‘DELETE‘, url: rootPath + "/mms/receive!delete.do?billNo=" + row[‘billNo‘], success: function () { com.message(‘success‘, ‘删除成功!‘); self.searchClick(); } }); } }); }; this.editClick = function () { var row = self.grid.datagrid(‘getSelected‘); if (!row) return com.message(‘warning‘, ‘请先选择一条收料单!‘); window.location.href = rootPath + ‘/mms/receive!edit.do?billNo=‘ + row[‘billNo‘]; }; this.searchClick = function () { var param = ko.toJS(this.form); this.grid.queryParams(param); }; this.clearClick = function () { $.each(self.form, function () { this(‘‘); }); this.searchClick(); }; this.grid.onDblClickRow = this.editClick; this.auditClick = function () { var row = self.grid.datagrid(‘getSelected‘); if (!row) return com.message(‘warning‘, ‘请先选择一条收料单!‘); mms.com.auditDialogList(function (d) { com.ajax({ type: ‘POST‘, url: rootPath + "/mms/receive!audit.do?billNo=" + row[‘billNo‘], data: JSON.stringify(d), success: function () { com.message(‘success‘, ‘单据已审核!‘); } }); }); }; }
材料收料单列表界面的数据模型,利用 knockout 与界面绑定
四. 效果图
1. 材料收料单列表界面
2. 材料收料单编辑界面
EasyUI+Knockout实现经典表单的查看、编辑