AngularJS grid - meetbill/butterfly-fe GitHub Wiki

ng-grid 和 ui-grid

0 ng-grid 与 ui-grid 选择

ng-grid 和 ui-grid 区别

ng-grid 是实现了基本的表格样式需求,ui-grid 要更加强大,里面封装了很多指令,实现表格换行、拖拽等更高级的表格功能

ng-grid 表格中内容无法复制,表格宽度无法动态调整等等,所以建议还是使用新的 ui-grid

1 ng-grid【弃用】

测试 ng-grid

Upgrading from ng-grid 2.0.x to ui-grid 3.0

1.1 开始使用

1. <div class="gridStyle" ng-grid="gridOptions"></div>
gridOptions 就是绑定表格的变量。

2. 可以先给表格设置一个基本的样式,如:
.gridStyle {
    border: 1px solid rgb(212,212,212);
    width: 400px; 
    height: 300px
}

3. 在控制器中添加数据:
$scope.myData = [{name: "Moroni", age: 50},
                 {name: "Tiancum", age: 43},
                 {name: "Jacob", age: 27},
                 {name: "Nephi", age: 29},
                 {name: "Enos", age: 34}];

4. 在同个控制器下初始化表格:
$scope.gridOptions = { data: 'myData' };

1.2 监听部分变量

totalServerItems: 'totalServerItems',

if (typeof options.totalServerItems === "string") {
    $scope.$on('$destroy', $scope.$parent.$watch(options.totalServerItems, function (newTotal, oldTotal) {
        if (!angular.isDefined(newTotal)) {
            $scope.totalServerItems = 0;
        }
        else {
            $scope.totalServerItems = newTotal;
        }
    }));
}
else {
    $scope.totalServerItems = 0;
}

2 Module name has changed from nggrid to ui.grid

Directive name has changed from ng-grid to ui-grid.

Before:

