Bootstrap原生的table组件只能满足简单的数据展示,满足不了更富有操作性的要求。当然了,你可以找到一款叫做“DataTables-1.10.11”的基于bootstrap的table组件,但如果你对API看得不甚了解的话,用起来可就痛苦了,但是如果你选择使用jqGrid,那么本篇教程就给你带来了解决这种富操作性table的解决方案。
一、效果展示
OK,就展示这一张图片,相信你已经爱上了bootstrap版的jqGrid,和bootstrap很兼容,简直完美,当然了,这需要我们在缘由的jqGrid上进行一些改动,同时对组件进行一定的封装。
二、资源下载
我反正挺热爱分享的,关于jqGrid的组件代码,你可以从jqGrid的官网上下载,但是下载下来需要一些改动,那么我直接将改动后的jqGrid上传到了git,你只需要把提供的文件导入到你对应的项目即可。
另外,你还需要下载一个jquery-ui-1.10.0.custom.css,我就不提供下载地址了,不过我相信,你肯定会找得到,就算是用频出事故的度娘,你也可以找得到。
三、本篇都讲一些什么
自从建了QQ群后,“络绎不绝”的有同学加入到群中,但我也发现,进群的一步人直接来找我要demo,或者项目代码,这个我可不喜欢,自己动手做一做,去实现以下,改造一下,才会是你自己的东西,完全照搬我的代码显然你得不到更多的帮助,希望以上同学学习的时候再主动一些。
说完上面这点小废话后,我们言归正传,来说说我们本篇博客主要来讲些什么,什么才是在bootstrap中嵌入jqGrid的关键所在,我总结有如下:
jqGrid在bootstrap中的布局方案jqGrid自身的构造化参数jqGrid在bootstrap中的模块化jqGrid的数据操作
暂定分为以上部分来说明,但必须注意,限于篇幅,博客中只提供思路和部分代码。
①、 jqGrid在bootstrap中的布局方案
<!DOCTYPE html> <html lang=\"zh-CN\"> <%@ page language=\"java\" contentType=\"text/html; charset=utf-8\" pageEncoding=\"utf-8\"%> <%@ include file=\"/components/common/taglib.jsp\"%> <%@ include file=\"/components/common/csslib.jsp\"%> <head> <link type=\"text/css\" rel=\"stylesheet\" href=\"${ctx}/css/deal/my_pay_list.css\" /> </head> <body> <div class=\"container\"> <form class=\"form-horizontal\" id=\"jqgridForm\" role=\"form\" action=\"${ctx}/deal/datablePayDealOrdersList\" method=\"post\"> <div class=\"form-group\"> <div class=\"col-sm-12\"> <label for=\"name\" class=\"control-label pull-left\">项目名称:</label> <div class=\"col-sm-3\"> <input type=\"text\" name=\"name\" class=\"form-control\" id=\"name\" placeholder=\"请输入项目名称\" value=\"\" /> </div> </div> </div> <div class=\"form-group\"> <div class=\"col-sm-12\"> <button type=\"button\" class=\"btn btn-primary pull-right\" id=\"searchBtn\">检索</button> </div> </div> <div class=\"form-group\"> <div class=\"col-sm-12\"> <table id=\"pageGrid\" rel=\"jqgridForm\" class=\"jqgrid\"></table> <div id=\"pageGridPager\"></div> </div> </div> </form> </div> <%@ include file=\"/components/common/jslib.jsp\"%> <script type=\"text/javascript\" src=\"${ctx}/js/deal/my_pay_list.js\"></script> </body> </html>
介于每个人的项目千差万别,列出的代码中我们只关注jqGrid部分:
id=\"jqgridForm\"
,此处我们为jqGrid包含一层检索条件的form表单,也就是效果图上列出的search部分,当点击检索按钮时,就把form表单域的查询条件提交到controller,进而获取的数据。id=\"searchBtn\"
,定义检索按钮,后面讲模块化会用得到。<table id=\"pageGrid\" rel=\"jqgridForm\" class=\"jqgrid\"></table> <div id=\"pageGridPager\"></div>
定义jqGrid的table元素和jqGrid的footer元素,使用的规则我的项目暂时约定这种规则,你也可以有你自己的规则。通过rel指定form的id,可以更便捷的使用table的检索form。②、 jqGrid自身的构造化参数
构造化参数,我把他提取到了①中的my_pay_list.js中。
$(function() { var jqOption = { datatype : \"xml\", mtype : \"POST\", shrinkToFit : true, viewrecords : false, rownumbers : false, autowidth : true, height : \"100%\", colNames : [ \'id\', \'status\', \'项目信息\', \'项目状态\', \'订单号\', \'项目名称\', \'下单时间\', \'支付金额\', \'支持数量\', \'订单状态\', \'操作\' ], colModel : [ { name : \'id\', index : \'id\', hidden : true }, { name : \'status\', index : \'status\', hidden : true }, { name : \'image_str\', index : \'image_str\', width : 140, resizable : false, sortable : false, formatter : function(cellvalue, options, rowObject) { if (cellvalue == \'支付总花费:\') { return cellvalue; } }, align : \'left\' }, { name : \'oper\', index : \'oper\', width : 90, resizable : false, sortable : false, align : \'center\', formatter : function(cellvalue, options, rowObject) { var status = parseInt($(rowObject).find(\"status\").text()); var id = $(rowObject).find(\"id\").text(); if (status == 0) { return \'<a class=\"color0088cc\" width=\"700\" target=\"dialog\" href=\"\' + common.ctx + \'/deal/initPayg/\' + id + \'\">去支付</a>\'; } if (status == 1 || status == 3) { return \'<a class=\"color0088cc\" target=\"_blank\" href=\"\' + common.ctx + \'/deal/showDealOr/\' + id + \'\">查看详情</a>\'; } if (status == 2) { return \'<a class=\"color0088cc\" target=\"ajaxTodo\" href=\"\' + common.ctx + \'/deal/receivder/\' + id + \'\">确认收货</a>\'; } }, } ], xmlReader : { repeatitems : false, root : \"PageGrid\", row : \"map\", page : \'page\', total : \'total\', records : \'records\', id : \'ID\' }, rowNum : 50, rowList : [ 50, 100, 200, 300 ], pager : \"#pageGridPager\", footerrow : true, loadError : YUNM.ajaxError, gridComplete : function() { var $form = $(\"#\" + $(\"#pageGrid\").attr(\"rel\")); $.ajax({ type : $form.method || \'POST\', url : common.ctx + \"/deal/getAllOrded\", data : $form.serializeArray(), dataType : \"json\", cache : false, success : function(json) { $(\"#pageGrid\").footerData(\"set\", { image_str : \"支付总花费:\", order_price : json.message }); }, error : YUNM.ajaxError }); if ($.fn.ajaxTodo) { $(\"a[target=ajaxTodo]\", $(\"#pageGrid\")).ajaxTodo(); } // dialog if ($.fn.ajaxTodialog) { $(\"a[target=dialog]\", $(\"#pageGrid\")).ajaxTodialog(); } }, }; initEnv(jqOption); });
一点都不熟悉jqGrid的同学,建议先看jqGrid的demo,以及jqGrid的官方文档,当然了,对于已经熟悉jqGrid的同学,doc和demo肯定是必看的。
以上文件列出的属性非常多,对于jqGrid,我不做过多的介绍,本篇的主旨主要来介绍如何将jqGrid嵌入到bootstrap,那么重点就不在于介绍jqGrid上,我只介绍几个关键点:
formatter: function(cellvalue, options, rowObject) {
,formatter还是很经常要使用的,那么对于如何获得对应单元格的值就很重要,我的jqGrid使用的是xml(datatype : “xml”)数据格式,那么可通过$(rowObject).find(\"deal_id\").text()
找到对应deal_id列的值。xmlReader : { repeatitems : false, root : \"PageGrid\",
,注意xmlReader中的参数值,在接下来介绍④jqGrid的数据操作会细节介绍,和后台的xml数据封装有关系。$(\"#pageGrid\").footerData(\"set\", {image_str : \"支付总花费:\", order_price : json.message});
,关于footerData方法,使用起来也很方便,效果可参照效果图。initEnv(jqOption);
方法,在页面onload后,我们将jqGrid的初始化参数传递给initEnv方法,后续③jqGrid在bootstrap中的模块化会介绍initEnv方法。③、jqGrid在bootstrap中的模块化
②中我们注意到initEnv方法,那么这个方法内部就是专门针对jqGrid做的模块化封装工作。
initEnv方法
function initEnv(jqOption) { $(window).resize(function() { initLayout(); }); initUI(null, jqOption); }
该方法中,我们将会看到initLayout方法和initUI方法,具体内容稍候介绍。
initLayout
function initLayout() { $(\"table[rel=jqgridForm]\").each(function() { var rel = $(this).attr(\"rel\"); if (rel) { var $form = $(\"#\" + rel); var tableWidth = $form.width(); $(this).setGridWidth(tableWidth, true); } }); }
也就是说,在窗口缩放的时候,我们为jqGrid重新绘制宽度,使其自适应于bootstrap的响应式布局。使用的方法就是jqGrid的setGridWidth方法。
initUI
function initUI(_box, jqOption) { var $p = $(_box || document); if (jqOption) { YUNM.debug(\"初始化jqgrid\"); var $form = $(\"#\" + $(\"#pageGrid\").attr(\"rel\")); YUNM.debug(YUNM.array2obj($form.serializeArray())); // 初始化 var op = $.extend({ url : $form.attr(\"action\"), postData : YUNM.array2obj($form.serializeArray()), }, jqOption); $(\"#pageGrid\").jqGrid(op); // 检索按钮 $(\"#searchBtn\", $form).click(function() { $(\"#pageGrid\").jqGrid(\'setGridParam\', { url : $form.attr(\"action\"), page : 1, postData : YUNM.array2obj($form.serializeArray()), }); $(\"#pageGrid\").trigger(\"reloadGrid\"); }); // toolbar,将button的圆角去掉 $(\".btn\", $form).each(function() { var $this = $(this); $this.css({ \"border-radius\" : \"0px\", \"border-bottom\" : \"0\", }); }); } } array2obj : function(array) { var params = $({}); $.each(array, function(i) { var $param = $(this)[0]; params.attr($param.name, $param.value); }); return params[0]; },
如果你曾看过我之前的系列文章,对于initUi方法就不会太陌生,熟悉dwz的朋友,自然也不会陌生,我项目中的大部分模板还是依赖于dwz,谢谢这些前辈们。
var $form = $(\"#\" + $(\"#pageGrid\").attr(\"rel\"));
由于我们在jqGrid上关联了form检索条件的form表单,此处就可以将form表单对象取到,取到form表单对象,自然也就去得到了检索域的值($form.serializeArray()
)。拿到form表单的检索域值后,此时就需要做一番处理了。我们知道,jqGrid在向controller传递参数时,必然需要上送分页、排序的相关字段(page、rows、sord、sidx),使用的方法是$(\"#pageGrid\").jqGrid({postData:xxx});
,通常情况下,我们上送form表单时,只需要使用$form.serializeArray()
就可以,但如果此时,只是将xxx替换为$form.serializeArray()
,那么controller中将不会获得分页、排序的相关字段(page、rows、sord、sidx),这是一个冲突,此时怎么处理呢?解决办法就是将form表单数据对象化(array2obj 方法),然后我们再通过var op =$.extend({url:$form.attr(\"action\"),postData:YUNM.array2obj($form.serializeArray()),},jqOption);$(\"#pageGrid\").jqGrid(op);
将检索域的值和分页、排序的相关字段一起上送到controller。$(\"#searchBtn\", $form).click
通过封装click事件,将jqGrid的数据重新加载。$(\".btn\", $form).each(function() {
此处的方法将检索button去圆角,使其更贴合jqGrid,见效果图。④ 、jqGrid的数据操作
数据操作部分,我认为包含有 检索参数传递、分页排序参数传递、sql语句的编写。
关于参数传递,前端的参数封装在③中已有介绍,我们来看一看controller中如何处理数据的。
首先,我们来定义PageGrid,也就是jqGrid中xmlReader的数据源。
package com.honzh.common.page; import java.util.List; import com.thoughtworks.xstream.annotations.XStreamAlias; @XStreamAlias(\"pageGrid\") @SuppressWarnings(\"rawtypes\") public class PageGrid { private int page; private int total; private int records; private List data; public int getPage() { return this.page; } public void setPage(int page) { this.page = page; } public int getTotal() { return this.total; } public void setTotal(int total) { this.total = total; } public int getRecords() { return this.records; } public void setRecords(int records) { this.records = records; } public List getData() { return this.data; } public void setData(List data) { this.data = data; } }
项目中需要xstream.jar,自行下载。
XStreamComponent.java
package com.honzh.common.page; import org.apache.commons.lang.StringUtils; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.io.xml.DomDriver; import com.thoughtworks.xstream.mapper.DefaultMapper; import com.thoughtworks.xstream.mapper.XStream11XmlFriendlyMapper; public class XStreamComponent { private XStream xstream; public static XStreamComponent newInstance() { XStreamComponent xmlComponent = new XStreamComponent(); xmlComponent.alias(new Class[] { PageGrid.class }); return xmlComponent; } public XStreamComponent() { this.xstream = new XStream(new DomDriver()); } public String toXML(Object obj) { return this.xstream.toXML(obj); } public String toPageXML(Object obj) { registerConverter(new MapCustomConverter(new DefaultMapper(XStream11XmlFriendlyMapper.class.getClassLoader()))); return toXML(obj); } public Object fromPageXML(String xml) { registerConverter(new MapCustomConverter(new DefaultMapper(XStream11XmlFriendlyMapper.class.getClassLoader()))); return fromXML(xml); } public Object fromXML(String xml) { return this.xstream.fromXML(xml); } @SuppressWarnings(\"rawtypes\") public void processAnnotations(Class type) { this.xstream.processAnnotations(type); } @SuppressWarnings(\"rawtypes\") public void processAnnotations(Class[] types) { this.xstream.processAnnotations(types); } @SuppressWarnings(\"rawtypes\") public void alias(String name, Class type) { this.xstream.alias(name, type); } @SuppressWarnings(\"rawtypes\") public void alias(Class[] types) { for (Class type : types) { String className = type.getName(); try { String[] classNames = StringUtils.split(className, \".\"); this.xstream.alias(classNames[(classNames.length - 1)], type); } catch (Exception ex) { this.xstream.alias(className, type); } } } public void registerConverter(Converter converter) { this.xstream.registerConverter(converter); } @SuppressWarnings(\"rawtypes\") public void useAttributeFor(Class definedIn, String fieldName) { this.xstream.useAttributeFor(definedIn, fieldName); } }
主要将pageGrid封装为xml对象,进而传递会前端。
MapCustomConverter.java
package com.honzh.common.page; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.mapper.Mapper; public class MapCustomConverter extends AbstractCollectionConverter { public MapCustomConverter(Mapper mapper) { super(mapper); } @SuppressWarnings(\"rawtypes\") public boolean canConvert(Class type) { return (type.equals(HashMap.class)) || (type.equals(Hashtable.class)) || (type.getName().equals(\"java.util.LinkedHashMap\")) || (type.getName().equals(\"sun.font.AttributeMap\")); } @SuppressWarnings({ \"rawtypes\" }) public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { Map map = (Map) source; for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { Map.Entry entry = (Map.Entry) iterator.next(); writer.startNode(entry.getKey() == null ? \"null\" : entry.getKey().toString()); writer.setValue(entry.getValue() == null ? \"\" : entry.getValue().toString()); writer.endNode(); } } @SuppressWarnings(\"rawtypes\") public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { Map map = (Map) createCollection(context.getRequiredType()); populateMap(reader, context, map); return map; } @SuppressWarnings({ \"rawtypes\", \"unchecked\" }) protected void populateMap(HierarchicalStreamReader reader, UnmarshallingContext context, Map map) { while (reader.hasMoreChildren()) { reader.moveDown(); Object key = reader.getNodeName(); Object value = reader.getValue(); map.put(key, value); reader.moveUp(); } } }
主要将数据库中获取的hashmap转换为标准的xml格式数据。
BaseConditionVO.java
package com.honzh.common.persistence; import java.util.HashMap; import java.util.Map; import org.apache.ibatis.session.RowBounds; /** * 分页查询时的参数设置类.<br> * * <P> * 1.PAGE_SHOW_COUNT──当然默认一页显示10。<br> * 2.pageNum──第几页。<br> * 3.numPerPage──一页显示多少,为空时,显示PAGE_SHOW_COUNT。<br> * 4.totalCount──总共数目。totalCount/numPerPage=多少页<br> * 5.orderField──排序的列。<br> * 6.orderDirection──排序的方向。 * </P> */ public class BaseConditionVO { public final static int PAGE_SHOW_COUNT = 50; private int pageNum = 1; private int numPerPage = 0; private long totalCount = 0; private String orderField = \"\"; private String orderDirection = \"\"; /** * @Fields ps : 对参数类型进行封装. */ private Map<String, Object> mo = new HashMap<String, Object>(); public int getPageNum() { return pageNum; } public void setPageNum(int pageNum) { this.pageNum = pageNum; } public int getNumPerPage() { return numPerPage > 0 ? numPerPage : PAGE_SHOW_COUNT; } public void setNumPerPage(int numPerPage) { this.numPerPage = numPerPage; } public String getOrderField() { return orderField; } public void setOrderField(String orderField) { this.orderField = orderField; } public String getOrderDirection() { return \"desc\".equals(orderDirection) ? \"desc\" : \"asc\"; } public void setOrderDirection(String orderDirection) { this.orderDirection = orderDirection; } public long getTotalCount() { return totalCount; } public void setTotalCount(long totalCount) { this.totalCount = totalCount; } public int getStartIndex() { int pageNum = this.getPageNum() > 0 ? this.getPageNum() - 1 : 0; return pageNum * this.getNumPerPage(); } public RowBounds createRowBounds() { RowBounds ro = new RowBounds(this.getStartIndex(), this.getNumPerPage()); return ro; } /** * @Title: addParams * @Description: 添加查询条件 * @param key * @param value */ public void addParams(String key, Object value) { this.getMo().put(key, value); } /** * @Title: getParams * @Description: 获取查询条件 * @param key * @return */ public Object getParams(String key) { return this.getMo().get(key); } /** * @return the mo */ public Map<String, Object> getMo() { return mo; } /** * @param mo * the mo to set */ public void setMo(Map<String, Object> mo) { this.mo = mo; } @Override public String toString() { return \"条件:\" + pageNum + \",\" + numPerPage + \",\" + totalCount + \",\" + orderField + \",\" + orderDirection + \",\" + mo; } }
分页的查询数据对象,包括分页、排序、检索域。
protected BaseConditionVO getBaseConditionVOForTable() { BaseConditionVO vo = new BaseConditionVO(); // 分页的参数 int currentPage = getParaToInt(\"page\"); int sizes = getParaToInt(\"rows\"); String sortOrder = getPara(\"sord\"); String sortCol = getPara(\"sidx\"); vo.setNumPerPage(sizes); vo.setPageNum(currentPage); vo.setOrderField(sortCol); vo.setOrderDirection(sortOrder); return vo; } 将jqGrid传递的参数转换为BaseConditionVO分页查询对象。 protected void renderXml(HttpServletResponse res, String xmlResponse) { try { res.setCharacterEncoding(\"UTF-8\"); res.setHeader(\"Content-type\", \"text/xml\"); PrintWriter out = res.getWriter(); out.print(xmlResponse); if (out != null) { out.close(); } } catch (IOException e) { logger.error(e.getMessage()); logger.error(e.getMessage(), e); } }
将xml写入到输出流中。
定义完了这些基础的对象,接下来,我们就要着手获取数据和传递数据了。
@SuppressWarnings(\"rawtypes\") @RequestMapping(value = \"datablePayDealOrdersList\") public void datablePayDealOrdersList(HttpServletResponse response) { try { logger.debug(\"获取我支付的订单\"); XStreamComponent xstreamComponent = XStreamComponent.newInstance(); // 获取列表参数 BaseConditionVO vo = getBaseConditionVOForTable(); vo.addParams(\"name\", getPara(\"name\")); logger.debug(\"我支付的订单查询\" + vo); // 我创建的项目 List myDealOrders = dealOrderService.getByIssueUid(vo, vo.createRowBounds()); Long count = dealOrderService.searchIssueTotalCount(vo); String xmlResponse = xstreamComponent.toPageXML(createPageGrid(myDealOrders, vo, count.intValue())); renderXml(response, xmlResponse.replaceAll(\"__\", \"_\")); } catch (UncategorizedSQLException e) { logger.error(e.getMessage()); logger.error(e.getMessage(), e); renderXml(response, Constants.QUERY_ERROR); } catch (Exception e) { logger.error(e.getMessage()); logger.error(e.getMessage(), e); renderXml(response, Constants.SERVER_ERROR); } }
我们来详细说明一下:
1. XStreamComponent.newInstance()
创建xml流对象。
2. BaseConditionVO vo = getBaseConditionVOForTable();
创建分页查询参数对象。
3. vo.addParams(\"name\", getPara(\"name\"));
将检索域的值放入到查询对象中。
4. dealOrderService.getByIssueUid(vo, vo.createRowBounds());
mybatis的分页查询方式,超简单,之前一个群里的朋友专门做了一种mybatis的分页组件,我觉得用原始的mybatis查询方法更有效率,之后,我们会写出对应的mybatis中xml的sql写法。
5. renderXml(response, xmlResponse.replaceAll(\"__\", \"_\"));
将数据写入到jsp的out输出流中。
最后,我们来介绍,通过mybatis如何获取分页数据。
mapper.java
package com.honzh.biz.database.mapper; import java.math.BigDecimal; import java.util.HashMap; import java.util.List; import org.apache.ibatis.session.RowBounds; import com.honzh.common.persistence.BaseConditionVO; public interface DealOrderMapper { @SuppressWarnings(\"rawtypes\") List<HashMap> getByIssueUid(BaseConditionVO vo, RowBounds createRowBounds); }
想mapper.xml传递的两个对象,分别是BaseConditionVO 还有分页的RowBounds ,xml中sql就会自动分页。
mapper.xml
<select id=\"getByIssueUid\" resultType=\"hashmap\" parameterType=\"map\"> select * from daa WHERE is_delete=0 <if test=\"mo.name != null and mo.name != \'\'\"> and y.name like CONCAT(\'%\',\'${mo.name}\',\'%\') </if> <choose> <when test=\"orderField !=null and orderField !=\'\'\"> ORDER BY ${orderField} <if test=\"orderDirection != null and orderDirection != \'\'\">${orderDirection}</if> </when> <otherwise> order by d.order_time DESC </otherwise> </choose> </select>
你完全可以不关注RowBounds ,mybatis内部会自动为你封装好limit的。检索域的name可以直接通过mo.name或得到。orderField、orderDirection也传递过来了。
到此为止,整篇的Bootstrap嵌入jqGrid就圆满结束了,ok,使你的table牛逼起来吧!
本文地址:https://www.stayed.cn/item/1492
转载请注明出处。
本站部分内容来源于网络,如侵犯到您的权益,请 联系我