Freewind @ Thoughtworks scala java javascript dart 工具 编程实践 月结 math python english [comments admin] [feed]

(2013-02-06) 使用ng-grid实现可配置的表格

广告: 云梯:翻墙vpn (省10元) 土行孙:科研用户翻墙http proxy (有优惠)

使用Angularjs在带来方便的同时,也有一些遗憾:很多基于jquery或其它的组件,在angularjs中需要集成一下才能用得流畅。但是一些比较复杂的组件,集成起来的工作量相当大,比如说grid。

大多数情况下,使用angularjs可以方便地实现简单的表格,甚至点击修改这样的功能也很容易。但是如果还希望增加更多的功能,比如拖动改变列的前后顺序,点击表头排序,拖动列宽度,隐藏某些列时,就不那么容易了。如果还想加上分组的功能,那就十分麻烦了。这时候我们就需要一个与angularjs配合很好的grid组件。

这几天我试用了http://angular-ui.github.com/ng-grid/,它完全是基于angularjs写的,所以可以使用angularjs的思路与它交互。使用感觉还不错,基本功能齐全,与之交互实现需要的效果也很顺畅。我用它简单地实现了一个表格设计器,在这里演示一下。

我实现的功能如下:

  1. 指定数据源后,可打开表格设计器,定制每一列的表头,添加删除列,调整列顺序等2. 可取出调整后的参数,保存在服务器中3. 在正式使用的表格中,可读取之前设计好的参数,直接使用

因为该项目的文档和示例写得比较好,而且当前还在紧张改进之中,所以我在这里不详细介绍如何使用,主要来演示效果。想使用的朋友可以上项目主页看,有问题在issues里提,作者回复很快很热心。

首先看成品效果

image

上面的按钮是我自己加的,下面的表格是由ng-grid提供的。可以看到它的外观跟普通的grid差不多,可单选(或多选),可调整宽度及列顺序,点击表头可排序,右上角有个小三角还有一些高级功能:

image

可在这里搜索(针对所有列),可以显示或者隐藏某列,右边那些小图标点击后会分组:

image

设计器效果

在上面有一个“配置表格”的按钮,点击后将会出现设计器窗口:

image

这是一个简单的设计器,它主要用来添加、删除列,改列头,以及调整顺序。左下角是当前已经使用的列,左边是field,对于json数据中某个key,右边是显示在列头的自定义名称;点击每行右边的[-]后,将删除该列;点击左下角的[+],将会新增一列。

左上角显示的是数据源中的json数据里存在但未被使用的key。点击旁边的[+]后,它就会移到下方。从下方删除后,就会回到上方。

右边是表格预览,我们可以调整列宽以及前后顺序。

可以看出这里调整的都是一些比较基本但又重要的参数,更详细的设置(比如为某列单元格自定义显示模板,配置列排序函数等),还得手动写代码实现。

当调整完成后,下方还有一个“保存”按钮(没在截图上显示),点击后,就会把当前的配置提交到服务器保存起来供使用。

相关代码

代码写得比较粗糙,因为只是为了检验是否能满足需求,还未细化,仅供参考。

为设计器定义的directive

在js中定义:

// http://angular-ui.github.com/ng-grid/#/api
admin.directive('configureGrid', [ 'JsRoutes', function (JsRoutes) {
    return {
        restrict: "A",
        controller: function ($scope, $element, $attrs, $transclude) {
            var params = $scope.$parent.$eval($attrs["configureGrid"]);
            $scope.gridId = params.id;
            var dataName = params.dataName;

            JsRoutes.Directives.getGridOptions.get({
                id: $scope.gridId
            }, function (options) {
                if (options && options.columns) {
                    $scope.columnDefs = options.columns;
                }
            })

            $scope.gridColumns = [];
            $scope.configureGridModalShown = false;
            $scope.$parent.configureGrid = function () {
                $scope.configureGridModalShown = true;
            }

            $scope.$watch('gridWidth', function (newV) {
                $scope.layoutPlugin1.updateGridLayout();
            });
            $scope.layoutPlugin1 = new ngGridLayoutPlugin();

            $scope.columnDefs = [ ];

            $scope.gridOptions = {
                data: dataName,
                columnDefs: 'columnDefs',
                plugins: [$scope.layoutPlugin1]
            }

            $scope.$on('ngGridEventColumns', function (newColumns) {
                var columns = newColumns.targetScope.columns;
                columns = columns.filter(function (col) {
                    return col.field !== '✔';
                })
                $scope.gridColumns = columns.map(function (col) {
                    return {
                        field: col.field,
                        displayName: col.displayName,
                        width: col.width
                    }
                });
                var total = 0;
                $scope.gridColumns.forEach(function (col) {
                    total += col.width;
                });
                $scope.gridWidth = total;
            });
            $scope.gridWidth = 500;

            $scope.$on('')

            $scope.getFields = function () {
                var data = $scope.$parent[dataName];
                if (data && data.length > 0) {
                    var keys = _.keys(data[0]);
                    return _.reject(keys, function (key) {
                        return _.find($scope.columnDefs, function (col) {
                            return col.field === key;
                        });
                    });
                }
                return [];
            }

            $scope.addColumn = function (field) {
                $scope.columnDefs.push({
                    field: field,
                    displayName: field,
                    width: 100
                });
            }
            $scope.removeColumn = function (col) {
                $scope.columnDefs = _.reject($scope.columnDefs, function (c) {
                    return c.field == col.field;
                });
            }

            $scope.$watch(dataName, function (data) {
                if (data.length > 0 && $scope.columnDefs.length == 0) {
                    $scope.getFields().forEach(function (f) {
                        $scope.addColumn(f);
                    });
                }
            })

            $scope.saveGridOptions = function () {
                JsRoutes.Directives.saveGridOptions.post({
                    id: $scope.gridId,
                    columns: $scope.gridColumns
                }, function () {
                    $scope.configureGridModalShown = false;
                }, null, {
                    postType: 'json'
                });
            }

        },
        scope: true,
        templateUrl: JsRoutes.Directives.configureGrid.link({format: 'html'})
    }
}])

