前兩篇:(列表頁的動態條件搜索,我是如何做列表頁的)分別介紹了我們是如何做後端業務系統數據展示類的列表頁以及動態搜索的,那麼還剩下最重要的一項:數據展示。數據展示一般包含三部分:
技術依賴項:基於angularjs的MVVM模式,後台是spring mvc。
數據表格需求:
第三方的組件:
最終展示的結果是html table,優點是支持嵌套表格等復雜功能,缺點是加載的文件大,且不能滿足上面的需求angular model更新,對於復雜數據展示控制起來也比較復雜,需要額外的js編碼工作。
最終展示的結果是div,與angularjs兼容性很好,能支持表格在線編輯,缺點同樣也是不能滿足上面的需求angular model更新,對於復雜數據展示控制起來也比較復雜,需要額外的js編碼工作。
第三方組件的特點就是功能多,但有些看起來很高級的功能我們基本上都不用,比如在線表格數據的編輯,嵌套表格等。我們必須的功能只有這些:排序,數據展示,分頁,如果能支持angular model更新更好。所以我決定結合html table,angularjs來完成上面的需求。
數據加載:
由於我們的數據動態查詢方案的存在,決定了大部分頁面前後台交互的模式是相同的,所以采用angular service來提供一個listService供列表頁使用。主要是分頁大小的選擇框,以及定義了一些可配置的參數,比如執行查詢的請求地址,由於我們的動態查詢以表單提交,所以是將整個表單參數序列化之後再加上分頁相關信息然後提交到後端查詢。
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,然後配置上參數即可:
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借鑒過來,邏輯並不復雜,無非就是顯示排序標簽以及根據用戶的點擊變換排序圖標。
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,值是需要排序的字段:
<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' > <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更新場景以及復雜列的運算以及控制。我目前還在尋找其它的組件,如果有即能滿足上述的需求又使用簡單那麼也是可以替換的,但做為學習總結總結倒也不錯。