一. ngTable功能简化
使用ngTable经常有分页,排序,过滤等功能,实现诸多功能较为麻烦。为了方便开发过程,可以抽取一些table共同点写一个公有方法。
注意:
1. 由于很多特别的需求,可能表格不一定适用于自己的项目,部分地方需要自己调试到适合自己的项目。
2. 部分功能没有使用ngTable自带的功能,只因为PM需求与之不符合
代码如下:
1. ListTableEX代码:
1 angular.module('newApp') 2 .factory('ListTableEX', ['ngTableParams', 'getStorage', function (ngTableParams, getStorage) { 3 var store = getStorage('localStorage'); 4 5 /** 6 * 创建表格 7 * @param options1 8 * @param options2 9 * @returns {ngTableParams|*} 10 */ 11 function createListTable(options1, options2) { 12 var listTable = new ngTableParams(options1, { 13 counts: options2.counts, 14 getData: function ($defer, params) { 15 // 如果不需要强制刷新数据且有缓存数据,那么直接使用缓存数据 16 // Note: 由于api可能返回空数据或者出错,因此不能通过判断 17 // 缓存数组容量是否为0来判断有无缓存,而是通过判断该数组是否为 18 // undefined 19 if (!listTable.needForceReload && params.items) { 20 // 复制items中的数据,以免shownItems的更改影响到items 21 params.shownItems = []; 22 for (var index in params.items) { 23 params.shownItems.push(params.items[index]); 24 } 25 var itemsFilter = options2.itemsFilter; 26 if (itemsFilter) { 27 params.shownItems = itemsFilter(params.shownItems); 28 } 29 sortTable(listTable); 30 listTable.calculatePages(); 31 var itemsCount = params.shownItems.length; 32 var itemCountPerPage = listTable.count(); 33 var currentPage = listTable.page(); 34 params.total(itemsCount); 35 $defer.resolve(subArray(params.shownItems, (currentPage - 1) * itemCountPerPage, itemCountPerPage)); 36 return; 37 } 38 listTable.needForceReload = false; 39 var apiParams = { 40 limit: 20000000, 41 index: 1 42 }; 43 options2.getData(apiParams).then(function (data) { 44 listTable.resetSortParams(); 45 listTable.resetCheckboxes(); 46 var resData = data.data.data; 47 var onDataResult = options2.onDataResult; 48 if(onDataResult) { 49 onDataResult(resData); 50 } 51 var items = resData.items; 52 var dataPreprocessFunc = options2.dataPreprocessFunc; 53 if (dataPreprocessFunc) { 54 dataPreprocessFunc(items); 55 } 56 // 即使api没有返回数据,也应该设置缓存数组,否则会导致无限刷新 57 params.items = items ? items : []; 58 // 由于已经获取到了数据,那么重新reload一次,复用有缓存数据时的处理逻辑 59 listTable.reload(); 60 }, function () { 61 }); 62 } 63 }); 64 listTable.tableName = options1.tableName; 65 listTable.titles = options1.titles; 66 listTable.needForceReload = false; 67 listTable.getItemId = options2.getItemId; 68 listTable.forceReload = function () { 69 listTable.needForceReload = true; 70 listTable.reload(); 71 } 72 return listTable; 73 }; 74 75 /** 76 * 初始化排序功能 77 * @param listTable 78 */ 79 function initSort(listTable) { 80 listTable.nextSortType = function () { 81 return listTable.sortParams.type == 'asc' ? 'desc' : 'asc'; 82 }; 83 listTable.isColumnSorting = function (key) { 84 return listTable.sortParams.key == key; 85 }; 86 listTable.isColumnSortingByASC = function (key) { 87 return listTable.isColumnSorting(key) && listTable.sortParams.type == 'asc'; 88 }; 89 listTable.isColumnSortingByDESC = function (key) { 90 return listTable.isColumnSorting(key) && listTable.sortParams.type == 'desc'; 91 }; 92 listTable.resetSortParams = function () { 93 listTable.sortParams = {key: '', type: ""}; 94 } 95 listTable.resetSortParams(); 96 }; 97 98 /** 99 * 初始化条目选择框 100 * @param listTable 101 * @param withCheckboxes 102 * @returns {*} 103 */ 104 function initCheckboxes(listTable, withCheckboxes) { 105 if (withCheckboxes) { 106 listTable.toggleCheckAll = function () { 107 var checkedCount = listTable.checkedIds().length; 108 if (checkedCount == listTable.shownItems.length) { 109 listTable.deselectAll(); 110 } else { 111 listTable.selectAll(); 112 } 113 }; 114 listTable.selectAll = function () { 115 listTable.resetCheckboxes(); 116 listTable.checkboxes.selectAll = true; 117 for (var index in listTable.shownItems) { 118 listTable.checkboxes.items[listTable.getItemId(listTable.shownItems[index])] = true; 119 } 120 }; 121 listTable.deselectAll = function () { 122 listTable.resetCheckboxes(); 123 listTable.checkboxes.selectAll = false; 124 for (var index in listTable.shownItems) { 125 listTable.checkboxes.items[listTable.getItemId(listTable.shownItems[index])] = false; 126 } 127 }; 128 listTable.checkedIds = function () { 129 var ids = []; 130 for (var index in listTable.shownItems) { 131 var id = listTable.getItemId(listTable.shownItems[index]); 132 if (listTable.checkboxes.items[id]) { 133 ids.push(id); 134 } 135 } 136 listTable.checkboxes.selectAll = listTable.shownItems ? (listTable.shownItems.length == ids.length) : false; 137 return ids; 138 } 139 listTable.resetCheckboxes = function () { 140 listTable.checkboxes = { 141 selectAll: false, 142 items: {} 143 }; 144 }; 145 } else { 146 listTable.resetCheckboxes = function () { 147 } 148 } 149 listTable.resetCheckboxes(); 150 return listTable; 151 }; 152 153 /** 154 * 初始话分页功能 155 * @param listTable 表格 156 * @private 157 */ 158 function initPagination(listTable) { 159 var STORAGE_PAGESIZE_KEY = getKeyForStoragePageSize(listTable); 160 161 if (store.get(STORAGE_PAGESIZE_KEY)) { 162 listTable.count(store.get(STORAGE_PAGESIZE_KEY)['value']); 163 } 164 listTable.editCount = listTable.count(); 165 166 listTable.prePage = function () { 167 var page = listTable.page(); 168 if (page > 1) { 169 listTable.page(page - 1); 170 } 171 }; 172 listTable.nextPage = function () { 173 var page = listTable.page(); 174 if (page < Math.ceil(1.0 * listTable.total() / listTable.count())) { 175 listTable.page(page + 1); 176 } 177 }; 178 listTable.calculatePages = function () { 179 var itemsCount = listTable.shownItems.length; 180 // 计算页导航条数据 181 var itemCountPerPage = listTable.count(); 182 store.set(STORAGE_PAGESIZE_KEY, { 183 'value': itemCountPerPage 184 }); 185 var totalPage = Math.ceil(1.0 * itemsCount / itemCountPerPage); 186 // 当前页最大为页面总数,最小为1 187 var currentPage = Math.max(1, Math.min(listTable.page(), totalPage)); 188 listTable.page(currentPage); 189 listTable.pages = calculateTablePages(currentPage, totalPage); 190 } 191 }; 192 193 function getKeyForStoragePageSize(listTable) { 194 return 'list_table_page_size@' + listTable.tableName; 195 }; 196 197 /** 198 * 从原始数组中分割出一段连续的数组,该操作不会影响原始数组 199 * @param array 原始数组 200 * @param startIndex 分割起始位置 201 * @param length 分割数量 202 * @returns {Array} 分割结果 203 */ 204 function subArray(array, startIndex, length) { 205 var result = []; 206 if (array && startIndex > -1 && length > 0) { 207 var maxIndex = Math.min(startIndex + length, array.length); 208 for (var i = startIndex; i < maxIndex; i++) { 209 result.push(array[i]); 210 } 211 } 212 return result; 213 }; 214 215 /** 216 * 对数据进行排序,该操作将直接操作传入的数组顺序 217 * @param items 数据数组 218 * @param key 数据对象的排序关键字 219 * @param sortType 排序类型,'asc'或者'desc' 220 */ 221 function sortItems(items, key, sortType) { 222 if (!items || items.length < 1 || !key || key.length < 1) { 223 return; 224 } 225 items.sort( 226 function compareFunction(param1, param2) { 227 var value1 = param1[key]; 228 var value2 = param2[key]; 229 if (typeof(value1) === 'number' && typeof(value2) === 'number') { 230 return sortType * (value1 - value2); 231 } 232 return sortType * ((value1 + '').localeCompare(value2 + '')); 233 } 234 ); 235 }; 236 237 function calculateTablePages(currentPage, totalPage) { 238 if (totalPage == 0) { 239 return []; 240 } 241 var end = Math.min(9, totalPage); 242 var pages = [currentPage]; 243 while (pages.length < end) { 244 var pre = pages[0] - 1; 245 var next = pages[pages.length - 1] + 1; 246 if (pre >= 1) { 247 pages.unshift(pre); 248 } 249 if (next <= totalPage) { 250 pages.push(next); 251 } 252 } 253 return pages; 254 }; 255 256 function sortTable(listTable) { 257 var sortParams = listTable.sorting(); 258 var sortKey; 259 var sortType; 260 for (var key in sortParams) { 261 sortKey = key; 262 sortType = sortParams[key]; 263 } 264 if (!sortType || sortType.length < 1) { 265 sortType = 'asc'; 266 } 267 listTable.sortParams.key = sortKey; 268 listTable.sortParams.type = sortType; 269 if (sortKey && sortKey.length > 0) { 270 sortItems(listTable.shownItems, sortKey, sortType == 'asc' ? 1 : -1); 271 } 272 }; 273 274 function init(options1, options2) { 275 var listTable = createListTable(options1, options2); 276 initSort(listTable); 277 initCheckboxes(listTable, options1.withCheckboxes); 278 initPagination(listTable); 279 return listTable; 280 }; 281 282 return { 283 init: init, 284 }; 285 }]);
2. ListTableEX分页代码:
1 <div class="ng-cloak dahuo-pagination" ng-if="params.pages.length > 0"> 2 <div class="col-lg-6"> 3 <div class="dataTables_info" role="status" aria-live="polite"> 4 {{ params.count() * (params.page() - 1) + 1 }}~ 5 {{ params.count() * (params.page() - 1) + params.data.length }}条,共{{ 6 params.total() }}条记录 7 </div> 8 </div> 9 <div class="col-lg-6 pagination2" style="margin-bottom: 40px"> 10 <div class="dataTables_paginate paging_simple_numbers"> 11 <ul class="pagination ng-table-pagination"> 12 <li><a href="" ng-click="params.prePage()">上一页</a></li> 13 <li ng-repeat="page in params.pages" 14 ng-class="{'active' : page == params.page()}"> 15 <a href="" ng-click="params.page(page)">{{ page }}</a> 16 </li> 17 <li><a href="" ng-click="params.nextPage()">下一页</a></li> 18 </ul> 19 </div> 20 <div class="col-lg-3 pull-right"> 21 {{params.testOK}} 22 <select class="select form-control" ng-model="params.editCount" ng-change="params.count(params.editCount)" 23 style="padding: 6px; margin-top: 2px;min-width: 80px;margin-right: 10px" 24 ng-options="size for size in params.settings().counts"> 25 </select> 26 </div> 27 </div> 28 </div>
由于PM的需求变更较大,使用方法可能各不相同。
基本使用方法如下:
1. HTML
本表格是一个有checkBox的表格,具有编辑,新建,上线,下线等功能。
1 <div> 2 <div class="row"> 3 <div class="col-lg-12 portlets"> 4 <div class="panel"> 5 <div class="panel-content"> 6 <div class="row dahuo-table-toolbar"> 7 <div class="col-xs-6"> 8 <button type="button" class="btn btn-default" ng-click="activityupload()" ng-disabled="checkedCount() < 1">发布</button> 9 <button type="button" class="btn btn-default" ng-click="activityoffline()" ng-disabled="checkedCount() < 1">下线</button> 10 {{ checkedCount() }}个被选中 11 </div> 12 <div class="col-xs-6"> 13 <div class="pull-right"> 14 <a href="#operation-activityadd"> 15 <button type="button" class="btn btn-primary">+ 新建活动</button> 16 </a> 17 </div> 18 </div> 19 </div> 20 <div class="table-responsive"> 21 <table ng-table="activityTable" class="table trans-table table-hover text-left dahuo-table" 22 template-pagination="layouts/pagination_v2.html" width="100%"> 23 <thead> 24 <tr> 25 <th width="5%" class="dahuo-table-header-th text-center" header="'ng-table/headers/checkbox.html'"> 26 <span class="list-checkbox"> 27 <input type="checkbox" ng-model="activityTable.checkboxes.selectAll" ng-disabled="activityTable.items.length < 1" ng-click="activityTable.toggleCheckAll()"/> 28 <span></span> 29 </span> 30 </th> 31 <th width="{{title.width}}" ng-repeat="title in activityTable.titles" ng-class="{'sort': title.sortable}" ng-click="title.sortable ? activityTable.sorting(title.key, activityTable.nextSortType()) : 0">{{ title.name }} 32 <li class="fa pull-right" ng-if="title.sortable" ng-class="{'fa-sort-asc': activityTable.isColumnSortingByASC(title.key), 'fa-sort-desc': activityTable.isColumnSortingByDESC(title.key), 'fa-unsorted': !activityTable.isColumnSorting(title.key)}" aria-hidden="true" ng-style="{'color' : (!activityTable.isColumnSorting(title.key) ? '#c9c9c9' : '')}"></li> 33 </th> 34 </tr> 35 </thead> 36 <tbody> 37 <tr ng-repeat="item in $data"> 38 <td class="text-center" header="'ng-table/headers/checkbox.html'"> 39 <span class="list-checkbox"> 40 <input type="checkbox" ng-model="activityTable.checkboxes.items[item.id]"/> 41 <span></span> 42 </span> 43 </td> 44 <td><a href="#operation-activityedit/{{item.id}}">{{自定义绑值}}</a></td> 45 <td>{{自定义绑值}}</td> 46 <td>{{自定义绑值}}</td> 47 <td>{{自定义绑值}}</td> 48 <td>{{自定义绑值}}</td> 49 </tr> 50 <tr style="height: 100px" ng-if="$data.length < 1"> 51 <td colspan="6" class="text-center">暂时没有数据</td> 52 </tr> 53 </tbody> 54 </table> 55 </div> 56 </div> 57 </div> 58 </div> 59 </div> 60 </div>
2. JS
字段解释:
(1).tableName:table名称,用于缓存数据时使用的localStorage的key
(2).titles:table表头控制项目,key是绑定数据的key,name是显示名称,sortable是否可排序
(3).withCheckboxes:表格是否包含checkboxes的列
(4).page:当前页
(5).count:每页显示条目数量
(6).sorting:默认排序的key和升降序
(7).counts:每页显示条数数量更改项
(8).getData:获取表格的数据,一般是发送API。这里有一个坑,后续会详细说明。
(9).getItemId:对应checkBoxes的itemId,复制即可。
依赖项:
DS:API请求依赖
logger:提示信息依赖
1 angular.module('newApp') 2 .controller('ActivityListCtrl', ['$scope', 'ds.activity', 'logger', 'ListTableEX', 3 function($scope, DS, logger, ListTableEX) { 4 $scope.activityTable = new ListTableEX.init( 5 { 6 tableName:'operation.activity.activity.list', 7 titles:[ 8 {key: '', name: "活动ID", sortable: true, width: '10%'}, 9 {key: '', name: "活动名称", sortable: true, width: '35%'}, 10 {key: '', name: "开始时间", sortable: true, width: '20%'}, 11 {key: '', name: "结束时间", sortable: true, width: '20%'}, 12 {key: '', name: "状态", sortable: true, width: '10%'}, 13 ], 14 withCheckboxes: true, 15 page: 1, 16 count: 25, 17 sorting: {'id': 'desc'}, 18 }, 19 { 20 counts: [10, 25, 50, 100], 21 getData: function(apiParams) { 22 return DS.listActivity(apiParams); 23 }, 24 getItemId: function(item) { 25 return item.id; 26 } 27 } 28 ); 29 30 $scope.checkedCount = function() { 31 return $scope.activityTable.checkedIds().length; 32 } 33 34 $scope.activityupload = function() { 35 var checkedIds = $scope.activityTable.checkedIds(); 36 if (checkedIds.length < 1) { 37 logger.warning('请选择操作对象'); 38 return; 39 } 40 41 DS.activityUpload(checkedIds) 42 .then(function() { 43 $scope.activityTable.forceReload(); 44 logger.success('上线成功'); 45 }, function(error) { 46 logger.error('上线失败'); 47 }); 48 }; 49 50 $scope.activityoffline = function() { 51 var checkedIds = $scope.activityTable.checkedIds(); 52 if (checkedIds.length < 1) { 53 logger.warning('请选择操作对象'); 54 return; 55 } 56 57 DS.activityOffline(checkedIds) 58 .then(function() { 59 $scope.activityTable.forceReload(); 60 logger.success('下线成功'); 61 }, function(error) { 62 logger.error('下线失败'); 63 }); 64 }; 65 } 66 ]);
二. 使用中注意的问题:
1. 需要自己更改适应服务端返回数据的处理代码:
对应代码段时listTableEX的“var resData = data.data.data”
2. 使用中可能Table数据不是来自于API:
需要使用如下代码:
1 getData: function(apiParams) { 2 return {then: function(callback){ 3 var data = { 4 data:{ 5 data:{ 6 items: [], 7 } 8 } 9 } 10 callback(data); 11 }} 12 }
然后强行加载数据:
1 $scope.ActivityTable.items = items; 2 $scope.ActivityTable.page(1); 3 $scope.ActivityTable.reload();
3. 可能数据来自API,但是触发时机不是Table new出来时:
New出来的代码更改如下:
1 getData: function(apiParams) { 2 if (满足API发送条件){ 3 var apiParams = { 4 (自定义) 5 }; 6 return DS.activityList(apiParams); 7 } 8 return {then: function(callback){ 9 var data = { 10 data:{ 11 data:{ 12 items: [], 13 } 14 } 15 } 16 callback(data); 17 }} 18 }
然后发送API获取数据:
1 $scope.ActivityTable.page(1) 2 $scope.ActivityTable.forceReload();
三. 搜索,过滤,导出CSV插件
代码都有较长的注释,这里不做过多的介绍。
导出CSV依赖插件FileSaver.js。
angular.module('newApp') .factory('ListTableEXExtend', function (getStorage) { /* First method: ListTableEXExtend.searchFilter(Array, String, Boolean); Input: Array: ['a', 'b', 'c', 'aa', 'ab', 'cc'] String: 'a' Boolean: true Output: Array: ['a', 'aa', 'ab'] Second method: ListTableEXExtend.searchFilter(ObjectArray, String, Boolean, Array) Input: ObjectArray: [ {id:111, name:'aaa', ex1:'sss', ex2: 'sss'}, {id:222, name:'11aa', ex1:'sss', ex2: 'sss'}, {id:333, name:'a1a1', ex1:'sss', ex2: 'sss'}, {id:444, name:'bbbb', ex1:'sss', ex2: 'sss'} ] String: 1 or '1' Boolean: true Array: ['id', 'name'] Output: ObjectArray: [ {id:111, name:'aaa', ex1:'sss', ex2: 'sss'}, {id:222, name:'11aa', ex1:'sss', ex2: 'sss'}, {id:333, name:'a1a1', ex1:'sss', ex2: 'sss'} ] Others: return items Note: if has too much data, Please use method: delay reload Table */ function searchFilter(items, keyword, isIgnoreUpper, regArr) { if (!items) { console.log("Items must be defined"); return; } if (typeof(items) !== "object" || items.constructor !== Array) { console.log("Items must be a array"); return items; } if (items.length === 0) { return items; } if (!keyword && keyword !== 0 && keyword !== '0') { return items; } /* Copy Items*/ var newItems = items.concat(); if (isIgnoreUpper){ var keyword = keyword.toString().toLowerCase(); }else { var keyword = keyword.toString(); } if (!regArr) { var resultItem = []; for (var index in items) { try { if (isIgnoreUpper){ var newItem = newItems[index].toString().toLowerCase(); }else { var newItem = newItems[index].toString(); } } catch (e) { console.log(e.name + ": " + e.message); } if (newItem.indexOf(keyword) !== -1) { resultItem.push(items[index]); } } return resultItem; } if (typeof(regArr) !== "object" || regArr.constructor !== Array) { console.log("regArr must be a array"); return items; } if (regArr.length === 0) { return items; } var newRegArr = regArr.concat(); for (var index in newRegArr) { try { newRegArr[index].toString(); } catch (e) { console.log(e.name + ": " + e.message); } } var resultItem = []; for (var index in items) { try { var newItem = newItems[index]; if (typeof(newItem) !== "object" || newItem.constructor !== Object) { console.log("Items must be a object array"); return items; } for (var index2 in newRegArr) { if (!newItem[newRegArr[index2]] && newItem[newRegArr[index2]] !== 0 && newItem[newRegArr[index2]] !== '0') { continue; } if (isIgnoreUpper){ var newItemToStr = newItem[newRegArr[index2]].toString().toLowerCase(); }else { var newItemToStr = newItem[newRegArr[index2]].toString(); } if (newItemToStr.indexOf(keyword) !== -1) { resultItem.push(items[index]); break; } } } catch (e) { console.log(e.name + ": " + e.message); } } return resultItem; } /* Method: ListTableEXExtend.selectFilter(ObjectArray, Key, Value) Input: ObjectArray: [ {id:111, name:'aaa', product_id:'123', ex: 'sss'}, {id:222, name:'11aa', product_id:'113', ex: 'sss'}, {id:333, name:'a1a1', product_id:'123', ex: 'sss'}, {id:444, name:'bbbb', product_id:'113', ex: 'sss'}, ] Key: product_id Value: 123 Output: ObjectArray: [ {id:111, name:'aaa', product_id:'123', ex: 'sss'}, {id:333, name:'a1a1', product_id:'123', ex: 'sss'} ] Others: return items */ function selectFilter(items, key, value) { if (!items) { console.log("Items must be defined"); return; } if (!key || !value) { console.log("Key, value must be defined"); return items; } if (typeof(items) !== "object" || items.constructor !== Array) { console.log("Items must be a array"); return items; } if (items.length === 0) { return items; } key = key.toString(); var resultItem = [] for (var index in items) { try { var item = items[index]; if (typeof(item) !== "object" || item.constructor !== Object) { console.log("Items must be a object array"); return items; } if (item[key] == undefined || item[key] == null || item[key] === '') { continue; } if (value === 'defined'){ resultItem.push(items[index]); continue; } if (item[key] == value) { resultItem.push(items[index]); } } catch (e) { console.log(e.name + ": " + e.message); } } return resultItem; } function _getDateTime(date){ if (angular.isDate(date)){ return date.getTime(); }else { return new Date(date).getTime(); } } function timeFilter(items, start, end, key){ if (!items) { console.log("Items must be defined"); return; } if (!angular.isArray(items)) { console.log("Items must be a array"); return items; } var startTime = _getDateTime(start); var endTime = _getDateTime(end); var newItems = items.concat(); var resultItems = []; var newKey = key.toString() for (var index in items){ var newItem = items[index]; if (!angular.isObject(newItem)) { console.log("Items must be a object array"); return items; } if (!newItem[newKey]) { continue; } var newItemDate = _getDateTime(newItem[newKey]); if (newItemDate >= startTime && newItemDate <= endTime){ resultItems.push(newItem); } } return resultItems; } function editItem(items, item, primaryKey, isCreated){ if (!items) { console.log("Items must be defined"); return; } if (!item || !primaryKey) { console.log("Item, primaryKey must be defined"); return items; } if (!angular.isArray(items)) { console.log("Items must be a array"); return items; } if (!angular.isObject(item)) { console.log("Item must be a object"); return items; } primaryKey = primaryKey.toString(); /* Copy Items*/ var newItems = items.concat(); for (var index in newItems) { try { var newItem = newItems[index]; if (!angular.isObject(newItem)) { console.log("Items must be a object array"); return items; } if (!newItem[primaryKey] && newItem[primaryKey] != 0 && !item[primaryKey] && item[primaryKey] != 0) { continue; }else { if (newItem[primaryKey] == item[primaryKey]){ newItems[index] = item; isCreated = false; } } } catch (e) { console.log(e.name + ": " + e.message); return items; } } if (isCreated){ newItems.push(item); } return newItems; } /* Usage: var csv = new ListTableEXExtend.Csv({ --titles(Option): ObjectArray. just lick listtableex.titles, 'key' and 'name' is needed. This is used for defining Csv col. --items(Option): ObjectArray. just lick listtableex.items. --filterData(Option): FuncObject. This is used for switch data. For example: {id(Must be titles'key): function(input){return input + 'Test'}}. --defaultChar(Option): String or Object. String: All table's default char. Object: One col default char(Must be titles'key). This is used for set default char when <td></td> is like this. --handleData(Option): Func. Must return Data. Data is the fileData. --fileName(Option): String. --delimiter(Option): String. This is used for maping <td> and another <td> with 'char'. Default is ',' used for Csv. }); csv has to public method: --setItems(Option) Note: The items maybe is undefined because API are not repond this time. So you need to set items again. --setTitles(Option) Note: The titles maybe changed. So you need to set titles again. --exportCsv(Necessary, Main) Others are private. If you have to get proto, you can add public method. */ function Csv(options) { var items = options.items; var titles = options.titles; var filterData = options.filterData; var handleData = options.handleData; var defaultChar = options.defaultChar; var type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8"; var fileName = options.fileName ? options.fileName : 'download.csv'; var delimiter = options.delimiter ? options.delimiter : ','; var data = ''; function _download(data, fileName) { var blob = new Blob(["\ufeff" + data], { type: type }); saveAs(blob, fileName); } function _getData(items, titles) { if (!items || !titles || typeof(items) !== "object" || items.constructor !== Array || typeof(titles) !== "object" || titles.constructor !== Array) { console.log("Usage:\n\titems: ObjectArray\n\ttitles: ObjectArray\n"); return false; } return true; } function _handleData(items, titles, delimiter, filterData, handleData, defaultChar) { data = ''; if (handleData) { data = handleData(items, titles); } else { var rowData = ''; for (var index in titles) { rowData += titles[index].name + delimiter; } rowData = rowData.slice(0, rowData.length - 1); data += rowData + '\n'; for (var i in items) { var item = items[i]; var rowData = ''; for (var j in titles) { var title = titles[j]; if (!title.key) { return ''; } else if (item[title.key] === undefined || item[title.key] === '') { if (defaultChar) { if (angular.isObject(defaultChar)) { if (defaultChar[title.key] !== undefined) { rowData += defaultChar[title.key] + delimiter; } else { rowData += delimiter; } } else { rowData += defaultChar + delimiter; } } else { rowData += delimiter; } } else { if (filterData) { if (title.filterKey && filterData[title.filterKey]) { var filterResultData = filterData[title.filterKey](item[title.key]); rowData += filterResultData + delimiter; } else if (filterData[title.key]) { var filterResultData = filterData[title.key](item[title.key]); rowData += filterResultData + delimiter; } else { rowData += item[title.key] + delimiter; } } else { rowData += item[title.key] + delimiter; } } } rowData = rowData.slice(0, rowData.length - 1); data += rowData + '\n'; } } return data; } this.setItems = function (value) { items = value; } this.setTitles = function (value) { titles = value; } this.exportCsv = function () { if (!_getData(items, titles)) { return data; } data = _handleData(items, titles, delimiter, filterData, handleData, defaultChar); _download(data, fileName); } } return { searchFilter: searchFilter, selectFilter: selectFilter, timeFilter: timeFilter, editItem: editItem, Csv: Csv } })
以上导出CSV方法是导出所有分页的数据,当表结构并非以[{...}, {...}]这种结构返回则比较难以实现通用性。对于没有分页的表,可以解析html来导出当页的数据到CSV。
angular.module('newApp') .directive('exportCsv', ['$parse', '$timeout', function ($parse, $timeout) { /* This is used for export ShownTable, Only current page. You do not need to handle data. For example: if ngTable: <a class="btn btn-primary" ng-click='csv.generate($event, "FileName", "ngTableBindname")' href=''>export</a> <table ng-table="ngTableBindname" class="table trans-table table-hover dahuo-table" width="100%" export-csv="csv"> else: <a class="btn btn-primary" ng-click='csv.generate($event, "FileName", "tableId")' href=''>export</a> <table id="tableId" class="table trans-table table-hover dahuo-table" width="100%" export-csv="csv"> */ var delimiter = ','; var tables = {}; var type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8"; return { restrict: 'A', scope: false, link: function(scope, element, attrs) { var data = ''; if (attrs.ngTable){ tables[attrs.ngTable] = element; }else { tables[attrs.id] = element; } function stringify(str) { return '"' + str.replace(/^\s\s*/, '').replace(/\s*\s$/, '').replace(/"/g,'""') + '"'; } function parseTable(table) { data = ''; if (tables[table]){ var rows = tables[table].find('tr'); }else { return data } angular.forEach(rows, function(row, i) { var tr = angular.element(row), tds = tr.find('th'), rowData = ''; if (tr.hasClass('ng-table-filters')) { return; } if (tds.length === 0) { tds = tr.find('td'); } angular.forEach(tds, function(td) { rowData += stringify(angular.element(td).text()) + Array.apply(null, Array(td.colSpan)).map(function () { return delimiter; }).join(''); }); rowData = rowData.slice(0, rowData.length - 1); data += rowData + '\n'; }); } function download(data, filename) { var blob = new Blob(["\ufeff" + data], { type: type }); saveAs(blob, filename); } var csv = { generate: function(event, filename, table) { if (table){ parseTable(table); download(data, filename); } else { parseTable(); download(data, filename); } } }; $parse(attrs.exportCsv).assign(scope.$parent, csv); } } }])