对应的html代码:

<div id="configure-modal">
    <style type="text/css">
        #configure-modal .modal {
            position: fixed;
            margin-left: -45%;
            margin-top: 30px;
            width: 90%;
        }        #configure-modal .modal-body {
            max-height: none;
        }        #configure-modal .fade.in {
            top: 30px;
        }    </style>
    <div ui-modal class="fade configure-modal" ng-model="configureGridModalShown">
    #{debug /}
        <div class="modal-header"><h3>定制表格 [{{gridId}}]</h3></div>
        <div class="modal-body">
            <table class="table-as-layout">
                <tr>
                    <td ui-fixed-width="300">
                        <ul>
                            <li ng-repeat="field in getFields()">{{field}}
                                <a ng-click="addColumn(field)">[+]</a>
                            </li>
                        </ul>
                        <hr/>
                        <ul>
                            <li ng-repeat="col in columnDefs">
                                <div>
                                    <span ng-click="details=!details" style="cursor: pointer">
                                    <input type="text" class="input input-small" ng-model="col.field" ng-model-onblur/>
                                    - <input type="text" class="input input-small" ng-model="col.displayName" ng-model-onblur/>
                                    </span>
                                    <a ng-click="removeColumn(col)">[-]</a>
                                </div>
                            </li>
                            <li><a ng-click="addColumn('todo')">[+]</a></li>
                        </ul>
                    </td>
                    <td width="100%">
                        <div class="gridStyle" style="width:{{gridWidth}}px;height:600px" ng-grid="gridOptions"></div>
                    </td>
                </tr>
            </table>
        </div>
        <div class="modal-footer">
            <a class="btn btn-primary" data-dismiss="modal" ng-click="saveGridOptions()">保存</a>
            <a class="btn" data-dismiss="modal">关闭</a>
        </div>
    </div>
</div>

调用该设计器的代码:

<button class="btn" ng-click="configureGrid()">
    <i class="icon-table"> </i> 配置表格
</button>
<div configure-grid="{id:gridId, dataName: gridDataName}">
</div><script type="text/javascript">
    function Ctrl($scope, JsRoutes, Commons) {
        $scope.users = [];
        $scope.gridId = '402880843CAE4701013CAE594B390001';
        $scope.gridDataName = 'users';
        $scope.columnDefs = [];
        $scope.gridOptions = {
            data: $scope.gridDataName,
            columnDefs: 'columnDefs',
            beforeSelectionChange: function (event) {
                event.entity.checked = !event.selected;
                return true;
            }
        }
    }
</script>


在页面中使用保存的参数显示表格

Html代码:

<div ng-grid="gridOptions" style="width: 100%;height: 500px"></div>

Js代码:

$scope.users = [];
$scope.gridId = '402880843CAE4701013CAE594B390001';
$scope.gridDataName = 'users';
$scope.columnDefs = [];
$scope.gridOptions = {
    data: $scope.gridDataName,
    columnDefs: 'columnDefs',
    beforeSelectionChange: function (event) {
        event.entity.checked = !event.selected;
        return true;
    }
}
JsRoutes.Directives.getGridOptions.get({
    id: $scope.gridId
}, function (options) {
    console.log(options);
    if (options && options.columns) {
        $scope.columnDefs = options.columns;
    }
})

注意,以上代码仅供参考思路,里面缺了东西,不能直接运行。另外,ng-grid的代码改得比较快,请注意它的最新文档。

动态演示

image

comments powered by Disqus