前两篇:(列表页的动态条件搜索,我是如何作列表页的)分别介绍了咱们是如何作后端业务系统数据展现类的列表页以及动态搜索的,那么还剩下最重要的一项:数据展现。数据展现通常包含三部分:javascript
技术依赖项:基于angularjs的MVVM模式,后台是spring mvc。html
数据表格需求:java
第三方的组件:jquery
最终展现的结果是html table,优势是支持嵌套表格等复杂功能,缺点是加载的文件大,且不能知足上面的需求angular model更新,对于复杂数据展现控制起来也比较复杂,须要额外的js编码工做。git
最终展现的结果是div,与angularjs兼容性很好,能支持表格在线编辑,缺点一样也是不能知足上面的需求angular model更新,对于复杂数据展现控制起来也比较复杂,须要额外的js编码工做。
第三方组件的特色就是功能多,但有些看起来很高级的功能咱们基本上都不用,好比在线表格数据的编辑,嵌套表格等。咱们必须的功能只有这些:排序,数据展现,分页,若是能支持angular model更新更好。因此我决定结合html table,angularjs来完成上面的需求。
数据加载:angularjs
因为咱们的数据动态查询方案的存在,决定了大部分页面先后台交互的模式是相同的,因此采用angular service来提供一个listService供列表页使用。主要是分页大小的选择框,以及定义了一些可配置的参数,好比执行查询的请求地址,因为咱们的动态查询以表单提交,因此是将整个表单参数序列化以后再加上分页相关信息而后提交到后端查询。github
angular.module('app.service', ['app.constant']) .service('$listService', function () { var $scopeLocal = {}; var pageSizeList = [{ "text": "10", "value": "10" }, { "text": "20", "value": "20" }]; var defaultOptions = { beforeSend: function () { }, callback: function ($scope, data) { }, error: function () { }, pageSize: pageSizeList[0].value, searchFormId: "searchForm", }; this.init = function ($scope, option) { var options = $.extend({}, defaultOptions, option); $scopeLocal = $scope; $scopeLocal.pageSizeList = pageSizeList; $scopeLocal.pageRequest = { "pageNum": 1, "pageSize": options.pageSize }; $scopeLocal.pageRequest.getResponse = function (orderBy) { var requestData = $("#" + options.searchFormId + "").serialize(); var url = options.listUrl + "?" + requestData + "&pageNum=" + $scopeLocal.pageRequest.pageNum + "&pageSize=" + $scopeLocal.pageRequest.pageSize; if(angular.isDefined($scopeLocal.pageRequest.orderBy)&&$scopeLocal.pageRequest.orderBy!=""){ url+="&orderBy="+$scopeLocal.pageRequest.orderBy; } $.ajax({ type: "POST", url: url, dataType: 'json', async: false, beforeSend: options.beforeSend, error: options.error, success: function (data) { $scopeLocal.pageResponse = data; $scopeLocal.content = data.list; options.callback($scopeLocal, data); } }); }; this.get = function () { $scopeLocal.pageRequest.getResponse(); }; }; })
使用时,只须要注入listService,而后配置上参数便可:web
mainApp.controller('manageCtrl', function ($scope, $http, $listService) { var options = { listUrl:"<c:url value="/theme/getAllByPage"/>" }; $listService.init($scope, options); $listService.get(); });
数据排序:
列头排序的方案是在列头上增长排序字段,经过angular directive来实现。排序的图片以及变换的样式是从jquery.datatable借鉴过来,逻辑并不复杂,无非就是显示排序标签以及根据用户的点击变换排序图标。ajax
angular.module('app.directives', []).directive("sortName", [ function() { return { restict : "A", link : function(scope, element, attrs) { var sortName = attrs["sortName"]; var sortType = attrs["sortType"]; if (!angular.isString(sortName) || sortName == "") return; if (!angular.isString(sortType) || sortType == "") { element.removeClass("sorting").removeClass("sorting_asc").removeClass("sorting_desc").addClass("sorting").attr("sort-type","asc"); } $(element).bind("click", function(){ var thisObj=$(this); var sortType = thisObj.attr("sort-type"); if (!angular.isString(sortName) || sortName == "") return; if (!angular.isString(sortType) || sortType == "") return; var orderBy = sortName + " " + sortType; scope.pageRequest.orderBy=orderBy; scope.pageRequest.getResponse(); if (sortType == "asc") { thisObj.removeClass("sorting").removeClass("sorting_asc").removeClass("sorting_desc").addClass("sorting_asc").attr("sort-type","desc"); } else if (sortType == "desc") { thisObj.removeClass("sorting").removeClass("sorting_asc").removeClass("sorting_desc").addClass("sorting_desc").attr("sort-type","asc"); } thisObj.siblings().each(function (){ var item=$(this); if(typeof(item.attr("sort-name"))!="undefined"){ item.removeClass("sorting").removeClass("sorting_asc").removeClass("sorting_desc").addClass("sorting"); } }); scope.$apply(); }); } } } ]);
一个scope做用域的问题,在directive中得到的scope比较特殊,它的值变动不会影响外层页面上的$scope。由于咱们须要在用户点击排序按钮后进行数据更新,因此咱们须要调用$apply方法将scope的变化传播出去。
html中只须要在列头指定排序字段便可实现排序功能:sort-name,值是须要排序的字段:spring
<th class="col-md-1" sort-name ="id">编号</th> <th class="col-md-4" sort-name ="name">名称</th>
数据展现:
直接在页面中采用table来布局,数据行采用angularjs来作加载。table布局的优势在于:直观上很清晰,处理某些特殊数据行时也比较容易,重要的是可以很容易的支持angular model 更新。
<table id="datatableTheme" cellpadding="0" cellspacing="0" border="0" class="datatable table table-striped table-bordered table-hover"> <thead> <tr> <th>编号</th> <th>名称</th> <th>状态</th> <th>描述</th> <th>操做</th> </tr> </thead> <tbody> <tr ng-repeat="item in content"> <td ng-bind="item.id"></td> <td ng-bind="item.name"></td> <td> <div ng-show="item.status=='1'"> <span class="label label-success">启用</span> </div> <div ng-show="item.status =='0'"> <span class="label label-danger">禁用</span> </div> </td> <td ng-bind="item.description"></td> <td> <a href="javascript:void(0)" data-toggle="modal" ng-click="edit(item.id)" data-original-title="编辑"> <span class="label label-primary">编辑</span> </a> <a href="javascript:void(0)" ng-show="item.status=='0'" ng-click="enabled(item)"> <span class="label label-primary">启用</span> </a> <a href="javascript:void(0)" ng-show="item.status=='1'" ng-click="enabled(item)"> <span class="label label-primary">禁用</span> </a> </td> </tr> </tbody> </table>
分页信息:
采用angularjs ui自带的uib-pagination。因为须要支持当前页记录大小的选择,若是每一个页面都须要包含分页相关内容,这样代码会比较冗余,于时很容易的咱们能够借助angular directive来解决:
angular.module('app.directives', []).directive("pagerFooter", [ function() { return { restrict : "A", link : function(scope, element) { return null; }, templateUrl : "../app/template/pagerFooter.html" } } ])
<meta charset="UTF-8"> <div class="row form-inline"> <div class="col-md-6"> <span> 每页 <ui-select ng-model="pageRequest.pageSize" ng-change="pageRequest.getResponse()" theme='select2' style="min-width:35px;" > <ui-select-match>{{$select.selected.text}}</ui-select-match> <ui-select-choices repeat="item.value as item in (pageSizeList | filter: $select.search)"> <div ng-bind="item.text"></div> </ui-select-choices> </ui-select> 条记录 总共<span ng-bind="pageResponse.total"></span>条记录 </span> </div> <div class="col-md-6 text-right"> <uib-pagination total-items="pageResponse.total" ng-model="pageRequest.pageNum" max-size="4" class="pagination-sm" boundary-links="true" force-ellipses="false" first-text="首页" last-text="末页" previous-text="上一页" next-text="下一页" num-pages="pageResponse.pages" ng-change="pageRequest.getResponse()" items-per-page="pageRequest.pageSize" > </uib-pagination> </div> </div>
使用时,咱们只须要这样指定:加一个pager-footer的属性。
<div class="box-body" pager-footer> </div>
写这个指令时遇到一个编码问题,模板页中出现的中文,在spring mvc环境下调用中乱码,最终在web.xml中增长配置得以解决:
<mime-mapping> <extension>html</extension> <mime-type>text/html;charset=utf-8</mime-type> </mime-mapping>
目前还有一个疑问没有获得解决,就是模板页中必须还要指定<meta charset="UTF-8">,不然也会显示成乱码,回头找时间总体研究下spring mvc下的编码。
数据行数据的model更新
以免经过二次请求或者刷新页面来从新加载数据。好比行数据中有状态一栏,操做列会根据状态值动态显示启用或者停用按钮,当用户点击启用按钮操做成功后,当前数据行的状态栏数据须要动态更新,且不须要请求后台也不须要刷新页面,咱们能够很是容易的通用ng-bind来让其自动更新:
操做列绑定事件:
<td> <a href="javascript:void(0)" data-toggle="modal" ng-click="edit(item.id)" data-original-title="编辑"> <span class="label label-primary">编辑</span> </a> <a href="javascript:void(0)" ng-show="item.status=='0'" ng-click="enabled(item)"> <span class="label label-primary">启用</span> </a> <a href="javascript:void(0)" ng-show="item.status=='1'" ng-click="enabled(item)"> <span class="label label-primary">禁用</span> </a> </td>
操做成功后更新model,页面数据自动更新。
$scope.enabled=function(theme) { bootbox.confirm("确认操做吗?", function (flag) { if (flag) { var status=theme.status==1?0:1; var model={id:theme.id,status:status}; $http.post("<c:url value="/theme/enabled"/>",model).success(function(ret){ if (ret.err) { bootbox.alert(ret.err); } else { theme.status=status; bootbox.alert("操做成功!"); } }); } }); };
列表页最终效果
上述的功能虽然不能解决全部场景的问题(嵌套表格,在线编辑表格,换肤等),但常见的业务操做均能知足,足够简单,不须要依赖第三方组件,重要的是可以完成其它js组件所不擅长的model更新场景以及复杂列的运算以及控制。我目前还在寻找其它的组件,若是有即能知足上述的需求又使用简单那么也是能够替换的,但作为学习总结总结倒也不错。