<div ng-grid="{ data: data }"></div>
angular.module('yourModule', ['ngGrid'];

After:

<div ui-grid="{ data: data }"></div>
angular.module('yourModule', ['ui.grid'];

2.1 A string value in options.columnDefs is no longer supported.

ColumnDefs are now always watched via $watchCollection.

Before:

$scope.myColDefs = {[...]};
$scope.gridOptions.columnDefs = 'myColDefs'

After:

$scope.gridOptions.columnDefs = $scope.myColDefs = [...];
or
$scope.gridOptions.columnDefs = [...];

2.2 All columns within columnDefs must have a name or a field.

Before:

$scope.gridOptions = {
  columnDefs: [
    { displayName: 'Edit' }
  ],
  data: myData
};

After:

$scope.gridOptions = {
  columnDefs: [
    { name: 'edit', displayName: 'Edit' }
  ],
  data: myData
};

2.3 ui-grid uses an isolate scope

You can no longer access data or functions directly on the parent scope. You must use grid.appScope to get a reference to the parent scope

Before:

$scope.gridOptions = {
  columnDefs = [
    { name: 'edit', displayName: 'Edit', cellTemplate: '<button ng-click="edit(row.entity)" >Edit</button>' }
  ],
  data: myData
};

$scope.edit = function( entity ) {
  ...some custom function using entity...
};
<div ng-grid="gridOptions" ></div>

After:

$scope.gridScope = $scope;
$scope.gridOptions = {
  columnDefs = [
    { name: 'edit', displayName: 'Edit', cellTemplate: '<button ng-click="grid.appScope.edit(row.entity)" >Edit</button>' }
  ],
  data: myData
};

$scope.edit = function( entity ) {
  ...some custom function using entity...
};

row.entity

row.entity 会获取到此行的对象,,,,, 添加按钮的时候非常有用的对象

2.4 Some features previously included in the base are now plugins.

Refer to the tutorials and API documentation at http://ui-grid.info/docs/ for more detail, an example provided below is column resizing. The plugins are available in the base javascript, using them requires only including the appropriate directive in the grid declaration:

After:

<div ui-grid="gridOptions" ui-grid-resize-columns ></div>

3 ng-grid option

ng-grid option

ng-grid 的 option 包括 Grid Options 和 ColumnDefs Options

3.1 Grid Options

$scope.gridOptions

pagingOptions

{
    pageSizes        : 页面大小列表
    pageSize         : 当前选定的页面大小
    currentPage      : 当前页
}

totalServerItems

num: 总数

filterOptions

{
    filterText: '',
    useExternalFilter: false
}

data

[]: 在网格中显示的数据。数组中的每个项都映射到要显示的行。

Ps:data: 'myData'

enablePaging

true: 是否启用服务器端分页功能

Ps: 如果设置为 ture ,则会显示分页及切换的按钮
源码:
<div class="ngPagerContainer" style="float: right; margin-top: 10px;" ng-show="enablePaging" ng-class="{'ngNoMultiSelect': !multiSelect}">
    <div style="float:left; margin-right: 10px;" class="ngRowCountPicker">"
        <span style="float: left; margin-top: 3px;" class="ngLabel">__i18n.ngPageSizeLabel__</span>
            <select style="float: left;height: 27px; width: 100px" ng-model="pagingOptions.pageSize" >
                <option ng-repeat="size in pagingOptions.pageSizes">__size__</option>
            </select>
    </div>
    <div style="float:left; margin-right: 10px; line-height:25px;" class="ngPagerControl" style="float: left; min-width: 135px;">
        // 跳到第一页
        <button type="button" class="ngPagerButton" ng-click="pageToFirst()" ng-disabled="cantPageBackward()" title="__i18n.ngPagerFirstTitle__"><div class="ngPagerFirstTriangle"><div class="ngPagerFirstBar"></div></div></button>
        // 上一页
        <button type="button" class="ngPagerButton" ng-click="pageBackward()" ng-disabled="cantPageBackward()" title="__i18n.ngPagerPrevTitle__"><div class="ngPagerFirstTriangle ngPagerPrevTriangle"></div></button>
        <input class="ngPagerCurrent" min="1" max="__currentMaxPages__" type="number" style="width:50px; height: 24px; margin-top: 1px; padding: 0 4px;" ng-model="pagingOptions.currentPage"/>
        <span class="ngGridMaxPagesNumber" ng-show="maxPages() > 0"> __maxPages()__</span>
        // 下一页
        <button type="button" class="ngPagerButton" ng-click="pageForward()" ng-disabled="cantPageForward()" title="__i18n.ngPagerNextTitle__"><div class="ngPagerLastTriangle ngPagerNextTriangle"></div></button>
        // 最后一页
        <button type="button" class="ngPagerButton" ng-click="pageToLast()" ng-disabled="cantPageToLast()" title="__i18n.ngPagerLastTitle__"><div class="ngPagerLastTriangle"><div class="ngPagerLastBar"></div></div></button>
    </div>
</div>

showFooter

true: 显示或隐藏页脚,默认情况下页脚被禁用

rowHeight

36: 网格行高

headerRowHeight

36: 表头行高

enableRowSelection

false:行选择是否可用,默认为 true;

3.2 ColumnDefs Options

self.orig = fromCol;
self.width = fromCol.width;-------------------------# 表格宽度,int (42), string px('42px'),percentage string ('42%')
self.groupIndex = fromCol.groupIndex;
self.isGroupedBy = fromCol.isGroupedBy;
self.displayName = fromCol.displayName;-------------# 显示名称
self.index = fromCol.index;
self.isAggCol = fromCol.isAggCol;
self.cellClass = fromCol.cellClass;-----------------# 为列单元格追加 css 类
self.cellFilter = fromCol.cellFilter;
self.field = fromCol.field;-------------------------# 数据模型的属性
self.aggLabelFilter = fromCol.aggLabelFilter;
self.visible = fromCol.visible;
self.sortable = fromCol.sortable;
self.resizable = fromCol.resizable;-----------------# 列是否可调整大小
self.pinnable = fromCol.pinnable;-------------------# 是否可以将列固定到左侧
self.pinned = fromCol.pinned;
self.originalIndex = fromCol.originalIndex;
self.sortDirection = fromCol.sortDirection;
self.sortingAlgorithm = fromCol.sortingAlgorithm;
self.headerClass = fromCol.headerClass; ------------# 为列标题追加一个 css 类
self.headerCellTemplate = fromCol.headerCellTemplate;
self.cellTemplate = fromCol.cellTemplate;
self.cellEditTemplate = fromCol.cellEditTemplate;

4 tips

4.1 使用 ng-class 动态变换样式

ng-class 使用

demo

<div ng-class="row.entity[col.field] == 'success' ? 'successClass' : 'errorClass'">{{row.entity[col.field]}}</div>

需要注意的是 ng-class="'successClass'" ,value 是需要有单引号的,切记

ng-class 推荐姿势

class 名加 ''

如果需要控制的 class 名为多个单词,不加引号以 - 拼接会报错,小驼峰命名不会,但如果加引号,使用 - 拼接可小驼峰都没问题。


<span ng-class="{redColor: vm.status}">听风是风</span> ------- 正确
<span ng-class="{'red-color': vm.status}">听风是风</span>----- 正确(推荐)
<span ng-class="{red-color: vm.status}">听风是风</span>------- 错误

如下是 ng-class 常用的几种方式

ng-class 三元表达式

<div ng-class="row.entity[col.field] == 'success' ? 'successClass' : 'errorClass'">{{row.entity[col.field]}}</div>

ng-class 对象写法

<select name="" id="" ng-model="vm.status">
    <option value="color-blue">蓝色</option>
    <option value="color-red">红色</option>
</select>
<span ng-class="{color-blue:'blue',color-red:'red'}[vm.status]">听风是风</span>

ng-class 评估表达式

cellTemplate : '<span class="label" ng-class="{\'label-warning\' : row.entity.status == 1,\'label-info\':row.entity.status == 2,\'label-success\' : row.entity.status == 4,\'label-danger\' : row.entity.status == 5,\'label-primary\' : row.entity.status == 6,}">__html(row.entity.status)__ </span>',

ng-class 入坑指北

页面首次加载时时正常,但是搜索条件改变后,未及时变化,修改为评估表达式后正常

cellTemplate : '<span class="label" ng-class="__mycolor(row.entity.status)__">__html(row.entity.status)__ </span>',

$scope.mycolor = function (stat) {
    switch (stat) {
        case '1':
            return "\'label-warning\'";
            break;
        case '2':
            return "\'label-info\'";
            break;
        case '4':
            return "\'label-success\'";
            break;
        case '5':
            return "\'label-danger\'";
            break;
        case '6':
            return "\'label-primary\'";
            break;
        default:
            return "\'label-default\'";
            break;
    }
}

4.2 ng-grid 中按钮设置和 cell 一样宽度

width: __col.width__px

<button class="btn btn-success" style="width: __col.width__px" ng-click="open(row.entity.id)">详情</button>

4.3 自定义模板内容如何居中

cellTemplate:'<div class="ui-grid-cell-contents" > __row.entity.create_time | date:"yyyy-MM-dd hh:mm:ss"__</div>',

参考:https://github.com/relaxtudio/ng-amproj/blob/f50360e43c561dc3383c4979a6a256c279bf2425/adm/js/controller/simctrl.js

4.4 条件单元模板

cellTemplate : '<span class="label" ng-class="{\'label-warning\' : row.entity.status == 1,\'label-info\':row.entity.status == 2,\'label-success\' : row.entity.status == 4,\'label-danger\' : row.entity.status == 5,\'label-primary\' : row.entity.status == 6,}">__grid.appScope.html(row.entity.status)__ </span>',

更新成 ui.grid 后,cellTemplate 使用变量函数时,需要加 grid.appScope.,如果方法和 grid 不在同一个 controller 中时,可以不加

参考:ui-grid angularjs 中的条件单元模板

4.5 浏览器中动态变更页码等参数

以【wuxing】history 列表举例

4.5.1 router

state('app.wuxing.item_history_list', {
    url: '/item_history_list?item_type&item_id&page_index',
    templateUrl: 'static/tpl/wuxing/item_history_list.html',
    resolve: load(['ui.grid', 'ui.grid.resizeColumns','ui.grid.pagination' ,'static/js/controllers/wuxing/item_history.js'])
})

4.5.2 controller

var app = angular.module('app');
app.controller('WuxingItemHistoryListCtrl', ['$scope', '$modal', 'i18nService', 'Request', '$httpParamSerializer', '$stateParams', '$state',
function ($scope, $modal, i18nService, Request, $httpParamSerializer, $stateParams, $state) {
    var getPagedDataAsync = function (curPage) {
        setTimeout(function () {
            var data;
            var params = {};

            params['item_type'] = $stateParams.item_type;
            params['item_id'] = $stateParams.item_id;

            if (curPage) {
                params['page_index'] = curPage;
            }

            // update url, 添加下面一行 <===============================================
            $state.go('.', params,  {notify: false});

            if ($scope.searchValue) {
                params[$scope.selectedName] = $scope.searchValue;
            }

            params['page_size'] = 15;

            Request.get('/wuxing/item_history_list?' + $httpParamSerializer(params),
                function (res) {
                    if (res.data.list)
                    {
                        $scope.myData = res.data.list;
                        $scope.gridOptions.totalItems = res.data.total;
                    }
                    else
                    {
                        $scope.myData = [];
                        $scope.gridOptions.totalItems = 0;
                    }
                }, false, true
            );
        },
        100);
    };
}]);

修改 URL 参数,而不重新刷新视图的方法

5 ui-grid 使用

var app = angular.module('myApp', ['ui.grid', 'ui.grid.selection', 'ui.grid.edit', 'ui.grid.exporter', 'ui.grid.pagination', 'ui.grid.resizeColumns', 'ui.grid.autoResize']);

app.controller('MyCtrl',
function($scope, i18nService) {
    // 国际化;
    i18nService.setCurrentLang("zh-cn");

    $scope.gridOptions = {
        data: 'myData',
        columnDefs: [{
            field: 'name',
            displayName: '名字',
            width: '10%',
            enableColumnMenu: false,
            // 是否显示列头部菜单按钮
            enableHiding: false,
            suppressRemoveSort: true,
            enableCellEdit: false // 是否可编辑
        },
        {
            field: "age"
        },
        {
            field: "birthday"
        },
        {
            field: "salary"
        }],

        enableSorting: true,
        // 是否排序
        useExternalSorting: false,
        // 是否使用自定义排序规则
        enableGridMenu: true,
        // 是否显示 grid 菜单
        showGridFooter: true,
        // 是否显示 grid footer,grid footer 为页脚的"总行数: 15" 而非分页按钮
        enableHorizontalScrollbar: 1,
        //grid 水平滚动条是否显示,0- 不显示  1- 显示(默认是 1)
        enableVerticalScrollbar: 0,
        //grid 垂直滚动条是否显示,0- 不显示  1- 显示(默认是 1)
        //-------- 分页属性 ----------------
        enablePagination: true,
        // 是否分页,默认为 true,设置为 false 时,即使显示分页按钮也无效果
        enablePaginationControls: true,
        // 使用默认的底部分页,设置为 false 时,底部分页按钮消失
        paginationPageSizes: [10, 15, 20],
        // 每页显示个数可选项
        paginationCurrentPage: 1,
        // 当前页码
        paginationPageSize: 10,
        // 每页显示个数
        //paginationTemplate:"<div></div>", // 自定义底部分页代码
        totalItems: 0,
        // 总数量
        useExternalPagination: true,
        // 是否使用分页按钮

        //----------- 选中 ----------------------
        enableFooterTotalSelected: true,
        // 是否显示选中的总数,默认为 true, 如果显示,showGridFooter 必须为 true
        enableFullRowSelection: true,
        // 是否点击行任意位置后选中,默认为 false, 当为 true 时,checkbox 可以显示但是不可选中
        enableRowHeaderSelection: true,
        // 是否显示选中 checkbox 框 , 默认为 true
        enableRowSelection: true,
        // 行选择是否可用,默认为 true;
        enableSelectAll: true,
        // 选择所有 checkbox 是否可用,默认为 true;
        enableSelectionBatchEvent: true,
        // 默认 true
        isRowSelectable: function(row) { //GridRow
            if (row.entity.age > 45) {
                row.grid.api.selection.selectRow(row.entity); // 选中行
            }
        },
        modifierKeysToMultiSelect: false,
        // 默认 false, 为 true 时只能 按 ctrl 或 shift 键进行多选,multiSelect 必须为 true;
        multiSelect: true,
        // 是否可以选择多个,默认为 true;
        noUnselect: false,
        // 默认 false, 选中后是否可以取消选中
        selectionRowHeaderWidth: 30,
        // 默认 30 ,设置选择列的宽度;
        //-------------- 导出 ----------------------------------
        exporterAllDataFn: function() {
            return getPage(1, $scope.gridOptions.totalItems);
        },
        exporterCsvColumnSeparator: ',',
        exporterCsvFilename: 'download.csv',
        exporterFieldCallback: function(grid, row, col, value) {
            if (value == 50) {
                value = "可以退休";
            }
            return value;
        },
        exporterHeaderFilter: function(displayName) {
            return 'col: ' + name;
        },
        exporterHeaderFilterUseName: true,
        exporterMenuCsv: true,
        exporterMenuLabel: "Export",
        exporterMenuPdf: true,
        exporterOlderExcelCompatibility: false,
        exporterPdfCustomFormatter: function(docDefinition) {
            docDefinition.styles.footerStyle = {
                bold: true,
                fontSize: 10
            };
            return docDefinition;
        },
        exporterPdfFooter: {
            text: 'My footer',
            style: 'footerStyle'
        },
        exporterPdfDefaultStyle: {
            fontSize: 11,
            font: 'simblack' //font 设置自定义字体
        },
        exporterPdfFilename: 'download.pdf',
        /* exporterPdfFooter : {
                 columns: [
                   'Left part',
                   { text: 'Right part', alignment: 'right' }
                 ]
                },
                或 */
        exporterPdfFooter: function(currentPage, pageCount) {
            return currentPage.toString() + ' of ' + pageCount;
        },
        exporterPdfHeader: function(currentPage, pageCount) {
            return currentPage.toString() + ' of ' + pageCount;
        },
        exporterPdfMaxGridWidth: 720,
        exporterPdfOrientation: 'landscape',
        //  'landscape' 或 'portrait' pdf 横向或纵向
        exporterPdfPageSize: 'A4',
        // 'A4' or 'LETTER'
        exporterPdfTableHeaderStyle: {
            bold: true,
            fontSize: 12,
            color: 'black'
        },
        exporterPdfTableLayout: null,
        exporterPdfTableStyle: {
            margin: [0, 5, 0, 15]
        },
        exporterSuppressColumns: ['buttons'],
        exporterSuppressMenu: false,

        //---------------api---------------------
        onRegisterApi: function(gridApi) {
            $scope.gridApi = gridApi;
            // 分页按钮事件
            gridApi.pagination.on.paginationChanged($scope,
            function(newPage, pageSize) {
                if (getPage) {
                    getPage(newPage, pageSize);
                }
            });
            // 行选中事件
            $scope.gridApi.selection.on.rowSelectionChanged($scope,
            function(row, event) {
                if (row) {
                    $scope.testRow = row.entity;
                }
            });
        }
    };

    var getPage = function(curPage, pageSize) {
        var firstRow = (curPage - 1) * pageSize;
        $scope.gridOptions.totalItems = mydefalutData.length;
        $scope.gridOptions.data = mydefalutData.slice(firstRow, firstRow + pageSize);
        // 或者像下面这种写法
        //$scope.myData = mydefalutData.slice(firstRow, firstRow + pageSize);
    };

    var mydefalutData = [{
        name: "Moroni",
        age: 50,
        birthday: "Oct 28, 1970",
        salary: "60,000"
    },
    {
        name: "Tiancum",
        age: 43,
        birthday: "Feb 12, 1985",
        salary: "70,000"
    },
    {
        name: "Jacob",
        age: 27,
        birthday: "Aug 23, 1983",
        salary: "50,000"
    },
    {
        name: "Nephi",
        age: 29,
        birthday: "May 31, 2010",
        salary: "40,000"
    },
    {
        name: "Enos",
        age: 34,
        birthday: "Aug 3, 2008",
        salary: "30,000"
    },
    {
        name: "Moroni",
        age: 50,
        birthday: "Oct 28, 1970",
        salary: "60,000"
    },
    {
        name: "Tiancum",
        age: 43,
        birthday: "Feb 12, 1985",
        salary: "70,000"
    },
    {
        name: "Jacob",
        age: 27,
        birthday: "Aug 23, 1983",
        salary: "40,000"
    },
    {
        name: "Nephi",
        age: 29,
        birthday: "May 31, 2010",
        salary: "50,000"
    },
    {
        name: "Enos",
        age: 34,
        birthday: "Aug 3, 2008",
        salary: "30,000"
    },
    {
        name: "Moroni",
        age: 50,
        birthday: "Oct 28, 1970",
        salary: "60,000"
    },
    {
        name: "Tiancum",
        age: 43,
        birthday: "Feb 12, 1985",
        salary: "70,000"
    },
    {
        name: "Jacob",
        age: 27,
        birthday: "Aug 23, 1983",
        salary: "40,000"
    },
    {
        name: "Nephi",
        age: 29,
        birthday: "May 31, 2010",
        salary: "50,000"
    },
    {
        name: "Enos",
        age: 34,
        birthday: "Aug 3, 2008",
        salary: "30,000"
    }];

    getPage(1, $scope.gridOptions.paginationPageSize);
});

6 常见问题

6.1 单击按钮在 Celltemplate 中不起作用

需要使用 grid.appScope.

$scope.gridScope = $scope;
$scope.gridOptions = {
  columnDefs = [
    { name: 'edit', displayName: 'Edit', cellTemplate: '<button ng-click="grid.appScope.edit(row.entity)" >Edit</button>' }
  ],
  data: myData
};

$scope.edit = function( entity ) {
  ...some custom function using entity...
};
⚠️ **GitHub.com Fallback** ⚠️